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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 [cd track] +==================== +*/ +void CL_Record_f (void) +{ + int c, track; + char name[MAX_OSPATH]; + char vabuf[1024]; + + c = Cmd_Argc(); + if (c != 2 && c != 3 && c != 4) + { + Con_Print("record [ [cd track]]\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Print("Relative pathnames are not allowed.\n"); + return; + } + + if (c == 2 && cls.state == ca_connected) + { + Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n"); + return; + } + + if (cls.state == ca_connected) + CL_Disconnect(); + + // write the forced cd track number, or -1 + if (c == 4) + { + track = atoi(Cmd_Argv(3)); + Con_Printf("Forcing CD track to %i\n", cls.forcetrack); + } + else + track = -1; + + // get the demo name + strlcpy (name, Cmd_Argv(1), sizeof (name)); + FS_DefaultExtension (name, ".dem", sizeof (name)); + + // start the map up + if (c > 2) + Cmd_ExecuteString ( va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(2)), src_command, false); + + // open the demo file + Con_Printf("recording to %s.\n", name); + cls.demofile = FS_OpenRealFile(name, "wb", false); + if (!cls.demofile) + { + Con_Print("ERROR: couldn't open.\n"); + return; + } + strlcpy(cls.demoname, name, sizeof(cls.demoname)); + + cls.forcetrack = track; + FS_Printf(cls.demofile, "%i\n", cls.forcetrack); + + cls.demorecording = true; + cls.demo_lastcsprogssize = -1; + cls.demo_lastcsprogscrc = -1; +} + + +/* +==================== +CL_PlayDemo_f + +play [demoname] +==================== +*/ +void CL_PlayDemo_f (void) +{ + char name[MAX_QPATH]; + int c; + qboolean neg = false; + qfile_t *f; + + if (Cmd_Argc() != 2) + { + Con_Print("play : plays a demo\n"); + return; + } + + // open the demo file + strlcpy (name, Cmd_Argv(1), sizeof (name)); + FS_DefaultExtension (name, ".dem", sizeof (name)); + f = FS_OpenVirtualFile(name, false); + if (!f) + { + Con_Printf("ERROR: couldn't open %s.\n", name); + cls.demonum = -1; // stop demo loop + return; + } + + cls.demostarting = true; + + // disconnect from server + CL_Disconnect (); + Host_ShutdownServer (); + + // update networking ports (this is mainly just needed at startup) + NetConn_UpdateSockets(); + + cls.protocol = PROTOCOL_QUAKE; + + Con_Printf("Playing demo %s.\n", name); + cls.demofile = f; + strlcpy(cls.demoname, name, sizeof(cls.demoname)); + + cls.demoplayback = true; + cls.state = ca_connected; + cls.forcetrack = 0; + + while ((c = FS_Getc (cls.demofile)) != '\n') + if (c == '-') + neg = true; + else + cls.forcetrack = cls.forcetrack * 10 + (c - '0'); + + if (neg) + cls.forcetrack = -cls.forcetrack; + + cls.demostarting = false; +} + +typedef struct +{ + int frames; + double time, totalfpsavg; + double fpsmin, fpsavg, fpsmax; +} +benchmarkhistory_t; +static size_t doublecmp_offset; +static int doublecmp_withoffset(const void *a_, const void *b_) +{ + const double *a = (const double *) ((const char *) a_ + doublecmp_offset); + const double *b = (const double *) ((const char *) b_ + doublecmp_offset); + if(*a > *b) + return +1; + if(*a < *b) + return -1; + return 0; +} + +/* +==================== +CL_FinishTimeDemo + +==================== +*/ +static void CL_FinishTimeDemo (void) +{ + int frames; + int i; + double time, totalfpsavg; + double fpsmin, fpsavg, fpsmax; // report min/avg/max fps + static int benchmark_runs = 0; + char vabuf[1024]; + + cls.timedemo = false; + + frames = cls.td_frames; + time = realtime - cls.td_starttime; + totalfpsavg = time > 0 ? frames / time : 0; + fpsmin = cls.td_onesecondminfps; + fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0; + fpsmax = cls.td_onesecondmaxfps; + // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max + Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); + Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | run %d | result %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), buildstring, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); + if (COM_CheckParm("-benchmark")) + { + ++benchmark_runs; + i = COM_CheckParm("-benchmarkruns"); + if(i && i + 1 < com_argc) + { + static benchmarkhistory_t *history = NULL; + if(!history) + history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(com_argv[i + 1])); + + history[benchmark_runs - 1].frames = frames; + history[benchmark_runs - 1].time = time; + history[benchmark_runs - 1].totalfpsavg = totalfpsavg; + history[benchmark_runs - 1].fpsmin = fpsmin; + history[benchmark_runs - 1].fpsavg = fpsavg; + history[benchmark_runs - 1].fpsmax = fpsmax; + + if(atoi(com_argv[i + 1]) > benchmark_runs) + { + // restart the benchmark + Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname)); + // cannot execute here + } + else + { + // print statistics + int first = COM_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0; + if(benchmark_runs > first) + { +#define DO_MIN(f) \ + for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f + +#define DO_MAX(f) \ + for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f + +#define DO_MED(f) \ + doublecmp_offset = (char *)&history->f - (char *)history; \ + qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \ + if((first + benchmark_runs) & 1) \ + f = history[(first + benchmark_runs - 1) / 2].f; \ + else \ + f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2 + + DO_MIN(frames); + DO_MAX(time); + DO_MIN(totalfpsavg); + DO_MIN(fpsmin); + DO_MIN(fpsavg); + DO_MIN(fpsmax); + Con_Printf("MIN: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); + + DO_MED(frames); + DO_MED(time); + DO_MED(totalfpsavg); + DO_MED(fpsmin); + DO_MED(fpsavg); + DO_MED(fpsmax); + Con_Printf("MED: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); + + DO_MAX(frames); + DO_MIN(time); + DO_MAX(totalfpsavg); + DO_MAX(fpsmin); + DO_MAX(fpsavg); + DO_MAX(fpsmax); + Con_Printf("MAX: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); + } + Z_Free(history); + history = NULL; + Host_Quit_f(); + } + } + else + Host_Quit_f(); + } +} + +/* +==================== +CL_TimeDemo_f + +timedemo [demoname] +==================== +*/ +void CL_TimeDemo_f (void) +{ + if (Cmd_Argc() != 2) + { + Con_Print("timedemo : gets demo speeds\n"); + return; + } + + srand(0); // predictable random sequence for benchmarking + + CL_PlayDemo_f (); + +// cls.td_starttime will be grabbed at the second frame of the demo, so +// all the loading time doesn't get counted + + // instantly hide console and deactivate it + key_dest = key_game; + key_consoleactive = 0; + scr_con_current = 0; + + cls.timedemo = true; + cls.td_frames = -2; // skip the first frame + cls.demonum = -1; // stop demo loop +} + diff --git a/app/jni/cl_dyntexture.c b/app/jni/cl_dyntexture.c new file mode 100644 index 0000000..fb378de --- /dev/null +++ b/app/jni/cl_dyntexture.c @@ -0,0 +1,93 @@ +// Andreas Kirsch 07 + +#include "quakedef.h" +#include "cl_dyntexture.h" + +typedef struct dyntexture_s { + // everything after DYNAMIC_TEXTURE_PATH_PREFIX + char name[ MAX_QPATH + 32 ]; + // texture pointer (points to r_texture_white at first) + rtexture_t *texture; +} dyntexture_t; + +static dyntexture_t dyntextures[ MAX_DYNAMIC_TEXTURE_COUNT ]; +static unsigned dyntexturecount; + +#define DEFAULT_DYNTEXTURE r_texture_grey128 + +static dyntexture_t * cl_finddyntexture( const char *name, qboolean warnonfailure ) { + unsigned i; + dyntexture_t *dyntexture = NULL; + + // sanity checks - make sure its actually a dynamic texture path + if( !name || !*name || strncmp( name, CLDYNTEXTUREPREFIX, sizeof( CLDYNTEXTUREPREFIX ) - 1 ) != 0 ) { + // TODO: print a warning or something + if (warnonfailure) + Con_Printf( "cl_finddyntexture: Bad dynamic texture name '%s'\n", name ); + return NULL; + } + + for( i = 0 ; i < dyntexturecount ; i++ ) { + dyntexture = &dyntextures[ i ]; + if( dyntexture->name && strcmp( dyntexture->name, name ) == 0 ) { + return dyntexture; + } + } + + if( dyntexturecount == MAX_DYNAMIC_TEXTURE_COUNT ) { + // TODO: warn or expand the array, etc. + return NULL; + } + dyntexture = &dyntextures[ dyntexturecount++ ]; + strlcpy( dyntexture->name, name, sizeof( dyntexture->name ) ); + dyntexture->texture = DEFAULT_DYNTEXTURE; + return dyntexture; +} + +rtexture_t * CL_GetDynTexture( const char *name ) { + dyntexture_t *dyntexture = cl_finddyntexture( name, false ); + if( dyntexture ) { + return dyntexture->texture; + } else { + return NULL; + } +} + +void CL_LinkDynTexture( const char *name, rtexture_t *texture ) { + dyntexture_t *dyntexture; + cachepic_t *cachepic; + skinframe_t *skinframe; + + dyntexture = cl_finddyntexture( name, true ); + if( !dyntexture ) { + Con_Printf( "CL_LinkDynTexture: internal error in cl_finddyntexture!\n" ); + return; + } + // TODO: assert dyntexture != NULL! + if( dyntexture->texture != texture ) { + dyntexture->texture = texture; + + cachepic = Draw_CachePic_Flags( name, CACHEPICFLAG_NOTPERSISTENT ); + // TODO: assert cachepic and skinframe should be valid pointers... + // TODO: assert cachepic->tex = dyntexture->texture + cachepic->tex = texture; + // update cachepic's size, too + cachepic->width = R_TextureWidth( texture ); + cachepic->height = R_TextureHeight( texture ); + + // update skinframes + skinframe = NULL; + while( (skinframe = R_SkinFrame_FindNextByName( skinframe, name )) != NULL ) { + skinframe->base = texture; + // simply reset the compare* attributes of skinframe + skinframe->comparecrc = 0; + skinframe->comparewidth = skinframe->compareheight = 0; + // this is kind of hacky + } + } +} + +void CL_UnlinkDynTexture( const char *name ) { + CL_LinkDynTexture( name, DEFAULT_DYNTEXTURE ); +} + diff --git a/app/jni/cl_dyntexture.h b/app/jni/cl_dyntexture.h new file mode 100644 index 0000000..130dc16 --- /dev/null +++ b/app/jni/cl_dyntexture.h @@ -0,0 +1,20 @@ +// Andreas 'Black' Kirsch 07 +#ifndef CL_DYNTEXTURE_H +#define CL_DYNTEXTURE_H + +#define CLDYNTEXTUREPREFIX "_dynamic/" + +// always path fully specified names to the dynamic texture functions! (ie. with the _dynamic/ prefix, etc.!) + +// return a valid texture handle for a dynamic texture (might be filler texture if it hasnt been initialized yet) +// or NULL if its not a valid dynamic texture name +rtexture_t * CL_GetDynTexture( const char *name ); + +// link a texture handle as dynamic texture and update texture handles in the renderer and draw_* accordingly +void CL_LinkDynTexture( const char *name, rtexture_t *texture ); + +// unlink a texture handle from its name +void CL_UnlinkDynTexture( const char *name ); + +#endif + diff --git a/app/jni/cl_input.c b/app/jni/cl_input.c new file mode 100644 index 0000000..1c87f43 --- /dev/null +++ b/app/jni/cl_input.c @@ -0,0 +1,2297 @@ +/* +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. + +*/ +// cl.input.c -- builds an intended movement command to send to the server + +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. + +#include "quakedef.h" +#include "csprogs.h" +#include "thread.h" + +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook, in_klook; +kbutton_t in_left, in_right, in_forward, in_back; +kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; +kbutton_t in_strafe, in_speed, in_jump, in_attack, in_use; +kbutton_t in_up, in_down; +// LordHavoc: added 6 new buttons +kbutton_t in_button3, in_button4, in_button5, in_button6, in_button7, in_button8; +//even more +kbutton_t in_button9, in_button10, in_button11, in_button12, in_button13, in_button14, in_button15, in_button16; + +int in_impulse; + + + +static void KeyDown (kbutton_t *b) +{ + int k; + const char *c; + + c = Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + Con_Print("Three keys down for a button!\n"); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +static void KeyUp (kbutton_t *b) +{ + int k; + const char *c; + + c = Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + return; // some other key is still holding it down + + if (!(b->state & 1)) + return; // still up (this should not happen) + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +static void IN_KLookDown (void) {KeyDown(&in_klook);} +static void IN_KLookUp (void) {KeyUp(&in_klook);} +static void IN_MLookDown (void) {KeyDown(&in_mlook);} +static void IN_MLookUp (void) +{ + KeyUp(&in_mlook); + if ( !(in_mlook.state&1) && lookspring.value) + V_StartPitchDrift(); +} +static void IN_UpDown(void) {KeyDown(&in_up);} +static void IN_UpUp(void) {KeyUp(&in_up);} +static void IN_DownDown(void) {KeyDown(&in_down);} +static void IN_DownUp(void) {KeyUp(&in_down);} +static void IN_LeftDown(void) {KeyDown(&in_left);} +static void IN_LeftUp(void) {KeyUp(&in_left);} +static void IN_RightDown(void) {KeyDown(&in_right);} +static void IN_RightUp(void) {KeyUp(&in_right);} +static void IN_ForwardDown(void) {KeyDown(&in_forward);} +static void IN_ForwardUp(void) {KeyUp(&in_forward);} +static void IN_BackDown(void) {KeyDown(&in_back);} +static void IN_BackUp(void) {KeyUp(&in_back);} +static void IN_LookupDown(void) {KeyDown(&in_lookup);} +static void IN_LookupUp(void) {KeyUp(&in_lookup);} +static void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +static void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +static void IN_MoveleftDown(void) {KeyDown(&in_moveleft);} +static void IN_MoveleftUp(void) {KeyUp(&in_moveleft);} +static void IN_MoverightDown(void) {KeyDown(&in_moveright);} +static void IN_MoverightUp(void) {KeyUp(&in_moveright);} + +static void IN_SpeedDown(void) {KeyDown(&in_speed);} +static void IN_SpeedUp(void) {KeyUp(&in_speed);} +static void IN_StrafeDown(void) {KeyDown(&in_strafe);} +static void IN_StrafeUp(void) {KeyUp(&in_strafe);} + +static void IN_AttackDown(void) {KeyDown(&in_attack);} +static void IN_AttackUp(void) {KeyUp(&in_attack);} + +static void IN_UseDown(void) {KeyDown(&in_use);} +static void IN_UseUp(void) {KeyUp(&in_use);} + +// LordHavoc: added 6 new buttons +static void IN_Button3Down(void) {KeyDown(&in_button3);} +static void IN_Button3Up(void) {KeyUp(&in_button3);} +static void IN_Button4Down(void) {KeyDown(&in_button4);} +static void IN_Button4Up(void) {KeyUp(&in_button4);} +static void IN_Button5Down(void) {KeyDown(&in_button5);} +static void IN_Button5Up(void) {KeyUp(&in_button5);} +static void IN_Button6Down(void) {KeyDown(&in_button6);} +static void IN_Button6Up(void) {KeyUp(&in_button6);} +static void IN_Button7Down(void) {KeyDown(&in_button7);} +static void IN_Button7Up(void) {KeyUp(&in_button7);} +static void IN_Button8Down(void) {KeyDown(&in_button8);} +static void IN_Button8Up(void) {KeyUp(&in_button8);} + +static void IN_Button9Down(void) {KeyDown(&in_button9);} +static void IN_Button9Up(void) {KeyUp(&in_button9);} +static void IN_Button10Down(void) {KeyDown(&in_button10);} +static void IN_Button10Up(void) {KeyUp(&in_button10);} +static void IN_Button11Down(void) {KeyDown(&in_button11);} +static void IN_Button11Up(void) {KeyUp(&in_button11);} +static void IN_Button12Down(void) {KeyDown(&in_button12);} +static void IN_Button12Up(void) {KeyUp(&in_button12);} +static void IN_Button13Down(void) {KeyDown(&in_button13);} +static void IN_Button13Up(void) {KeyUp(&in_button13);} +static void IN_Button14Down(void) {KeyDown(&in_button14);} +static void IN_Button14Up(void) {KeyUp(&in_button14);} +static void IN_Button15Down(void) {KeyDown(&in_button15);} +static void IN_Button15Up(void) {KeyUp(&in_button15);} +static void IN_Button16Down(void) {KeyDown(&in_button16);} +static void IN_Button16Up(void) {KeyUp(&in_button16);} + +static void IN_JumpDown (void) {KeyDown(&in_jump);} +static void IN_JumpUp (void) {KeyUp(&in_jump);} + +static void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));} + +in_bestweapon_info_t in_bestweapon_info[IN_BESTWEAPON_MAX]; + +static void IN_BestWeapon_Register(const char *name, int impulse, int weaponbit, int activeweaponcode, int ammostat, int ammomin) +{ + int i; + for(i = 0; i < IN_BESTWEAPON_MAX && in_bestweapon_info[i].impulse; ++i) + if(in_bestweapon_info[i].impulse == impulse) + break; + if(i >= IN_BESTWEAPON_MAX) + { + Con_Printf("no slot left for weapon definition; increase IN_BESTWEAPON_MAX\n"); + return; // sorry + } + strlcpy(in_bestweapon_info[i].name, name, sizeof(in_bestweapon_info[i].name)); + in_bestweapon_info[i].impulse = impulse; + if(weaponbit != -1) + in_bestweapon_info[i].weaponbit = weaponbit; + if(activeweaponcode != -1) + in_bestweapon_info[i].activeweaponcode = activeweaponcode; + if(ammostat != -1) + in_bestweapon_info[i].ammostat = ammostat; + if(ammomin != -1) + in_bestweapon_info[i].ammomin = ammomin; +} + +void IN_BestWeapon_ResetData (void) +{ + memset(in_bestweapon_info, 0, sizeof(in_bestweapon_info)); + IN_BestWeapon_Register("1", 1, IT_AXE, IT_AXE, STAT_SHELLS, 0); + IN_BestWeapon_Register("2", 2, IT_SHOTGUN, IT_SHOTGUN, STAT_SHELLS, 1); + IN_BestWeapon_Register("3", 3, IT_SUPER_SHOTGUN, IT_SUPER_SHOTGUN, STAT_SHELLS, 1); + IN_BestWeapon_Register("4", 4, IT_NAILGUN, IT_NAILGUN, STAT_NAILS, 1); + IN_BestWeapon_Register("5", 5, IT_SUPER_NAILGUN, IT_SUPER_NAILGUN, STAT_NAILS, 1); + IN_BestWeapon_Register("6", 6, IT_GRENADE_LAUNCHER, IT_GRENADE_LAUNCHER, STAT_ROCKETS, 1); + IN_BestWeapon_Register("7", 7, IT_ROCKET_LAUNCHER, IT_ROCKET_LAUNCHER, STAT_ROCKETS, 1); + IN_BestWeapon_Register("8", 8, IT_LIGHTNING, IT_LIGHTNING, STAT_CELLS, 1); + IN_BestWeapon_Register("9", 9, 128, 128, STAT_CELLS, 1); // generic energy weapon for mods + IN_BestWeapon_Register("p", 209, 128, 128, STAT_CELLS, 1); // dpmod plasma gun + IN_BestWeapon_Register("w", 210, 8388608, 8388608, STAT_CELLS, 1); // dpmod plasma wave cannon + IN_BestWeapon_Register("l", 225, HIT_LASER_CANNON, HIT_LASER_CANNON, STAT_CELLS, 1); // hipnotic laser cannon + IN_BestWeapon_Register("h", 226, HIT_MJOLNIR, HIT_MJOLNIR, STAT_CELLS, 0); // hipnotic mjolnir hammer +} + +static void IN_BestWeapon_Register_f (void) +{ + if(Cmd_Argc() == 7) + { + IN_BestWeapon_Register( + Cmd_Argv(1), + atoi(Cmd_Argv(2)), + atoi(Cmd_Argv(3)), + atoi(Cmd_Argv(4)), + atoi(Cmd_Argv(5)), + atoi(Cmd_Argv(6)) + ); + } + else if(Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), "clear")) + { + memset(in_bestweapon_info, 0, sizeof(in_bestweapon_info)); + } + else if(Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), "quake")) + { + IN_BestWeapon_ResetData(); + } + else + { + Con_Printf("Usage: %s weaponshortname impulse itemcode activeweaponcode ammostat ammomin; %s clear; %s quake\n", Cmd_Argv(0), Cmd_Argv(0), Cmd_Argv(0)); + } +} + +static void IN_BestWeapon (void) +{ + int i, n; + const char *t; + if (Cmd_Argc() < 2) + { + Con_Printf("bestweapon requires 1 or more parameters\n"); + return; + } + for (i = 1;i < Cmd_Argc();i++) + { + t = Cmd_Argv(i); + // figure out which weapon this character refers to + for (n = 0;n < IN_BESTWEAPON_MAX && in_bestweapon_info[n].impulse;n++) + { + if (!strcmp(in_bestweapon_info[n].name, t)) + { + // we found out what weapon this character refers to + // check if the inventory contains the weapon and enough ammo + if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n].weaponbit) && (cl.stats[in_bestweapon_info[n].ammostat] >= in_bestweapon_info[n].ammomin)) + { + // we found one of the weapons the player wanted + // send an impulse to switch to it + in_impulse = in_bestweapon_info[n].impulse; + return; + } + break; + } + } + // if we couldn't identify the weapon we just ignore it and continue checking for other weapons + } + // if we couldn't find any of the weapons, there's nothing more we can do... +} + +#if 0 +void IN_CycleWeapon (void) +{ + int i, n; + int first = -1; + qboolean found = false; + const char *t; + if (Cmd_Argc() < 2) + { + Con_Printf("bestweapon requires 1 or more parameters\n"); + return; + } + for (i = 1;i < Cmd_Argc();i++) + { + t = Cmd_Argv(i); + // figure out which weapon this character refers to + for (n = 0;n < IN_BESTWEAPON_MAX && in_bestweapon_info[n].impulse;n++) + { + if (!strcmp(in_bestweapon_info[n].name, t)) + { + // we found out what weapon this character refers to + // check if the inventory contains the weapon and enough ammo + if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n].weaponbit) && (cl.stats[in_bestweapon_info[n].ammostat] >= in_bestweapon_info[n].ammomin)) + { + // we found one of the weapons the player wanted + if(first == -1) + first = n; + if(found) + { + in_impulse = in_bestweapon_info[n].impulse; + return; + } + if(cl.stats[STAT_ACTIVEWEAPON] == in_bestweapon_info[n].activeweaponcode) + found = true; + } + break; + } + } + // if we couldn't identify the weapon we just ignore it and continue checking for other weapons + } + if(first != -1) + { + in_impulse = in_bestweapon_info[first].impulse; + return; + } + // if we couldn't find any of the weapons, there's nothing more we can do... +} +#endif + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +extern float analogx; +extern float analogy; +extern int analogenabled; + +float CL_KeyState (kbutton_t *key) +{ + float val; + qboolean impulsedown, impulseup, down; + + impulsedown = (key->state & 2) != 0; + impulseup = (key->state & 4) != 0; + down = (key->state & 1) != 0; + val = 0; + + if (impulsedown && !impulseup) + { + if (down) + val = 0.5; // pressed and held this frame + else + val = 0; // I_Error (); + } + if (impulseup && !impulsedown) + { + if (down) + val = 0; // I_Error (); + else + val = 0; // released this frame + } + if (!impulsedown && !impulseup) + { + if (down) + val = 1.0; // held the entire frame + else + val = 0; // up the entire frame + } + if (impulsedown && impulseup) + { + if (down) + val = 0.75; // released and re-pressed this frame + else + val = 0.25; // pressed and released this frame + } + + key->state &= 1; // clear impulses + + //ANALOG + if (analogenabled) + { + if (key==&in_moveright) + return max(0,analogx); + if (key==&in_moveleft) + return max(0,-analogx); + if (key==&in_forward) + return max(0,analogy); + if (key==&in_back) + return max(0,-analogy); + } + //END + + return val; +} + + + + +//========================================================================== + +cvar_t cl_upspeed = {CVAR_SAVE, "cl_upspeed","400","vertical movement speed (while swimming or flying)"}; +cvar_t cl_forwardspeed = {CVAR_SAVE, "cl_forwardspeed","200","forward movement speed"}; +cvar_t cl_backspeed = {CVAR_SAVE, "cl_backspeed","200","backward movement speed"}; +cvar_t cl_sidespeed = {CVAR_SAVE, "cl_sidespeed","200","strafe movement speed"}; + +cvar_t cl_movespeedkey = {CVAR_SAVE, "cl_movespeedkey","2.0","how much +speed multiplies keyboard movement speed"}; +cvar_t cl_movecliptokeyboard = {0, "cl_movecliptokeyboard", "0", "if set to 1, any move is clipped to the nine keyboard states; if set to 2, only the direction is clipped, not the amount"}; + +cvar_t cl_yawmode = {CVAR_SAVE, "cl_yawmode","0","0 = swivel-chair, 1 = comfort, 2 = stick, 3 = look-turn"}; +cvar_t cl_pitchmode = {CVAR_SAVE, "cl_pitchmode","0","0 = locked to hmd, 1 = free, 2 = free (inverted)"}; +cvar_t cl_comfort = {CVAR_SAVE, "cl_comfort","45.0","angle by which comfort mode adjusts yaw"}; +cvar_t cl_yawspeed = {CVAR_SAVE, "cl_yawspeed","150","keyboard yaw turning speed"}; +cvar_t cl_pitchspeed = {CVAR_SAVE, "cl_pitchspeed","150","keyboard pitch turning speed"}; +cvar_t cl_yawmult = {CVAR_SAVE, "cl_yawmult","1.0","Multiplier for yaw (leave at 1.0)"}; +cvar_t cl_pitchmult = {CVAR_SAVE, "cl_pitchmult","1.0","Multiplier for yaw (leave at 1.0)"}; + +cvar_t cl_anglespeedkey = {CVAR_SAVE, "cl_anglespeedkey","1.5","how much +speed multiplies keyboard turning speed"}; + +cvar_t cl_movement = {CVAR_SAVE, "cl_movement", "0", "enables clientside prediction of your player movement"}; +cvar_t cl_movement_replay = {0, "cl_movement_replay", "1", "use engine prediction"}; +cvar_t cl_movement_nettimeout = {CVAR_SAVE, "cl_movement_nettimeout", "0.3", "stops predicting moves when server is lagging badly (avoids major performance problems), timeout in seconds"}; +cvar_t cl_movement_minping = {CVAR_SAVE, "cl_movement_minping", "0", "whether to use prediction when ping is lower than this value in milliseconds"}; +cvar_t cl_movement_track_canjump = {CVAR_SAVE, "cl_movement_track_canjump", "1", "track if the player released the jump key between two jumps to decide if he is able to jump or not; when off, this causes some \"sliding\" slightly above the floor when the jump key is held too long; if the mod allows repeated jumping by holding space all the time, this has to be set to zero too"}; +cvar_t cl_movement_maxspeed = {0, "cl_movement_maxspeed", "320", "how fast you can move (should match sv_maxspeed)"}; +cvar_t cl_movement_maxairspeed = {0, "cl_movement_maxairspeed", "30", "how fast you can move while in the air (should match sv_maxairspeed)"}; +cvar_t cl_movement_stopspeed = {0, "cl_movement_stopspeed", "100", "speed below which you will be slowed rapidly to a stop rather than sliding endlessly (should match sv_stopspeed)"}; +cvar_t cl_movement_friction = {0, "cl_movement_friction", "4", "how fast you slow down (should match sv_friction)"}; +cvar_t cl_movement_wallfriction = {0, "cl_movement_wallfriction", "1", "how fast you slow down while sliding along a wall (should match sv_wallfriction)"}; +cvar_t cl_movement_waterfriction = {0, "cl_movement_waterfriction", "-1", "how fast you slow down (should match sv_waterfriction), if less than 0 the cl_movement_friction variable is used instead"}; +cvar_t cl_movement_edgefriction = {0, "cl_movement_edgefriction", "1", "how much to slow down when you may be about to fall off a ledge (should match edgefriction)"}; +cvar_t cl_movement_stepheight = {0, "cl_movement_stepheight", "18", "how tall a step you can step in one instant (should match sv_stepheight)"}; +cvar_t cl_movement_accelerate = {0, "cl_movement_accelerate", "10", "how fast you accelerate (should match sv_accelerate)"}; +cvar_t cl_movement_airaccelerate = {0, "cl_movement_airaccelerate", "-1", "how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead"}; +cvar_t cl_movement_wateraccelerate = {0, "cl_movement_wateraccelerate", "-1", "how fast you accelerate while in water (should match sv_wateraccelerate), if less than 0 the cl_movement_accelerate variable is used instead"}; +cvar_t cl_movement_jumpvelocity = {0, "cl_movement_jumpvelocity", "270", "how fast you move upward when you begin a jump (should match the quakec code)"}; +cvar_t cl_movement_airaccel_qw = {0, "cl_movement_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration (reduces speed gain when zigzagging) (should match sv_airaccel_qw); when < 0, the speed is clamped against the maximum allowed forward speed after the move"}; +cvar_t cl_movement_airaccel_sideways_friction = {0, "cl_movement_airaccel_sideways_friction", "0", "anti-sideways movement stabilization (should match sv_airaccel_sideways_friction); when < 0, only so much friction is applied that braking (by accelerating backwards) cannot be stronger"}; + +cvar_t in_pitch_min = {0, "in_pitch_min", "-90", "how far downward you can aim (quake used -70"}; +cvar_t in_pitch_max = {0, "in_pitch_max", "90", "how far upward you can aim (quake used 80"}; + +cvar_t m_filter = {CVAR_SAVE, "m_filter","0", "smoothes mouse movement, less responsive but smoother aiming"}; +cvar_t m_accelerate = {CVAR_SAVE, "m_accelerate","1", "mouse acceleration factor (try 2)"}; +cvar_t m_accelerate_minspeed = {CVAR_SAVE, "m_accelerate_minspeed","5000", "below this speed, no acceleration is done"}; +cvar_t m_accelerate_maxspeed = {CVAR_SAVE, "m_accelerate_maxspeed","10000", "above this speed, full acceleration is done"}; +cvar_t m_accelerate_filter = {CVAR_SAVE, "m_accelerate_filter","0.1", "mouse acceleration factor filtering"}; + +cvar_t cl_netfps = {CVAR_SAVE, "cl_netfps","72", "how many input packets to send to server each second"}; +cvar_t cl_netrepeatinput = {CVAR_SAVE, "cl_netrepeatinput", "1", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server), only affects DP7 and later servers (Quake uses 0, QuakeWorld uses 2, and just for comparison Quake3 uses 1)"}; +cvar_t cl_netimmediatebuttons = {CVAR_SAVE, "cl_netimmediatebuttons", "1", "sends extra packets whenever your buttons change or an impulse is used (basically: whenever you click fire or change weapon)"}; + +cvar_t cl_nodelta = {0, "cl_nodelta", "0", "disables delta compression of non-player entities in QW network protocol"}; + +cvar_t cl_csqc_generatemousemoveevents = {0, "cl_csqc_generatemousemoveevents", "1", "enables calls to CSQC_InputEvent with type 2, for compliance with EXT_CSQC spec"}; + +extern cvar_t v_flipped; + +qboolean headtracking = true; + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +static void CL_AdjustAngles (qboolean firstCall) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + speed = cl.realframetime * cl_anglespeedkey.value; + else + speed = cl.realframetime; + + if (!(in_strafe.state & 1)) + { + //Comfort mode + if ((cl_yawmode.integer == 1) && firstCall) + { + cl.viewangles[YAW] = (float)(cl.comfortInc) * cl_comfort.value; + } + //Stick control mode + if (cl_yawmode.integer == 2) + { + cl.viewangles[YAW] -= speed*cl_yawspeed.value*CL_KeyState (&in_right); + cl.viewangles[YAW] += speed*cl_yawspeed.value*CL_KeyState (&in_left); + } + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + cl.viewangles[PITCH] -= speed*cl_pitchspeed.value * CL_KeyState (&in_forward); + cl.viewangles[PITCH] += speed*cl_pitchspeed.value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + cl.viewangles[PITCH] -= speed*cl_pitchspeed.value * up; + cl.viewangles[PITCH] += speed*cl_pitchspeed.value * down; + + if (up || down) + V_StopPitchDrift (); + + cl.viewangles[YAW] = ANGLEMOD(cl.viewangles[YAW]); + cl.viewangles[PITCH] = ANGLEMOD(cl.viewangles[PITCH]); + if (cl.viewangles[YAW] >= 180) + cl.viewangles[YAW] -= 360; + if (cl.viewangles[PITCH] >= 180) + cl.viewangles[PITCH] -= 360; + cl.viewangles[PITCH] = bound(in_pitch_min.value, cl.viewangles[PITCH], in_pitch_max.value); + cl.viewangles[ROLL] = bound(-180, cl.viewangles[ROLL], 180); +} + +int cl_ignoremousemoves = 2; + +/* +================ +CL_Input + +Send the intended movement message to the server +================ +*/ +void CL_Input (void) +{ + float mx, my; + static float old_mouse_x = 0, old_mouse_y = 0; + + // clamp before the move to prevent starting with bad angles + CL_AdjustAngles (true); + + if(v_flipped.integer) + cl.viewangles[YAW] = -cl.viewangles[YAW]; + + // reset some of the command fields + cl.cmd.forwardmove = 0; + cl.cmd.sidemove = 0; + cl.cmd.upmove = 0; + + // get basic movement from keyboard + if (in_strafe.state & 1) + { + cl.cmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_right); + cl.cmd.sidemove -= cl_sidespeed.value * CL_KeyState (&in_left); + } + + cl.cmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_moveright); + cl.cmd.sidemove -= cl_sidespeed.value * CL_KeyState (&in_moveleft); + + cl.cmd.upmove += cl_upspeed.value * CL_KeyState (&in_up); + cl.cmd.upmove -= cl_upspeed.value * CL_KeyState (&in_down); + + if (! (in_klook.state & 1) ) + { + cl.cmd.forwardmove += cl_forwardspeed.value * CL_KeyState (&in_forward); + cl.cmd.forwardmove -= cl_backspeed.value * CL_KeyState (&in_back); + } + + // adjust for speed key + if (in_speed.state & 1) + { + cl.cmd.forwardmove *= cl_movespeedkey.value; + cl.cmd.sidemove *= cl_movespeedkey.value; + cl.cmd.upmove *= cl_movespeedkey.value; + } + + // allow mice or other external controllers to add to the move + IN_Move (); + + // send mouse move to csqc + if (cl.csqc_loaded && cl_csqc_generatemousemoveevents.integer) + { + if (cl.csqc_wantsmousemove) + { + // event type 3 is a DP_CSQC thing + static int oldwindowmouse[2]; + if (oldwindowmouse[0] != in_windowmouse_x || oldwindowmouse[1] != in_windowmouse_y) + { + CL_VM_InputEvent(3, in_windowmouse_x * vid_conwidth.integer / vid.width, in_windowmouse_y * vid_conheight.integer / vid.height); + oldwindowmouse[0] = in_windowmouse_x; + oldwindowmouse[1] = in_windowmouse_y; + } + } + else + { + if (in_mouse_x || in_mouse_y) + CL_VM_InputEvent(2, in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height); + } + } + + // apply m_accelerate if it is on + if(m_accelerate.value > 1) + { + static float averagespeed = 0; + float speed, f, mi, ma; + + speed = sqrt(in_mouse_x * in_mouse_x + in_mouse_y * in_mouse_y) / cl.realframetime; + if(m_accelerate_filter.value > 0) + f = bound(0, cl.realframetime / m_accelerate_filter.value, 1); + else + f = 1; + averagespeed = speed * f + averagespeed * (1 - f); + + mi = max(1, m_accelerate_minspeed.value); + ma = max(m_accelerate_minspeed.value + 1, m_accelerate_maxspeed.value); + + if(averagespeed <= mi) + { + f = 1; + } + else if(averagespeed >= ma) + { + f = m_accelerate.value; + } + else + { + f = averagespeed; + f = (f - mi) / (ma - mi) * (m_accelerate.value - 1) + 1; + } + + in_mouse_x *= f; + in_mouse_y *= f; + } + + // apply m_filter if it is on + mx = in_mouse_x; + my = in_mouse_y; + if (m_filter.integer) + { + in_mouse_x = (mx + old_mouse_x) * 0.5; + in_mouse_y = (my + old_mouse_y) * 0.5; + } + old_mouse_x = mx; + old_mouse_y = my; + + // ignore a mouse move if mouse was activated/deactivated this frame + if (cl_ignoremousemoves) + { + cl_ignoremousemoves--; + in_mouse_x = old_mouse_x = 0; + in_mouse_y = old_mouse_y = 0; + } + + // if not in menu, apply mouse move to viewangles/movement + if (!key_consoleactive && key_dest == key_game && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0) + { + float modulatedsensitivity = sensitivity.value * cl.sensitivityscale; + if (in_strafe.state & 1) + { + // strafing mode, all looking is movement + V_StopPitchDrift(); + cl.cmd.sidemove += m_side.value * in_mouse_x * modulatedsensitivity; + if (noclip_anglehack) + cl.cmd.upmove -= m_forward.value * in_mouse_y * modulatedsensitivity; + else + cl.cmd.forwardmove -= m_forward.value * in_mouse_y * modulatedsensitivity; + } + else if ((in_mlook.state & 1) || freelook.integer) + { + // mouselook, lookstrafe causes turning to become strafing + V_StopPitchDrift(); + if (lookstrafe.integer) + cl.cmd.sidemove += m_side.value * in_mouse_x * modulatedsensitivity; + else + cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * modulatedsensitivity * cl.viewzoom; + cl.viewangles[PITCH] += m_pitch.value * in_mouse_y * modulatedsensitivity * cl.viewzoom; + } + else + { + // non-mouselook, yaw turning and forward/back movement + cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * modulatedsensitivity * cl.viewzoom; + cl.cmd.forwardmove -= m_forward.value * in_mouse_y * modulatedsensitivity; + } + } + else // don't pitch drift when csqc is controlling the mouse + { + // mouse interacting with the scene, mostly stationary view + V_StopPitchDrift(); + // update prydon cursor + cl.cmd.cursor_screen[0] = in_windowmouse_x * 2.0 / vid.width - 1.0; + cl.cmd.cursor_screen[1] = in_windowmouse_y * 2.0 / vid.height - 1.0; + } + + if(v_flipped.integer) + { + cl.viewangles[YAW] = -cl.viewangles[YAW]; + cl.cmd.sidemove = -cl.cmd.sidemove; + } + + // clamp after the move to prevent rendering with bad angles + CL_AdjustAngles (false); + + if(cl_movecliptokeyboard.integer) + { + vec_t f = 1; + if (in_speed.state & 1) + f *= cl_movespeedkey.value; + if(cl_movecliptokeyboard.integer == 2) + { + // digital direction, analog amount + vec_t wishvel_x, wishvel_y; + wishvel_x = fabs(cl.cmd.forwardmove); + wishvel_y = fabs(cl.cmd.sidemove); + if(wishvel_x != 0 && wishvel_y != 0 && wishvel_x != wishvel_y) + { + vec_t wishspeed = sqrt(wishvel_x * wishvel_x + wishvel_y * wishvel_y); + if(wishvel_x >= 2 * wishvel_y) + { + // pure X motion + if(cl.cmd.forwardmove > 0) + cl.cmd.forwardmove = wishspeed; + else + cl.cmd.forwardmove = -wishspeed; + cl.cmd.sidemove = 0; + } + else if(wishvel_y >= 2 * wishvel_x) + { + // pure Y motion + cl.cmd.forwardmove = 0; + if(cl.cmd.sidemove > 0) + cl.cmd.sidemove = wishspeed; + else + cl.cmd.sidemove = -wishspeed; + } + else + { + // diagonal + if(cl.cmd.forwardmove > 0) + cl.cmd.forwardmove = 0.70710678118654752440 * wishspeed; + else + cl.cmd.forwardmove = -0.70710678118654752440 * wishspeed; + if(cl.cmd.sidemove > 0) + cl.cmd.sidemove = 0.70710678118654752440 * wishspeed; + else + cl.cmd.sidemove = -0.70710678118654752440 * wishspeed; + } + } + } + else if(cl_movecliptokeyboard.integer) + { + // digital direction, digital amount + if(cl.cmd.sidemove >= cl_sidespeed.value * f * 0.5) + cl.cmd.sidemove = cl_sidespeed.value * f; + else if(cl.cmd.sidemove <= -cl_sidespeed.value * f * 0.5) + cl.cmd.sidemove = -cl_sidespeed.value * f; + else + cl.cmd.sidemove = 0; + if(cl.cmd.forwardmove >= cl_forwardspeed.value * f * 0.5) + cl.cmd.forwardmove = cl_forwardspeed.value * f; + else if(cl.cmd.forwardmove <= -cl_backspeed.value * f * 0.5) + cl.cmd.forwardmove = -cl_backspeed.value * f; + else + cl.cmd.forwardmove = 0; + } + } +} + +#include "cl_collision.h" + +static void CL_UpdatePrydonCursor(void) +{ + vec3_t temp; + + if (cl_prydoncursor.integer <= 0) + VectorClear(cl.cmd.cursor_screen); + + /* + if (cl.cmd.cursor_screen[0] < -1) + { + cl.viewangles[YAW] -= m_yaw.value * (cl.cmd.cursor_screen[0] - -1) * vid.width * sensitivity.value * cl.viewzoom; + cl.cmd.cursor_screen[0] = -1; + } + if (cl.cmd.cursor_screen[0] > 1) + { + cl.viewangles[YAW] -= m_yaw.value * (cl.cmd.cursor_screen[0] - 1) * vid.width * sensitivity.value * cl.viewzoom; + cl.cmd.cursor_screen[0] = 1; + } + if (cl.cmd.cursor_screen[1] < -1) + { + cl.viewangles[PITCH] += m_pitch.value * (cl.cmd.cursor_screen[1] - -1) * vid.height * sensitivity.value * cl.viewzoom; + cl.cmd.cursor_screen[1] = -1; + } + if (cl.cmd.cursor_screen[1] > 1) + { + cl.viewangles[PITCH] += m_pitch.value * (cl.cmd.cursor_screen[1] - 1) * vid.height * sensitivity.value * cl.viewzoom; + cl.cmd.cursor_screen[1] = 1; + } + */ + cl.cmd.cursor_screen[0] = bound(-1, cl.cmd.cursor_screen[0], 1); + cl.cmd.cursor_screen[1] = bound(-1, cl.cmd.cursor_screen[1], 1); + cl.cmd.cursor_screen[2] = 1; + + // calculate current view matrix + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, cl.cmd.cursor_start); + // calculate direction vector of cursor in viewspace by using frustum slopes + VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, (v_flipped.integer ? -1 : 1) * cl.cmd.cursor_screen[0] * -r_refdef.view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_refdef.view.frustum_y * 1000000); + Matrix4x4_Transform(&r_refdef.view.matrix, temp, cl.cmd.cursor_end); + // trace from view origin to the cursor + if (cl_prydoncursor_notrace.integer) + { + cl.cmd.cursor_fraction = 1.0f; + VectorCopy(cl.cmd.cursor_end, cl.cmd.cursor_impact); + VectorClear(cl.cmd.cursor_normal); + cl.cmd.cursor_entitynumber = 0; + } + else + cl.cmd.cursor_fraction = CL_SelectTraceLine(cl.cmd.cursor_start, cl.cmd.cursor_end, cl.cmd.cursor_impact, cl.cmd.cursor_normal, &cl.cmd.cursor_entitynumber, (chase_active.integer || cl.intermission) ? &cl.entities[cl.playerentity].render : NULL); +} + +#define NUMOFFSETS 27 +static vec3_t offsets[NUMOFFSETS] = +{ +// 1 no nudge (just return the original if this test passes) + { 0.000, 0.000, 0.000}, +// 6 simple nudges + { 0.000, 0.000, 0.125}, { 0.000, 0.000, -0.125}, + {-0.125, 0.000, 0.000}, { 0.125, 0.000, 0.000}, + { 0.000, -0.125, 0.000}, { 0.000, 0.125, 0.000}, +// 4 diagonal flat nudges + {-0.125, -0.125, 0.000}, { 0.125, -0.125, 0.000}, + {-0.125, 0.125, 0.000}, { 0.125, 0.125, 0.000}, +// 8 diagonal upward nudges + {-0.125, 0.000, 0.125}, { 0.125, 0.000, 0.125}, + { 0.000, -0.125, 0.125}, { 0.000, 0.125, 0.125}, + {-0.125, -0.125, 0.125}, { 0.125, -0.125, 0.125}, + {-0.125, 0.125, 0.125}, { 0.125, 0.125, 0.125}, +// 8 diagonal downward nudges + {-0.125, 0.000, -0.125}, { 0.125, 0.000, -0.125}, + { 0.000, -0.125, -0.125}, { 0.000, 0.125, -0.125}, + {-0.125, -0.125, -0.125}, { 0.125, -0.125, -0.125}, + {-0.125, 0.125, -0.125}, { 0.125, 0.125, -0.125}, +}; + +static qboolean CL_ClientMovement_Unstick(cl_clientmovement_state_t *s) +{ + int i; + vec3_t neworigin; + for (i = 0;i < NUMOFFSETS;i++) + { + VectorAdd(offsets[i], s->origin, neworigin); + if (!CL_TraceBox(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, true).startsolid) + { + VectorCopy(neworigin, s->origin); + return true; + } + } + // if all offsets failed, give up + return false; +} + +static void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s) +{ + vec_t f; + vec3_t origin1, origin2; + trace_t trace; + + // make sure player is not stuck + CL_ClientMovement_Unstick(s); + + // set crouched + if (s->cmd.crouch) + { + // wants to crouch, this always works.. + if (!s->crouched) + s->crouched = true; + } + else + { + // wants to stand, if currently crouching we need to check for a + // low ceiling first + if (s->crouched) + { + trace = CL_TraceBox(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, true); + if (!trace.startsolid) + s->crouched = false; + } + } + if (s->crouched) + { + VectorCopy(cl.playercrouchmins, s->mins); + VectorCopy(cl.playercrouchmaxs, s->maxs); + } + else + { + VectorCopy(cl.playerstandmins, s->mins); + VectorCopy(cl.playerstandmaxs, s->maxs); + } + + // set onground + VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + 1); + VectorSet(origin2, s->origin[0], s->origin[1], s->origin[2] - 1); // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :) + trace = CL_TraceBox(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, true); + if(trace.fraction < 1 && trace.plane.normal[2] > 0.7) + { + s->onground = true; + + // this code actually "predicts" an impact; so let's clip velocity first + f = DotProduct(s->velocity, trace.plane.normal); + if(f < 0) // only if moving downwards actually + VectorMA(s->velocity, -f, trace.plane.normal, s->velocity); + } + else + s->onground = false; + + // set watertype/waterlevel + VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + s->mins[2] + 1); + s->waterlevel = WATERLEVEL_NONE; + s->watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s->self, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK; + if (s->watertype) + { + s->waterlevel = WATERLEVEL_WETFEET; + origin1[2] = s->origin[2] + (s->mins[2] + s->maxs[2]) * 0.5f; + if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s->self, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) + { + s->waterlevel = WATERLEVEL_SWIMMING; + origin1[2] = s->origin[2] + 22; + if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s->self, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) + s->waterlevel = WATERLEVEL_SUBMERGED; + } + } + + // water jump prediction + if (s->onground || s->velocity[2] <= 0 || s->waterjumptime <= 0) + s->waterjumptime = 0; +} + +static void CL_ClientMovement_Move(cl_clientmovement_state_t *s) +{ + int bump; + double t; + vec_t f; + vec3_t neworigin; + vec3_t currentorigin2; + vec3_t neworigin2; + vec3_t primalvelocity; + trace_t trace; + trace_t trace2; + trace_t trace3; + CL_ClientMovement_UpdateStatus(s); + VectorCopy(s->velocity, primalvelocity); + for (bump = 0, t = s->cmd.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++) + { + VectorMA(s->origin, t, s->velocity, neworigin); + trace = CL_TraceBox(s->origin, s->mins, s->maxs, neworigin, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, true); + if (trace.fraction < 1 && trace.plane.normal[2] == 0) + { + // may be a step or wall, try stepping up + // first move forward at a higher level + VectorSet(currentorigin2, s->origin[0], s->origin[1], s->origin[2] + cl.movevars_stepheight); + VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + cl.movevars_stepheight); + trace2 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, true); + if (!trace2.startsolid) + { + // then move down from there + VectorCopy(trace2.endpos, currentorigin2); + VectorSet(neworigin2, trace2.endpos[0], trace2.endpos[1], s->origin[2]); + trace3 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, true); + //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]); + // accept the new trace if it made some progress + if (fabs(trace3.endpos[0] - trace.endpos[0]) >= 0.03125 || fabs(trace3.endpos[1] - trace.endpos[1]) >= 0.03125) + { + trace = trace2; + VectorCopy(trace3.endpos, trace.endpos); + } + } + } + + // check if it moved at all + if (trace.fraction >= 0.001) + VectorCopy(trace.endpos, s->origin); + + // check if it moved all the way + if (trace.fraction == 1) + break; + + // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate + // I'm pretty sure I commented it out solely because it seemed redundant + // this got commented out in a change that supposedly makes the code match QW better + // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block + if (trace.plane.normal[2] > 0.7) + s->onground = true; + + t -= t * trace.fraction; + + f = DotProduct(s->velocity, trace.plane.normal); + VectorMA(s->velocity, -f, trace.plane.normal, s->velocity); + } + if (s->waterjumptime > 0) + VectorCopy(primalvelocity, s->velocity); +} + + +static void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s) +{ + vec_t wishspeed; + vec_t f; + vec3_t wishvel; + vec3_t wishdir; + + // water jump only in certain situations + // this mimics quakeworld code + if (s->cmd.jump && s->waterlevel == 2 && s->velocity[2] >= -180) + { + vec3_t forward; + vec3_t yawangles; + vec3_t spot; + VectorSet(yawangles, 0, s->cmd.viewangles[1], 0); + AngleVectors(yawangles, forward, NULL, NULL); + VectorMA(s->origin, 24, forward, spot); + spot[2] += 8; + if (CL_TracePoint(spot, MOVE_NOMONSTERS, s->self, 0, true, false, NULL, false).startsolid) + { + spot[2] += 24; + if (!CL_TracePoint(spot, MOVE_NOMONSTERS, s->self, 0, true, false, NULL, false).startsolid) + { + VectorScale(forward, 50, s->velocity); + s->velocity[2] = 310; + s->waterjumptime = 2; + s->onground = false; + s->cmd.canjump = false; + } + } + } + + if (!(s->cmd.forwardmove*s->cmd.forwardmove + s->cmd.sidemove*s->cmd.sidemove + s->cmd.upmove*s->cmd.upmove)) + { + // drift towards bottom + VectorSet(wishvel, 0, 0, -60); + } + else + { + // swim + vec3_t forward; + vec3_t right; + vec3_t up; + // calculate movement vector + AngleVectors(s->cmd.viewangles, forward, right, up); + VectorSet(up, 0, 0, 1); + VectorMAMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, s->cmd.upmove, up, wishvel); + } + + // split wishvel into wishspeed and wishdir + wishspeed = VectorLength(wishvel); + if (wishspeed) + VectorScale(wishvel, 1 / wishspeed, wishdir); + else + VectorSet( wishdir, 0.0, 0.0, 0.0 ); + wishspeed = min(wishspeed, cl.movevars_maxspeed) * 0.7; + + if (s->crouched) + wishspeed *= 0.5; + + if (s->waterjumptime <= 0) + { + // water friction + f = 1 - s->cmd.frametime * cl.movevars_waterfriction * (cls.protocol == PROTOCOL_QUAKEWORLD ? s->waterlevel : 1); + f = bound(0, f, 1); + VectorScale(s->velocity, f, s->velocity); + + // water acceleration + f = wishspeed - DotProduct(s->velocity, wishdir); + if (f > 0) + { + f = min(cl.movevars_wateraccelerate * s->cmd.frametime * wishspeed, f); + VectorMA(s->velocity, f, wishdir, s->velocity); + } + + // holding jump button swims upward slowly + if (s->cmd.jump) + { + if (s->watertype & SUPERCONTENTS_LAVA) + s->velocity[2] = 50; + else if (s->watertype & SUPERCONTENTS_SLIME) + s->velocity[2] = 80; + else + { + if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + s->velocity[2] = 200; + else + s->velocity[2] = 100; + } + } + } + + CL_ClientMovement_Move(s); +} + +static vec_t CL_IsMoveInDirection(vec_t forward, vec_t side, vec_t angle) +{ + if(forward == 0 && side == 0) + return 0; // avoid division by zero + angle -= RAD2DEG(atan2(side, forward)); + angle = (ANGLEMOD(angle + 180) - 180) / 45; + if(angle > 1) + return 0; + if(angle < -1) + return 0; + return 1 - fabs(angle); +} + +static vec_t CL_GeomLerp(vec_t a, vec_t lerp, vec_t b) +{ + if(a == 0) + { + if(lerp < 1) + return 0; + else + return b; + } + if(b == 0) + { + if(lerp > 0) + return 0; + else + return a; + } + return a * pow(fabs(b / a), lerp); +} + +static void CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed) +{ + vec_t zspeed, speed, dot, k; + +#if 0 + // this doesn't play well with analog input + if(s->cmd.forwardmove == 0 || s->cmd.sidemove != 0) + return; + k = 32; +#else + k = 32 * (2 * CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, 0) - 1); + if(k <= 0) + return; +#endif + + k *= bound(0, wishspeed / cl.movevars_maxairspeed, 1); + + zspeed = s->velocity[2]; + s->velocity[2] = 0; + speed = VectorNormalizeLength(s->velocity); + + dot = DotProduct(s->velocity, wishdir); + + if(dot > 0) { // we can't change direction while slowing down + k *= pow(dot, cl.movevars_aircontrol_power)*s->cmd.frametime; + speed = max(0, speed - cl.movevars_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32); + k *= cl.movevars_aircontrol; + VectorMAM(speed, s->velocity, k, wishdir, s->velocity); + VectorNormalize(s->velocity); + } + + VectorScale(s->velocity, speed, s->velocity); + s->velocity[2] = zspeed; +} + +static float CL_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor) +{ + return + (accelqw < 0 ? -1 : +1) + * + bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1); +} + +static void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t wishspeed0, vec_t accel, vec_t accelqw, vec_t stretchfactor, vec_t sidefric, vec_t speedlimit) +{ + vec_t vel_straight; + vec_t vel_z; + vec3_t vel_perpend; + vec_t step; + vec3_t vel_xy; + vec_t vel_xy_current; + vec_t vel_xy_backward, vel_xy_forward; + vec_t speedclamp; + + if(stretchfactor > 0) + speedclamp = stretchfactor; + else if(accelqw < 0) + speedclamp = 1; + else + speedclamp = -1; // no clamping + + if(accelqw < 0) + accelqw = -accelqw; + + if(cl.moveflags & MOVEFLAG_Q2AIRACCELERATE) + wishspeed0 = wishspeed; // don't need to emulate this Q1 bug + + vel_straight = DotProduct(s->velocity, wishdir); + vel_z = s->velocity[2]; + VectorCopy(s->velocity, vel_xy); vel_xy[2] -= vel_z; + VectorMA(vel_xy, -vel_straight, wishdir, vel_perpend); + + step = accel * s->cmd.frametime * wishspeed0; + + vel_xy_current = VectorLength(vel_xy); + if(speedlimit > 0) + accelqw = CL_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed)); + vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw); + vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw); + if(vel_xy_backward < 0) + vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards + + vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw); + + if(sidefric < 0 && VectorLength2(vel_perpend)) + // negative: only apply so much sideways friction to stay below the speed you could get by "braking" + { + vec_t f, fmin; + f = max(0, 1 + s->cmd.frametime * wishspeed * sidefric); + fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / VectorLength2(vel_perpend); + // assume: fmin > 1 + // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend + // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend + // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy + // obviously, this cannot be + if(fmin <= 0) + VectorScale(vel_perpend, f, vel_perpend); + else + { + fmin = sqrt(fmin); + VectorScale(vel_perpend, max(fmin, f), vel_perpend); + } + } + else + VectorScale(vel_perpend, max(0, 1 - s->cmd.frametime * wishspeed * sidefric), vel_perpend); + + VectorMA(vel_perpend, vel_straight, wishdir, s->velocity); + + if(speedclamp >= 0) + { + vec_t vel_xy_preclamp; + vel_xy_preclamp = VectorLength(s->velocity); + if(vel_xy_preclamp > 0) // prevent division by zero + { + vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp; + if(vel_xy_current < vel_xy_preclamp) + VectorScale(s->velocity, (vel_xy_current / vel_xy_preclamp), s->velocity); + } + } + + s->velocity[2] += vel_z; +} + +static void CL_ClientMovement_Physics_PM_AirAccelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed) +{ + vec3_t curvel, wishvel, acceldir, curdir; + float addspeed, accelspeed, curspeed; + float dot; + + float airforwardaccel = cl.movevars_warsowbunny_airforwardaccel; + float bunnyaccel = cl.movevars_warsowbunny_accel; + float bunnytopspeed = cl.movevars_warsowbunny_topspeed; + float turnaccel = cl.movevars_warsowbunny_turnaccel; + float backtosideratio = cl.movevars_warsowbunny_backtosideratio; + + if( !wishspeed ) + return; + + VectorCopy( s->velocity, curvel ); + curvel[2] = 0; + curspeed = VectorLength( curvel ); + + if( wishspeed > curspeed * 1.01f ) + { + float accelspeed = curspeed + airforwardaccel * cl.movevars_maxairspeed * s->cmd.frametime; + if( accelspeed < wishspeed ) + wishspeed = accelspeed; + } + else + { + float f = ( bunnytopspeed - curspeed ) / ( bunnytopspeed - cl.movevars_maxairspeed ); + if( f < 0 ) + f = 0; + wishspeed = max( curspeed, cl.movevars_maxairspeed ) + bunnyaccel * f * cl.movevars_maxairspeed * s->cmd.frametime; + } + VectorScale( wishdir, wishspeed, wishvel ); + VectorSubtract( wishvel, curvel, acceldir ); + addspeed = VectorNormalizeLength( acceldir ); + + accelspeed = turnaccel * cl.movevars_maxairspeed /* wishspeed */ * s->cmd.frametime; + if( accelspeed > addspeed ) + accelspeed = addspeed; + + if( backtosideratio < 1.0f ) + { + VectorNormalize2( curvel, curdir ); + dot = DotProduct( acceldir, curdir ); + if( dot < 0 ) + VectorMA( acceldir, -( 1.0f - backtosideratio ) * dot, curdir, acceldir ); + } + + VectorMA( s->velocity, accelspeed, acceldir, s->velocity ); +} + +static void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s) +{ + vec_t friction; + vec_t wishspeed; + vec_t addspeed; + vec_t accelspeed; + vec_t f; + vec_t gravity; + vec3_t forward; + vec3_t right; + vec3_t up; + vec3_t wishvel; + vec3_t wishdir; + vec3_t yawangles; + trace_t trace; + + // jump if on ground with jump button pressed but only if it has been + // released at least once since the last jump + if (s->cmd.jump) + { + if (s->onground && (s->cmd.canjump || !cl_movement_track_canjump.integer)) + { + s->velocity[2] += cl.movevars_jumpvelocity; + s->onground = false; + s->cmd.canjump = false; + } + } + else + s->cmd.canjump = true; + + // calculate movement vector + VectorSet(yawangles, 0, s->cmd.viewangles[1], 0); + AngleVectors(yawangles, forward, right, up); + VectorMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, wishvel); + + // split wishvel into wishspeed and wishdir + wishspeed = VectorLength(wishvel); + if (wishspeed) + VectorScale(wishvel, 1 / wishspeed, wishdir); + else + VectorSet( wishdir, 0.0, 0.0, 0.0 ); + // check if onground + if (s->onground) + { + wishspeed = min(wishspeed, cl.movevars_maxspeed); + if (s->crouched) + wishspeed *= 0.5; + + // apply edge friction + f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]); + if (f > 0) + { + friction = cl.movevars_friction; + if (cl.movevars_edgefriction != 1) + { + vec3_t neworigin2; + vec3_t neworigin3; + // note: QW uses the full player box for the trace, and yet still + // uses s->origin[2] + s->mins[2], which is clearly an bug, but + // this mimics it for compatibility + VectorSet(neworigin2, s->origin[0] + s->velocity[0]*(16/f), s->origin[1] + s->velocity[1]*(16/f), s->origin[2] + s->mins[2]); + VectorSet(neworigin3, neworigin2[0], neworigin2[1], neworigin2[2] - 34); + if (cls.protocol == PROTOCOL_QUAKEWORLD) + trace = CL_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, true); + else + trace = CL_TraceLine(neworigin2, neworigin3, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, true, false); + if (trace.fraction == 1 && !trace.startsolid) + friction *= cl.movevars_edgefriction; + } + // apply ground friction + f = 1 - s->cmd.frametime * friction * ((f < cl.movevars_stopspeed) ? (cl.movevars_stopspeed / f) : 1); + f = max(f, 0); + VectorScale(s->velocity, f, s->velocity); + } + addspeed = wishspeed - DotProduct(s->velocity, wishdir); + if (addspeed > 0) + { + accelspeed = min(cl.movevars_accelerate * s->cmd.frametime * wishspeed, addspeed); + VectorMA(s->velocity, accelspeed, wishdir, s->velocity); + } + gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime; + if(!(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND)) + { + if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) + s->velocity[2] -= gravity * 0.5f; + else + s->velocity[2] -= gravity; + } + if (cls.protocol == PROTOCOL_QUAKEWORLD) + s->velocity[2] = 0; + if (VectorLength2(s->velocity)) + CL_ClientMovement_Move(s); + if(!(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !s->onground) + { + if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) + s->velocity[2] -= gravity * 0.5f; + } + } + else + { + if (s->waterjumptime <= 0) + { + // apply air speed limit + vec_t accel, wishspeed0, wishspeed2, accelqw, strafity; + qboolean accelerating; + + accelqw = cl.movevars_airaccel_qw; + wishspeed0 = wishspeed; + wishspeed = min(wishspeed, cl.movevars_maxairspeed); + if (s->crouched) + wishspeed *= 0.5; + accel = cl.movevars_airaccelerate; + + accelerating = (DotProduct(s->velocity, wishdir) > 0); + wishspeed2 = wishspeed; + + // CPM: air control + if(cl.movevars_airstopaccelerate != 0) + { + vec3_t curdir; + curdir[0] = s->velocity[0]; + curdir[1] = s->velocity[1]; + curdir[2] = 0; + VectorNormalize(curdir); + accel = accel + (cl.movevars_airstopaccelerate - accel) * max(0, -DotProduct(curdir, wishdir)); + } + strafity = CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, -90) + CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, +90); // if one is nonzero, other is always zero + if(cl.movevars_maxairstrafespeed) + wishspeed = min(wishspeed, CL_GeomLerp(cl.movevars_maxairspeed, strafity, cl.movevars_maxairstrafespeed)); + if(cl.movevars_airstrafeaccelerate) + accel = CL_GeomLerp(cl.movevars_airaccelerate, strafity, cl.movevars_airstrafeaccelerate); + if(cl.movevars_airstrafeaccel_qw) + accelqw = + (((strafity > 0.5 ? cl.movevars_airstrafeaccel_qw : cl.movevars_airaccel_qw) >= 0) ? +1 : -1) + * + (1 - CL_GeomLerp(1 - fabs(cl.movevars_airaccel_qw), strafity, 1 - fabs(cl.movevars_airstrafeaccel_qw))); + // !CPM + + if(cl.movevars_warsowbunny_turnaccel && accelerating && s->cmd.sidemove == 0 && s->cmd.forwardmove != 0) + CL_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2); + else + CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, cl.movevars_airaccel_qw_stretchfactor, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed, cl.movevars_airspeedlimit_nonqw); + + if(cl.movevars_aircontrol) + CL_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2); + } + gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime; + if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) + s->velocity[2] -= gravity * 0.5f; + else + s->velocity[2] -= gravity; + CL_ClientMovement_Move(s); + if(!(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !s->onground) + { + if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) + s->velocity[2] -= gravity * 0.5f; + } + } +} + +static void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s) +{ + //Con_Printf(" %f", frametime); + if (!s->cmd.jump) + s->cmd.canjump = true; + s->waterjumptime -= s->cmd.frametime; + CL_ClientMovement_UpdateStatus(s); + if (s->waterlevel >= WATERLEVEL_SWIMMING) + CL_ClientMovement_Physics_Swim(s); + else + CL_ClientMovement_Physics_Walk(s); +} + +extern cvar_t slowmo; +void CL_UpdateMoveVars(void) +{ + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + cl.moveflags = 0; + } + else if (cl.stats[STAT_MOVEVARS_TICRATE]) + { + cl.moveflags = cl.stats[STAT_MOVEFLAGS]; + cl.movevars_ticrate = cl.statsf[STAT_MOVEVARS_TICRATE]; + cl.movevars_timescale = cl.statsf[STAT_MOVEVARS_TIMESCALE]; + cl.movevars_gravity = cl.statsf[STAT_MOVEVARS_GRAVITY]; + cl.movevars_stopspeed = cl.statsf[STAT_MOVEVARS_STOPSPEED] ; + cl.movevars_maxspeed = cl.statsf[STAT_MOVEVARS_MAXSPEED]; + cl.movevars_spectatormaxspeed = cl.statsf[STAT_MOVEVARS_SPECTATORMAXSPEED]; + cl.movevars_accelerate = cl.statsf[STAT_MOVEVARS_ACCELERATE]; + cl.movevars_airaccelerate = cl.statsf[STAT_MOVEVARS_AIRACCELERATE]; + cl.movevars_wateraccelerate = cl.statsf[STAT_MOVEVARS_WATERACCELERATE]; + cl.movevars_entgravity = cl.statsf[STAT_MOVEVARS_ENTGRAVITY]; + cl.movevars_jumpvelocity = cl.statsf[STAT_MOVEVARS_JUMPVELOCITY]; + cl.movevars_edgefriction = cl.statsf[STAT_MOVEVARS_EDGEFRICTION]; + cl.movevars_maxairspeed = cl.statsf[STAT_MOVEVARS_MAXAIRSPEED]; + cl.movevars_stepheight = cl.statsf[STAT_MOVEVARS_STEPHEIGHT]; + cl.movevars_airaccel_qw = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW]; + cl.movevars_airaccel_qw_stretchfactor = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR]; + cl.movevars_airaccel_sideways_friction = cl.statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION]; + cl.movevars_friction = cl.statsf[STAT_MOVEVARS_FRICTION]; + cl.movevars_wallfriction = cl.statsf[STAT_MOVEVARS_WALLFRICTION]; + cl.movevars_waterfriction = cl.statsf[STAT_MOVEVARS_WATERFRICTION]; + cl.movevars_airstopaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTOPACCELERATE]; + cl.movevars_airstrafeaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE]; + cl.movevars_maxairstrafespeed = cl.statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED]; + cl.movevars_airstrafeaccel_qw = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW]; + cl.movevars_aircontrol = cl.statsf[STAT_MOVEVARS_AIRCONTROL]; + cl.movevars_aircontrol_power = cl.statsf[STAT_MOVEVARS_AIRCONTROL_POWER]; + cl.movevars_aircontrol_penalty = cl.statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY]; + cl.movevars_warsowbunny_airforwardaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL]; + cl.movevars_warsowbunny_accel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL]; + cl.movevars_warsowbunny_topspeed = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED]; + cl.movevars_warsowbunny_turnaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL]; + cl.movevars_warsowbunny_backtosideratio = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO]; + cl.movevars_airspeedlimit_nonqw = cl.statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW]; + } + else + { + cl.moveflags = 0; + cl.movevars_ticrate = (cls.demoplayback ? 1.0f : slowmo.value) / bound(1.0f, cl_netfps.value, 1000.0f); + cl.movevars_timescale = (cls.demoplayback ? 1.0f : slowmo.value); + cl.movevars_gravity = sv_gravity.value; + cl.movevars_stopspeed = cl_movement_stopspeed.value; + cl.movevars_maxspeed = cl_movement_maxspeed.value; + cl.movevars_spectatormaxspeed = cl_movement_maxspeed.value; + cl.movevars_accelerate = cl_movement_accelerate.value; + cl.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value; + cl.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value; + cl.movevars_friction = cl_movement_friction.value; + cl.movevars_wallfriction = cl_movement_wallfriction.value; + cl.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value; + cl.movevars_entgravity = 1; + cl.movevars_jumpvelocity = cl_movement_jumpvelocity.value; + cl.movevars_edgefriction = cl_movement_edgefriction.value; + cl.movevars_maxairspeed = cl_movement_maxairspeed.value; + cl.movevars_stepheight = cl_movement_stepheight.value; + cl.movevars_airaccel_qw = cl_movement_airaccel_qw.value; + cl.movevars_airaccel_qw_stretchfactor = 0; + cl.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value; + cl.movevars_airstopaccelerate = 0; + cl.movevars_airstrafeaccelerate = 0; + cl.movevars_maxairstrafespeed = 0; + cl.movevars_airstrafeaccel_qw = 0; + cl.movevars_aircontrol = 0; + cl.movevars_aircontrol_power = 2; + cl.movevars_aircontrol_penalty = 0; + cl.movevars_warsowbunny_airforwardaccel = 0; + cl.movevars_warsowbunny_accel = 0; + cl.movevars_warsowbunny_topspeed = 0; + cl.movevars_warsowbunny_turnaccel = 0; + cl.movevars_warsowbunny_backtosideratio = 0; + cl.movevars_airspeedlimit_nonqw = 0; + } + + if(!(cl.moveflags & MOVEFLAG_VALID)) + { + if(gamemode == GAME_NEXUIZ) + cl.moveflags = MOVEFLAG_Q2AIRACCELERATE; + } + + if(cl.movevars_aircontrol_power <= 0) + cl.movevars_aircontrol_power = 2; // CPMA default +} + +void CL_ClientMovement_PlayerMove_Frame(cl_clientmovement_state_t *s) +{ + // if a move is more than 50ms, do it as two moves (matching qwsv) + //Con_Printf("%i ", s.cmd.msec); + if(s->cmd.frametime > 0.0005) + { + if (s->cmd.frametime > 0.05) + { + s->cmd.frametime /= 2; + CL_ClientMovement_PlayerMove(s); + } + CL_ClientMovement_PlayerMove(s); + } + else + { + // we REALLY need this handling to happen, even if the move is not executed + if (!s->cmd.jump) + s->cmd.canjump = true; + } +} + +void CL_ClientMovement_Replay(void) +{ + int i; + double totalmovemsec; + cl_clientmovement_state_t s; + + VectorCopy(cl.mvelocity[0], cl.movement_velocity); + + if (cl.movement_predicted && !cl.movement_replay) + return; + + if (!cl_movement_replay.integer) + return; + + // set up starting state for the series of moves + memset(&s, 0, sizeof(s)); + VectorCopy(cl.entities[cl.playerentity].state_current.origin, s.origin); + VectorCopy(cl.mvelocity[0], s.velocity); + s.crouched = true; // will be updated on first move + //Con_Printf("movement replay starting org %f %f %f vel %f %f %f\n", s.origin[0], s.origin[1], s.origin[2], s.velocity[0], s.velocity[1], s.velocity[2]); + + totalmovemsec = 0; + for (i = 0;i < CL_MAX_USERCMDS;i++) + if (cl.movecmd[i].sequence > cls.servermovesequence) + totalmovemsec += cl.movecmd[i].msec; + cl.movement_predicted = totalmovemsec >= cl_movement_minping.value && cls.servermovesequence && (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission); + //Con_Printf("%i = %.0f >= %.0f && %i && (%i && %i && %i == %i && %i > 0 && %i\n", cl.movement_predicted, totalmovemsec, cl_movement_minping.value, cls.servermovesequence, cl_movement.integer, !cls.demoplayback, cls.signon, SIGNONS, cl.stats[STAT_HEALTH], !cl.intermission); + if (cl.movement_predicted) + { + //Con_Printf("%ims\n", cl.movecmd[0].msec); + + // replay the input queue to predict current location + // note: this relies on the fact there's always one queue item at the end + + // find how many are still valid + for (i = 0;i < CL_MAX_USERCMDS;i++) + if (cl.movecmd[i].sequence <= cls.servermovesequence) + break; + // now walk them in oldest to newest order + for (i--;i >= 0;i--) + { + s.cmd = cl.movecmd[i]; + if (i < CL_MAX_USERCMDS - 1) + s.cmd.canjump = cl.movecmd[i+1].canjump; + + CL_ClientMovement_PlayerMove_Frame(&s); + + cl.movecmd[i].canjump = s.cmd.canjump; + } + //Con_Printf("\n"); + CL_ClientMovement_UpdateStatus(&s); + } + else + { + // get the first movement queue entry to know whether to crouch and such + s.cmd = cl.movecmd[0]; + } + + if (!cls.demoplayback) // for bob, speedometer + { + cl.movement_replay = false; + // update the interpolation target position and velocity + VectorCopy(s.origin, cl.movement_origin); + VectorCopy(s.velocity, cl.movement_velocity); + } + + // update the onground flag if appropriate + if (cl.movement_predicted) + { + // when predicted we simply set the flag according to the UpdateStatus + cl.onground = s.onground; + } + else + { + // when not predicted, cl.onground is cleared by cl_parse.c each time + // an update packet is received, but can be forced on here to hide + // server inconsistencies in the onground flag + // (which mostly occur when stepping up stairs at very high framerates + // where after the step up the move continues forward and not + // downward so the ground is not detected) + // + // such onground inconsistencies can cause jittery gun bobbing and + // stair smoothing, so we set onground if UpdateStatus says so + if (s.onground) + cl.onground = true; + } +} + +static void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to) +{ + int bits; + + bits = 0; + if (to->viewangles[0] != from->viewangles[0]) + bits |= QW_CM_ANGLE1; + if (to->viewangles[1] != from->viewangles[1]) + bits |= QW_CM_ANGLE2; + if (to->viewangles[2] != from->viewangles[2]) + bits |= QW_CM_ANGLE3; + if (to->forwardmove != from->forwardmove) + bits |= QW_CM_FORWARD; + if (to->sidemove != from->sidemove) + bits |= QW_CM_SIDE; + if (to->upmove != from->upmove) + bits |= QW_CM_UP; + if (to->buttons != from->buttons) + bits |= QW_CM_BUTTONS; + if (to->impulse != from->impulse) + bits |= QW_CM_IMPULSE; + + MSG_WriteByte(buf, bits); + if (bits & QW_CM_ANGLE1) + MSG_WriteAngle16i(buf, to->viewangles[0]); + if (bits & QW_CM_ANGLE2) + MSG_WriteAngle16i(buf, to->viewangles[1]); + if (bits & QW_CM_ANGLE3) + MSG_WriteAngle16i(buf, to->viewangles[2]); + if (bits & QW_CM_FORWARD) + MSG_WriteShort(buf, (short) to->forwardmove); + if (bits & QW_CM_SIDE) + MSG_WriteShort(buf, (short) to->sidemove); + if (bits & QW_CM_UP) + MSG_WriteShort(buf, (short) to->upmove); + if (bits & QW_CM_BUTTONS) + MSG_WriteByte(buf, to->buttons); + if (bits & QW_CM_IMPULSE) + MSG_WriteByte(buf, to->impulse); + MSG_WriteByte(buf, to->msec); +} + +void CL_NewFrameReceived(int num) +{ + if (developer_networkentities.integer >= 10) + Con_Printf("recv: svc_entities %i\n", num); + cl.latestframenums[cl.latestframenumsposition] = num; + cl.latestsendnums[cl.latestframenumsposition] = cl.cmd.sequence; + cl.latestframenumsposition = (cl.latestframenumsposition + 1) % LATESTFRAMENUMS; +} + +void CL_RotateMoves(const matrix4x4_t *m) +{ + // rotate viewangles in all previous moves + vec3_t v; + vec3_t f, r, u; + int i; + for (i = 0;i < CL_MAX_USERCMDS;i++) + { + if (cl.movecmd[i].sequence > cls.servermovesequence) + { + usercmd_t *c = &cl.movecmd[i]; + AngleVectors(c->viewangles, f, r, u); + Matrix4x4_Transform(m, f, v); VectorCopy(v, f); + Matrix4x4_Transform(m, u, v); VectorCopy(v, u); + AnglesFromVectors(c->viewangles, f, u, false); + } + } +} + +/* +============== +CL_SendMove +============== +*/ +usercmd_t nullcmd; // for delta compression of qw moves +void CL_SendMove(void) +{ + int i, j, packetloss; + int checksumindex; + int bits; + int maxusercmds; + usercmd_t *cmd; + sizebuf_t buf; + unsigned char data[1024]; + double packettime; + int msecdelta; + qboolean quemove; + qboolean important; + + // if playing a demo, do nothing + if (!cls.netcon) + return; + + // we don't que moves during a lag spike (potential network timeout) + quemove = realtime - cl.last_received_message < cl_movement_nettimeout.value; + + // we build up cl.cmd and then decide whether to send or not + // we store this into cl.movecmd[0] for prediction each frame even if we + // do not send, to make sure that prediction is instant + cl.cmd.time = cl.time; + cl.cmd.sequence = cls.netcon->outgoing_unreliable_sequence; + + // set button bits + // LordHavoc: added 6 new buttons and use and chat buttons, and prydon cursor active button + bits = 0; + if (in_attack.state & 3) bits |= 1; + if (in_jump.state & 3) bits |= 2; + if (in_button3.state & 3) bits |= 4; + if (in_button4.state & 3) bits |= 8; + if (in_button5.state & 3) bits |= 16; + if (in_button6.state & 3) bits |= 32; + if (in_button7.state & 3) bits |= 64; + if (in_button8.state & 3) bits |= 128; + if (in_use.state & 3) bits |= 256; + if (key_dest != key_game || key_consoleactive) bits |= 512; + if (cl_prydoncursor.integer > 0) bits |= 1024; + if (in_button9.state & 3) bits |= 2048; + if (in_button10.state & 3) bits |= 4096; + if (in_button11.state & 3) bits |= 8192; + if (in_button12.state & 3) bits |= 16384; + if (in_button13.state & 3) bits |= 32768; + if (in_button14.state & 3) bits |= 65536; + if (in_button15.state & 3) bits |= 131072; + if (in_button16.state & 3) bits |= 262144; + // button bits 19-31 unused currently + // rotate/zoom view serverside if PRYDON_CLIENTCURSOR cursor is at edge of screen + if(cl_prydoncursor.integer > 0) + { + if (cl.cmd.cursor_screen[0] <= -1) bits |= 8; + if (cl.cmd.cursor_screen[0] >= 1) bits |= 16; + if (cl.cmd.cursor_screen[1] <= -1) bits |= 32; + if (cl.cmd.cursor_screen[1] >= 1) bits |= 64; + } + + // set buttons and impulse + cl.cmd.buttons = bits; + cl.cmd.impulse = in_impulse; + + // set viewangles + VectorCopy(cl.viewangles, cl.cmd.viewangles); + + msecdelta = (int)(floor(cl.cmd.time * 1000) - floor(cl.movecmd[1].time * 1000)); + cl.cmd.msec = (unsigned char)bound(0, msecdelta, 255); + // ridiculous value rejection (matches qw) + if (cl.cmd.msec > 250) + cl.cmd.msec = 100; + cl.cmd.frametime = cl.cmd.msec * (1.0 / 1000.0); + + cl.cmd.predicted = cl_movement.integer != 0; + + // movement is set by input code (forwardmove/sidemove/upmove) + // always dump the first two moves, because they may contain leftover inputs from the last level + if (cl.cmd.sequence <= 2) + cl.cmd.forwardmove = cl.cmd.sidemove = cl.cmd.upmove = cl.cmd.impulse = cl.cmd.buttons = 0; + + cl.cmd.jump = (cl.cmd.buttons & 2) != 0; + cl.cmd.crouch = 0; + switch (cls.protocol) + { + case PROTOCOL_QUAKEWORLD: + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + case PROTOCOL_NEHAHRAMOVIE: + case PROTOCOL_NEHAHRABJP: + case PROTOCOL_NEHAHRABJP2: + case PROTOCOL_NEHAHRABJP3: + case PROTOCOL_DARKPLACES1: + case PROTOCOL_DARKPLACES2: + case PROTOCOL_DARKPLACES3: + case PROTOCOL_DARKPLACES4: + case PROTOCOL_DARKPLACES5: + break; + case PROTOCOL_DARKPLACES6: + case PROTOCOL_DARKPLACES7: + // FIXME: cl.cmd.buttons & 16 is +button5, Nexuiz/Xonotic specific + cl.cmd.crouch = (cl.cmd.buttons & 16) != 0; + break; + case PROTOCOL_UNKNOWN: + break; + } + + if (quemove) + cl.movecmd[0] = cl.cmd; + + // don't predict more than 200fps + if (realtime >= cl.lastpackettime + 0.005) + cl.movement_replay = true; // redo the prediction + + // now decide whether to actually send this move + // (otherwise it is only for prediction) + + // don't send too often or else network connections can get clogged by a + // high renderer framerate + packettime = 1.0 / bound(1, cl_netfps.value, 1000); + if (cl.movevars_timescale && cl.movevars_ticrate) + { + float maxtic = cl.movevars_ticrate / cl.movevars_timescale; + packettime = min(packettime, maxtic); + } + + // do not send 0ms packets because they mess up physics + if(cl.cmd.msec == 0 && cl.time > cl.oldtime && (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS)) + return; + // always send if buttons changed or an impulse is pending + // even if it violates the rate limit! + important = (cl.cmd.impulse || (cl_netimmediatebuttons.integer && cl.cmd.buttons != cl.movecmd[1].buttons)); + // don't send too often (cl_netfps) + if (!important && realtime < cl.lastpackettime + packettime) + return; + // don't choke the connection with packets (obey rate limit) + // it is important that this check be last, because it adds a new + // frame to the shownetgraph output and any cancelation after this + // will produce a nasty spike-like look to the netgraph + // we also still send if it is important + if (!NetConn_CanSend(cls.netcon) && !important) + return; + // try to round off the lastpackettime to a multiple of the packet interval + // (this causes it to emit packets at a steady beat) + if (packettime > 0) + cl.lastpackettime = floor(realtime / packettime) * packettime; + else + cl.lastpackettime = realtime; + + buf.maxsize = sizeof(data); + buf.cursize = 0; + buf.data = data; + + // send the movement message + // PROTOCOL_QUAKE clc_move = 16 bytes total + // PROTOCOL_QUAKEDP clc_move = 16 bytes total + // PROTOCOL_NEHAHRAMOVIE clc_move = 16 bytes total + // PROTOCOL_DARKPLACES1 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES2 clc_move = 25 bytes total + // PROTOCOL_DARKPLACES3 clc_move = 25 bytes total + // PROTOCOL_DARKPLACES4 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES5 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES6 clc_move = 52 bytes total + // PROTOCOL_DARKPLACES7 clc_move = 56 bytes total per move (can be up to 16 moves) + // PROTOCOL_QUAKEWORLD clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll) + + // set prydon cursor info + CL_UpdatePrydonCursor(); + + if (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS) + { + switch (cls.protocol) + { + case PROTOCOL_QUAKEWORLD: + MSG_WriteByte(&buf, qw_clc_move); + // save the position for a checksum byte + checksumindex = buf.cursize; + MSG_WriteByte(&buf, 0); + // packet loss percentage + for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++) + if (cls.netcon->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) + packetloss++; + packetloss = packetloss * 100 / NETGRAPH_PACKETS; + MSG_WriteByte(&buf, packetloss); + // write most recent 3 moves + QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, &cl.movecmd[2]); + QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[2], &cl.movecmd[1]); + QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.cmd); + // calculate the checksum + buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->outgoing_unreliable_sequence); + // if delta compression history overflows, request no delta + if (cls.netcon->outgoing_unreliable_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1) + cl.qw_validsequence = 0; + // request delta compression if appropriate + if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording) + { + cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = cl.qw_validsequence; + MSG_WriteByte(&buf, qw_clc_delta); + MSG_WriteByte(&buf, cl.qw_validsequence & 255); + } + else + cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = -1; + break; + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + case PROTOCOL_NEHAHRAMOVIE: + case PROTOCOL_NEHAHRABJP: + case PROTOCOL_NEHAHRABJP2: + case PROTOCOL_NEHAHRABJP3: + // 5 bytes + MSG_WriteByte (&buf, clc_move); + MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time + // 3 bytes (6 bytes in proquake) + if (cls.proquake_servermod == 1) // MOD_PROQUAKE + { + for (i = 0;i < 3;i++) + MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]); + } + else + { + for (i = 0;i < 3;i++) + MSG_WriteAngle8i (&buf, cl.cmd.viewangles[i]); + } + // 6 bytes + MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); + MSG_WriteCoord16i (&buf, cl.cmd.sidemove); + MSG_WriteCoord16i (&buf, cl.cmd.upmove); + // 2 bytes + MSG_WriteByte (&buf, cl.cmd.buttons); + MSG_WriteByte (&buf, cl.cmd.impulse); + break; + case PROTOCOL_DARKPLACES2: + case PROTOCOL_DARKPLACES3: + // 5 bytes + MSG_WriteByte (&buf, clc_move); + MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time + // 12 bytes + for (i = 0;i < 3;i++) + MSG_WriteAngle32f (&buf, cl.cmd.viewangles[i]); + // 6 bytes + MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); + MSG_WriteCoord16i (&buf, cl.cmd.sidemove); + MSG_WriteCoord16i (&buf, cl.cmd.upmove); + // 2 bytes + MSG_WriteByte (&buf, cl.cmd.buttons); + MSG_WriteByte (&buf, cl.cmd.impulse); + break; + case PROTOCOL_DARKPLACES1: + case PROTOCOL_DARKPLACES4: + case PROTOCOL_DARKPLACES5: + // 5 bytes + MSG_WriteByte (&buf, clc_move); + MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time + // 6 bytes + for (i = 0;i < 3;i++) + MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]); + // 6 bytes + MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); + MSG_WriteCoord16i (&buf, cl.cmd.sidemove); + MSG_WriteCoord16i (&buf, cl.cmd.upmove); + // 2 bytes + MSG_WriteByte (&buf, cl.cmd.buttons); + MSG_WriteByte (&buf, cl.cmd.impulse); + case PROTOCOL_DARKPLACES6: + case PROTOCOL_DARKPLACES7: + // set the maxusercmds variable to limit how many should be sent + maxusercmds = bound(1, cl_netrepeatinput.integer + 1, min(3, CL_MAX_USERCMDS)); + // when movement prediction is off, there's not much point in repeating old input as it will just be ignored + if (!cl.cmd.predicted) + maxusercmds = 1; + + // send the latest moves in order, the old ones will be + // ignored by the server harmlessly, however if the previous + // packets were lost these moves will be used + // + // this reduces packet loss impact on gameplay. + for (j = 0, cmd = &cl.movecmd[maxusercmds-1];j < maxusercmds;j++, cmd--) + { + // don't repeat any stale moves + if (cmd->sequence && cmd->sequence < cls.servermovesequence) + continue; + // 5/9 bytes + MSG_WriteByte (&buf, clc_move); + if (cls.protocol != PROTOCOL_DARKPLACES6) + MSG_WriteLong (&buf, cmd->predicted ? cmd->sequence : 0); + MSG_WriteFloat (&buf, cmd->time); // last server packet time + // 6 bytes + for (i = 0;i < 3;i++) + MSG_WriteAngle16i (&buf, cmd->viewangles[i]); + // 6 bytes + MSG_WriteCoord16i (&buf, cmd->forwardmove); + MSG_WriteCoord16i (&buf, cmd->sidemove); + MSG_WriteCoord16i (&buf, cmd->upmove); + // 5 bytes + MSG_WriteLong (&buf, cmd->buttons); + MSG_WriteByte (&buf, cmd->impulse); + // PRYDON_CLIENTCURSOR + // 30 bytes + MSG_WriteShort (&buf, (short)(cmd->cursor_screen[0] * 32767.0f)); + MSG_WriteShort (&buf, (short)(cmd->cursor_screen[1] * 32767.0f)); + MSG_WriteFloat (&buf, cmd->cursor_start[0]); + MSG_WriteFloat (&buf, cmd->cursor_start[1]); + MSG_WriteFloat (&buf, cmd->cursor_start[2]); + MSG_WriteFloat (&buf, cmd->cursor_impact[0]); + MSG_WriteFloat (&buf, cmd->cursor_impact[1]); + MSG_WriteFloat (&buf, cmd->cursor_impact[2]); + MSG_WriteShort (&buf, cmd->cursor_entitynumber); + } + break; + case PROTOCOL_UNKNOWN: + break; + } + } + + if (cls.protocol != PROTOCOL_QUAKEWORLD && buf.cursize) + { + // ack entity frame numbers received since the last input was sent + // (redundent to improve handling of client->server packet loss) + // if cl_netrepeatinput is 1 and client framerate matches server + // framerate, this is 10 bytes, if client framerate is lower this + // will be more... + int i, j; + int oldsequence = cl.cmd.sequence - bound(1, cl_netrepeatinput.integer + 1, 3); + if (oldsequence < 1) + oldsequence = 1; + for (i = 0;i < LATESTFRAMENUMS;i++) + { + j = (cl.latestframenumsposition + i) % LATESTFRAMENUMS; + if (cl.latestsendnums[j] >= oldsequence) + { + if (developer_networkentities.integer >= 10) + Con_Printf("send clc_ackframe %i\n", cl.latestframenums[j]); + MSG_WriteByte(&buf, clc_ackframe); + MSG_WriteLong(&buf, cl.latestframenums[j]); + } + } + } + + // PROTOCOL_DARKPLACES6 = 67 bytes per packet + // PROTOCOL_DARKPLACES7 = 71 bytes per packet + + // acknowledge any recently received data blocks + for (i = 0;i < CL_MAX_DOWNLOADACKS && (cls.dp_downloadack[i].start || cls.dp_downloadack[i].size);i++) + { + MSG_WriteByte(&buf, clc_ackdownloaddata); + MSG_WriteLong(&buf, cls.dp_downloadack[i].start); + MSG_WriteShort(&buf, cls.dp_downloadack[i].size); + cls.dp_downloadack[i].start = 0; + cls.dp_downloadack[i].size = 0; + } + + // send the reliable message (forwarded commands) if there is one + if (buf.cursize || cls.netcon->message.cursize) + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer), false); + + if (quemove) + { + // update the cl.movecmd array which holds the most recent moves, + // because we now need a new slot for the next input + for (i = CL_MAX_USERCMDS - 1;i >= 1;i--) + cl.movecmd[i] = cl.movecmd[i-1]; + cl.movecmd[0].msec = 0; + cl.movecmd[0].frametime = 0; + } + + // clear button 'click' states + in_attack.state &= ~2; + in_jump.state &= ~2; + in_button3.state &= ~2; + in_button4.state &= ~2; + in_button5.state &= ~2; + in_button6.state &= ~2; + in_button7.state &= ~2; + in_button8.state &= ~2; + in_use.state &= ~2; + in_button9.state &= ~2; + in_button10.state &= ~2; + in_button11.state &= ~2; + in_button12.state &= ~2; + in_button13.state &= ~2; + in_button14.state &= ~2; + in_button15.state &= ~2; + in_button16.state &= ~2; + // clear impulse + in_impulse = 0; + + if (cls.netcon->message.overflowed) + { + Con_Print("CL_SendMove: lost server connection\n"); + CL_Disconnect(); + SV_LockThreadMutex(); + Host_ShutdownServer(); + SV_UnlockThreadMutex(); + } +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput (void) +{ + Cmd_AddCommand ("+moveup",IN_UpDown, "swim upward"); + Cmd_AddCommand ("-moveup",IN_UpUp, "stop swimming upward"); + Cmd_AddCommand ("+movedown",IN_DownDown, "swim downward"); + Cmd_AddCommand ("-movedown",IN_DownUp, "stop swimming downward"); + Cmd_AddCommand ("+left",IN_LeftDown, "turn left"); + Cmd_AddCommand ("-left",IN_LeftUp, "stop turning left"); + Cmd_AddCommand ("+right",IN_RightDown, "turn right"); + Cmd_AddCommand ("-right",IN_RightUp, "stop turning right"); + Cmd_AddCommand ("+forward",IN_ForwardDown, "move forward"); + Cmd_AddCommand ("-forward",IN_ForwardUp, "stop moving forward"); + Cmd_AddCommand ("+back",IN_BackDown, "move backward"); + Cmd_AddCommand ("-back",IN_BackUp, "stop moving backward"); + Cmd_AddCommand ("+lookup", IN_LookupDown, "look upward"); + Cmd_AddCommand ("-lookup", IN_LookupUp, "stop looking upward"); + Cmd_AddCommand ("+lookdown", IN_LookdownDown, "look downward"); + Cmd_AddCommand ("-lookdown", IN_LookdownUp, "stop looking downward"); + Cmd_AddCommand ("+strafe", IN_StrafeDown, "activate strafing mode (move instead of turn)"); + Cmd_AddCommand ("-strafe", IN_StrafeUp, "deactivate strafing mode"); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown, "strafe left"); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp, "stop strafing left"); + Cmd_AddCommand ("+moveright", IN_MoverightDown, "strafe right"); + Cmd_AddCommand ("-moveright", IN_MoverightUp, "stop strafing right"); + Cmd_AddCommand ("+speed", IN_SpeedDown, "activate run mode (faster movement and turning)"); + Cmd_AddCommand ("-speed", IN_SpeedUp, "deactivate run mode"); + Cmd_AddCommand ("+attack", IN_AttackDown, "begin firing"); + Cmd_AddCommand ("-attack", IN_AttackUp, "stop firing"); + Cmd_AddCommand ("+jump", IN_JumpDown, "jump"); + Cmd_AddCommand ("-jump", IN_JumpUp, "end jump (so you can jump again)"); + Cmd_AddCommand ("impulse", IN_Impulse, "send an impulse number to server (select weapon, use item, etc)"); + Cmd_AddCommand ("+klook", IN_KLookDown, "activate keyboard looking mode, do not recenter view"); + Cmd_AddCommand ("-klook", IN_KLookUp, "deactivate keyboard looking mode"); + Cmd_AddCommand ("+mlook", IN_MLookDown, "activate mouse looking mode, do not recenter view"); + Cmd_AddCommand ("-mlook", IN_MLookUp, "deactivate mouse looking mode"); + + // LordHavoc: added use button + Cmd_AddCommand ("+use", IN_UseDown, "use something (may be used by some mods)"); + Cmd_AddCommand ("-use", IN_UseUp, "stop using something"); + + // LordHavoc: added 6 new buttons + Cmd_AddCommand ("+button3", IN_Button3Down, "activate button3 (behavior depends on mod)"); + Cmd_AddCommand ("-button3", IN_Button3Up, "deactivate button3"); + Cmd_AddCommand ("+button4", IN_Button4Down, "activate button4 (behavior depends on mod)"); + Cmd_AddCommand ("-button4", IN_Button4Up, "deactivate button4"); + Cmd_AddCommand ("+button5", IN_Button5Down, "activate button5 (behavior depends on mod)"); + Cmd_AddCommand ("-button5", IN_Button5Up, "deactivate button5"); + Cmd_AddCommand ("+button6", IN_Button6Down, "activate button6 (behavior depends on mod)"); + Cmd_AddCommand ("-button6", IN_Button6Up, "deactivate button6"); + Cmd_AddCommand ("+button7", IN_Button7Down, "activate button7 (behavior depends on mod)"); + Cmd_AddCommand ("-button7", IN_Button7Up, "deactivate button7"); + Cmd_AddCommand ("+button8", IN_Button8Down, "activate button8 (behavior depends on mod)"); + Cmd_AddCommand ("-button8", IN_Button8Up, "deactivate button8"); + Cmd_AddCommand ("+button9", IN_Button9Down, "activate button9 (behavior depends on mod)"); + Cmd_AddCommand ("-button9", IN_Button9Up, "deactivate button9"); + Cmd_AddCommand ("+button10", IN_Button10Down, "activate button10 (behavior depends on mod)"); + Cmd_AddCommand ("-button10", IN_Button10Up, "deactivate button10"); + Cmd_AddCommand ("+button11", IN_Button11Down, "activate button11 (behavior depends on mod)"); + Cmd_AddCommand ("-button11", IN_Button11Up, "deactivate button11"); + Cmd_AddCommand ("+button12", IN_Button12Down, "activate button12 (behavior depends on mod)"); + Cmd_AddCommand ("-button12", IN_Button12Up, "deactivate button12"); + Cmd_AddCommand ("+button13", IN_Button13Down, "activate button13 (behavior depends on mod)"); + Cmd_AddCommand ("-button13", IN_Button13Up, "deactivate button13"); + Cmd_AddCommand ("+button14", IN_Button14Down, "activate button14 (behavior depends on mod)"); + Cmd_AddCommand ("-button14", IN_Button14Up, "deactivate button14"); + Cmd_AddCommand ("+button15", IN_Button15Down, "activate button15 (behavior depends on mod)"); + Cmd_AddCommand ("-button15", IN_Button15Up, "deactivate button15"); + Cmd_AddCommand ("+button16", IN_Button16Down, "activate button16 (behavior depends on mod)"); + Cmd_AddCommand ("-button16", IN_Button16Up, "deactivate button16"); + + // LordHavoc: added bestweapon command + Cmd_AddCommand ("bestweapon", IN_BestWeapon, "send an impulse number to server to select the first usable weapon out of several (example: 8 7 6 5 4 3 2 1)"); +#if 0 + Cmd_AddCommand ("cycleweapon", IN_CycleWeapon, "send an impulse number to server to select the next usable weapon out of several (example: 9 4 8) if you are holding one of these, and choose the first one if you are holding none of these"); +#endif + Cmd_AddCommand ("register_bestweapon", IN_BestWeapon_Register_f, "(for QC usage only) change weapon parameters to be used by bestweapon; stuffcmd this in ClientConnect"); + + Cvar_RegisterVariable(&cl_yawmode); + Cvar_RegisterVariable(&cl_pitchmode); + Cvar_RegisterVariable(&cl_comfort); + Cvar_RegisterVariable(&cl_yawspeed); + Cvar_RegisterVariable(&cl_pitchmult); + Cvar_RegisterVariable(&cl_yawmult); + + Cvar_RegisterVariable(&cl_movecliptokeyboard); + Cvar_RegisterVariable(&cl_movement); + Cvar_RegisterVariable(&cl_movement_replay); + Cvar_RegisterVariable(&cl_movement_nettimeout); + Cvar_RegisterVariable(&cl_movement_minping); + Cvar_RegisterVariable(&cl_movement_track_canjump); + Cvar_RegisterVariable(&cl_movement_maxspeed); + Cvar_RegisterVariable(&cl_movement_maxairspeed); + Cvar_RegisterVariable(&cl_movement_stopspeed); + Cvar_RegisterVariable(&cl_movement_friction); + Cvar_RegisterVariable(&cl_movement_wallfriction); + Cvar_RegisterVariable(&cl_movement_waterfriction); + Cvar_RegisterVariable(&cl_movement_edgefriction); + Cvar_RegisterVariable(&cl_movement_stepheight); + Cvar_RegisterVariable(&cl_movement_accelerate); + Cvar_RegisterVariable(&cl_movement_airaccelerate); + Cvar_RegisterVariable(&cl_movement_wateraccelerate); + Cvar_RegisterVariable(&cl_movement_jumpvelocity); + Cvar_RegisterVariable(&cl_movement_airaccel_qw); + Cvar_RegisterVariable(&cl_movement_airaccel_sideways_friction); + + Cvar_RegisterVariable(&in_pitch_min); + Cvar_RegisterVariable(&in_pitch_max); + Cvar_RegisterVariable(&m_filter); + Cvar_RegisterVariable(&m_accelerate); + Cvar_RegisterVariable(&m_accelerate_minspeed); + Cvar_RegisterVariable(&m_accelerate_maxspeed); + Cvar_RegisterVariable(&m_accelerate_filter); + + Cvar_RegisterVariable(&cl_netfps); + Cvar_RegisterVariable(&cl_netrepeatinput); + Cvar_RegisterVariable(&cl_netimmediatebuttons); + + Cvar_RegisterVariable(&cl_nodelta); + + Cvar_RegisterVariable(&cl_csqc_generatemousemoveevents); +} + diff --git a/app/jni/cl_main.c b/app/jni/cl_main.c new file mode 100644 index 0000000..567993f --- /dev/null +++ b/app/jni/cl_main.c @@ -0,0 +1,2499 @@ +/* +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. + +*/ +// cl_main.c -- client main loop + +#include "quakedef.h" +#include "cl_collision.h" +#include "cl_video.h" +#include "image.h" +#include "csprogs.h" +#include "r_shadow.h" +#include "libcurl.h" +#include "snd_main.h" + +// we need to declare some mouse variables here, because the menu system +// references them even when on a unix system. + +cvar_t csqc_progname = {0, "csqc_progname","csprogs.dat","name of csprogs.dat file to load"}; +cvar_t csqc_progcrc = {CVAR_READONLY, "csqc_progcrc","-1","CRC of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1"}; +cvar_t csqc_progsize = {CVAR_READONLY, "csqc_progsize","-1","file size of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1"}; +cvar_t csqc_usedemoprogs = {0, "csqc_usedemoprogs","1","use csprogs stored in demos"}; + +cvar_t cl_shownet = {0, "cl_shownet","0","1 = print packet size, 2 = print packet message list"}; +cvar_t cl_nolerp = {0, "cl_nolerp", "0","network update smoothing"}; +cvar_t cl_lerpexcess = {0, "cl_lerpexcess", "0","maximum allowed lerp excess (hides, not fixes, some packet loss)"}; +cvar_t cl_lerpanim_maxdelta_server = {0, "cl_lerpanim_maxdelta_server", "0.1","maximum frame delta for smoothing between server-controlled animation frames (when 0, one network frame)"}; +cvar_t cl_lerpanim_maxdelta_framegroups = {0, "cl_lerpanim_maxdelta_framegroups", "0.1","maximum frame delta for smoothing between framegroups (when 0, one network frame)"}; + +cvar_t cl_itembobheight = {0, "cl_itembobheight", "0","how much items bob up and down (try 8)"}; +cvar_t cl_itembobspeed = {0, "cl_itembobspeed", "0.5","how frequently items bob up and down"}; + +cvar_t lookspring = {CVAR_SAVE, "lookspring","0","returns pitch to level with the floor when no longer holding a pitch key"}; +cvar_t lookstrafe = {CVAR_SAVE, "lookstrafe","0","move instead of turning"}; +cvar_t sensitivity = {CVAR_SAVE, "sensitivity","3","mouse speed multiplier"}; + +cvar_t m_pitch = {CVAR_SAVE, "m_pitch","0.022","mouse pitch speed multiplier"}; +cvar_t m_yaw = {CVAR_SAVE, "m_yaw","0.022","mouse yaw speed multiplier"}; +cvar_t m_forward = {CVAR_SAVE, "m_forward","1","mouse forward speed multiplier"}; +cvar_t m_side = {CVAR_SAVE, "m_side","0.8","mouse side speed multiplier"}; + +cvar_t freelook = {CVAR_SAVE, "freelook", "1","mouse controls pitch instead of forward/back"}; + +cvar_t cl_nosplashscreen = {CVAR_SAVE, "cl_nosplashscreen", "0", "prevents the credits splashscreen from being displayed on game start" }; + +cvar_t cl_autodemo = {CVAR_SAVE, "cl_autodemo", "0", "records every game played, using the date/time and map name to name the demo file" }; +cvar_t cl_autodemo_nameformat = {CVAR_SAVE, "cl_autodemo_nameformat", "autodemos/%Y-%m-%d_%H-%M", "The format of the cl_autodemo filename, followed by the map name (the date is encoded using strftime escapes)" }; +cvar_t cl_autodemo_delete = {0, "cl_autodemo_delete", "0", "Delete demos after recording. This is a bitmask, bit 1 gives the default, bit 0 the value for the current demo. Thus, the values are: 0 = disabled; 1 = delete current demo only; 2 = delete all demos except the current demo; 3 = delete all demos from now on" }; + +cvar_t r_draweffects = {0, "r_draweffects", "1","renders temporary sprite effects"}; + +cvar_t cl_explosions_alpha_start = {CVAR_SAVE, "cl_explosions_alpha_start", "1.5","starting alpha of an explosion shell"}; +cvar_t cl_explosions_alpha_end = {CVAR_SAVE, "cl_explosions_alpha_end", "0","end alpha of an explosion shell (just before it disappears)"}; +cvar_t cl_explosions_size_start = {CVAR_SAVE, "cl_explosions_size_start", "16","starting size of an explosion shell"}; +cvar_t cl_explosions_size_end = {CVAR_SAVE, "cl_explosions_size_end", "128","ending alpha of an explosion shell (just before it disappears)"}; +cvar_t cl_explosions_lifetime = {CVAR_SAVE, "cl_explosions_lifetime", "0.5","how long an explosion shell lasts"}; + +cvar_t cl_stainmaps = {CVAR_SAVE, "cl_stainmaps", "0","stains lightmaps, much faster than decals but blurred"}; +cvar_t cl_stainmaps_clearonload = {CVAR_SAVE, "cl_stainmaps_clearonload", "1","clear stainmaps on map restart"}; + +cvar_t cl_beams_polygons = {CVAR_SAVE, "cl_beams_polygons", "1","use beam polygons instead of models"}; +cvar_t cl_beams_quakepositionhack = {CVAR_SAVE, "cl_beams_quakepositionhack", "1", "makes your lightning gun appear to fire from your waist (as in Quake and QuakeWorld)"}; +cvar_t cl_beams_instantaimhack = {CVAR_SAVE, "cl_beams_instantaimhack", "0", "makes your lightning gun aiming update instantly"}; +cvar_t cl_beams_lightatend = {CVAR_SAVE, "cl_beams_lightatend", "0", "make a light at the end of the beam"}; + +cvar_t cl_deathfade = {CVAR_SAVE, "cl_deathfade", "0", "fade screen to dark red when dead, value represents how fast the fade is (higher is faster)"}; + +cvar_t cl_noplayershadow = {CVAR_SAVE, "cl_noplayershadow", "0","hide player shadow"}; + +cvar_t cl_dlights_decayradius = {CVAR_SAVE, "cl_dlights_decayradius", "1", "reduces size of light flashes over time"}; +cvar_t cl_dlights_decaybrightness = {CVAR_SAVE, "cl_dlights_decaybrightness", "1", "reduces brightness of light flashes over time"}; + +cvar_t qport = {0, "qport", "0", "identification key for playing on qw servers (allows you to maintain a connection to a quakeworld server even if your port changes)"}; + +cvar_t cl_prydoncursor = {0, "cl_prydoncursor", "0", "enables a mouse pointer which is able to click on entities in the world, useful for point and click mods, see PRYDON_CLIENTCURSOR extension in dpextensions.qc"}; +cvar_t cl_prydoncursor_notrace = {0, "cl_prydoncursor_notrace", "0", "disables traceline used in prydon cursor reporting to the game, saving some cpu time"}; + +cvar_t cl_deathnoviewmodel = {0, "cl_deathnoviewmodel", "1", "hides gun model when dead"}; + +cvar_t cl_locs_enable = {CVAR_SAVE, "locs_enable", "1", "enables replacement of certain % codes in chat messages: %l (location), %d (last death location), %h (health), %a (armor), %x (rockets), %c (cells), %r (rocket launcher status), %p (powerup status), %w (weapon status), %t (current time in level)"}; +cvar_t cl_locs_show = {0, "locs_show", "0", "shows defined locations for editing purposes"}; + +extern cvar_t r_equalize_entities_fullbright; + +client_static_t cls; +client_state_t cl; + +/* +===================== +CL_ClearState + +===================== +*/ +void CL_ClearState(void) +{ + int i; + entity_t *ent; + + CL_VM_ShutDown(); + +// wipe the entire cl structure + Mem_EmptyPool(cls.levelmempool); + memset (&cl, 0, sizeof(cl)); + + S_StopAllSounds(); + + // reset the view zoom interpolation + cl.mviewzoom[0] = cl.mviewzoom[1] = 1; + cl.sensitivityscale = 1.0f; + + // enable rendering of the world and such + cl.csqc_vidvars.drawworld = r_drawworld.integer != 0; + cl.csqc_vidvars.drawenginesbar = true; + cl.csqc_vidvars.drawcrosshair = true; + + // set up the float version of the stats array for easier access to float stats + cl.statsf = (float *)cl.stats; + + cl.num_entities = 0; + cl.num_static_entities = 0; + cl.num_brushmodel_entities = 0; + + // tweak these if the game runs out + cl.max_csqcrenderentities = 0; + cl.max_entities = MAX_ENITIES_INITIAL; + cl.max_static_entities = MAX_STATICENTITIES; + cl.max_effects = MAX_EFFECTS; + cl.max_beams = MAX_BEAMS; + cl.max_dlights = MAX_DLIGHTS; + cl.max_lightstyle = MAX_LIGHTSTYLES; + cl.max_brushmodel_entities = MAX_EDICTS; + cl.max_particles = MAX_PARTICLES_INITIAL; // grows dynamically + cl.max_decals = MAX_DECALS_INITIAL; // grows dynamically + cl.max_showlmps = 0; + + cl.num_dlights = 0; + cl.num_effects = 0; + cl.num_beams = 0; + + cl.csqcrenderentities = NULL; + cl.entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_entities * sizeof(entity_t)); + cl.entities_active = (unsigned char *)Mem_Alloc(cls.levelmempool, cl.max_brushmodel_entities * sizeof(unsigned char)); + cl.static_entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_static_entities * sizeof(entity_t)); + cl.effects = (cl_effect_t *)Mem_Alloc(cls.levelmempool, cl.max_effects * sizeof(cl_effect_t)); + cl.beams = (beam_t *)Mem_Alloc(cls.levelmempool, cl.max_beams * sizeof(beam_t)); + cl.dlights = (dlight_t *)Mem_Alloc(cls.levelmempool, cl.max_dlights * sizeof(dlight_t)); + cl.lightstyle = (lightstyle_t *)Mem_Alloc(cls.levelmempool, cl.max_lightstyle * sizeof(lightstyle_t)); + cl.brushmodel_entities = (int *)Mem_Alloc(cls.levelmempool, cl.max_brushmodel_entities * sizeof(int)); + cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t)); + cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t)); + cl.showlmps = NULL; + + // LordHavoc: have to set up the baseline info for alpha and other stuff + for (i = 0;i < cl.max_entities;i++) + { + cl.entities[i].state_baseline = defaultstate; + cl.entities[i].state_previous = defaultstate; + cl.entities[i].state_current = defaultstate; + } + + if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + VectorSet(cl.playerstandmins, -16, -16, -24); + VectorSet(cl.playerstandmaxs, 16, 16, 45); + VectorSet(cl.playercrouchmins, -16, -16, -24); + VectorSet(cl.playercrouchmaxs, 16, 16, 25); + } + else + { + VectorSet(cl.playerstandmins, -16, -16, -24); + VectorSet(cl.playerstandmaxs, 16, 16, 24); + VectorSet(cl.playercrouchmins, -16, -16, -24); + VectorSet(cl.playercrouchmaxs, 16, 16, 24); + } + + // disable until we get textures for it + R_ResetSkyBox(); + + ent = &cl.entities[0]; + // entire entity array was cleared, so just fill in a few fields + ent->state_current.active = true; + ent->render.model = cl.worldmodel = NULL; // no world model yet + ent->render.alpha = 1; + ent->render.flags = RENDER_SHADOW | RENDER_LIGHT; + Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, 0, 0, 0, 0, 0, 0, 1); + ent->render.allowdecals = true; + CL_UpdateRenderEntity(&ent->render); + + // noclip is turned off at start + noclip_anglehack = false; + + // mark all frames invalid for delta + memset(cl.qw_deltasequence, -1, sizeof(cl.qw_deltasequence)); + + // set bestweapon data back to Quake data + IN_BestWeapon_ResetData(); + + CL_Screen_NewMap(); +} + +void CL_SetInfo(const char *key, const char *value, qboolean send, qboolean allowstarkey, qboolean allowmodel, qboolean quiet) +{ + int i; + qboolean fail = false; + char vabuf[1024]; + if (!allowstarkey && key[0] == '*') + fail = true; + if (!allowmodel && (!strcasecmp(key, "pmodel") || !strcasecmp(key, "emodel"))) + fail = true; + for (i = 0;key[i];i++) + if (ISWHITESPACE(key[i]) || key[i] == '\"') + fail = true; + for (i = 0;value[i];i++) + if (value[i] == '\r' || value[i] == '\n' || value[i] == '\"') + fail = true; + if (fail) + { + if (!quiet) + Con_Printf("Can't setinfo \"%s\" \"%s\"\n", key, value); + return; + } + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), key, value); + if (cls.state == ca_connected && cls.netcon) + { + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "setinfo \"%s\" \"%s\"", key, value)); + } + else if (!strcasecmp(key, "name")) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "name \"%s\"", value)); + } + else if (!strcasecmp(key, "playermodel")) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "playermodel \"%s\"", value)); + } + else if (!strcasecmp(key, "playerskin")) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "playerskin \"%s\"", value)); + } + else if (!strcasecmp(key, "topcolor")) + { + // don't send anything, the combined color code will be updated manually + } + else if (!strcasecmp(key, "bottomcolor")) + { + // don't send anything, the combined color code will be updated manually + } + else if (!strcasecmp(key, "rate")) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "rate \"%s\"", value)); + } + } +} + +void CL_ExpandEntities(int num) +{ + int i, oldmaxentities; + entity_t *oldentities; + if (num >= cl.max_entities) + { + if (!cl.entities) + Sys_Error("CL_ExpandEntities: cl.entities not initialized"); + if (num >= MAX_EDICTS) + Host_Error("CL_ExpandEntities: num %i >= %i", num, MAX_EDICTS); + oldmaxentities = cl.max_entities; + oldentities = cl.entities; + cl.max_entities = (num & ~255) + 256; + cl.entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_entities * sizeof(entity_t)); + memcpy(cl.entities, oldentities, oldmaxentities * sizeof(entity_t)); + Mem_Free(oldentities); + for (i = oldmaxentities;i < cl.max_entities;i++) + { + cl.entities[i].state_baseline = defaultstate; + cl.entities[i].state_previous = defaultstate; + cl.entities[i].state_current = defaultstate; + } + } +} + +void CL_ExpandCSQCRenderEntities(int num) +{ + int i; + int oldmaxcsqcrenderentities; + entity_render_t *oldcsqcrenderentities; + if (num >= cl.max_csqcrenderentities) + { + if (num >= MAX_EDICTS) + Host_Error("CL_ExpandEntities: num %i >= %i", num, MAX_EDICTS); + oldmaxcsqcrenderentities = cl.max_csqcrenderentities; + oldcsqcrenderentities = cl.csqcrenderentities; + cl.max_csqcrenderentities = (num & ~255) + 256; + cl.csqcrenderentities = (entity_render_t *)Mem_Alloc(cls.levelmempool, cl.max_csqcrenderentities * sizeof(entity_render_t)); + if (oldcsqcrenderentities) + { + memcpy(cl.csqcrenderentities, oldcsqcrenderentities, oldmaxcsqcrenderentities * sizeof(entity_render_t)); + for (i = 0;i < r_refdef.scene.numentities;i++) + if(r_refdef.scene.entities[i] >= oldcsqcrenderentities && r_refdef.scene.entities[i] < (oldcsqcrenderentities + oldmaxcsqcrenderentities)) + r_refdef.scene.entities[i] = cl.csqcrenderentities + (r_refdef.scene.entities[i] - oldcsqcrenderentities); + Mem_Free(oldcsqcrenderentities); + } + } +} + +/* +===================== +CL_Disconnect + +Sends a disconnect message to the server +This is also called on Host_Error, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect(void) +{ + if (cls.state == ca_dedicated) + return; + + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(false); + + Curl_Clear_forthismap(); + + Con_DPrintf("CL_Disconnect\n"); + + Cvar_SetValueQuick(&csqc_progcrc, -1); + Cvar_SetValueQuick(&csqc_progsize, -1); + CL_VM_ShutDown(); +// stop sounds (especially looping!) + S_StopAllSounds (); + + cl.parsingtextexpectingpingforscores = 0; // just in case no reply has come yet + + // clear contents blends + cl.cshifts[0].percent = 0; + cl.cshifts[1].percent = 0; + cl.cshifts[2].percent = 0; + cl.cshifts[3].percent = 0; + + cl.worldmodel = NULL; + + CL_Parse_ErrorCleanUp(); + + if (cls.demoplayback) + CL_StopPlayback(); + else if (cls.netcon) + { + sizebuf_t buf; + unsigned char bufdata[8]; + if (cls.demorecording) + CL_Stop_f(); + + // send disconnect message 3 times to improve chances of server + // receiving it (but it still fails sometimes) + memset(&buf, 0, sizeof(buf)); + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + Con_DPrint("Sending drop command\n"); + MSG_WriteByte(&buf, qw_clc_stringcmd); + MSG_WriteString(&buf, "drop"); + } + else + { + Con_DPrint("Sending clc_disconnect\n"); + MSG_WriteByte(&buf, clc_disconnect); + } + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, false); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, false); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, false); + NetConn_Close(cls.netcon); + cls.netcon = NULL; + } + cls.state = ca_disconnected; + cl.islocalgame = false; + + cls.demoplayback = cls.timedemo = false; + cls.signon = 0; +} + +void CL_Disconnect_f(void) +{ + CL_Disconnect (); + if (sv.active) + Host_ShutdownServer (); +} + + + + +/* +===================== +CL_EstablishConnection + +Host should be either "local" or a net address +===================== +*/ +void CL_EstablishConnection(const char *host, int firstarg) +{ + if (cls.state == ca_dedicated) + return; + + // don't connect to a server if we're benchmarking a demo + if (COM_CheckParm("-benchmark")) + return; + + // clear menu's connect error message + M_Update_Return_Reason(""); + cls.demonum = -1; + + // stop demo loop in case this fails + if (cls.demoplayback) + CL_StopPlayback(); + + // if downloads are running, cancel their finishing action + Curl_Clear_forthismap(); + + // make sure the client ports are open before attempting to connect + NetConn_UpdateSockets(); + + if (LHNETADDRESS_FromString(&cls.connect_address, host, 26000) && (cls.connect_mysocket = NetConn_ChooseClientSocketForAddress(&cls.connect_address))) + { + cls.connect_trying = true; + cls.connect_remainingtries = 3; + cls.connect_nextsendtime = 0; + + // only NOW, set connect_userinfo + if(firstarg >= 0) + { + int i; + *cls.connect_userinfo = 0; + for(i = firstarg; i+2 <= Cmd_Argc(); i += 2) + InfoString_SetValue(cls.connect_userinfo, sizeof(cls.connect_userinfo), Cmd_Argv(i), Cmd_Argv(i+1)); + } + else if(firstarg < -1) + { + // -1: keep as is (reconnect) + // -2: clear + *cls.connect_userinfo = 0; + } + + M_Update_Return_Reason("Trying to connect..."); + } + else + { + Con_Print("Unable to find a suitable network socket to connect to server.\n"); + M_Update_Return_Reason("No network"); + } +} + +/* +============== +CL_PrintEntities_f +============== +*/ +static void CL_PrintEntities_f(void) +{ + entity_t *ent; + int i; + + for (i = 0, ent = cl.entities;i < cl.num_entities;i++, ent++) + { + const char* modelname; + + if (!ent->state_current.active) + continue; + + if (ent->render.model) + modelname = ent->render.model->name; + else + modelname = "--no model--"; + Con_Printf("%3i: %-25s:%4i (%5i %5i %5i) [%3i %3i %3i] %4.2f %5.3f\n", i, modelname, ent->render.framegroupblend[0].frame, (int) ent->state_current.origin[0], (int) ent->state_current.origin[1], (int) ent->state_current.origin[2], (int) ent->state_current.angles[0] % 360, (int) ent->state_current.angles[1] % 360, (int) ent->state_current.angles[2] % 360, ent->render.scale, ent->render.alpha); + } +} + +/* +=============== +CL_ModelIndexList_f + +List information on all models in the client modelindex +=============== +*/ +static void CL_ModelIndexList_f(void) +{ + int i; + dp_model_t *model; + + // Print Header + Con_Printf("%3s: %-30s %-8s %-8s\n", "ID", "Name", "Type", "Triangles"); + + for (i = -MAX_MODELS;i < MAX_MODELS;i++) + { + model = CL_GetModelByIndex(i); + if (!model) + continue; + if(model->loaded || i == 1) + Con_Printf("%3i: %-30s %-8s %-10i\n", i, model->name, model->modeldatatypestring, model->surfmesh.num_triangles); + else + Con_Printf("%3i: %-30s %-30s\n", i, model->name, "--no local model found--"); + i++; + } +} + +/* +=============== +CL_SoundIndexList_f + +List all sounds in the client soundindex +=============== +*/ +static void CL_SoundIndexList_f(void) +{ + int i = 1; + + while(cl.sound_precache[i] && i != MAX_SOUNDS) + { // Valid Sound + Con_Printf("%i : %s\n", i, cl.sound_precache[i]->name); + i++; + } +} + +/* +=============== +CL_UpdateRenderEntity + +Updates inversematrix, animation interpolation factors, scale, and mins/maxs +=============== +*/ +void CL_UpdateRenderEntity(entity_render_t *ent) +{ + vec3_t org; + vec_t scale; + dp_model_t *model = ent->model; + // update the inverse matrix for the renderer + Matrix4x4_Invert_Simple(&ent->inversematrix, &ent->matrix); + // update the animation blend state + VM_FrameBlendFromFrameGroupBlend(ent->frameblend, ent->framegroupblend, ent->model, cl.time); + // we need the matrix origin to center the box + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + // update entity->render.scale because the renderer needs it + ent->scale = scale = Matrix4x4_ScaleFromMatrix(&ent->matrix); + if (model) + { + // NOTE: this directly extracts vector components from the matrix, which relies on the matrix orientation! +#ifdef MATRIX4x4_OPENGLORIENTATION + if (ent->matrix.m[0][2] != 0 || ent->matrix.m[1][2] != 0) +#else + if (ent->matrix.m[2][0] != 0 || ent->matrix.m[2][1] != 0) +#endif + { + // pitch or roll + VectorMA(org, scale, model->rotatedmins, ent->mins); + VectorMA(org, scale, model->rotatedmaxs, ent->maxs); + } +#ifdef MATRIX4x4_OPENGLORIENTATION + else if (ent->matrix.m[1][0] != 0 || ent->matrix.m[0][1] != 0) +#else + else if (ent->matrix.m[0][1] != 0 || ent->matrix.m[1][0] != 0) +#endif + { + // yaw + VectorMA(org, scale, model->yawmins, ent->mins); + VectorMA(org, scale, model->yawmaxs, ent->maxs); + } + else + { + VectorMA(org, scale, model->normalmins, ent->mins); + VectorMA(org, scale, model->normalmaxs, ent->maxs); + } + } + else + { + ent->mins[0] = org[0] - 16; + ent->mins[1] = org[1] - 16; + ent->mins[2] = org[2] - 16; + ent->maxs[0] = org[0] + 16; + ent->maxs[1] = org[1] + 16; + ent->maxs[2] = org[2] + 16; + } +} + +/* +=============== +CL_LerpPoint + +Determines the fraction between the last two messages that the objects +should be put at. +=============== +*/ +static float CL_LerpPoint(void) +{ + float f; + + if (cl_nettimesyncboundmode.integer == 1) + cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); + + // LordHavoc: lerp in listen games as the server is being capped below the client (usually) + if (cl.mtime[0] <= cl.mtime[1]) + { + cl.time = cl.mtime[0]; + return 1; + } + + f = (cl.time - cl.mtime[1]) / (cl.mtime[0] - cl.mtime[1]); + return bound(0, f, 1 + cl_lerpexcess.value); +} + +void CL_ClearTempEntities (void) +{ + r_refdef.scene.numtempentities = 0; + // grow tempentities buffer on request + if (r_refdef.scene.expandtempentities) + { + Con_Printf("CL_NewTempEntity: grow maxtempentities from %i to %i\n", r_refdef.scene.maxtempentities, r_refdef.scene.maxtempentities * 2); + r_refdef.scene.maxtempentities *= 2; + r_refdef.scene.tempentities = (entity_render_t *)Mem_Realloc(cls.permanentmempool, r_refdef.scene.tempentities, sizeof(entity_render_t) * r_refdef.scene.maxtempentities); + r_refdef.scene.expandtempentities = false; + } +} + +entity_render_t *CL_NewTempEntity(double shadertime) +{ + entity_render_t *render; + + if (r_refdef.scene.numentities >= r_refdef.scene.maxentities) + return NULL; + if (r_refdef.scene.numtempentities >= r_refdef.scene.maxtempentities) + { + r_refdef.scene.expandtempentities = true; // will be reallocated next frame since current frame may have pointers set already + return NULL; + } + render = &r_refdef.scene.tempentities[r_refdef.scene.numtempentities++]; + memset (render, 0, sizeof(*render)); + r_refdef.scene.entities[r_refdef.scene.numentities++] = render; + + render->shadertime = shadertime; + render->alpha = 1; + VectorSet(render->colormod, 1, 1, 1); + VectorSet(render->glowmod, 1, 1, 1); + return render; +} + +void CL_Effect(vec3_t org, int modelindex, int startframe, int framecount, float framerate) +{ + int i; + cl_effect_t *e; + if (!modelindex) // sanity check + return; + if (framerate < 1) + { + Con_Printf("CL_Effect: framerate %f is < 1\n", framerate); + return; + } + if (framecount < 1) + { + Con_Printf("CL_Effect: framecount %i is < 1\n", framecount); + return; + } + for (i = 0, e = cl.effects;i < cl.max_effects;i++, e++) + { + if (e->active) + continue; + e->active = true; + VectorCopy(org, e->origin); + e->modelindex = modelindex; + e->starttime = cl.time; + e->startframe = startframe; + e->endframe = startframe + framecount; + e->framerate = framerate; + + e->frame = 0; + e->frame1time = cl.time; + e->frame2time = cl.time; + cl.num_effects = max(cl.num_effects, i + 1); + break; + } +} + +void CL_AllocLightFlash(entity_render_t *ent, matrix4x4_t *matrix, float radius, float red, float green, float blue, float decay, float lifetime, int cubemapnum, int style, int shadowenable, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) +{ + int i; + dlight_t *dl; + +// then look for anything else + dl = cl.dlights; + for (i = 0;i < cl.max_dlights;i++, dl++) + if (!dl->radius) + break; + + // unable to find one + if (i == cl.max_dlights) + return; + + //Con_Printf("dlight %i : %f %f %f : %f %f %f\n", i, org[0], org[1], org[2], red * radius, green * radius, blue * radius); + memset (dl, 0, sizeof(*dl)); + cl.num_dlights = max(cl.num_dlights, i + 1); + Matrix4x4_Normalize(&dl->matrix, matrix); + dl->ent = ent; + Matrix4x4_OriginFromMatrix(&dl->matrix, dl->origin); + CL_FindNonSolidLocation(dl->origin, dl->origin, 6); + Matrix4x4_SetOrigin(&dl->matrix, dl->origin[0], dl->origin[1], dl->origin[2]); + dl->radius = radius; + dl->color[0] = red; + dl->color[1] = green; + dl->color[2] = blue; + dl->initialradius = radius; + dl->initialcolor[0] = red; + dl->initialcolor[1] = green; + dl->initialcolor[2] = blue; + dl->decay = decay / radius; // changed decay to be a percentage decrease + dl->intensity = 1; // this is what gets decayed + if (lifetime) + dl->die = cl.time + lifetime; + else + dl->die = 0; + if (cubemapnum > 0) + dpsnprintf(dl->cubemapname, sizeof(dl->cubemapname), "cubemaps/%i", cubemapnum); + else + dl->cubemapname[0] = 0; + dl->style = style; + dl->shadow = shadowenable; + dl->corona = corona; + dl->flags = flags; + dl->coronasizescale = coronasizescale; + dl->ambientscale = ambientscale; + dl->diffusescale = diffusescale; + dl->specularscale = specularscale; +} + +static void CL_DecayLightFlashes(void) +{ + int i, oldmax; + dlight_t *dl; + float time; + + time = bound(0, cl.time - cl.oldtime, 0.1); + oldmax = cl.num_dlights; + cl.num_dlights = 0; + for (i = 0, dl = cl.dlights;i < oldmax;i++, dl++) + { + if (dl->radius) + { + dl->intensity -= time * dl->decay; + if (cl.time < dl->die && dl->intensity > 0) + { + if (cl_dlights_decayradius.integer) + dl->radius = dl->initialradius * dl->intensity; + else + dl->radius = dl->initialradius; + if (cl_dlights_decaybrightness.integer) + VectorScale(dl->initialcolor, dl->intensity, dl->color); + else + VectorCopy(dl->initialcolor, dl->color); + cl.num_dlights = i + 1; + } + else + dl->radius = 0; + } + } +} + +// called before entity relinking +void CL_RelinkLightFlashes(void) +{ + int i, j, k, l; + dlight_t *dl; + float frac, f; + matrix4x4_t tempmatrix; + + if (r_dynamic.integer) + { + for (i = 0, dl = cl.dlights;i < cl.num_dlights && r_refdef.scene.numlights < MAX_DLIGHTS;i++, dl++) + { + if (dl->radius) + { + tempmatrix = dl->matrix; + Matrix4x4_Scale(&tempmatrix, dl->radius, 1); + // we need the corona fading to be persistent + R_RTLight_Update(&dl->rtlight, false, &tempmatrix, dl->color, dl->style, dl->cubemapname, dl->shadow, dl->corona, dl->coronasizescale, dl->ambientscale, dl->diffusescale, dl->specularscale, dl->flags); + r_refdef.scene.lights[r_refdef.scene.numlights++] = &dl->rtlight; + } + } + } + + if (!cl.lightstyle) + { + for (j = 0;j < cl.max_lightstyle;j++) + { + r_refdef.scene.rtlightstylevalue[j] = 1; + r_refdef.scene.lightstylevalue[j] = 256; + } + return; + } + +// light animations +// 'm' is normal light, 'a' is no light, 'z' is double bright + f = cl.time * 10; + i = (int)floor(f); + frac = f - i; + for (j = 0;j < cl.max_lightstyle;j++) + { + if (!cl.lightstyle[j].length) + { + r_refdef.scene.rtlightstylevalue[j] = 1; + r_refdef.scene.lightstylevalue[j] = 256; + continue; + } + // static lightstyle "=value" + if (cl.lightstyle[j].map[0] == '=') + { + r_refdef.scene.rtlightstylevalue[j] = atof(cl.lightstyle[j].map + 1); + if ( r_lerplightstyles.integer || ((int)f - f) < 0.01) + r_refdef.scene.lightstylevalue[j] = r_refdef.scene.rtlightstylevalue[j]; + continue; + } + k = i % cl.lightstyle[j].length; + l = (i-1) % cl.lightstyle[j].length; + k = cl.lightstyle[j].map[k] - 'a'; + l = cl.lightstyle[j].map[l] - 'a'; + // rtlightstylevalue is always interpolated because it has no bad + // consequences for performance + // lightstylevalue is subject to a cvar for performance reasons; + // skipping lightmap updates on most rendered frames substantially + // improves framerates (but makes light fades look bad) + r_refdef.scene.rtlightstylevalue[j] = ((k*frac)+(l*(1-frac)))*(22/256.0f); + r_refdef.scene.lightstylevalue[j] = r_lerplightstyles.integer ? (unsigned short)(((k*frac)+(l*(1-frac)))*22) : k*22; + } +} + +static void CL_AddQWCTFFlagModel(entity_t *player, int skin) +{ + int frame = player->render.framegroupblend[0].frame; + float f; + entity_render_t *flagrender; + matrix4x4_t flagmatrix; + + // this code taken from QuakeWorld + f = 14; + if (frame >= 29 && frame <= 40) + { + if (frame >= 29 && frame <= 34) + { //axpain + if (frame == 29) f = f + 2; + else if (frame == 30) f = f + 8; + else if (frame == 31) f = f + 12; + else if (frame == 32) f = f + 11; + else if (frame == 33) f = f + 10; + else if (frame == 34) f = f + 4; + } + else if (frame >= 35 && frame <= 40) + { // pain + if (frame == 35) f = f + 2; + else if (frame == 36) f = f + 10; + else if (frame == 37) f = f + 10; + else if (frame == 38) f = f + 8; + else if (frame == 39) f = f + 4; + else if (frame == 40) f = f + 2; + } + } + else if (frame >= 103 && frame <= 118) + { + if (frame >= 103 && frame <= 104) f = f + 6; //nailattack + else if (frame >= 105 && frame <= 106) f = f + 6; //light + else if (frame >= 107 && frame <= 112) f = f + 7; //rocketattack + else if (frame >= 112 && frame <= 118) f = f + 7; //shotattack + } + // end of code taken from QuakeWorld + + flagrender = CL_NewTempEntity(player->render.shadertime); + if (!flagrender) + return; + + flagrender->model = CL_GetModelByIndex(cl.qw_modelindex_flag); + flagrender->skinnum = skin; + flagrender->alpha = 1; + VectorSet(flagrender->colormod, 1, 1, 1); + VectorSet(flagrender->glowmod, 1, 1, 1); + // attach the flag to the player matrix + Matrix4x4_CreateFromQuakeEntity(&flagmatrix, -f, -22, 0, 0, 0, -45, 1); + Matrix4x4_Concat(&flagrender->matrix, &player->render.matrix, &flagmatrix); + CL_UpdateRenderEntity(flagrender); +} + +matrix4x4_t viewmodelmatrix_withbob; +matrix4x4_t viewmodelmatrix_nobob; + +static const vec3_t muzzleflashorigin = {18, 0, 0}; + +void CL_SetEntityColormapColors(entity_render_t *ent, int colormap) +{ + const unsigned char *cbcolor; + if (colormap >= 0) + { + cbcolor = palette_rgb_pantscolormap[colormap & 0xF]; + VectorScale(cbcolor, (1.0f / 255.0f), ent->colormap_pantscolor); + cbcolor = palette_rgb_shirtcolormap[(colormap & 0xF0) >> 4]; + VectorScale(cbcolor, (1.0f / 255.0f), ent->colormap_shirtcolor); + } + else + { + VectorClear(ent->colormap_pantscolor); + VectorClear(ent->colormap_shirtcolor); + } +} + +// note this is a recursive function, recursionlimit should be 32 or so on the initial call +static void CL_UpdateNetworkEntity(entity_t *e, int recursionlimit, qboolean interpolate) +{ + const matrix4x4_t *matrix; + matrix4x4_t blendmatrix, tempmatrix, matrix2; + int frame; + vec_t origin[3], angles[3], lerp; + entity_t *t; + entity_render_t *r; + //entity_persistent_t *p = &e->persistent; + //entity_render_t *r = &e->render; + // skip inactive entities and world + if (!e->state_current.active || e == cl.entities) + return; + if (recursionlimit < 1) + return; + e->render.alpha = e->state_current.alpha * (1.0f / 255.0f); // FIXME: interpolate? + e->render.scale = e->state_current.scale * (1.0f / 16.0f); // FIXME: interpolate? + e->render.flags = e->state_current.flags; + e->render.effects = e->state_current.effects; + VectorScale(e->state_current.colormod, (1.0f / 32.0f), e->render.colormod); + VectorScale(e->state_current.glowmod, (1.0f / 32.0f), e->render.glowmod); + if(e >= cl.entities && e < cl.entities + cl.num_entities) + e->render.entitynumber = e - cl.entities; + else + e->render.entitynumber = 0; + if (e->state_current.flags & RENDER_COLORMAPPED) + CL_SetEntityColormapColors(&e->render, e->state_current.colormap); + else if (e->state_current.colormap > 0 && e->state_current.colormap <= cl.maxclients && cl.scores != NULL) + CL_SetEntityColormapColors(&e->render, cl.scores[e->state_current.colormap-1].colors); + else + CL_SetEntityColormapColors(&e->render, -1); + e->render.skinnum = e->state_current.skin; + if (e->state_current.tagentity) + { + // attached entity (gun held in player model's hand, etc) + // if the tag entity is currently impossible, skip it + if (e->state_current.tagentity >= cl.num_entities) + return; + t = cl.entities + e->state_current.tagentity; + // if the tag entity is inactive, skip it + if (t->state_current.active) + { + // update the parent first + CL_UpdateNetworkEntity(t, recursionlimit - 1, interpolate); + r = &t->render; + } + else + { + // it may still be a CSQC entity... trying to use its + // info from last render frame (better than nothing) + if(!cl.csqc_server2csqcentitynumber[e->state_current.tagentity]) + return; + r = cl.csqcrenderentities + cl.csqc_server2csqcentitynumber[e->state_current.tagentity]; + if(!r->entitynumber) + return; // neither CSQC nor legacy entity... can't attach + } + // make relative to the entity + matrix = &r->matrix; + // some properties of the tag entity carry over + e->render.flags |= r->flags & (RENDER_EXTERIORMODEL | RENDER_VIEWMODEL); + // if a valid tagindex is used, make it relative to that tag instead + if (e->state_current.tagentity && e->state_current.tagindex >= 1 && r->model) + { + if(!Mod_Alias_GetTagMatrix(r->model, r->frameblend, r->skeleton, e->state_current.tagindex - 1, &blendmatrix)) // i.e. no error + { + // concat the tag matrices onto the entity matrix + Matrix4x4_Concat(&tempmatrix, &r->matrix, &blendmatrix); + // use the constructed tag matrix + matrix = &tempmatrix; + } + } + } + else if (e->render.flags & RENDER_VIEWMODEL) + { + // view-relative entity (guns and such) + if (e->render.effects & EF_NOGUNBOB) + matrix = &viewmodelmatrix_nobob; // really attached to view + else + matrix = &viewmodelmatrix_withbob; // attached to gun bob matrix + } + else + { + // world-relative entity (the normal kind) + matrix = &identitymatrix; + } + + // movement lerp + // if it's the predicted player entity, update according to client movement + // but don't lerp if going through a teleporter as it causes a bad lerp + // also don't use the predicted location if fixangle was set on both of + // the most recent server messages, as that cause means you are spectating + // someone or watching a cutscene of some sort + if (cl_nolerp.integer || cls.timedemo) + interpolate = false; + if (e == cl.entities + cl.playerentity && cl.movement_predicted && (!cl.fixangle[1] || !cl.fixangle[0])) + { + VectorCopy(cl.movement_origin, origin); + VectorSet(angles, 0, cl.viewangles[1], 0); + } + else if (interpolate && e->persistent.lerpdeltatime > 0 && (lerp = (cl.time - e->persistent.lerpstarttime) / e->persistent.lerpdeltatime) < 1 + cl_lerpexcess.value) + { + // interpolate the origin and angles + lerp = max(0, lerp); + VectorLerp(e->persistent.oldorigin, lerp, e->persistent.neworigin, origin); +#if 0 + // this fails at the singularity of euler angles + VectorSubtract(e->persistent.newangles, e->persistent.oldangles, delta); + if (delta[0] < -180) delta[0] += 360;else if (delta[0] >= 180) delta[0] -= 360; + if (delta[1] < -180) delta[1] += 360;else if (delta[1] >= 180) delta[1] -= 360; + if (delta[2] < -180) delta[2] += 360;else if (delta[2] >= 180) delta[2] -= 360; + VectorMA(e->persistent.oldangles, lerp, delta, angles); +#else + { + vec3_t f0, u0, f1, u1; + AngleVectors(e->persistent.oldangles, f0, NULL, u0); + AngleVectors(e->persistent.newangles, f1, NULL, u1); + VectorMAM(1-lerp, f0, lerp, f1, f0); + VectorMAM(1-lerp, u0, lerp, u1, u0); + AnglesFromVectors(angles, f0, u0, false); + } +#endif + } + else + { + // no interpolation + VectorCopy(e->persistent.neworigin, origin); + VectorCopy(e->persistent.newangles, angles); + } + + // model setup and some modelflags + frame = e->state_current.frame; + e->render.model = CL_GetModelByIndex(e->state_current.modelindex); + if (e->render.model) + { + if (e->render.skinnum >= e->render.model->numskins) + e->render.skinnum = 0; + if (frame >= e->render.model->numframes) + frame = 0; + // models can set flags such as EF_ROCKET + // this 0xFF800000 mask is EF_NOMODELFLAGS plus all the higher EF_ flags such as EF_ROCKET + if (!(e->render.effects & 0xFF800000)) + e->render.effects |= e->render.model->effects; + // if model is alias or this is a tenebrae-like dlight, reverse pitch direction + if (e->render.model->type == mod_alias) + angles[0] = -angles[0]; + if ((e->render.effects & EF_SELECTABLE) && cl.cmd.cursor_entitynumber == e->state_current.number) + { + VectorScale(e->render.colormod, 2, e->render.colormod); + VectorScale(e->render.glowmod, 2, e->render.glowmod); + } + } + // if model is alias or this is a tenebrae-like dlight, reverse pitch direction + else if (e->state_current.lightpflags & PFLAGS_FULLDYNAMIC) + angles[0] = -angles[0]; + // NOTE: this must be synced to SV_GetPitchSign! + + if ((e->render.effects & EF_ROTATE) && !(e->render.flags & RENDER_VIEWMODEL)) + { + angles[1] = ANGLEMOD(100*cl.time); + if (cl_itembobheight.value) + origin[2] += (cos(cl.time * cl_itembobspeed.value * (2.0 * M_PI)) + 1.0) * 0.5 * cl_itembobheight.value; + } + + // animation lerp + e->render.skeleton = NULL; + if (e->render.flags & RENDER_COMPLEXANIMATION) + { + e->render.framegroupblend[0] = e->state_current.framegroupblend[0]; + e->render.framegroupblend[1] = e->state_current.framegroupblend[1]; + e->render.framegroupblend[2] = e->state_current.framegroupblend[2]; + e->render.framegroupblend[3] = e->state_current.framegroupblend[3]; + if (e->state_current.skeletonobject.model && e->state_current.skeletonobject.relativetransforms) + e->render.skeleton = &e->state_current.skeletonobject; + } + else if (e->render.framegroupblend[0].frame == frame) + { + // update frame lerp fraction + e->render.framegroupblend[0].lerp = 1; + e->render.framegroupblend[1].lerp = 0; + if (e->render.framegroupblend[0].start > e->render.framegroupblend[1].start) + { + // make sure frame lerp won't last longer than 100ms + // (this mainly helps with models that use framegroups and + // switch between them infrequently) + float maxdelta = cl_lerpanim_maxdelta_server.value; + if(e->render.model) + if(e->render.model->animscenes) + if(e->render.model->animscenes[e->render.framegroupblend[0].frame].framecount > 1 || e->render.model->animscenes[e->render.framegroupblend[1].frame].framecount > 1) + maxdelta = cl_lerpanim_maxdelta_framegroups.value; + maxdelta = max(maxdelta, cl.mtime[0] - cl.mtime[1]); + e->render.framegroupblend[0].lerp = (cl.time - e->render.framegroupblend[0].start) / min(e->render.framegroupblend[0].start - e->render.framegroupblend[1].start, maxdelta); + e->render.framegroupblend[0].lerp = bound(0, e->render.framegroupblend[0].lerp, 1); + e->render.framegroupblend[1].lerp = 1 - e->render.framegroupblend[0].lerp; + } + } + else + { + // begin a new frame lerp + e->render.framegroupblend[1] = e->render.framegroupblend[0]; + e->render.framegroupblend[1].lerp = 1; + e->render.framegroupblend[0].frame = frame; + e->render.framegroupblend[0].start = cl.time; + e->render.framegroupblend[0].lerp = 0; + } + + // set up the render matrix + if (matrix) + { + // attached entity, this requires a matrix multiply (concat) + // FIXME: e->render.scale should go away + Matrix4x4_CreateFromQuakeEntity(&matrix2, origin[0], origin[1], origin[2], angles[0], angles[1], angles[2], e->render.scale); + // concat the matrices to make the entity relative to its tag + Matrix4x4_Concat(&e->render.matrix, matrix, &matrix2); + // get the origin from the new matrix + Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); + } + else + { + // unattached entities are faster to process + Matrix4x4_CreateFromQuakeEntity(&e->render.matrix, origin[0], origin[1], origin[2], angles[0], angles[1], angles[2], e->render.scale); + } + + // tenebrae's sprites are all additive mode (weird) + if (gamemode == GAME_TENEBRAE && e->render.model && e->render.model->type == mod_sprite) + e->render.flags |= RENDER_ADDITIVE; + // player model is only shown with chase_active on + if (e->state_current.number == cl.viewentity) + e->render.flags |= RENDER_EXTERIORMODEL; + // either fullbright or lit + if(!r_fullbright.integer) + { + if (!(e->render.effects & EF_FULLBRIGHT)) + e->render.flags |= RENDER_LIGHT; + else if(r_equalize_entities_fullbright.integer) + e->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; + } + // hide player shadow during intermission or nehahra movie + if (!(e->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) + && (e->render.alpha >= 1) + && !(e->render.flags & RENDER_VIEWMODEL) + && (!(e->render.flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer))) + e->render.flags |= RENDER_SHADOW; + if (e->render.flags & RENDER_VIEWMODEL) + e->render.flags |= RENDER_NOSELFSHADOW; + if (e->render.effects & EF_NOSELFSHADOW) + e->render.flags |= RENDER_NOSELFSHADOW; + if (e->render.effects & EF_NODEPTHTEST) + e->render.flags |= RENDER_NODEPTHTEST; + if (e->render.effects & EF_ADDITIVE) + e->render.flags |= RENDER_ADDITIVE; + if (e->render.effects & EF_DOUBLESIDED) + e->render.flags |= RENDER_DOUBLESIDED; + if (e->render.effects & EF_DYNAMICMODELLIGHT) + e->render.flags |= RENDER_DYNAMICMODELLIGHT; + + // make the other useful stuff + e->render.allowdecals = true; + CL_UpdateRenderEntity(&e->render); +} + +// creates light and trails from an entity +static void CL_UpdateNetworkEntityTrail(entity_t *e) +{ + effectnameindex_t trailtype; + vec3_t origin; + + // bmodels are treated specially since their origin is usually '0 0 0' and + // their actual geometry is far from '0 0 0' + if (e->render.model && e->render.model->soundfromcenter) + { + vec3_t o; + VectorMAM(0.5f, e->render.model->normalmins, 0.5f, e->render.model->normalmaxs, o); + Matrix4x4_Transform(&e->render.matrix, o, origin); + } + else + Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); + + // handle particle trails and such effects now that we know where this + // entity is in the world... + trailtype = EFFECT_NONE; + // LordHavoc: if the entity has no effects, don't check each + if (e->render.effects & (EF_BRIGHTFIELD | EF_FLAME | EF_STARDUST)) + { + if (e->render.effects & EF_BRIGHTFIELD) + { + if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + trailtype = EFFECT_TR_NEXUIZPLASMA; + else + CL_EntityParticles(e); + } + if (e->render.effects & EF_FLAME) + CL_ParticleTrail(EFFECT_EF_FLAME, bound(0, cl.time - cl.oldtime, 0.1), origin, origin, vec3_origin, vec3_origin, NULL, 0, false, true, NULL, NULL); + if (e->render.effects & EF_STARDUST) + CL_ParticleTrail(EFFECT_EF_STARDUST, bound(0, cl.time - cl.oldtime, 0.1), origin, origin, vec3_origin, vec3_origin, NULL, 0, false, true, NULL, NULL); + } + if (e->render.internaleffects & (INTEF_FLAG1QW | INTEF_FLAG2QW)) + { + // these are only set on player entities + CL_AddQWCTFFlagModel(e, (e->render.internaleffects & INTEF_FLAG2QW) != 0); + } + // muzzleflash fades over time + if (e->persistent.muzzleflash > 0) + e->persistent.muzzleflash -= bound(0, cl.time - cl.oldtime, 0.1) * 20; + // LordHavoc: if the entity has no effects, don't check each + if (e->render.effects && !(e->render.flags & RENDER_VIEWMODEL)) + { + if (e->render.effects & EF_GIB) + trailtype = EFFECT_TR_BLOOD; + else if (e->render.effects & EF_ZOMGIB) + trailtype = EFFECT_TR_SLIGHTBLOOD; + else if (e->render.effects & EF_TRACER) + trailtype = EFFECT_TR_WIZSPIKE; + else if (e->render.effects & EF_TRACER2) + trailtype = EFFECT_TR_KNIGHTSPIKE; + else if (e->render.effects & EF_ROCKET) + trailtype = EFFECT_TR_ROCKET; + else if (e->render.effects & EF_GRENADE) + { + // LordHavoc: e->render.alpha == -1 is for Nehahra dem compatibility (cigar smoke) + trailtype = e->render.alpha == -1 ? EFFECT_TR_NEHAHRASMOKE : EFFECT_TR_GRENADE; + } + else if (e->render.effects & EF_TRACER3) + trailtype = EFFECT_TR_VORESPIKE; + } + // do trails + if (e->render.flags & RENDER_GLOWTRAIL) + trailtype = EFFECT_TR_GLOWTRAIL; + if (e->state_current.traileffectnum) + trailtype = (effectnameindex_t)e->state_current.traileffectnum; + // check if a trail is allowed (it is not after a teleport for example) + if (trailtype && e->persistent.trail_allowed) + { + float len; + vec3_t vel; + VectorSubtract(e->state_current.origin, e->state_previous.origin, vel); + len = e->state_current.time - e->state_previous.time; + if (len > 0) + len = 1.0f / len; + VectorScale(vel, len, vel); + // pass time as count so that trails that are time based (such as an emitter) will emit properly as long as they don't use trailspacing + CL_ParticleTrail(trailtype, bound(0, cl.time - cl.oldtime, 0.1), e->persistent.trail_origin, origin, vel, vel, e, e->state_current.glowcolor, false, true, NULL, NULL); + } + // now that the entity has survived one trail update it is allowed to + // leave a real trail on later frames + e->persistent.trail_allowed = true; + VectorCopy(origin, e->persistent.trail_origin); +} + + +/* +=============== +CL_UpdateViewEntities +=============== +*/ +void CL_UpdateViewEntities(void) +{ + int i; + // update any RENDER_VIEWMODEL entities to use the new view matrix + for (i = 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + entity_t *ent = cl.entities + i; + if ((ent->render.flags & RENDER_VIEWMODEL) || ent->state_current.tagentity) + CL_UpdateNetworkEntity(ent, 32, true); + } + } + // and of course the engine viewmodel needs updating as well + CL_UpdateNetworkEntity(&cl.viewent, 32, true); +} + +/* +=============== +CL_UpdateNetworkCollisionEntities +=============== +*/ +static void CL_UpdateNetworkCollisionEntities(void) +{ + entity_t *ent; + int i; + + // start on the entity after the world + cl.num_brushmodel_entities = 0; + for (i = cl.maxclients + 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + ent = cl.entities + i; + if (ent->state_current.active && ent->render.model && ent->render.model->name[0] == '*' && ent->render.model->TraceBox) + { + // do not interpolate the bmodels for this + CL_UpdateNetworkEntity(ent, 32, false); + cl.brushmodel_entities[cl.num_brushmodel_entities++] = i; + } + } + } +} + +/* +=============== +CL_UpdateNetworkEntities +=============== +*/ +static void CL_UpdateNetworkEntities(void) +{ + entity_t *ent; + int i; + + // start on the entity after the world + for (i = 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + ent = cl.entities + i; + if (ent->state_current.active) + { + CL_UpdateNetworkEntity(ent, 32, true); + // view models should never create light/trails + if (!(ent->render.flags & RENDER_VIEWMODEL)) + CL_UpdateNetworkEntityTrail(ent); + } + else + { + R_DecalSystem_Reset(&ent->render.decalsystem); + cl.entities_active[i] = false; + } + } + } +} + +static void CL_UpdateViewModel(void) +{ + entity_t *ent; + ent = &cl.viewent; + ent->state_previous = ent->state_current; + ent->state_current = defaultstate; + ent->state_current.time = cl.time; + ent->state_current.number = (unsigned short)-1; + ent->state_current.active = true; + ent->state_current.modelindex = cl.stats[STAT_WEAPON]; + ent->state_current.frame = cl.stats[STAT_WEAPONFRAME]; + ent->state_current.flags = RENDER_VIEWMODEL; + if ((cl.stats[STAT_HEALTH] <= 0 && cl_deathnoviewmodel.integer) || cl.intermission) + ent->state_current.modelindex = 0; + else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) + { + if (gamemode == GAME_TRANSFUSION) + ent->state_current.alpha = 128; + else + ent->state_current.modelindex = 0; + } + ent->state_current.alpha = cl.entities[cl.viewentity].state_current.alpha; + ent->state_current.effects = EF_NOSHADOW | (cl.entities[cl.viewentity].state_current.effects & (EF_ADDITIVE | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB)); + + // reset animation interpolation on weaponmodel if model changed + if (ent->state_previous.modelindex != ent->state_current.modelindex) + { + ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; + ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; + } + CL_UpdateNetworkEntity(ent, 32, true); +} + +// note this is a recursive function, but it can never get in a runaway loop (because of the delayedlink flags) +static void CL_LinkNetworkEntity(entity_t *e) +{ + effectnameindex_t trailtype; + vec3_t origin; + vec3_t dlightcolor; + vec_t dlightradius; + char vabuf[1024]; + + // skip inactive entities and world + if (!e->state_current.active || e == cl.entities) + return; + if (e->state_current.tagentity) + { + // if the tag entity is currently impossible, skip it + if (e->state_current.tagentity >= cl.num_entities) + return; + // if the tag entity is inactive, skip it + if (!cl.entities[e->state_current.tagentity].state_current.active) + { + if(!cl.csqc_server2csqcentitynumber[e->state_current.tagentity]) + return; + if(!cl.csqcrenderentities[cl.csqc_server2csqcentitynumber[e->state_current.tagentity]].entitynumber) + return; + // if we get here, it's properly csqc networked and attached + } + } + + // create entity dlights associated with this entity + if (e->render.model && e->render.model->soundfromcenter) + { + // bmodels are treated specially since their origin is usually '0 0 0' + vec3_t o; + VectorMAM(0.5f, e->render.model->normalmins, 0.5f, e->render.model->normalmaxs, o); + Matrix4x4_Transform(&e->render.matrix, o, origin); + } + else + Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); + trailtype = EFFECT_NONE; + dlightradius = 0; + dlightcolor[0] = 0; + dlightcolor[1] = 0; + dlightcolor[2] = 0; + // LordHavoc: if the entity has no effects, don't check each + if (e->render.effects & (EF_BRIGHTFIELD | EF_DIMLIGHT | EF_BRIGHTLIGHT | EF_RED | EF_BLUE | EF_FLAME | EF_STARDUST)) + { + if (e->render.effects & EF_BRIGHTFIELD) + { + if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + trailtype = EFFECT_TR_NEXUIZPLASMA; + } + if (e->render.effects & EF_DIMLIGHT) + { + dlightradius = max(dlightradius, 200); + dlightcolor[0] += 1.50f; + dlightcolor[1] += 1.50f; + dlightcolor[2] += 1.50f; + } + if (e->render.effects & EF_BRIGHTLIGHT) + { + dlightradius = max(dlightradius, 400); + dlightcolor[0] += 3.00f; + dlightcolor[1] += 3.00f; + dlightcolor[2] += 3.00f; + } + // LordHavoc: more effects + if (e->render.effects & EF_RED) // red + { + dlightradius = max(dlightradius, 200); + dlightcolor[0] += 1.50f; + dlightcolor[1] += 0.15f; + dlightcolor[2] += 0.15f; + } + if (e->render.effects & EF_BLUE) // blue + { + dlightradius = max(dlightradius, 200); + dlightcolor[0] += 0.15f; + dlightcolor[1] += 0.15f; + dlightcolor[2] += 1.50f; + } + if (e->render.effects & EF_FLAME) + CL_ParticleTrail(EFFECT_EF_FLAME, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0, true, false, NULL, NULL); + if (e->render.effects & EF_STARDUST) + CL_ParticleTrail(EFFECT_EF_STARDUST, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0, true, false, NULL, NULL); + } + // muzzleflash fades over time, and is offset a bit + if (e->persistent.muzzleflash > 0 && r_refdef.scene.numlights < MAX_DLIGHTS) + { + vec3_t v2; + vec3_t color; + trace_t trace; + matrix4x4_t tempmatrix; + Matrix4x4_Transform(&e->render.matrix, muzzleflashorigin, v2); + trace = CL_TraceLine(origin, v2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, NULL, false, false); + Matrix4x4_Normalize(&tempmatrix, &e->render.matrix); + Matrix4x4_SetOrigin(&tempmatrix, trace.endpos[0], trace.endpos[1], trace.endpos[2]); + Matrix4x4_Scale(&tempmatrix, 150, 1); + VectorSet(color, e->persistent.muzzleflash * 4.0f, e->persistent.muzzleflash * 4.0f, e->persistent.muzzleflash * 4.0f); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, color, -1, NULL, true, 0, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + // LordHavoc: if the model has no flags, don't check each + if (e->render.model && e->render.effects && !(e->render.flags & RENDER_VIEWMODEL)) + { + if (e->render.effects & EF_GIB) + trailtype = EFFECT_TR_BLOOD; + else if (e->render.effects & EF_ZOMGIB) + trailtype = EFFECT_TR_SLIGHTBLOOD; + else if (e->render.effects & EF_TRACER) + trailtype = EFFECT_TR_WIZSPIKE; + else if (e->render.effects & EF_TRACER2) + trailtype = EFFECT_TR_KNIGHTSPIKE; + else if (e->render.effects & EF_ROCKET) + trailtype = EFFECT_TR_ROCKET; + else if (e->render.effects & EF_GRENADE) + { + // LordHavoc: e->render.alpha == -1 is for Nehahra dem compatibility (cigar smoke) + trailtype = e->render.alpha == -1 ? EFFECT_TR_NEHAHRASMOKE : EFFECT_TR_GRENADE; + } + else if (e->render.effects & EF_TRACER3) + trailtype = EFFECT_TR_VORESPIKE; + } + // LordHavoc: customizable glow + if (e->state_current.glowsize) + { + // * 4 for the expansion from 0-255 to 0-1023 range, + // / 255 to scale down byte colors + dlightradius = max(dlightradius, e->state_current.glowsize * 4); + VectorMA(dlightcolor, (1.0f / 255.0f), palette_rgb[e->state_current.glowcolor], dlightcolor); + } + // custom rtlight + if ((e->state_current.lightpflags & PFLAGS_FULLDYNAMIC) && r_refdef.scene.numlights < MAX_DLIGHTS) + { + matrix4x4_t dlightmatrix; + vec4_t light; + VectorScale(e->state_current.light, (1.0f / 256.0f), light); + light[3] = e->state_current.light[3]; + if (light[0] == 0 && light[1] == 0 && light[2] == 0) + VectorSet(light, 1, 1, 1); + if (light[3] == 0) + light[3] = 350; + // FIXME: add ambient/diffuse/specular scales as an extension ontop of TENEBRAE_GFX_DLIGHTS? + Matrix4x4_Normalize(&dlightmatrix, &e->render.matrix); + Matrix4x4_Scale(&dlightmatrix, light[3], 1); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &dlightmatrix, light, e->state_current.lightstyle, e->state_current.skin > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", e->state_current.skin) : NULL, !(e->state_current.lightpflags & PFLAGS_NOSHADOW), (e->state_current.lightpflags & PFLAGS_CORONA) != 0, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + // make the glow dlight + else if (dlightradius > 0 && (dlightcolor[0] || dlightcolor[1] || dlightcolor[2]) && !(e->render.flags & RENDER_VIEWMODEL) && r_refdef.scene.numlights < MAX_DLIGHTS) + { + matrix4x4_t dlightmatrix; + Matrix4x4_Normalize(&dlightmatrix, &e->render.matrix); + // hack to make glowing player light shine on their gun + //if (e->state_current.number == cl.viewentity/* && !chase_active.integer*/) + // Matrix4x4_AdjustOrigin(&dlightmatrix, 0, 0, 30); + Matrix4x4_Scale(&dlightmatrix, dlightradius, 1); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &dlightmatrix, dlightcolor, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + // do trail light + if (e->render.flags & RENDER_GLOWTRAIL) + trailtype = EFFECT_TR_GLOWTRAIL; + if (e->state_current.traileffectnum) + trailtype = (effectnameindex_t)e->state_current.traileffectnum; + if (trailtype) + CL_ParticleTrail(trailtype, 1, origin, origin, vec3_origin, vec3_origin, NULL, e->state_current.glowcolor, true, false, NULL, NULL); + + // don't show entities with no modelindex (note: this still shows + // entities which have a modelindex that resolved to a NULL model) + if (e->render.model && !(e->render.effects & EF_NODRAW) && r_refdef.scene.numentities < r_refdef.scene.maxentities) + r_refdef.scene.entities[r_refdef.scene.numentities++] = &e->render; + //if (cl.viewentity && e->state_current.number == cl.viewentity) + // Matrix4x4_Print(&e->render.matrix); +} + +static void CL_RelinkWorld(void) +{ + entity_t *ent = &cl.entities[0]; + // FIXME: this should be done at load + ent->render.matrix = identitymatrix; + ent->render.flags = RENDER_SHADOW; + if (!r_fullbright.integer) + ent->render.flags |= RENDER_LIGHT; + VectorSet(ent->render.colormod, 1, 1, 1); + VectorSet(ent->render.glowmod, 1, 1, 1); + ent->render.allowdecals = true; + CL_UpdateRenderEntity(&ent->render); + r_refdef.scene.worldentity = &ent->render; + r_refdef.scene.worldmodel = cl.worldmodel; +} + +static void CL_RelinkStaticEntities(void) +{ + int i; + entity_t *e; + for (i = 0, e = cl.static_entities;i < cl.num_static_entities && r_refdef.scene.numentities < r_refdef.scene.maxentities;i++, e++) + { + e->render.flags = 0; + // if the model was not loaded when the static entity was created we + // need to re-fetch the model pointer + e->render.model = CL_GetModelByIndex(e->state_baseline.modelindex); + // either fullbright or lit + if(!r_fullbright.integer) + { + if (!(e->render.effects & EF_FULLBRIGHT)) + e->render.flags |= RENDER_LIGHT; + else if(r_equalize_entities_fullbright.integer) + e->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; + } + // hide player shadow during intermission or nehahra movie + if (!(e->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (e->render.alpha >= 1)) + e->render.flags |= RENDER_SHADOW; + VectorSet(e->render.colormod, 1, 1, 1); + VectorSet(e->render.glowmod, 1, 1, 1); + VM_FrameBlendFromFrameGroupBlend(e->render.frameblend, e->render.framegroupblend, e->render.model, cl.time); + e->render.allowdecals = true; + CL_UpdateRenderEntity(&e->render); + r_refdef.scene.entities[r_refdef.scene.numentities++] = &e->render; + } +} + +/* +=============== +CL_RelinkEntities +=============== +*/ +static void CL_RelinkNetworkEntities(void) +{ + entity_t *ent; + int i; + + // start on the entity after the world + for (i = 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + ent = cl.entities + i; + if (ent->state_current.active) + CL_LinkNetworkEntity(ent); + else + cl.entities_active[i] = false; + } + } +} + +static void CL_RelinkEffects(void) +{ + int i, intframe; + cl_effect_t *e; + entity_render_t *entrender; + float frame; + + for (i = 0, e = cl.effects;i < cl.num_effects;i++, e++) + { + if (e->active) + { + frame = (cl.time - e->starttime) * e->framerate + e->startframe; + intframe = (int)frame; + if (intframe < 0 || intframe >= e->endframe) + { + memset(e, 0, sizeof(*e)); + while (cl.num_effects > 0 && !cl.effects[cl.num_effects - 1].active) + cl.num_effects--; + continue; + } + + if (intframe != e->frame) + { + e->frame = intframe; + e->frame1time = e->frame2time; + e->frame2time = cl.time; + } + + // if we're drawing effects, get a new temp entity + // (NewTempEntity adds it to the render entities list for us) + if (r_draweffects.integer && (entrender = CL_NewTempEntity(e->starttime))) + { + // interpolation stuff + entrender->framegroupblend[0].frame = intframe; + entrender->framegroupblend[0].lerp = 1 - frame - intframe; + entrender->framegroupblend[0].start = e->frame1time; + if (intframe + 1 >= e->endframe) + { + entrender->framegroupblend[1].frame = 0; // disappear + entrender->framegroupblend[1].lerp = 0; + entrender->framegroupblend[1].start = 0; + } + else + { + entrender->framegroupblend[1].frame = intframe + 1; + entrender->framegroupblend[1].lerp = frame - intframe; + entrender->framegroupblend[1].start = e->frame2time; + } + + // normal stuff + entrender->model = CL_GetModelByIndex(e->modelindex); + entrender->alpha = 1; + VectorSet(entrender->colormod, 1, 1, 1); + VectorSet(entrender->glowmod, 1, 1, 1); + + Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, e->origin[0], e->origin[1], e->origin[2], 0, 0, 0, 1); + CL_UpdateRenderEntity(entrender); + } + } + } +} + +void CL_Beam_CalculatePositions(const beam_t *b, vec3_t start, vec3_t end) +{ + VectorCopy(b->start, start); + VectorCopy(b->end, end); + + // if coming from the player, update the start position + if (b->entity == cl.viewentity) + { + if (cl_beams_quakepositionhack.integer && !chase_active.integer) + { + // LordHavoc: this is a stupid hack from Quake that makes your + // lightning appear to come from your waist and cover less of your + // view + // in Quake this hack was applied to all players (causing the + // infamous crotch-lightning), but in darkplaces and QuakeWorld it + // only applies to your own lightning, and only in first person + Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, start); + } + if (cl_beams_instantaimhack.integer) + { + vec3_t dir, localend; + vec_t len; + // LordHavoc: this updates the beam direction to match your + // viewangles + VectorSubtract(end, start, dir); + len = VectorLength(dir); + VectorNormalize(dir); + VectorSet(localend, len, 0, 0); + Matrix4x4_Transform(&r_refdef.view.matrix, localend, end); + } + } +} + +void CL_RelinkBeams(void) +{ + int i; + beam_t *b; + vec3_t dist, org, start, end; + float d; + entity_render_t *entrender; + double yaw, pitch; + float forward; + matrix4x4_t tempmatrix; + + for (i = 0, b = cl.beams;i < cl.num_beams;i++, b++) + { + if (!b->model) + continue; + if (b->endtime < cl.time) + { + b->model = NULL; + continue; + } + + CL_Beam_CalculatePositions(b, start, end); + + if (b->lightning) + { + if (cl_beams_lightatend.integer && r_refdef.scene.numlights < MAX_DLIGHTS) + { + // FIXME: create a matrix from the beam start/end orientation + vec3_t dlightcolor; + VectorSet(dlightcolor, 0.3, 0.7, 1); + Matrix4x4_CreateFromQuakeEntity(&tempmatrix, end[0], end[1], end[2], 0, 0, 0, 200); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, dlightcolor, -1, NULL, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + if (cl_beams_polygons.integer) + continue; + } + + // calculate pitch and yaw + // (this is similar to the QuakeC builtin function vectoangles) + VectorSubtract(end, start, dist); + if (dist[1] == 0 && dist[0] == 0) + { + yaw = 0; + if (dist[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = atan2(dist[1], dist[0]) * 180 / M_PI; + if (yaw < 0) + yaw += 360; + + forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]); + pitch = atan2(dist[2], forward) * 180 / M_PI; + if (pitch < 0) + pitch += 360; + } + + // add new entities for the lightning + VectorCopy (start, org); + d = VectorNormalizeLength(dist); + while (d > 0) + { + entrender = CL_NewTempEntity (0); + if (!entrender) + return; + //VectorCopy (org, ent->render.origin); + entrender->model = b->model; + //ent->render.effects = EF_FULLBRIGHT; + //ent->render.angles[0] = pitch; + //ent->render.angles[1] = yaw; + //ent->render.angles[2] = rand()%360; + Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, org[0], org[1], org[2], -pitch, yaw, lhrandom(0, 360), 1); + CL_UpdateRenderEntity(entrender); + VectorMA(org, 30, dist, org); + d -= 30; + } + } + + while (cl.num_beams > 0 && !cl.beams[cl.num_beams - 1].model) + cl.num_beams--; +} + +static void CL_RelinkQWNails(void) +{ + int i; + vec_t *v; + entity_render_t *entrender; + + for (i = 0;i < cl.qw_num_nails;i++) + { + v = cl.qw_nails[i]; + + // if we're drawing effects, get a new temp entity + // (NewTempEntity adds it to the render entities list for us) + if (!(entrender = CL_NewTempEntity(0))) + continue; + + // normal stuff + entrender->model = CL_GetModelByIndex(cl.qw_modelindex_spike); + entrender->alpha = 1; + VectorSet(entrender->colormod, 1, 1, 1); + VectorSet(entrender->glowmod, 1, 1, 1); + + Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, v[0], v[1], v[2], v[3], v[4], v[5], 1); + CL_UpdateRenderEntity(entrender); + } +} + +static void CL_LerpPlayer(float frac) +{ + int i; + + cl.viewzoom = cl.mviewzoom[1] + frac * (cl.mviewzoom[0] - cl.mviewzoom[1]); + for (i = 0;i < 3;i++) + { + cl.punchangle[i] = cl.mpunchangle[1][i] + frac * (cl.mpunchangle[0][i] - cl.mpunchangle[1][i]); + cl.punchvector[i] = cl.mpunchvector[1][i] + frac * (cl.mpunchvector[0][i] - cl.mpunchvector[1][i]); + cl.velocity[i] = cl.mvelocity[1][i] + frac * (cl.mvelocity[0][i] - cl.mvelocity[1][i]); + } + + // interpolate the angles if playing a demo or spectating someone + if (cls.demoplayback || cl.fixangle[0]) + { + for (i = 0;i < 3;i++) + { + float d = cl.mviewangles[0][i] - cl.mviewangles[1][i]; + if (d > 180) + d -= 360; + else if (d < -180) + d += 360; + cl.viewangles[i] = cl.mviewangles[1][i] + frac * d; + } + } +} + +void CSQC_RelinkAllEntities (int drawmask) +{ + // link stuff + CL_RelinkWorld(); + CL_RelinkStaticEntities(); + CL_RelinkBeams(); + CL_RelinkEffects(); + + // link stuff + if (drawmask & ENTMASK_ENGINE) + { + CL_RelinkNetworkEntities(); + if (drawmask & ENTMASK_ENGINEVIEWMODELS) + CL_LinkNetworkEntity(&cl.viewent); // link gun model + CL_RelinkQWNails(); + } + + // update view blend + V_CalcViewBlend(); +} + +/* +=============== +CL_UpdateWorld + +Update client game world for a new frame +=============== +*/ +void CL_UpdateWorld(void) +{ + r_refdef.scene.extraupdate = !r_speeds.integer; + r_refdef.scene.numentities = 0; + r_refdef.scene.numlights = 0; + r_refdef.view.matrix = identitymatrix; + r_refdef.view.quality = 1; + + cl.num_brushmodel_entities = 0; + + if (cls.state == ca_connected && cls.signon == SIGNONS) + { + // prepare for a new frame + CL_LerpPlayer(CL_LerpPoint()); + CL_DecayLightFlashes(); + CL_ClearTempEntities(); + V_DriftPitch(); + V_FadeViewFlashs(); + + // if prediction is enabled we have to update all the collidable + // network entities before the prediction code can be run + CL_UpdateNetworkCollisionEntities(); + + // now update the player prediction + CL_ClientMovement_Replay(); + + // update the player entity (which may be predicted) + CL_UpdateNetworkEntity(cl.entities + cl.viewentity, 32, true); + + // now update the view (which depends on that player entity) + V_CalcRefdef(); + + // now update all the network entities and create particle trails + // (some entities may depend on the view) + CL_UpdateNetworkEntities(); + + // update the engine-based viewmodel + CL_UpdateViewModel(); + + CL_RelinkLightFlashes(); + CSQC_RelinkAllEntities(ENTMASK_ENGINE | ENTMASK_ENGINEVIEWMODELS); + + // decals, particles, and explosions will be updated during rneder + } + + r_refdef.scene.time = cl.time; +} + +// LordHavoc: pausedemo command +static void CL_PauseDemo_f (void) +{ + cls.demopaused = !cls.demopaused; + if (cls.demopaused) + Con_Print("Demo paused\n"); + else + Con_Print("Demo unpaused\n"); +} + +/* +====================== +CL_Fog_f +====================== +*/ +static void CL_Fog_f (void) +{ + if (Cmd_Argc () == 1) + { + Con_Printf("\"fog\" is \"%f %f %f %f %f %f %f %f %f\"\n", r_refdef.fog_density, r_refdef.fog_red, r_refdef.fog_green, r_refdef.fog_blue, r_refdef.fog_alpha, r_refdef.fog_start, r_refdef.fog_end, r_refdef.fog_height, r_refdef.fog_fadedepth); + return; + } + FOG_clear(); // so missing values get good defaults + if(Cmd_Argc() > 1) + r_refdef.fog_density = atof(Cmd_Argv(1)); + if(Cmd_Argc() > 2) + r_refdef.fog_red = atof(Cmd_Argv(2)); + if(Cmd_Argc() > 3) + r_refdef.fog_green = atof(Cmd_Argv(3)); + if(Cmd_Argc() > 4) + r_refdef.fog_blue = atof(Cmd_Argv(4)); + if(Cmd_Argc() > 5) + r_refdef.fog_alpha = atof(Cmd_Argv(5)); + if(Cmd_Argc() > 6) + r_refdef.fog_start = atof(Cmd_Argv(6)); + if(Cmd_Argc() > 7) + r_refdef.fog_end = atof(Cmd_Argv(7)); + if(Cmd_Argc() > 8) + r_refdef.fog_height = atof(Cmd_Argv(8)); + if(Cmd_Argc() > 9) + r_refdef.fog_fadedepth = atof(Cmd_Argv(9)); +} + +/* +====================== +CL_FogHeightTexture_f +====================== +*/ +static void CL_Fog_HeightTexture_f (void) +{ + if (Cmd_Argc () < 11) + { + Con_Printf("\"fog_heighttexture\" is \"%f %f %f %f %f %f %f %f %f %s\"\n", r_refdef.fog_density, r_refdef.fog_red, r_refdef.fog_green, r_refdef.fog_blue, r_refdef.fog_alpha, r_refdef.fog_start, r_refdef.fog_end, r_refdef.fog_height, r_refdef.fog_fadedepth, r_refdef.fog_height_texturename); + return; + } + FOG_clear(); // so missing values get good defaults + r_refdef.fog_density = atof(Cmd_Argv(1)); + r_refdef.fog_red = atof(Cmd_Argv(2)); + r_refdef.fog_green = atof(Cmd_Argv(3)); + r_refdef.fog_blue = atof(Cmd_Argv(4)); + r_refdef.fog_alpha = atof(Cmd_Argv(5)); + r_refdef.fog_start = atof(Cmd_Argv(6)); + r_refdef.fog_end = atof(Cmd_Argv(7)); + r_refdef.fog_height = atof(Cmd_Argv(8)); + r_refdef.fog_fadedepth = atof(Cmd_Argv(9)); + strlcpy(r_refdef.fog_height_texturename, Cmd_Argv(10), sizeof(r_refdef.fog_height_texturename)); +} + + +/* +==================== +CL_TimeRefresh_f + +For program optimization +==================== +*/ +static void CL_TimeRefresh_f (void) +{ + int i; + double timestart, timedelta; + + r_refdef.scene.extraupdate = false; + + timestart = Sys_DirtyTime(); + for (i = 0;i < 128;i++) + { + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], 0, i / 128.0 * 360.0, 0, 1); + r_refdef.view.quality = 1; + CL_BeginUpdateScreen(); + SCR_DrawScreen(0, 0); + CL_EndUpdateScreen(); + } + timedelta = Sys_DirtyTime() - timestart; + + Con_Printf("%f seconds (%f fps)\n", timedelta, 128/timedelta); +} + +static void CL_AreaStats_f(void) +{ + World_PrintAreaStats(&cl.world, "client"); +} + +cl_locnode_t *CL_Locs_FindNearest(const vec3_t point) +{ + int i; + cl_locnode_t *loc; + cl_locnode_t *best; + vec3_t nearestpoint; + vec_t dist, bestdist; + best = NULL; + bestdist = 0; + for (loc = cl.locnodes;loc;loc = loc->next) + { + for (i = 0;i < 3;i++) + nearestpoint[i] = bound(loc->mins[i], point[i], loc->maxs[i]); + dist = VectorDistance2(nearestpoint, point); + if (bestdist > dist || !best) + { + bestdist = dist; + best = loc; + if (bestdist < 1) + break; + } + } + return best; +} + +void CL_Locs_FindLocationName(char *buffer, size_t buffersize, vec3_t point) +{ + cl_locnode_t *loc; + loc = CL_Locs_FindNearest(point); + if (loc) + strlcpy(buffer, loc->name, buffersize); + else + dpsnprintf(buffer, buffersize, "LOC=%.0f:%.0f:%.0f", point[0], point[1], point[2]); +} + +static void CL_Locs_FreeNode(cl_locnode_t *node) +{ + cl_locnode_t **pointer, **next; + for (pointer = &cl.locnodes;*pointer;pointer = next) + { + next = &(*pointer)->next; + if (*pointer == node) + { + *pointer = node->next; + Mem_Free(node); + return; + } + } + Con_Printf("CL_Locs_FreeNode: no such node! (%p)\n", (void *)node); +} + +static void CL_Locs_AddNode(vec3_t mins, vec3_t maxs, const char *name) +{ + cl_locnode_t *node, **pointer; + int namelen; + if (!name) + name = ""; + namelen = strlen(name); + node = (cl_locnode_t *) Mem_Alloc(cls.levelmempool, sizeof(cl_locnode_t) + namelen + 1); + VectorSet(node->mins, min(mins[0], maxs[0]), min(mins[1], maxs[1]), min(mins[2], maxs[2])); + VectorSet(node->maxs, max(mins[0], maxs[0]), max(mins[1], maxs[1]), max(mins[2], maxs[2])); + node->name = (char *)(node + 1); + memcpy(node->name, name, namelen); + node->name[namelen] = 0; + // link it into the tail of the list to preserve the order + for (pointer = &cl.locnodes;*pointer;pointer = &(*pointer)->next) + ; + *pointer = node; +} + +static void CL_Locs_Add_f(void) +{ + vec3_t mins, maxs; + if (Cmd_Argc() != 5 && Cmd_Argc() != 8) + { + Con_Printf("usage: %s x y z[ x y z] name\n", Cmd_Argv(0)); + return; + } + mins[0] = atof(Cmd_Argv(1)); + mins[1] = atof(Cmd_Argv(2)); + mins[2] = atof(Cmd_Argv(3)); + if (Cmd_Argc() == 8) + { + maxs[0] = atof(Cmd_Argv(4)); + maxs[1] = atof(Cmd_Argv(5)); + maxs[2] = atof(Cmd_Argv(6)); + CL_Locs_AddNode(mins, maxs, Cmd_Argv(7)); + } + else + CL_Locs_AddNode(mins, mins, Cmd_Argv(4)); +} + +static void CL_Locs_RemoveNearest_f(void) +{ + cl_locnode_t *loc; + loc = CL_Locs_FindNearest(r_refdef.view.origin); + if (loc) + CL_Locs_FreeNode(loc); + else + Con_Printf("no loc point or box found for your location\n"); +} + +static void CL_Locs_Clear_f(void) +{ + while (cl.locnodes) + CL_Locs_FreeNode(cl.locnodes); +} + +static void CL_Locs_Save_f(void) +{ + cl_locnode_t *loc; + qfile_t *outfile; + char locfilename[MAX_QPATH]; + if (!cl.locnodes) + { + Con_Printf("No loc points/boxes exist!\n"); + return; + } + if (cls.state != ca_connected || !cl.worldmodel) + { + Con_Printf("No level loaded!\n"); + return; + } + dpsnprintf(locfilename, sizeof(locfilename), "%s.loc", cl.worldnamenoextension); + + outfile = FS_OpenRealFile(locfilename, "w", false); + if (!outfile) + return; + // if any boxes are used then this is a proquake-format loc file, which + // allows comments, so add some relevant information at the start + for (loc = cl.locnodes;loc;loc = loc->next) + if (!VectorCompare(loc->mins, loc->maxs)) + break; + if (loc) + { + FS_Printf(outfile, "// %s %s saved by %s\n// x,y,z,x,y,z,\"name\"\n\n", locfilename, Sys_TimeString("%Y-%m-%d"), engineversion); + for (loc = cl.locnodes;loc;loc = loc->next) + if (VectorCompare(loc->mins, loc->maxs)) + break; + if (loc) + Con_Printf("Warning: writing loc file containing a mixture of qizmo-style points and proquake-style boxes may not work in qizmo or proquake!\n"); + } + for (loc = cl.locnodes;loc;loc = loc->next) + { + if (VectorCompare(loc->mins, loc->maxs)) + { + int len; + const char *s; + const char *in = loc->name; + char name[MAX_INPUTLINE]; + for (len = 0;len < (int)sizeof(name) - 1 && *in;) + { + if (*in == ' ') {s = "$loc_name_separator";in++;} + else if (!strncmp(in, "SSG", 3)) {s = "$loc_name_ssg";in += 3;} + else if (!strncmp(in, "NG", 2)) {s = "$loc_name_ng";in += 2;} + else if (!strncmp(in, "SNG", 3)) {s = "$loc_name_sng";in += 3;} + else if (!strncmp(in, "GL", 2)) {s = "$loc_name_gl";in += 2;} + else if (!strncmp(in, "RL", 2)) {s = "$loc_name_rl";in += 2;} + else if (!strncmp(in, "LG", 2)) {s = "$loc_name_lg";in += 2;} + else if (!strncmp(in, "GA", 2)) {s = "$loc_name_ga";in += 2;} + else if (!strncmp(in, "YA", 2)) {s = "$loc_name_ya";in += 2;} + else if (!strncmp(in, "RA", 2)) {s = "$loc_name_ra";in += 2;} + else if (!strncmp(in, "MEGA", 4)) {s = "$loc_name_mh";in += 4;} + else s = NULL; + if (s) + { + while (len < (int)sizeof(name) - 1 && *s) + name[len++] = *s++; + continue; + } + name[len++] = *in++; + } + name[len] = 0; + FS_Printf(outfile, "%.0f %.0f %.0f %s\n", loc->mins[0]*8, loc->mins[1]*8, loc->mins[2]*8, name); + } + else + FS_Printf(outfile, "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,\"%s\"\n", loc->mins[0], loc->mins[1], loc->mins[2], loc->maxs[0], loc->maxs[1], loc->maxs[2], loc->name); + } + FS_Close(outfile); +} + +void CL_Locs_Reload_f(void) +{ + int i, linenumber, limit, len; + const char *s; + char *filedata, *text, *textend, *linestart, *linetext, *lineend; + fs_offset_t filesize; + vec3_t mins, maxs; + char locfilename[MAX_QPATH]; + char name[MAX_INPUTLINE]; + + if (cls.state != ca_connected || !cl.worldmodel) + { + Con_Printf("No level loaded!\n"); + return; + } + + CL_Locs_Clear_f(); + + // try maps/something.loc first (LordHavoc: where I think they should be) + dpsnprintf(locfilename, sizeof(locfilename), "%s.loc", cl.worldnamenoextension); + filedata = (char *)FS_LoadFile(locfilename, cls.levelmempool, false, &filesize); + if (!filedata) + { + // try proquake name as well (LordHavoc: I hate path mangling) + dpsnprintf(locfilename, sizeof(locfilename), "locs/%s.loc", cl.worldbasename); + filedata = (char *)FS_LoadFile(locfilename, cls.levelmempool, false, &filesize); + if (!filedata) + return; + } + text = filedata; + textend = filedata + filesize; + for (linenumber = 1;text < textend;linenumber++) + { + linestart = text; + for (;text < textend && *text != '\r' && *text != '\n';text++) + ; + lineend = text; + if (text + 1 < textend && *text == '\r' && text[1] == '\n') + text++; + if (text < textend) + text++; + // trim trailing whitespace + while (lineend > linestart && ISWHITESPACE(lineend[-1])) + lineend--; + // trim leading whitespace + while (linestart < lineend && ISWHITESPACE(*linestart)) + linestart++; + // check if this is a comment + if (linestart + 2 <= lineend && !strncmp(linestart, "//", 2)) + continue; + linetext = linestart; + limit = 3; + for (i = 0;i < limit;i++) + { + if (linetext >= lineend) + break; + // note: a missing number is interpreted as 0 + if (i < 3) + mins[i] = atof(linetext); + else + maxs[i - 3] = atof(linetext); + // now advance past the number + while (linetext < lineend && !ISWHITESPACE(*linetext) && *linetext != ',') + linetext++; + // advance through whitespace + if (linetext < lineend) + { + if (*linetext == ',') + { + linetext++; + limit = 6; + // note: comma can be followed by whitespace + } + if (ISWHITESPACE(*linetext)) + { + // skip whitespace + while (linetext < lineend && ISWHITESPACE(*linetext)) + linetext++; + } + } + } + // if this is a quoted name, remove the quotes + if (i == 6) + { + if (linetext >= lineend || *linetext != '"') + continue; // proquake location names are always quoted + lineend--; + linetext++; + len = min(lineend - linetext, (int)sizeof(name) - 1); + memcpy(name, linetext, len); + name[len] = 0; + // add the box to the list + CL_Locs_AddNode(mins, maxs, name); + } + // if a point was parsed, it needs to be scaled down by 8 (since + // point-based loc files were invented by a proxy which dealt + // directly with quake protocol coordinates, which are *8), turn + // it into a box + else if (i == 3) + { + // interpret silly fuhquake macros + for (len = 0;len < (int)sizeof(name) - 1 && linetext < lineend;) + { + if (*linetext == '$') + { + if (linetext + 18 <= lineend && !strncmp(linetext, "$loc_name_separator", 19)) {s = " ";linetext += 19;} + else if (linetext + 13 <= lineend && !strncmp(linetext, "$loc_name_ssg", 13)) {s = "SSG";linetext += 13;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ng", 12)) {s = "NG";linetext += 12;} + else if (linetext + 13 <= lineend && !strncmp(linetext, "$loc_name_sng", 13)) {s = "SNG";linetext += 13;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_gl", 12)) {s = "GL";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_rl", 12)) {s = "RL";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_lg", 12)) {s = "LG";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ga", 12)) {s = "GA";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ya", 12)) {s = "YA";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ra", 12)) {s = "RA";linetext += 12;} + else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_mh", 12)) {s = "MEGA";linetext += 12;} + else s = NULL; + if (s) + { + while (len < (int)sizeof(name) - 1 && *s) + name[len++] = *s++; + continue; + } + } + name[len++] = *linetext++; + } + name[len] = 0; + // add the point to the list + VectorScale(mins, (1.0 / 8.0), mins); + CL_Locs_AddNode(mins, mins, name); + } + else + continue; + } +} + +/* +=========== +CL_Shutdown +=========== +*/ +void CL_Shutdown (void) +{ + CL_Screen_Shutdown(); + CL_Particles_Shutdown(); + CL_Parse_Shutdown(); + + Mem_FreePool (&cls.permanentmempool); + Mem_FreePool (&cls.levelmempool); +} + +/* +================= +CL_Init +================= +*/ +void CL_Init (void) +{ + + cls.levelmempool = Mem_AllocPool("client (per-level memory)", 0, NULL); + cls.permanentmempool = Mem_AllocPool("client (long term memory)", 0, NULL); + + memset(&r_refdef, 0, sizeof(r_refdef)); + // max entities sent to renderer per frame + r_refdef.scene.maxentities = MAX_EDICTS + 256 + 512; + r_refdef.scene.entities = (entity_render_t **)Mem_Alloc(cls.permanentmempool, sizeof(entity_render_t *) * r_refdef.scene.maxentities); + + // max temp entities + r_refdef.scene.maxtempentities = MAX_TEMPENTITIES; + r_refdef.scene.tempentities = (entity_render_t *)Mem_Alloc(cls.permanentmempool, sizeof(entity_render_t) * r_refdef.scene.maxtempentities); + + CL_InitInput (); + +// +// register our commands +// + Cvar_RegisterVariable (&cl_upspeed); + Cvar_RegisterVariable (&cl_forwardspeed); + Cvar_RegisterVariable (&cl_backspeed); + Cvar_RegisterVariable (&cl_sidespeed); + Cvar_RegisterVariable (&cl_movespeedkey); + Cvar_RegisterVariable (&cl_yawspeed); + Cvar_RegisterVariable (&cl_pitchspeed); + Cvar_RegisterVariable (&cl_anglespeedkey); + Cvar_RegisterVariable (&cl_shownet); + Cvar_RegisterVariable (&cl_nolerp); + Cvar_RegisterVariable (&cl_lerpexcess); + Cvar_RegisterVariable (&cl_lerpanim_maxdelta_server); + Cvar_RegisterVariable (&cl_lerpanim_maxdelta_framegroups); + Cvar_RegisterVariable (&cl_deathfade); + Cvar_RegisterVariable (&lookspring); + Cvar_RegisterVariable (&lookstrafe); + Cvar_RegisterVariable (&sensitivity); + Cvar_RegisterVariable (&freelook); + + Cvar_RegisterVariable (&m_pitch); + Cvar_RegisterVariable (&m_yaw); + Cvar_RegisterVariable (&m_forward); + Cvar_RegisterVariable (&m_side); + + Cvar_RegisterVariable (&cl_itembobspeed); + Cvar_RegisterVariable (&cl_itembobheight); + + Cmd_AddCommand ("entities", CL_PrintEntities_f, "print information on network entities known to client"); + Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server (or disconnect all clients if running a server)"); + Cmd_AddCommand ("record", CL_Record_f, "record a demo"); + Cmd_AddCommand ("stop", CL_Stop_f, "stop recording or playing a demo"); + Cmd_AddCommand ("playdemo", CL_PlayDemo_f, "watch a demo file"); + Cmd_AddCommand ("timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log"); + + // Support Client-side Model Index List + Cmd_AddCommand ("cl_modelindexlist", CL_ModelIndexList_f, "list information on all models in the client modelindex"); + // Support Client-side Sound Index List + Cmd_AddCommand ("cl_soundindexlist", CL_SoundIndexList_f, "list all sounds in the client soundindex"); + + + Cvar_RegisterVariable (&cl_nosplashscreen); + + Cvar_RegisterVariable (&cl_autodemo); + Cvar_RegisterVariable (&cl_autodemo_nameformat); + Cvar_RegisterVariable (&cl_autodemo_delete); + + Cmd_AddCommand ("fog", CL_Fog_f, "set global fog parameters (density red green blue [alpha [mindist [maxdist [top [fadedepth]]]]])"); + Cmd_AddCommand ("fog_heighttexture", CL_Fog_HeightTexture_f, "set global fog parameters (density red green blue alpha mindist maxdist top depth textures/mapname/fogheight.tga)"); + + // LordHavoc: added pausedemo + Cmd_AddCommand ("pausedemo", CL_PauseDemo_f, "pause demo playback (can also safely pause demo recording if using QUAKE, QUAKEDP or NEHAHRAMOVIE protocol, useful for making movies)"); + + Cmd_AddCommand ("cl_areastats", CL_AreaStats_f, "prints statistics on entity culling during collision traces"); + + Cvar_RegisterVariable(&r_draweffects); + Cvar_RegisterVariable(&cl_explosions_alpha_start); + Cvar_RegisterVariable(&cl_explosions_alpha_end); + Cvar_RegisterVariable(&cl_explosions_size_start); + Cvar_RegisterVariable(&cl_explosions_size_end); + Cvar_RegisterVariable(&cl_explosions_lifetime); + Cvar_RegisterVariable(&cl_stainmaps); + Cvar_RegisterVariable(&cl_stainmaps_clearonload); + Cvar_RegisterVariable(&cl_beams_polygons); + Cvar_RegisterVariable(&cl_beams_quakepositionhack); + Cvar_RegisterVariable(&cl_beams_instantaimhack); + Cvar_RegisterVariable(&cl_beams_lightatend); + Cvar_RegisterVariable(&cl_noplayershadow); + Cvar_RegisterVariable(&cl_dlights_decayradius); + Cvar_RegisterVariable(&cl_dlights_decaybrightness); + + Cvar_RegisterVariable(&cl_prydoncursor); + Cvar_RegisterVariable(&cl_prydoncursor_notrace); + + Cvar_RegisterVariable(&cl_deathnoviewmodel); + + // for QW connections + Cvar_RegisterVariable(&qport); + Cvar_SetValueQuick(&qport, (rand() * RAND_MAX + rand()) & 0xffff); + + Cmd_AddCommand("timerefresh", CL_TimeRefresh_f, "turn quickly and print rendering statistcs"); + + Cvar_RegisterVariable(&cl_locs_enable); + Cvar_RegisterVariable(&cl_locs_show); + Cmd_AddCommand("locs_add", CL_Locs_Add_f, "add a point or box location (usage: x y z[ x y z] \"name\", if two sets of xyz are supplied it is a box, otherwise point)"); + Cmd_AddCommand("locs_removenearest", CL_Locs_RemoveNearest_f, "remove the nearest point or box (note: you need to be very near a box to remove it)"); + Cmd_AddCommand("locs_clear", CL_Locs_Clear_f, "remove all loc points/boxes"); + Cmd_AddCommand("locs_reload", CL_Locs_Reload_f, "reload .loc file for this map"); + Cmd_AddCommand("locs_save", CL_Locs_Save_f, "save .loc file for this map containing currently defined points and boxes"); + + CL_Parse_Init(); + CL_Particles_Init(); + CL_Screen_Init(); + + CL_Video_Init(); +} diff --git a/app/jni/cl_parse.c b/app/jni/cl_parse.c new file mode 100644 index 0000000..0a93685 --- /dev/null +++ b/app/jni/cl_parse.c @@ -0,0 +1,4278 @@ +/* +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. + +*/ +// cl_parse.c -- parse a message received from the server + +#include "quakedef.h" +#include "cdaudio.h" +#include "cl_collision.h" +#include "csprogs.h" +#include "libcurl.h" +#include "utf8lib.h" +#include "menu.h" +#include "cl_video.h" + +const char *svc_strings[128] = +{ + "svc_bad", + "svc_nop", + "svc_disconnect", + "svc_updatestat", + "svc_version", // [int] server version + "svc_setview", // [short] entity number + "svc_sound", // + "svc_time", // [float] server time + "svc_print", // [string] null terminated string + "svc_stufftext", // [string] stuffed into client's console buffer + // the string should be \n terminated + "svc_setangle", // [vec3] set the view angle to this absolute value + + "svc_serverinfo", // [int] version + // [string] signon string + // [string]..[0]model cache [string]...[0]sounds cache + // [string]..[0]item cache + "svc_lightstyle", // [byte] [string] + "svc_updatename", // [byte] [string] + "svc_updatefrags", // [byte] [short] + "svc_clientdata", // + "svc_stopsound", // + "svc_updatecolors", // [byte] [byte] + "svc_particle", // [vec3] + "svc_damage", // [byte] impact [byte] blood [vec3] from + + "svc_spawnstatic", + "OBSOLETE svc_spawnbinary", + "svc_spawnbaseline", + + "svc_temp_entity", // + "svc_setpause", + "svc_signonnum", + "svc_centerprint", + "svc_killedmonster", + "svc_foundsecret", + "svc_spawnstaticsound", + "svc_intermission", + "svc_finale", // [string] music [string] text + "svc_cdtrack", // [byte] track [byte] looptrack + "svc_sellscreen", + "svc_cutscene", + "svc_showlmp", // [string] iconlabel [string] lmpfile [short] x [short] y + "svc_hidelmp", // [string] iconlabel + "svc_skybox", // [string] skyname + "", // 38 + "", // 39 + "", // 40 + "", // 41 + "", // 42 + "", // 43 + "", // 44 + "", // 45 + "", // 46 + "", // 47 + "", // 48 + "", // 49 + "svc_downloaddata", // 50 // [int] start [short] size [variable length] data + "svc_updatestatubyte", // 51 // [byte] stat [byte] value + "svc_effect", // 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate + "svc_effect2", // 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate + "svc_sound2", // 54 // short soundindex instead of byte + "svc_spawnbaseline2", // 55 // short modelindex instead of byte + "svc_spawnstatic2", // 56 // short modelindex instead of byte + "svc_entities", // 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata + "svc_csqcentities", // 58 // [short] entnum [variable length] entitydata ... [short] 0x0000 + "svc_spawnstaticsound2", // 59 // [coord3] [short] samp [byte] vol [byte] aten + "svc_trailparticles", // 60 // [short] entnum [short] effectnum [vector] start [vector] end + "svc_pointparticles", // 61 // [short] effectnum [vector] start [vector] velocity [short] count + "svc_pointparticles1", // 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 +}; + +const char *qw_svc_strings[128] = +{ + "qw_svc_bad", // 0 + "qw_svc_nop", // 1 + "qw_svc_disconnect", // 2 + "qw_svc_updatestat", // 3 // [byte] [byte] + "", // 4 + "qw_svc_setview", // 5 // [short] entity number + "qw_svc_sound", // 6 // + "", // 7 + "qw_svc_print", // 8 // [byte] id [string] null terminated string + "qw_svc_stufftext", // 9 // [string] stuffed into client's console buffer + "qw_svc_setangle", // 10 // [angle3] set the view angle to this absolute value + "qw_svc_serverdata", // 11 // [long] protocol ... + "qw_svc_lightstyle", // 12 // [byte] [string] + "", // 13 + "qw_svc_updatefrags", // 14 // [byte] [short] + "", // 15 + "qw_svc_stopsound", // 16 // + "", // 17 + "", // 18 + "qw_svc_damage", // 19 + "qw_svc_spawnstatic", // 20 + "", // 21 + "qw_svc_spawnbaseline", // 22 + "qw_svc_temp_entity", // 23 // variable + "qw_svc_setpause", // 24 // [byte] on / off + "", // 25 + "qw_svc_centerprint", // 26 // [string] to put in center of the screen + "qw_svc_killedmonster", // 27 + "qw_svc_foundsecret", // 28 + "qw_svc_spawnstaticsound", // 29 // [coord3] [byte] samp [byte] vol [byte] aten + "qw_svc_intermission", // 30 // [vec3_t] origin [vec3_t] angle + "qw_svc_finale", // 31 // [string] text + "qw_svc_cdtrack", // 32 // [byte] track + "qw_svc_sellscreen", // 33 + "qw_svc_smallkick", // 34 // set client punchangle to 2 + "qw_svc_bigkick", // 35 // set client punchangle to 4 + "qw_svc_updateping", // 36 // [byte] [short] + "qw_svc_updateentertime", // 37 // [byte] [float] + "qw_svc_updatestatlong", // 38 // [byte] [long] + "qw_svc_muzzleflash", // 39 // [short] entity + "qw_svc_updateuserinfo", // 40 // [byte] slot [long] uid + "qw_svc_download", // 41 // [short] size [size bytes] + "qw_svc_playerinfo", // 42 // variable + "qw_svc_nails", // 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 + "qw_svc_chokecount", // 44 // [byte] packets choked + "qw_svc_modellist", // 45 // [strings] + "qw_svc_soundlist", // 46 // [strings] + "qw_svc_packetentities", // 47 // [...] + "qw_svc_deltapacketentities", // 48 // [...] + "qw_svc_maxspeed", // 49 // maxspeed change, for prediction + "qw_svc_entgravity", // 50 // gravity change, for prediction + "qw_svc_setinfo", // 51 // setinfo on a client + "qw_svc_serverinfo", // 52 // serverinfo + "qw_svc_updatepl", // 53 // [byte] [byte] +}; + +//============================================================================= + +cvar_t cl_worldmessage = {CVAR_READONLY, "cl_worldmessage", "", "title of current level"}; +cvar_t cl_worldname = {CVAR_READONLY, "cl_worldname", "", "name of current worldmodel"}; +cvar_t cl_worldnamenoextension = {CVAR_READONLY, "cl_worldnamenoextension", "", "name of current worldmodel without extension"}; +cvar_t cl_worldbasename = {CVAR_READONLY, "cl_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"}; + +cvar_t developer_networkentities = {0, "developer_networkentities", "0", "prints received entities, value is 0-10 (higher for more info, 10 being the most verbose)"}; +cvar_t cl_gameplayfix_soundsmovewithentities = {0, "cl_gameplayfix_soundsmovewithentities", "1", "causes sounds made by lifts, players, projectiles, and any other entities, to move with the entity, so for example a rocket noise follows the rocket rather than staying at the starting position"}; +cvar_t cl_sound_wizardhit = {0, "cl_sound_wizardhit", "wizard/hit.wav", "sound to play during TE_WIZSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_hknighthit = {0, "cl_sound_hknighthit", "hknight/hit.wav", "sound to play during TE_KNIGHTSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_tink1 = {0, "cl_sound_tink1", "weapons/tink1.wav", "sound to play with 80% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_ric1 = {0, "cl_sound_ric1", "weapons/ric1.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_ric2 = {0, "cl_sound_ric2", "weapons/ric2.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_sound_ric3 = {0, "cl_sound_ric3", "weapons/ric3.wav", "sound to play with 10% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; +cvar_t cl_readpicture_force = {0, "cl_readpicture_force", "0", "when enabled, the low quality pictures read by ReadPicture() are preferred over the high quality pictures on the file system"}; + +#define RIC_GUNSHOT 1 +#define RIC_GUNSHOTQUAD 2 +cvar_t cl_sound_ric_gunshot = {0, "cl_sound_ric_gunshot", "0", "specifies if and when the related cl_sound_ric and cl_sound_tink sounds apply to TE_GUNSHOT/TE_GUNSHOTQUAD, 0 = no sound, 1 = TE_GUNSHOT, 2 = TE_GUNSHOTQUAD, 3 = TE_GUNSHOT and TE_GUNSHOTQUAD"}; +cvar_t cl_sound_r_exp3 = {0, "cl_sound_r_exp3", "weapons/r_exp3.wav", "sound to play during TE_EXPLOSION and related effects (empty cvar disables sound)"}; +cvar_t cl_serverextension_download = {0, "cl_serverextension_download", "0", "indicates whether the server supports the download command"}; +cvar_t cl_joinbeforedownloadsfinish = {CVAR_SAVE, "cl_joinbeforedownloadsfinish", "1", "if non-zero the game will begin after the map is loaded before other downloads finish"}; +cvar_t cl_nettimesyncfactor = {CVAR_SAVE, "cl_nettimesyncfactor", "0", "rate at which client time adapts to match server time, 1 = instantly, 0.125 = slowly, 0 = not at all (bounding still applies)"}; +cvar_t cl_nettimesyncboundmode = {CVAR_SAVE, "cl_nettimesyncboundmode", "6", "method of restricting client time to valid values, 0 = no correction, 1 = tight bounding (jerky with packet loss), 2 = loose bounding (corrects it if out of bounds), 3 = leniant bounding (ignores temporary errors due to varying framerate), 4 = slow adjustment method from Quake3, 5 = slighttly nicer version of Quake3 method, 6 = bounding + Quake3"}; +cvar_t cl_nettimesyncboundtolerance = {CVAR_SAVE, "cl_nettimesyncboundtolerance", "0.25", "how much error is tolerated by bounding check, as a fraction of frametime, 0.25 = up to 25% margin of error tolerated, 1 = use only new time, 0 = use only old time (same effect as setting cl_nettimesyncfactor to 1)"}; +cvar_t cl_iplog_name = {CVAR_SAVE, "cl_iplog_name", "darkplaces_iplog.txt", "name of iplog file containing player addresses for iplog_list command and automatic ip logging when parsing status command"}; + +static qboolean QW_CL_CheckOrDownloadFile(const char *filename); +static void QW_CL_RequestNextDownload(void); +static void QW_CL_NextUpload(void); +//static qboolean QW_CL_IsUploading(void); +static void QW_CL_StopUpload(void); + +/* +================== +CL_ParseStartSoundPacket +================== +*/ +static void CL_ParseStartSoundPacket(int largesoundindex) +{ + vec3_t pos; + int channel, ent; + int sound_num; + int volume; + int field_mask; + float attenuation; + float speed; + int fflags = CHANNELFLAG_NONE; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + channel = MSG_ReadShort(&cl_message); + + if (channel & (1<<15)) + volume = MSG_ReadByte(&cl_message); + else + volume = DEFAULT_SOUND_PACKET_VOLUME; + + if (channel & (1<<14)) + attenuation = MSG_ReadByte(&cl_message) / 64.0; + else + attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + + speed = 1.0f; + + ent = (channel>>3)&1023; + channel &= 7; + + sound_num = MSG_ReadByte(&cl_message); + } + else + { + field_mask = MSG_ReadByte(&cl_message); + + if (field_mask & SND_VOLUME) + volume = MSG_ReadByte(&cl_message); + else + volume = DEFAULT_SOUND_PACKET_VOLUME; + + if (field_mask & SND_ATTENUATION) + attenuation = MSG_ReadByte(&cl_message) / 64.0; + else + attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + + if (field_mask & SND_SPEEDUSHORT4000) + speed = ((unsigned short)MSG_ReadShort(&cl_message)) / 4000.0f; + else + speed = 1.0f; + + if (field_mask & SND_LARGEENTITY) + { + ent = (unsigned short) MSG_ReadShort(&cl_message); + channel = MSG_ReadChar(&cl_message); + } + else + { + channel = (unsigned short) MSG_ReadShort(&cl_message); + ent = channel >> 3; + channel &= 7; + } + + if (largesoundindex || (field_mask & SND_LARGESOUND) || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) + sound_num = (unsigned short) MSG_ReadShort(&cl_message); + else + sound_num = MSG_ReadByte(&cl_message); + } + + channel = CHAN_NET2ENGINE(channel); + + MSG_ReadVector(&cl_message, pos, cls.protocol); + + if (sound_num >= MAX_SOUNDS) + { + Con_Printf("CL_ParseStartSoundPacket: sound_num (%i) >= MAX_SOUNDS (%i)\n", sound_num, MAX_SOUNDS); + return; + } + + if (ent >= MAX_EDICTS) + { + Con_Printf("CL_ParseStartSoundPacket: ent = %i", ent); + return; + } + + if (ent >= cl.max_entities) + CL_ExpandEntities(ent); + + if( !CL_VM_Event_Sound(sound_num, volume / 255.0f, channel, attenuation, ent, pos, fflags, speed) ) + S_StartSound_StartPosition_Flags (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0f, attenuation, 0, fflags, speed); +} + +/* +================== +CL_KeepaliveMessage + +When the client is taking a long time to load stuff, send keepalive messages +so the server doesn't disconnect. +================== +*/ + +static unsigned char olddata[NET_MAXMESSAGE]; +void CL_KeepaliveMessage (qboolean readmessages) +{ + static double lastdirtytime = 0; + static qboolean recursive = false; + double dirtytime; + double deltatime; + static double countdownmsg = 0; + static double countdownupdate = 0; + sizebuf_t old; + + qboolean thisrecursive; + + thisrecursive = recursive; + recursive = true; + + dirtytime = Sys_DirtyTime(); + deltatime = dirtytime - lastdirtytime; + lastdirtytime = dirtytime; + if (deltatime <= 0 || deltatime >= 1800.0) + return; + + countdownmsg -= deltatime; + countdownupdate -= deltatime; + + if(!thisrecursive) + { + if(cls.state != ca_dedicated) + { + if(countdownupdate <= 0) // check if time stepped backwards + { + SCR_UpdateLoadingScreenIfShown(); + countdownupdate = 2; + } + } + } + + // no need if server is local and definitely not if this is a demo + if (sv.active || !cls.netcon || cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon >= SIGNONS) + { + recursive = thisrecursive; + return; + } + + if (readmessages) + { + // read messages from server, should just be nops + old = cl_message; + memcpy(olddata, cl_message.data, cl_message.cursize); + + NetConn_ClientFrame(); + + cl_message = old; + memcpy(cl_message.data, olddata, cl_message.cursize); + } + + if (cls.netcon && countdownmsg <= 0) // check if time stepped backwards + { + sizebuf_t msg; + unsigned char buf[4]; + countdownmsg = 5; + // write out a nop + // LordHavoc: must use unreliable because reliable could kill the sigon message! + Con_Print("--> client to server keepalive\n"); + memset(&msg, 0, sizeof(msg)); + msg.data = buf; + msg.maxsize = sizeof(buf); + MSG_WriteChar(&msg, clc_nop); + NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, false); + } + + recursive = thisrecursive; +} + +void CL_ParseEntityLump(char *entdata) +{ + const char *data; + char key[128], value[MAX_INPUTLINE]; + FOG_clear(); // LordHavoc: no fog until set + // LordHavoc: default to the map's sky (q3 shader parsing sets this) + R_SetSkyBox(cl.worldmodel->brush.skybox); + data = entdata; + if (!data) + return; + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + if (com_token[0] != '{') + return; // error + while (1) + { + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strlcpy (key, com_token + 1, sizeof (key)); + else + strlcpy (key, com_token, sizeof (key)); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + strlcpy (value, com_token, sizeof (value)); + if (!strcmp("sky", key)) + R_SetSkyBox(value); + else if (!strcmp("skyname", key)) // non-standard, introduced by QuakeForge... sigh. + R_SetSkyBox(value); + else if (!strcmp("qlsky", key)) // non-standard, introduced by QuakeLives (EEK) + R_SetSkyBox(value); + else if (!strcmp("fog", key)) + { + FOG_clear(); // so missing values get good defaults + r_refdef.fog_start = 0; + r_refdef.fog_alpha = 1; + r_refdef.fog_end = 16384; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + sscanf(value, "%f %f %f %f %f %f %f %f %f", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth); + } + else if (!strcmp("fog_density", key)) + r_refdef.fog_density = atof(value); + else if (!strcmp("fog_red", key)) + r_refdef.fog_red = atof(value); + else if (!strcmp("fog_green", key)) + r_refdef.fog_green = atof(value); + else if (!strcmp("fog_blue", key)) + r_refdef.fog_blue = atof(value); + else if (!strcmp("fog_alpha", key)) + r_refdef.fog_alpha = atof(value); + else if (!strcmp("fog_start", key)) + r_refdef.fog_start = atof(value); + else if (!strcmp("fog_end", key)) + r_refdef.fog_end = atof(value); + else if (!strcmp("fog_height", key)) + r_refdef.fog_height = atof(value); + else if (!strcmp("fog_fadedepth", key)) + r_refdef.fog_fadedepth = atof(value); + else if (!strcmp("fog_heighttexture", key)) + { + FOG_clear(); // so missing values get good defaults +#if _MSC_VER >= 1400 + sscanf_s(value, "%f %f %f %f %f %f %f %f %f %s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename, (unsigned int)sizeof(r_refdef.fog_height_texturename)); +#else + sscanf(value, "%f %f %f %f %f %f %f %f %f %63s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename); +#endif + r_refdef.fog_height_texturename[63] = 0; + } + } +} + +static const vec3_t defaultmins = {-4096, -4096, -4096}; +static const vec3_t defaultmaxs = {4096, 4096, 4096}; +static void CL_SetupWorldModel(void) +{ + prvm_prog_t *prog = CLVM_prog; + // update the world model + cl.entities[0].render.model = cl.worldmodel = CL_GetModelByIndex(1); + CL_UpdateRenderEntity(&cl.entities[0].render); + + // make sure the cl.worldname and related cvars are set up now that we know the world model name + // set up csqc world for collision culling + if (cl.worldmodel) + { + strlcpy(cl.worldname, cl.worldmodel->name, sizeof(cl.worldname)); + FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, !strncmp(cl.worldnamenoextension, "maps/", 5) ? cl.worldnamenoextension + 5 : cl.worldnamenoextension, sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + World_SetSize(&cl.world, cl.worldname, cl.worldmodel->normalmins, cl.worldmodel->normalmaxs, prog); + } + else + { + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldnamenoextension, ""); + Cvar_SetQuick(&cl_worldbasename, ""); + World_SetSize(&cl.world, "", defaultmins, defaultmaxs, prog); + } + World_Start(&cl.world); + + // load or reload .loc file for team chat messages + CL_Locs_Reload_f(); + + // make sure we send enough keepalives + CL_KeepaliveMessage(false); + + // reset particles and other per-level things + R_Modules_NewMap(); + + // make sure we send enough keepalives + CL_KeepaliveMessage(false); + + // load the team chat beep if possible + cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav"); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // make menu know + MR_NewMap(); + + // load the csqc now + if (cl.loadcsqc) + { + cl.loadcsqc = false; + + CL_VM_Init(); + } +} + +static qboolean QW_CL_CheckOrDownloadFile(const char *filename) +{ + qfile_t *file; + char vabuf[1024]; + + // see if the file already exists + file = FS_OpenVirtualFile(filename, true); + if (file) + { + FS_Close(file); + return true; + } + + // download messages in a demo would be bad + if (cls.demorecording) + { + Con_Printf("Unable to download \"%s\" when recording.\n", filename); + return true; + } + + // don't try to download when playing a demo + if (!cls.netcon) + return true; + + strlcpy(cls.qw_downloadname, filename, sizeof(cls.qw_downloadname)); + Con_Printf("Downloading %s\n", filename); + + if (!cls.qw_downloadmemory) + { + cls.qw_downloadmemory = NULL; + cls.qw_downloadmemorycursize = 0; + cls.qw_downloadmemorymaxsize = 1024*1024; // start out with a 1MB buffer + } + + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "download %s", filename)); + + cls.qw_downloadnumber++; + cls.qw_downloadpercent = 0; + cls.qw_downloadmemorycursize = 0; + + return false; +} + +static void QW_CL_ProcessUserInfo(int slot); +static void QW_CL_RequestNextDownload(void) +{ + int i; + char vabuf[1024]; + + // clear name of file that just finished + cls.qw_downloadname[0] = 0; + + switch (cls.qw_downloadtype) + { + case dl_single: + break; + case dl_skin: + if (cls.qw_downloadnumber == 0) + Con_Printf("Checking skins...\n"); + for (;cls.qw_downloadnumber < cl.maxclients;cls.qw_downloadnumber++) + { + if (!cl.scores[cls.qw_downloadnumber].name[0]) + continue; + // check if we need to download the file, and return if so + if (!QW_CL_CheckOrDownloadFile(va(vabuf, sizeof(vabuf), "skins/%s.pcx", cl.scores[cls.qw_downloadnumber].qw_skin))) + return; + } + + cls.qw_downloadtype = dl_none; + + // load any newly downloaded skins + for (i = 0;i < cl.maxclients;i++) + QW_CL_ProcessUserInfo(i); + + // if we're still in signon stages, request the next one + if (cls.signon != SIGNONS) + { + cls.signon = SIGNONS-1; + // we'll go to SIGNONS when the first entity update is received + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "begin %i", cl.qw_servercount)); + } + break; + case dl_model: + if (cls.qw_downloadnumber == 0) + { + Con_Printf("Checking models...\n"); + cls.qw_downloadnumber = 1; + } + + for (;cls.qw_downloadnumber < MAX_MODELS && cl.model_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++) + { + // skip submodels + if (cl.model_name[cls.qw_downloadnumber][0] == '*') + continue; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/spike.mdl")) + cl.qw_modelindex_spike = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/player.mdl")) + cl.qw_modelindex_player = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/flag.mdl")) + cl.qw_modelindex_flag = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/s_explod.spr")) + cl.qw_modelindex_s_explod = cls.qw_downloadnumber; + // check if we need to download the file, and return if so + if (!QW_CL_CheckOrDownloadFile(cl.model_name[cls.qw_downloadnumber])) + return; + } + + cls.qw_downloadtype = dl_none; + + // touch all of the precached models that are still loaded so we can free + // anything that isn't needed + if (!sv.active) + Mod_ClearUsed(); + for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++) + Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL); + // precache any models used by the client (this also marks them used) + cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL); + cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL); + cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL); + cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL); + + // we purge the models and sounds later in CL_SignonReply + //Mod_PurgeUnused(); + + // now we try to load everything that is new + + // world model + cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, NULL); + if (cl.model_precache[1]->Draw == NULL) + Con_Printf("Map %s could not be found or downloaded\n", cl.model_name[1]); + + // normal models + for (i = 2;i < MAX_MODELS && cl.model_name[i][0];i++) + if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL))->Draw == NULL) + Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // now that we have a world model, set up the world entity, renderer + // modules and csqc + CL_SetupWorldModel(); + + // add pmodel/emodel CRCs to userinfo + CL_SetInfo("pmodel", va(vabuf, sizeof(vabuf), "%i", FS_CRCFile("progs/player.mdl", NULL)), true, true, true, true); + CL_SetInfo("emodel", va(vabuf, sizeof(vabuf), "%i", FS_CRCFile("progs/eyes.mdl", NULL)), true, true, true, true); + + // done checking sounds and models, send a prespawn command now + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "prespawn %i 0 %i", cl.qw_servercount, cl.model_precache[1]->brush.qw_md4sum2)); + + if (cls.qw_downloadmemory) + { + Mem_Free(cls.qw_downloadmemory); + cls.qw_downloadmemory = NULL; + } + + // done loading + cl.loadfinished = true; + break; + case dl_sound: + if (cls.qw_downloadnumber == 0) + { + Con_Printf("Checking sounds...\n"); + cls.qw_downloadnumber = 1; + } + + for (;cl.sound_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++) + { + // check if we need to download the file, and return if so + if (!QW_CL_CheckOrDownloadFile(va(vabuf, sizeof(vabuf), "sound/%s", cl.sound_name[cls.qw_downloadnumber]))) + return; + } + + cls.qw_downloadtype = dl_none; + + // clear sound usage flags for purging of unused sounds + S_ClearUsed(); + + // precache any sounds used by the client + cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true); + cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true); + cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true); + cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true); + cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true); + cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true); + cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true); + + // sounds used by the game + for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++) + cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true); + + // we purge the models and sounds later in CL_SignonReply + //S_PurgeUnused(); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // done with sound downloads, next we check models + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "modellist %i %i", cl.qw_servercount, 0)); + break; + case dl_none: + default: + Con_Printf("Unknown download type.\n"); + } +} + +static void QW_CL_ParseDownload(void) +{ + int size = (signed short)MSG_ReadShort(&cl_message); + int percent = MSG_ReadByte(&cl_message); + + //Con_Printf("download %i %i%% (%i/%i)\n", size, percent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize); + + // skip the download fragment if playing a demo + if (!cls.netcon) + { + if (size > 0) + cl_message.readcount += size; + return; + } + + if (size == -1) + { + Con_Printf("File not found.\n"); + QW_CL_RequestNextDownload(); + return; + } + + if (cl_message.readcount + (unsigned short)size > cl_message.cursize) + Host_Error("corrupt download message\n"); + + // make sure the buffer is big enough to include this new fragment + if (!cls.qw_downloadmemory || cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size) + { + unsigned char *old; + while (cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size) + cls.qw_downloadmemorymaxsize *= 2; + old = cls.qw_downloadmemory; + cls.qw_downloadmemory = (unsigned char *)Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize); + if (old) + { + memcpy(cls.qw_downloadmemory, old, cls.qw_downloadmemorycursize); + Mem_Free(old); + } + } + + // read the fragment out of the packet + MSG_ReadBytes(&cl_message, size, cls.qw_downloadmemory + cls.qw_downloadmemorycursize); + cls.qw_downloadmemorycursize += size; + cls.qw_downloadspeedcount += size; + + cls.qw_downloadpercent = percent; + + if (percent != 100) + { + // request next fragment + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "nextdl"); + } + else + { + // finished file + Con_Printf("Downloaded \"%s\"\n", cls.qw_downloadname); + + FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + + cls.qw_downloadpercent = 0; + + // start downloading the next file (or join the game) + QW_CL_RequestNextDownload(); + } +} + +static void QW_CL_ParseModelList(void) +{ + int n; + int nummodels = MSG_ReadByte(&cl_message); + char *str; + char vabuf[1024]; + + // parse model precache list + for (;;) + { + str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + if (!str[0]) + break; + nummodels++; + if (nummodels==MAX_MODELS) + Host_Error("Server sent too many model precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); + strlcpy(cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); + } + + n = MSG_ReadByte(&cl_message); + if (n) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "modellist %i %i", cl.qw_servercount, n)); + return; + } + + cls.signon = 2; + cls.qw_downloadnumber = 0; + cls.qw_downloadtype = dl_model; + QW_CL_RequestNextDownload(); +} + +static void QW_CL_ParseSoundList(void) +{ + int n; + int numsounds = MSG_ReadByte(&cl_message); + char *str; + char vabuf[1024]; + + // parse sound precache list + for (;;) + { + str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + if (!str[0]) + break; + numsounds++; + if (numsounds==MAX_SOUNDS) + Host_Error("Server sent too many sound precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); + strlcpy(cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); + } + + n = MSG_ReadByte(&cl_message); + + if (n) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "soundlist %i %i", cl.qw_servercount, n)); + return; + } + + cls.signon = 2; + cls.qw_downloadnumber = 0; + cls.qw_downloadtype = dl_sound; + QW_CL_RequestNextDownload(); +} + +static void QW_CL_Skins_f(void) +{ + cls.qw_downloadnumber = 0; + cls.qw_downloadtype = dl_skin; + QW_CL_RequestNextDownload(); +} + +static void QW_CL_Changing_f(void) +{ + if (cls.qw_downloadmemory) // don't change when downloading + return; + + S_StopAllSounds(); + cl.intermission = 0; + cls.signon = 1; // not active anymore, but not disconnected + Con_Printf("\nChanging map...\n"); +} + +void QW_CL_NextUpload(void) +{ + int r, percent, size; + + if (!cls.qw_uploaddata) + return; + + r = cls.qw_uploadsize - cls.qw_uploadpos; + if (r > 768) + r = 768; + size = min(1, cls.qw_uploadsize); + percent = (cls.qw_uploadpos+r)*100/size; + + MSG_WriteByte(&cls.netcon->message, qw_clc_upload); + MSG_WriteShort(&cls.netcon->message, r); + MSG_WriteByte(&cls.netcon->message, percent); + SZ_Write(&cls.netcon->message, cls.qw_uploaddata + cls.qw_uploadpos, r); + + Con_DPrintf("UPLOAD: %6d: %d written\n", cls.qw_uploadpos, r); + + cls.qw_uploadpos += r; + + if (cls.qw_uploadpos < cls.qw_uploadsize) + return; + + Con_Printf("Upload completed\n"); + + QW_CL_StopUpload(); +} + +void QW_CL_StartUpload(unsigned char *data, int size) +{ + // do nothing in demos or if not connected + if (!cls.netcon) + return; + + // abort existing upload if in progress + QW_CL_StopUpload(); + + Con_DPrintf("Starting upload of %d bytes...\n", size); + + cls.qw_uploaddata = (unsigned char *)Mem_Alloc(cls.permanentmempool, size); + memcpy(cls.qw_uploaddata, data, size); + cls.qw_uploadsize = size; + cls.qw_uploadpos = 0; + + QW_CL_NextUpload(); +} + +#if 0 +qboolean QW_CL_IsUploading(void) +{ + return cls.qw_uploaddata != NULL; +} +#endif + +void QW_CL_StopUpload(void) +{ + if (cls.qw_uploaddata) + Mem_Free(cls.qw_uploaddata); + cls.qw_uploaddata = NULL; + cls.qw_uploadsize = 0; + cls.qw_uploadpos = 0; +} + +static void QW_CL_ProcessUserInfo(int slot) +{ + int topcolor, bottomcolor; + char temp[2048]; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "name", cl.scores[slot].name, sizeof(cl.scores[slot].name)); + InfoString_GetValue(cl.scores[slot].qw_userinfo, "topcolor", temp, sizeof(temp));topcolor = atoi(temp); + InfoString_GetValue(cl.scores[slot].qw_userinfo, "bottomcolor", temp, sizeof(temp));bottomcolor = atoi(temp); + cl.scores[slot].colors = topcolor * 16 + bottomcolor; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "*spectator", temp, sizeof(temp)); + cl.scores[slot].qw_spectator = temp[0] != 0; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "team", cl.scores[slot].qw_team, sizeof(cl.scores[slot].qw_team)); + InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin)); + if (!cl.scores[slot].qw_skin[0]) + strlcpy(cl.scores[slot].qw_skin, "base", sizeof(cl.scores[slot].qw_skin)); + // TODO: skin cache +} + +static void QW_CL_UpdateUserInfo(void) +{ + int slot; + slot = MSG_ReadByte(&cl_message); + if (slot >= cl.maxclients) + { + Con_Printf("svc_updateuserinfo >= cl.maxclients\n"); + MSG_ReadLong(&cl_message); + MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + return; + } + cl.scores[slot].qw_userid = MSG_ReadLong(&cl_message); + strlcpy(cl.scores[slot].qw_userinfo, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(cl.scores[slot].qw_userinfo)); + + QW_CL_ProcessUserInfo(slot); +} + +static void QW_CL_SetInfo(void) +{ + int slot; + char key[2048]; + char value[2048]; + slot = MSG_ReadByte(&cl_message); + strlcpy(key, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(key)); + strlcpy(value, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(value)); + if (slot >= cl.maxclients) + { + Con_Printf("svc_setinfo >= cl.maxclients\n"); + return; + } + InfoString_SetValue(cl.scores[slot].qw_userinfo, sizeof(cl.scores[slot].qw_userinfo), key, value); + + QW_CL_ProcessUserInfo(slot); +} + +static void QW_CL_ServerInfo(void) +{ + char key[2048]; + char value[2048]; + char temp[32]; + strlcpy(key, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(key)); + strlcpy(value, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(value)); + Con_DPrintf("SERVERINFO: %s=%s\n", key, value); + InfoString_SetValue(cl.qw_serverinfo, sizeof(cl.qw_serverinfo), key, value); + InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp)); + cl.qw_teamplay = atoi(temp); +} + +static void QW_CL_ParseNails(void) +{ + int i, j; + int numnails = MSG_ReadByte(&cl_message); + vec_t *v; + unsigned char bits[6]; + for (i = 0;i < numnails;i++) + { + for (j = 0;j < 6;j++) + bits[j] = MSG_ReadByte(&cl_message); + if (cl.qw_num_nails > 255) + continue; + v = cl.qw_nails[cl.qw_num_nails++]; + v[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096; + v[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096; + v[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096; + v[3] = -360*(bits[4]>>4)/16; + v[4] = 360*bits[5]/256; + v[5] = 0; + } +} + +static void CL_UpdateItemsAndWeapon(void) +{ + int j; + // check for important changes + + // set flash times + if (cl.olditems != cl.stats[STAT_ITEMS]) + for (j = 0;j < 32;j++) + if ((cl.stats[STAT_ITEMS] & (1<= 0 + && cl_serverextension_download.integer + && (FS_CRCFile(csqc_progname.string, &progsize) != csqc_progcrc.integer || ((int)progsize != csqc_progsize.integer && csqc_progsize.integer != -1)) + && !FS_FileExists(va(vabuf, sizeof(vabuf), "dlcache/%s.%i.%i", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer))) + { + Con_Printf("Downloading new CSQC code to dlcache/%s.%i.%i\n", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer); + if(cl_serverextension_download.integer == 2 && FS_HasZlib()) + Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s deflate", csqc_progname.string)); + else + Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", csqc_progname.string)); + return; + } + } + + if (cl.loadmodel_current < cl.loadmodel_total) + { + // loading models + if(cl.loadmodel_current == 1) + { + // worldmodel counts as 16 models (15 + world model setup), for better progress bar + SCR_PushLoadingScreen(false, "Loading precached models", + ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) + ); + SCR_BeginLoadingPlaque(false); + } + for (;cl.loadmodel_current < cl.loadmodel_total;cl.loadmodel_current++) + { + SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], + ( + (cl.loadmodel_current == 1) ? LOADPROGRESSWEIGHT_WORLDMODEL : LOADPROGRESSWEIGHT_MODEL + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) + ); + if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw) + { + SCR_PopLoadingScreen(false); + if(cl.loadmodel_current == 1) + { + SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], 1.0 / cl.loadmodel_total); + SCR_PopLoadingScreen(false); + } + continue; + } + CL_KeepaliveMessage(true); + + // if running a local game, calling Mod_ForName is a completely wasted effort... + if (sv.active) + cl.model_precache[cl.loadmodel_current] = sv.models[cl.loadmodel_current]; + else + { + if(cl.loadmodel_current == 1) + { + // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3 + Mod_FreeQ3Shaders(); + } + cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.model_name[cl.loadmodel_current][0] == '*' ? cl.model_name[1] : NULL); + } + SCR_PopLoadingScreen(false); + if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw && cl.loadmodel_current == 1) + { + // we now have the worldmodel so we can set up the game world + SCR_PushLoadingScreen(true, "world model setup", + ( + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + ) + ); + CL_SetupWorldModel(); + SCR_PopLoadingScreen(true); + if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) + { + cl.loadfinished = true; + // now issue the spawn to move on to signon 2 like normal + if (cls.netcon) + Cmd_ForwardStringToServer("prespawn"); + } + } + } + SCR_PopLoadingScreen(false); + // finished loading models + } + + if (cl.loadsound_current < cl.loadsound_total) + { + // loading sounds + if(cl.loadsound_current == 1) + SCR_PushLoadingScreen(false, "Loading precached sounds", + ( + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) / ( + (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + + LOADPROGRESSWEIGHT_WORLDMODEL + + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND + ) + ); + for (;cl.loadsound_current < cl.loadsound_total;cl.loadsound_current++) + { + SCR_PushLoadingScreen(false, cl.sound_name[cl.loadsound_current], 1.0 / cl.loadsound_total); + if (cl.sound_precache[cl.loadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.loadsound_current])) + { + SCR_PopLoadingScreen(false); + continue; + } + CL_KeepaliveMessage(true); + cl.sound_precache[cl.loadsound_current] = S_PrecacheSound(cl.sound_name[cl.loadsound_current], false, true); + SCR_PopLoadingScreen(false); + } + SCR_PopLoadingScreen(false); + // finished loading sounds + } + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + Cvar_SetValueQuick(&cl_serverextension_download, false); + // in Nexuiz/Xonotic, the built in download protocol is kinda broken (misses lots + // of dependencies) anyway, and can mess around with the game directory; + // until this is fixed, only support pk3 downloads via curl, and turn off + // individual file downloads other than for CSQC + // on the other end of the download protocol, GAME_NEXUIZ/GAME_XONOTIC enforces writing + // to dlcache only + // idea: support download of pk3 files using this protocol later + + // note: the reason these loops skip already-loaded things is that it + // enables this command to be issued during the game if desired + + if (cl.downloadmodel_current < cl.loadmodel_total) + { + // loading models + + for (;cl.downloadmodel_current < cl.loadmodel_total;cl.downloadmodel_current++) + { + if (aborteddownload) + { + + if (cl.downloadmodel_current == 1) + { + // the worldmodel failed, but we need to set up anyway + Mod_FreeQ3Shaders(); + CL_SetupWorldModel(); + if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) + { + cl.loadfinished = true; + // now issue the spawn to move on to signon 2 like normal + if (cls.netcon) + Cmd_ForwardStringToServer("prespawn"); + } + } + aborteddownload = false; + continue; + } + if (cl.model_precache[cl.downloadmodel_current] && cl.model_precache[cl.downloadmodel_current]->Draw) + continue; + CL_KeepaliveMessage(true); + if (cl.model_name[cl.downloadmodel_current][0] != '*' && strcmp(cl.model_name[cl.downloadmodel_current], "null") && !FS_FileExists(cl.model_name[cl.downloadmodel_current])) + { + if (cl.downloadmodel_current == 1) + Con_Printf("Map %s not found\n", cl.model_name[cl.downloadmodel_current]); + else + Con_Printf("Model %s not found\n", cl.model_name[cl.downloadmodel_current]); + // regarding the * check: don't try to download submodels + if (cl_serverextension_download.integer && cls.netcon && cl.model_name[cl.downloadmodel_current][0] != '*' && !sv.active) + { + Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", cl.model_name[cl.downloadmodel_current])); + // we'll try loading again when the download finishes + return; + } + } + + if(cl.downloadmodel_current == 1) + { + // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3 + Mod_FreeQ3Shaders(); + } + + cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, true, cl.model_name[cl.downloadmodel_current][0] == '*' ? cl.model_name[1] : NULL); + if (cl.downloadmodel_current == 1) + { + // we now have the worldmodel so we can set up the game world + // or maybe we do not have it (cl_serverextension_download 0) + // then we need to continue loading ANYWAY! + CL_SetupWorldModel(); + if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) + { + cl.loadfinished = true; + // now issue the spawn to move on to signon 2 like normal + if (cls.netcon) + Cmd_ForwardStringToServer("prespawn"); + } + } + } + + // finished loading models + } + + if (cl.downloadsound_current < cl.loadsound_total) + { + // loading sounds + + for (;cl.downloadsound_current < cl.loadsound_total;cl.downloadsound_current++) + { + char soundname[MAX_QPATH]; + if (aborteddownload) + { + aborteddownload = false; + continue; + } + if (cl.sound_precache[cl.downloadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.downloadsound_current])) + continue; + CL_KeepaliveMessage(true); + dpsnprintf(soundname, sizeof(soundname), "sound/%s", cl.sound_name[cl.downloadsound_current]); + if (!FS_FileExists(soundname) && !FS_FileExists(cl.sound_name[cl.downloadsound_current])) + { + Con_Printf("Sound %s not found\n", soundname); + if (cl_serverextension_download.integer && cls.netcon && !sv.active) + { + Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", soundname)); + // we'll try loading again when the download finishes + return; + } + } + cl.sound_precache[cl.downloadsound_current] = S_PrecacheSound(cl.sound_name[cl.downloadsound_current], false, true); + } + + // finished loading sounds + } + + SCR_PopLoadingScreen(false); + + if (!cl.loadfinished) + { + cl.loadfinished = true; + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // now issue the spawn to move on to signon 2 like normal + if (cls.netcon) + Cmd_ForwardStringToServer("prespawn"); + } +} + +static void CL_BeginDownloads_f(void) +{ + // prevent cl_begindownloads from being issued multiple times in one match + // to prevent accidentally cancelled downloads + if(cl.loadbegun) + Con_Printf("cl_begindownloads is only valid once per match\n"); + else + CL_BeginDownloads(false); +} + +static void CL_StopDownload(int size, int crc) +{ + if (cls.qw_downloadmemory && cls.qw_downloadmemorycursize == size && CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize) == crc) + { + int existingcrc; + size_t existingsize; + const char *extension; + + if(cls.qw_download_deflate) + { + unsigned char *out; + size_t inflated_size; + out = FS_Inflate(cls.qw_downloadmemory, cls.qw_downloadmemorycursize, &inflated_size, tempmempool); + Mem_Free(cls.qw_downloadmemory); + if(out) + { + Con_Printf("Inflated download: new size: %u (%g%%)\n", (unsigned)inflated_size, 100.0 - 100.0*(cls.qw_downloadmemorycursize / (float)inflated_size)); + cls.qw_downloadmemory = out; + cls.qw_downloadmemorycursize = inflated_size; + } + else + { + cls.qw_downloadmemory = NULL; + cls.qw_downloadmemorycursize = 0; + Con_Printf("Cannot inflate download, possibly corrupt or zlib not present\n"); + } + } + + if(!cls.qw_downloadmemory) + { + Con_Printf("Download \"%s\" is corrupt (see above!)\n", cls.qw_downloadname); + } + else + { + crc = CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + size = cls.qw_downloadmemorycursize; + // finished file + // save to disk only if we don't already have it + // (this is mainly for playing back demos) + existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize); + if (existingsize || gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC || !strcmp(cls.qw_downloadname, csqc_progname.string)) + // let csprogs ALWAYS go to dlcache, to prevent "viral csprogs"; also, never put files outside dlcache for Nexuiz/Xonotic + { + if ((int)existingsize != size || existingcrc != crc) + { + // we have a mismatching file, pick another name for it + char name[MAX_QPATH*2]; + dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc); + if (!FS_FileExists(name)) + { + Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc); + FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + if(!strcmp(cls.qw_downloadname, csqc_progname.string)) + { + if(cls.caughtcsprogsdata) + Mem_Free(cls.caughtcsprogsdata); + cls.caughtcsprogsdata = (unsigned char *) Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorycursize); + memcpy(cls.caughtcsprogsdata, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + cls.caughtcsprogsdatasize = cls.qw_downloadmemorycursize; + Con_DPrintf("Buffered \"%s\"\n", name); + } + } + } + } + else + { + // we either don't have it or have a mismatching file... + // so it's time to accept the file + // but if we already have a mismatching file we need to rename + // this new one, and if we already have this file in renamed form, + // we do nothing + Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc); + FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + extension = FS_FileExtension(cls.qw_downloadname); + if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) + FS_Rescan(); + } + } + } + else if (cls.qw_downloadmemory && size) + { + Con_Printf("Download \"%s\" is corrupt (%i bytes, %i CRC, should be %i bytes, %i CRC), discarding\n", cls.qw_downloadname, size, crc, (int)cls.qw_downloadmemorycursize, (int)CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize)); + CL_BeginDownloads(true); + } + + if (cls.qw_downloadmemory) + Mem_Free(cls.qw_downloadmemory); + cls.qw_downloadmemory = NULL; + cls.qw_downloadname[0] = 0; + cls.qw_downloadmemorymaxsize = 0; + cls.qw_downloadmemorycursize = 0; + cls.qw_downloadpercent = 0; +} + +static void CL_ParseDownload(void) +{ + int i, start, size; + static unsigned char data[NET_MAXMESSAGE]; + start = MSG_ReadLong(&cl_message); + size = (unsigned short)MSG_ReadShort(&cl_message); + + // record the start/size information to ack in the next input packet + for (i = 0;i < CL_MAX_DOWNLOADACKS;i++) + { + if (!cls.dp_downloadack[i].start && !cls.dp_downloadack[i].size) + { + cls.dp_downloadack[i].start = start; + cls.dp_downloadack[i].size = size; + break; + } + } + + MSG_ReadBytes(&cl_message, size, data); + + if (!cls.qw_downloadname[0]) + { + if (size > 0) + Con_Printf("CL_ParseDownload: received %i bytes with no download active\n", size); + return; + } + + if (start + size > cls.qw_downloadmemorymaxsize) + Host_Error("corrupt download message\n"); + + // only advance cursize if the data is at the expected position + // (gaps are unacceptable) + memcpy(cls.qw_downloadmemory + start, data, size); + cls.qw_downloadmemorycursize = start + size; + cls.qw_downloadpercent = (int)floor((start+size) * 100.0 / cls.qw_downloadmemorymaxsize); + cls.qw_downloadpercent = bound(0, cls.qw_downloadpercent, 100); + cls.qw_downloadspeedcount += size; +} + +static void CL_DownloadBegin_f(void) +{ + int size = atoi(Cmd_Argv(1)); + + if (size < 0 || size > 1<<30 || FS_CheckNastyPath(Cmd_Argv(2), false)) + { + Con_Printf("cl_downloadbegin: received bogus information\n"); + CL_StopDownload(0, 0); + return; + } + + if (cls.qw_downloadname[0]) + Con_Printf("Download of %s aborted\n", cls.qw_downloadname); + + CL_StopDownload(0, 0); + + // we're really beginning a download now, so initialize stuff + strlcpy(cls.qw_downloadname, Cmd_Argv(2), sizeof(cls.qw_downloadname)); + cls.qw_downloadmemorymaxsize = size; + cls.qw_downloadmemory = (unsigned char *) Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize); + cls.qw_downloadnumber++; + + cls.qw_download_deflate = false; + if(Cmd_Argc() >= 4) + { + if(!strcmp(Cmd_Argv(3), "deflate")) + cls.qw_download_deflate = true; + // check further encodings here + } + + Cmd_ForwardStringToServer("sv_startdownload"); +} + +static void CL_StopDownload_f(void) +{ + Curl_CancelAll(); + if (cls.qw_downloadname[0]) + { + Con_Printf("Download of %s aborted\n", cls.qw_downloadname); + CL_StopDownload(0, 0); + } + CL_BeginDownloads(true); +} + +static void CL_DownloadFinished_f(void) +{ + if (Cmd_Argc() < 3) + { + Con_Printf("Malformed cl_downloadfinished command\n"); + return; + } + CL_StopDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2))); + CL_BeginDownloads(false); +} + +static void CL_SendPlayerInfo(void) +{ + char vabuf[1024]; + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "name \"%s\"", cl_name.string)); + + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "color %i %i", cl_color.integer >> 4, cl_color.integer & 15)); + + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "rate %i", cl_rate.integer)); + + if (cl_pmodel.integer) + { + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "pmodel %i", cl_pmodel.integer)); + } + if (*cl_playermodel.string) + { + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "playermodel %s", cl_playermodel.string)); + } + if (*cl_playerskin.string) + { + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "playerskin %s", cl_playerskin.string)); + } +} + +/* +===================== +CL_SignonReply + +An svc_signonnum has been received, perform a client side setup +===================== +*/ +static void CL_SignonReply (void) +{ + Con_DPrintf("CL_SignonReply: %i\n", cls.signon); + + switch (cls.signon) + { + case 1: + if (cls.netcon) + { + // send player info before we begin downloads + // (so that the server can see the player name while downloading) + CL_SendPlayerInfo(); + + // execute cl_begindownloads next frame + // (after any commands added by svc_stufftext have been executed) + // when done with downloads the "prespawn" will be sent + Cbuf_AddText("\ncl_begindownloads\n"); + + //MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + //MSG_WriteString (&cls.netcon->message, "prespawn"); + } + else // playing a demo... make sure loading occurs as soon as possible + CL_BeginDownloads(false); + break; + + case 2: + if (cls.netcon) + { + // LordHavoc: quake sent the player info here but due to downloads + // it is sent earlier instead + // CL_SendPlayerInfo(); + + // LordHavoc: changed to begin a loading stage and issue this when done + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, "spawn"); + } + break; + + case 3: + if (cls.netcon) + { + MSG_WriteByte (&cls.netcon->message, clc_stringcmd); + MSG_WriteString (&cls.netcon->message, "begin"); + } + break; + + case 4: + // after the level has been loaded, we shouldn't need the shaders, and + // if they are needed again they will be automatically loaded... + // we also don't need the unused models or sounds from the last level + Mod_FreeQ3Shaders(); + Mod_PurgeUnused(); + S_PurgeUnused(); + + Con_ClearNotify(); + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(true); + break; + } +} + +/* +================== +CL_ParseServerInfo +================== +*/ +static void CL_ParseServerInfo (void) +{ + char *str; + int i; + protocolversion_t protocol; + int nummodels, numsounds; + char vabuf[1024]; + + // if we start loading a level and a video is still playing, stop it + CL_VideoStop(); + + Con_DPrint("Serverinfo packet received.\n"); + Collision_Cache_Reset(true); + + // if server is active, we already began a loading plaque + if (!sv.active) + { + SCR_BeginLoadingPlaque(false); + S_StopAllSounds(); + // free q3 shaders so that any newly downloaded shaders will be active + Mod_FreeQ3Shaders(); + } + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // clear cl_serverextension cvars + Cvar_SetValueQuick(&cl_serverextension_download, 0); + +// +// wipe the client_state_t struct +// + CL_ClearState (); + +// parse protocol version number + i = MSG_ReadLong(&cl_message); + protocol = Protocol_EnumForNumber(i); + if (protocol == PROTOCOL_UNKNOWN) + { + Host_Error("CL_ParseServerInfo: Server is unrecognized protocol number (%i)", i); + return; + } + // hack for unmarked Nehahra movie demos which had a custom protocol + if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && gamemode == GAME_NEHAHRA) + protocol = PROTOCOL_NEHAHRAMOVIE; + cls.protocol = protocol; + Con_DPrintf("Server protocol is %s\n", Protocol_NameForEnum(cls.protocol)); + + cl.num_entities = 1; + + if (protocol == PROTOCOL_QUAKEWORLD) + { + char gamedir[1][MAX_QPATH]; + + cl.qw_servercount = MSG_ReadLong(&cl_message); + + str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + Con_Printf("server gamedir is %s\n", str); + strlcpy(gamedir[0], str, sizeof(gamedir[0])); + + // change gamedir if needed + if (!FS_ChangeGameDirs(1, gamedir, true, false)) + Host_Error("CL_ParseServerInfo: unable to switch to server specified gamedir"); + + cl.gametype = GAME_DEATHMATCH; + cl.maxclients = 32; + + // parse player number + i = MSG_ReadByte(&cl_message); + // cl.qw_spectator is an unneeded flag, cl.scores[cl.playerentity].qw_spectator works better (it can be updated by the server during the game) + //cl.qw_spectator = (i & 128) != 0; + cl.realplayerentity = cl.playerentity = cl.viewentity = (i & 127) + 1; + cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores)); + + // get the full level name + str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + strlcpy (cl.worldmessage, str, sizeof(cl.worldmessage)); + + // get the movevars that are defined in the qw protocol + cl.movevars_gravity = MSG_ReadFloat(&cl_message); + cl.movevars_stopspeed = MSG_ReadFloat(&cl_message); + cl.movevars_maxspeed = MSG_ReadFloat(&cl_message); + cl.movevars_spectatormaxspeed = MSG_ReadFloat(&cl_message); + cl.movevars_accelerate = MSG_ReadFloat(&cl_message); + cl.movevars_airaccelerate = MSG_ReadFloat(&cl_message); + cl.movevars_wateraccelerate = MSG_ReadFloat(&cl_message); + cl.movevars_friction = MSG_ReadFloat(&cl_message); + cl.movevars_waterfriction = MSG_ReadFloat(&cl_message); + cl.movevars_entgravity = MSG_ReadFloat(&cl_message); + + // other movevars not in the protocol... + cl.movevars_wallfriction = 0; + cl.movevars_timescale = 1; + cl.movevars_jumpvelocity = 270; + cl.movevars_edgefriction = 1; + cl.movevars_maxairspeed = 30; + cl.movevars_stepheight = 18; + cl.movevars_airaccel_qw = 1; + cl.movevars_airaccel_sideways_friction = 0; + + // seperate the printfs so the server message can have a color + Con_Printf("\n\n<===================================>\n\n\2%s\n", str); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + if (cls.netcon) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "soundlist %i %i", cl.qw_servercount, 0)); + } + + cl.loadbegun = false; + cl.loadfinished = false; + + cls.state = ca_connected; + cls.signon = 1; + + // note: on QW protocol we can't set up the gameworld until after + // downloads finish... + // (we don't even know the name of the map yet) + // this also means cl_autodemo does not work on QW protocol... + + strlcpy(cl.worldname, "", sizeof(cl.worldname)); + strlcpy(cl.worldnamenoextension, "", sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, "qw", sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + } + else + { + // parse maxclients + cl.maxclients = MSG_ReadByte(&cl_message); + if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) + { + Host_Error("Bad maxclients (%u) from server", cl.maxclients); + return; + } + cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores)); + + // parse gametype + cl.gametype = MSG_ReadByte(&cl_message); + // the original id singleplayer demos are bugged and contain + // GAME_DEATHMATCH even for singleplayer + if (cl.maxclients == 1 && cls.protocol == PROTOCOL_QUAKE) + cl.gametype = GAME_COOP; + + // parse signon message + str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + strlcpy (cl.worldmessage, str, sizeof(cl.worldmessage)); + + // seperate the printfs so the server message can have a color + if (cls.protocol != PROTOCOL_NEHAHRAMOVIE) // no messages when playing the Nehahra movie + Con_Printf("\n<===================================>\n\n\2%s\n", str); + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // parse model precache list + for (nummodels=1 ; ; nummodels++) + { + str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + if (!str[0]) + break; + if (nummodels==MAX_MODELS) + Host_Error ("Server sent too many model precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error ("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); + strlcpy (cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); + } + // parse sound precache list + for (numsounds=1 ; ; numsounds++) + { + str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + if (!str[0]) + break; + if (numsounds==MAX_SOUNDS) + Host_Error("Server sent too many sound precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); + strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); + } + + // set the base name for level-specific things... this gets updated again by CL_SetupWorldModel later + strlcpy(cl.worldname, cl.model_name[1], sizeof(cl.worldname)); + FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension)); + strlcpy(cl.worldbasename, !strncmp(cl.worldnamenoextension, "maps/", 5) ? cl.worldnamenoextension + 5 : cl.worldnamenoextension, sizeof(cl.worldbasename)); + Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); + Cvar_SetQuick(&cl_worldname, cl.worldname); + Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); + Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); + + // touch all of the precached models that are still loaded so we can free + // anything that isn't needed + if (!sv.active) + Mod_ClearUsed(); + for (i = 1;i < nummodels;i++) + Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL); + // precache any models used by the client (this also marks them used) + cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL); + cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL); + cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL); + cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL); + + // we purge the models and sounds later in CL_SignonReply + //Mod_PurgeUnused(); + //S_PurgeUnused(); + + // clear sound usage flags for purging of unused sounds + S_ClearUsed(); + + // precache any sounds used by the client + cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true); + cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true); + cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true); + cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true); + cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true); + cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true); + cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true); + + // sounds used by the game + for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++) + cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true); + + // now we try to load everything that is new + cl.loadmodel_current = 1; + cl.downloadmodel_current = 1; + cl.loadmodel_total = nummodels; + cl.loadsound_current = 1; + cl.downloadsound_current = 1; + cl.loadsound_total = numsounds; + cl.downloadcsqc = true; + cl.loadbegun = false; + cl.loadfinished = false; + cl.loadcsqc = true; + + // check memory integrity + Mem_CheckSentinelsGlobal(); + + // if cl_autodemo is set, automatically start recording a demo if one isn't being recorded already + if (cl_autodemo.integer && cls.netcon && cls.protocol != PROTOCOL_QUAKEWORLD) + { + char demofile[MAX_OSPATH]; + + if (cls.demorecording) + { + // finish the previous level's demo file + CL_Stop_f(); + } + + // start a new demo file + dpsnprintf (demofile, sizeof(demofile), "%s_%s.dem", Sys_TimeString (cl_autodemo_nameformat.string), cl.worldbasename); + + Con_Printf ("Auto-recording to %s.\n", demofile); + + // Reset bit 0 for every new demo + Cvar_SetValueQuick(&cl_autodemo_delete, + (cl_autodemo_delete.integer & ~0x1) + | + ((cl_autodemo_delete.integer & 0x2) ? 0x1 : 0) + ); + + cls.demofile = FS_OpenRealFile(demofile, "wb", false); + if (cls.demofile) + { + cls.forcetrack = -1; + FS_Printf (cls.demofile, "%i\n", cls.forcetrack); + cls.demorecording = true; + strlcpy(cls.demoname, demofile, sizeof(cls.demoname)); + cls.demo_lastcsprogssize = -1; + cls.demo_lastcsprogscrc = -1; + } + else + Con_Print ("ERROR: couldn't open.\n"); + } + } + cl.islocalgame = NetConn_IsLocalGame(); +} + +void CL_ValidateState(entity_state_t *s) +{ + dp_model_t *model; + + if (!s->active) + return; + + if (s->modelindex >= MAX_MODELS) + Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS); + + // these warnings are only warnings, no corrections are made to the state + // because states are often copied for decoding, which otherwise would + // propogate some of the corrections accidentally + // (this used to happen, sometimes affecting skin and frame) + + // colormap is client index + 1 + if (!(s->flags & RENDER_COLORMAPPED) && s->colormap > cl.maxclients) + Con_DPrintf("CL_ValidateState: colormap (%i) > cl.maxclients (%i)\n", s->colormap, cl.maxclients); + + if (developer_extra.integer) + { + model = CL_GetModelByIndex(s->modelindex); + if (model && model->type && s->frame >= model->numframes) + Con_DPrintf("CL_ValidateState: no such frame %i in \"%s\" (which has %i frames)\n", s->frame, model->name, model->numframes); + if (model && model->type && s->skin > 0 && s->skin >= model->numskins && !(s->lightpflags & PFLAGS_FULLDYNAMIC)) + Con_DPrintf("CL_ValidateState: no such skin %i in \"%s\" (which has %i skins)\n", s->skin, model->name, model->numskins); + } +} + +void CL_MoveLerpEntityStates(entity_t *ent) +{ + float odelta[3], adelta[3]; + VectorSubtract(ent->state_current.origin, ent->persistent.neworigin, odelta); + VectorSubtract(ent->state_current.angles, ent->persistent.newangles, adelta); + if (!ent->state_previous.active || ent->state_previous.modelindex != ent->state_current.modelindex) + { + // reset all persistent stuff if this is a new entity + ent->persistent.lerpdeltatime = 0; + ent->persistent.lerpstarttime = cl.mtime[1]; + VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); + VectorCopy(ent->state_current.angles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + // reset animation interpolation as well + ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; + ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; + ent->render.shadertime = cl.time; + // reset various persistent stuff + ent->persistent.muzzleflash = 0; + ent->persistent.trail_allowed = false; + } + else if ((ent->state_previous.effects & EF_TELEPORT_BIT) != (ent->state_current.effects & EF_TELEPORT_BIT)) + { + // don't interpolate the move + ent->persistent.lerpdeltatime = 0; + ent->persistent.lerpstarttime = cl.mtime[1]; + VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); + VectorCopy(ent->state_current.angles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + ent->persistent.trail_allowed = false; + + // if(ent->state_current.frame != ent->state_previous.frame) + // do this even if we did change the frame + // teleport bit is only used if an animation restart, or a jump, is necessary + // so it should be always harmless to do this + { + ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; + ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; + } + + // note that this case must do everything the following case does too + } + else if ((ent->state_previous.effects & EF_RESTARTANIM_BIT) != (ent->state_current.effects & EF_RESTARTANIM_BIT)) + { + ent->render.framegroupblend[1] = ent->render.framegroupblend[0]; + ent->render.framegroupblend[1].lerp = 1; + ent->render.framegroupblend[0].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = cl.time; + ent->render.framegroupblend[0].lerp = 0; + } + else if (DotProduct(odelta, odelta) > 1000*1000 + || (cl.fixangle[0] && !cl.fixangle[1]) + || (ent->state_previous.tagindex != ent->state_current.tagindex) + || (ent->state_previous.tagentity != ent->state_current.tagentity)) + { + // don't interpolate the move + // (the fixangle[] check detects teleports, but not constant fixangles + // such as when spectating) + ent->persistent.lerpdeltatime = 0; + ent->persistent.lerpstarttime = cl.mtime[1]; + VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); + VectorCopy(ent->state_current.angles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + ent->persistent.trail_allowed = false; + } + else if (ent->state_current.flags & RENDER_STEP) + { + // monster interpolation + if (DotProduct(odelta, odelta) + DotProduct(adelta, adelta) > 0.01) + { + ent->persistent.lerpdeltatime = bound(0, cl.mtime[1] - ent->persistent.lerpstarttime, 0.1); + ent->persistent.lerpstarttime = cl.mtime[1]; + VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin); + VectorCopy(ent->persistent.newangles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + } + } + else + { + // not a monster + ent->persistent.lerpstarttime = ent->state_previous.time; + // no lerp if it's singleplayer + if (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) + ent->persistent.lerpdeltatime = 0; + else + ent->persistent.lerpdeltatime = bound(0, ent->state_current.time - ent->state_previous.time, 0.1); + VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin); + VectorCopy(ent->persistent.newangles, ent->persistent.oldangles); + VectorCopy(ent->state_current.origin, ent->persistent.neworigin); + VectorCopy(ent->state_current.angles, ent->persistent.newangles); + } + // trigger muzzleflash effect if necessary + if (ent->state_current.effects & EF_MUZZLEFLASH) + ent->persistent.muzzleflash = 1; + + // restart animation bit + if ((ent->state_previous.effects & EF_RESTARTANIM_BIT) != (ent->state_current.effects & EF_RESTARTANIM_BIT)) + { + ent->render.framegroupblend[1] = ent->render.framegroupblend[0]; + ent->render.framegroupblend[1].lerp = 1; + ent->render.framegroupblend[0].frame = ent->state_current.frame; + ent->render.framegroupblend[0].start = cl.time; + ent->render.framegroupblend[0].lerp = 0; + } +} + +/* +================== +CL_ParseBaseline +================== +*/ +static void CL_ParseBaseline (entity_t *ent, int large) +{ + int i; + + ent->state_baseline = defaultstate; + // FIXME: set ent->state_baseline.number? + ent->state_baseline.active = true; + if (large) + { + ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort(&cl_message); + ent->state_baseline.frame = (unsigned short) MSG_ReadShort(&cl_message); + } + else if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) + { + ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort(&cl_message); + ent->state_baseline.frame = MSG_ReadByte(&cl_message); + } + else + { + ent->state_baseline.modelindex = MSG_ReadByte(&cl_message); + ent->state_baseline.frame = MSG_ReadByte(&cl_message); + } + ent->state_baseline.colormap = MSG_ReadByte(&cl_message); + ent->state_baseline.skin = MSG_ReadByte(&cl_message); + for (i = 0;i < 3;i++) + { + ent->state_baseline.origin[i] = MSG_ReadCoord(&cl_message, cls.protocol); + ent->state_baseline.angles[i] = MSG_ReadAngle(&cl_message, cls.protocol); + } + ent->state_previous = ent->state_current = ent->state_baseline; +} + + +/* +================== +CL_ParseClientdata + +Server information pertaining to this client only +================== +*/ +static void CL_ParseClientdata (void) +{ + int i, bits; + + VectorCopy (cl.mpunchangle[0], cl.mpunchangle[1]); + VectorCopy (cl.mpunchvector[0], cl.mpunchvector[1]); + VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); + cl.mviewzoom[1] = cl.mviewzoom[0]; + + if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3 || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5) + { + cl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT; + cl.stats[STAT_ITEMS] = 0; + cl.stats[STAT_VIEWZOOM] = 255; + } + cl.idealpitch = 0; + cl.mpunchangle[0][0] = 0; + cl.mpunchangle[0][1] = 0; + cl.mpunchangle[0][2] = 0; + cl.mpunchvector[0][0] = 0; + cl.mpunchvector[0][1] = 0; + cl.mpunchvector[0][2] = 0; + cl.mvelocity[0][0] = 0; + cl.mvelocity[0][1] = 0; + cl.mvelocity[0][2] = 0; + cl.mviewzoom[0] = 1; + + bits = (unsigned short) MSG_ReadShort(&cl_message); + if (bits & SU_EXTEND1) + bits |= (MSG_ReadByte(&cl_message) << 16); + if (bits & SU_EXTEND2) + bits |= (MSG_ReadByte(&cl_message) << 24); + + if (bits & SU_VIEWHEIGHT) + cl.stats[STAT_VIEWHEIGHT] = MSG_ReadChar(&cl_message); + + if (bits & SU_IDEALPITCH) + cl.idealpitch = MSG_ReadChar(&cl_message); + + for (i = 0;i < 3;i++) + { + if (bits & (SU_PUNCH1<= cl.max_static_entities) + Host_Error ("Too many static entities"); + ent = &cl.static_entities[cl.num_static_entities++]; + CL_ParseBaseline (ent, large); + + if (ent->state_baseline.modelindex == 0) + { + Con_DPrintf("svc_parsestatic: static entity without model at %f %f %f\n", ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2]); + cl.num_static_entities--; + // This is definitely a cheesy way to conserve resources... + return; + } + +// copy it to the current state + ent->render.model = CL_GetModelByIndex(ent->state_baseline.modelindex); + ent->render.framegroupblend[0].frame = ent->state_baseline.frame; + ent->render.framegroupblend[0].lerp = 1; + // make torchs play out of sync + ent->render.framegroupblend[0].start = lhrandom(-10, -1); + ent->render.skinnum = ent->state_baseline.skin; + ent->render.effects = ent->state_baseline.effects; + ent->render.alpha = 1; + + //VectorCopy (ent->state_baseline.origin, ent->render.origin); + //VectorCopy (ent->state_baseline.angles, ent->render.angles); + + Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2], ent->state_baseline.angles[0], ent->state_baseline.angles[1], ent->state_baseline.angles[2], 1); + ent->render.allowdecals = true; + CL_UpdateRenderEntity(&ent->render); +} + +/* +=================== +CL_ParseStaticSound +=================== +*/ +static void CL_ParseStaticSound (int large) +{ + vec3_t org; + int sound_num, vol, atten; + + MSG_ReadVector(&cl_message, org, cls.protocol); + if (large || cls.protocol == PROTOCOL_NEHAHRABJP2) + sound_num = (unsigned short) MSG_ReadShort(&cl_message); + else + sound_num = MSG_ReadByte(&cl_message); + vol = MSG_ReadByte(&cl_message); + atten = MSG_ReadByte(&cl_message); + + S_StaticSound (cl.sound_precache[sound_num], org, vol/255.0f, atten); +} + +static void CL_ParseEffect (void) +{ + vec3_t org; + int modelindex, startframe, framecount, framerate; + + MSG_ReadVector(&cl_message, org, cls.protocol); + modelindex = MSG_ReadByte(&cl_message); + startframe = MSG_ReadByte(&cl_message); + framecount = MSG_ReadByte(&cl_message); + framerate = MSG_ReadByte(&cl_message); + + CL_Effect(org, modelindex, startframe, framecount, framerate); +} + +static void CL_ParseEffect2 (void) +{ + vec3_t org; + int modelindex, startframe, framecount, framerate; + + MSG_ReadVector(&cl_message, org, cls.protocol); + modelindex = (unsigned short) MSG_ReadShort(&cl_message); + startframe = (unsigned short) MSG_ReadShort(&cl_message); + framecount = MSG_ReadByte(&cl_message); + framerate = MSG_ReadByte(&cl_message); + + CL_Effect(org, modelindex, startframe, framecount, framerate); +} + +void CL_NewBeam (int ent, vec3_t start, vec3_t end, dp_model_t *m, int lightning) +{ + int i; + beam_t *b = NULL; + + if (ent >= MAX_EDICTS) + { + Con_Printf("CL_NewBeam: invalid entity number %i\n", ent); + ent = 0; + } + + if (ent >= cl.max_entities) + CL_ExpandEntities(ent); + + // override any beam with the same entity + i = cl.max_beams; + if (ent) + for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++) + if (b->entity == ent) + break; + // if the entity was not found then just replace an unused beam + if (i == cl.max_beams) + for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++) + if (!b->model) + break; + if (i < cl.max_beams) + { + cl.num_beams = max(cl.num_beams, i + 1); + b->entity = ent; + b->lightning = lightning; + b->model = m; + b->endtime = cl.mtime[0] + 0.2; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + } + else + Con_Print("beam list overflow!\n"); +} + +static void CL_ParseBeam (dp_model_t *m, int lightning) +{ + int ent; + vec3_t start, end; + + ent = (unsigned short) MSG_ReadShort(&cl_message); + MSG_ReadVector(&cl_message, start, cls.protocol); + MSG_ReadVector(&cl_message, end, cls.protocol); + + if (ent >= MAX_EDICTS) + { + Con_Printf("CL_ParseBeam: invalid entity number %i\n", ent); + ent = 0; + } + + CL_NewBeam(ent, start, end, m, lightning); +} + +static void CL_ParseTempEntity(void) +{ + int type; + vec3_t pos, pos2; + vec3_t vel1, vel2; + vec3_t dir; + vec3_t color; + int rnd; + int colorStart, colorLength, count; + float velspeed, radius; + unsigned char *tempcolor; + matrix4x4_t tempmatrix; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + type = MSG_ReadByte(&cl_message); + switch (type) + { + case QW_TE_WIZSPIKE: + // spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); + break; + + case QW_TE_KNIGHTSPIKE: + // spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); + break; + + case QW_TE_SPIKE: + // spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case QW_TE_SUPERSPIKE: + // super spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + + case QW_TE_EXPLOSION: + // rocket explosion + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + CL_Effect(pos, cl.qw_modelindex_s_explod, 0, 6, 10); + break; + + case QW_TE_TAREXPLOSION: + // tarbaby explosion + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case QW_TE_LIGHTNING1: + // lightning bolts + CL_ParseBeam(cl.model_bolt, true); + break; + + case QW_TE_LIGHTNING2: + // lightning bolts + CL_ParseBeam(cl.model_bolt2, true); + break; + + case QW_TE_LIGHTNING3: + // lightning bolts + CL_ParseBeam(cl.model_bolt3, false); + break; + + case QW_TE_LAVASPLASH: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case QW_TE_TELEPORT: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case QW_TE_GUNSHOT: + // bullet hitting wall + radius = MSG_ReadByte(&cl_message); + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + VectorSet(pos2, pos[0] + radius, pos[1] + radius, pos[2] + radius); + VectorSet(pos, pos[0] - radius, pos[1] - radius, pos[2] - radius); + CL_ParticleEffect(EFFECT_TE_GUNSHOT, radius, pos, pos2, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOT) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } + break; + + case QW_TE_BLOOD: + count = MSG_ReadByte(&cl_message); + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case QW_TE_LIGHTNINGBLOOD: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_BLOOD, 2.5, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + default: + Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); + } + } + else + { + type = MSG_ReadByte(&cl_message); + switch (type) + { + case TE_WIZSPIKE: + // spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); + break; + + case TE_KNIGHTSPIKE: + // spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); + break; + + case TE_SPIKE: + // spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SPIKEQUAD: + // quad spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SPIKEQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SUPERSPIKE: + // super spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SUPERSPIKEQUAD: + // quad super spike hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKEQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + // LordHavoc: added for improved blood splatters + case TE_BLOOD: + // blood puff + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + dir[0] = MSG_ReadChar(&cl_message); + dir[1] = MSG_ReadChar(&cl_message); + dir[2] = MSG_ReadChar(&cl_message); + count = MSG_ReadByte(&cl_message); + CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos, dir, dir, NULL, 0); + break; + case TE_SPARK: + // spark shower + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + dir[0] = MSG_ReadChar(&cl_message); + dir[1] = MSG_ReadChar(&cl_message); + dir[2] = MSG_ReadChar(&cl_message); + count = MSG_ReadByte(&cl_message); + CL_ParticleEffect(EFFECT_TE_SPARK, count, pos, pos, dir, dir, NULL, 0); + break; + case TE_PLASMABURN: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_PLASMABURN, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + // LordHavoc: added for improved gore + case TE_BLOODSHOWER: + // vaporized body + MSG_ReadVector(&cl_message, pos, cls.protocol); // mins + MSG_ReadVector(&cl_message, pos2, cls.protocol); // maxs + velspeed = MSG_ReadCoord(&cl_message, cls.protocol); // speed + count = (unsigned short) MSG_ReadShort(&cl_message); // number of particles + vel1[0] = -velspeed; + vel1[1] = -velspeed; + vel1[2] = -velspeed; + vel2[0] = velspeed; + vel2[1] = velspeed; + vel2[2] = velspeed; + CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos2, vel1, vel2, NULL, 0); + break; + + case TE_PARTICLECUBE: + // general purpose particle effect + MSG_ReadVector(&cl_message, pos, cls.protocol); // mins + MSG_ReadVector(&cl_message, pos2, cls.protocol); // maxs + MSG_ReadVector(&cl_message, dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(&cl_message); // number of particles + colorStart = MSG_ReadByte(&cl_message); // color + colorLength = MSG_ReadByte(&cl_message); // gravity (1 or 0) + velspeed = MSG_ReadCoord(&cl_message, cls.protocol); // randomvel + CL_ParticleCube(pos, pos2, dir, count, colorStart, colorLength != 0, velspeed); + break; + + case TE_PARTICLERAIN: + // general purpose particle effect + MSG_ReadVector(&cl_message, pos, cls.protocol); // mins + MSG_ReadVector(&cl_message, pos2, cls.protocol); // maxs + MSG_ReadVector(&cl_message, dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(&cl_message); // number of particles + colorStart = MSG_ReadByte(&cl_message); // color + CL_ParticleRain(pos, pos2, dir, count, colorStart, 0); + break; + + case TE_PARTICLESNOW: + // general purpose particle effect + MSG_ReadVector(&cl_message, pos, cls.protocol); // mins + MSG_ReadVector(&cl_message, pos2, cls.protocol); // maxs + MSG_ReadVector(&cl_message, dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(&cl_message); // number of particles + colorStart = MSG_ReadByte(&cl_message); // color + CL_ParticleRain(pos, pos2, dir, count, colorStart, 1); + break; + + case TE_GUNSHOT: + // bullet hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_GUNSHOT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOT) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } + break; + + case TE_GUNSHOTQUAD: + // quad bullet hitting wall + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_GUNSHOTQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer & RIC_GUNSHOTQUAD) + { + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + } + break; + + case TE_EXPLOSION: + // rocket explosion + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_EXPLOSIONQUAD: + // quad rocket explosion + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSIONQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_EXPLOSION3: + // Nehahra movie colored lighting explosion + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + color[0] = MSG_ReadCoord(&cl_message, cls.protocol) * (2.0f / 1.0f); + color[1] = MSG_ReadCoord(&cl_message, cls.protocol) * (2.0f / 1.0f); + color[2] = MSG_ReadCoord(&cl_message, cls.protocol) * (2.0f / 1.0f); + CL_ParticleExplosion(pos); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_EXPLOSIONRGB: + // colored lighting explosion + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleExplosion(pos); + color[0] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); + color[1] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); + color[2] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_TAREXPLOSION: + // tarbaby explosion + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_SMALLFLASH: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_SMALLFLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case TE_CUSTOMFLASH: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + radius = (MSG_ReadByte(&cl_message) + 1) * 8; + velspeed = (MSG_ReadByte(&cl_message) + 1) * (1.0 / 256.0); + color[0] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); + color[1] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); + color[2] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocLightFlash(NULL, &tempmatrix, radius, color[0], color[1], color[2], radius / velspeed, velspeed, 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + break; + + case TE_FLAMEJET: + MSG_ReadVector(&cl_message, pos, cls.protocol); + MSG_ReadVector(&cl_message, dir, cls.protocol); + count = MSG_ReadByte(&cl_message); + CL_ParticleEffect(EFFECT_TE_FLAMEJET, count, pos, pos, dir, dir, NULL, 0); + break; + + case TE_LIGHTNING1: + // lightning bolts + CL_ParseBeam(cl.model_bolt, true); + break; + + case TE_LIGHTNING2: + // lightning bolts + CL_ParseBeam(cl.model_bolt2, true); + break; + + case TE_LIGHTNING3: + // lightning bolts + CL_ParseBeam(cl.model_bolt3, false); + break; + + // PGM 01/21/97 + case TE_BEAM: + // grappling hook beam + CL_ParseBeam(cl.model_beam, false); + break; + // PGM 01/21/97 + + // LordHavoc: for compatibility with the Nehahra movie... + case TE_LIGHTNING4NEH: + CL_ParseBeam(Mod_ForName(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), true, false, NULL), false); + break; + + case TE_LAVASPLASH: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case TE_TELEPORT: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case TE_EXPLOSION2: + // color mapped explosion + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + colorStart = MSG_ReadByte(&cl_message); + colorLength = MSG_ReadByte(&cl_message); + CL_ParticleExplosion2(pos, colorStart, colorLength); + tempcolor = palette_rgb[(rand()%colorLength) + colorStart]; + color[0] = tempcolor[0] * (2.0f / 255.0f); + color[1] = tempcolor[1] * (2.0f / 255.0f); + color[2] = tempcolor[2] * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_TEI_G3: + MSG_ReadVector(&cl_message, pos, cls.protocol); + MSG_ReadVector(&cl_message, pos2, cls.protocol); + MSG_ReadVector(&cl_message, dir, cls.protocol); + CL_ParticleEffect(EFFECT_TE_TEI_G3, 1, pos, pos2, dir, dir, NULL, 0); + break; + + case TE_TEI_SMOKE: + MSG_ReadVector(&cl_message, pos, cls.protocol); + MSG_ReadVector(&cl_message, dir, cls.protocol); + count = MSG_ReadByte(&cl_message); + CL_FindNonSolidLocation(pos, pos, 4); + CL_ParticleEffect(EFFECT_TE_TEI_SMOKE, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + case TE_TEI_BIGEXPLOSION: + MSG_ReadVector(&cl_message, pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleEffect(EFFECT_TE_TEI_BIGEXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; + + case TE_TEI_PLASMAHIT: + MSG_ReadVector(&cl_message, pos, cls.protocol); + MSG_ReadVector(&cl_message, dir, cls.protocol); + count = MSG_ReadByte(&cl_message); + CL_FindNonSolidLocation(pos, pos, 5); + CL_ParticleEffect(EFFECT_TE_TEI_PLASMAHIT, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); + break; + + default: + Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); + } + } +} + +static void CL_ParseTrailParticles(void) +{ + int entityindex; + int effectindex; + vec3_t start, end; + entityindex = (unsigned short)MSG_ReadShort(&cl_message); + if (entityindex >= MAX_EDICTS) + entityindex = 0; + if (entityindex >= cl.max_entities) + CL_ExpandEntities(entityindex); + effectindex = (unsigned short)MSG_ReadShort(&cl_message); + MSG_ReadVector(&cl_message, start, cls.protocol); + MSG_ReadVector(&cl_message, end, cls.protocol); + CL_ParticleEffect(effectindex, 1, start, end, vec3_origin, vec3_origin, entityindex > 0 ? cl.entities + entityindex : NULL, 0); +} + +static void CL_ParsePointParticles(void) +{ + int effectindex, count; + vec3_t origin, velocity; + effectindex = (unsigned short)MSG_ReadShort(&cl_message); + MSG_ReadVector(&cl_message, origin, cls.protocol); + MSG_ReadVector(&cl_message, velocity, cls.protocol); + count = (unsigned short)MSG_ReadShort(&cl_message); + CL_ParticleEffect(effectindex, count, origin, origin, velocity, velocity, NULL, 0); +} + +static void CL_ParsePointParticles1(void) +{ + int effectindex; + vec3_t origin; + effectindex = (unsigned short)MSG_ReadShort(&cl_message); + MSG_ReadVector(&cl_message, origin, cls.protocol); + CL_ParticleEffect(effectindex, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0); +} + +typedef struct cl_iplog_item_s +{ + char *address; + char *name; +} +cl_iplog_item_t; + +static qboolean cl_iplog_loaded = false; +static int cl_iplog_numitems = 0; +static int cl_iplog_maxitems = 0; +static cl_iplog_item_t *cl_iplog_items; + +static void CL_IPLog_Load(void); +static void CL_IPLog_Add(const char *address, const char *name, qboolean checkexisting, qboolean addtofile) +{ + int i; + size_t sz_name, sz_address; + if (!address || !address[0] || !name || !name[0]) + return; + if (!cl_iplog_loaded) + CL_IPLog_Load(); + if (developer_extra.integer) + Con_DPrintf("CL_IPLog_Add(\"%s\", \"%s\", %i, %i);\n", address, name, checkexisting, addtofile); + // see if it already exists + if (checkexisting) + { + for (i = 0;i < cl_iplog_numitems;i++) + { + if (!strcmp(cl_iplog_items[i].address, address) && !strcmp(cl_iplog_items[i].name, name)) + { + if (developer_extra.integer) + Con_DPrintf("... found existing \"%s\" \"%s\"\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + return; + } + } + } + // it does not already exist in the iplog, so add it + if (cl_iplog_maxitems <= cl_iplog_numitems || !cl_iplog_items) + { + cl_iplog_item_t *olditems = cl_iplog_items; + cl_iplog_maxitems = max(1024, cl_iplog_maxitems + 256); + cl_iplog_items = (cl_iplog_item_t *) Mem_Alloc(cls.permanentmempool, cl_iplog_maxitems * sizeof(cl_iplog_item_t)); + if (olditems) + { + if (cl_iplog_numitems) + memcpy(cl_iplog_items, olditems, cl_iplog_numitems * sizeof(cl_iplog_item_t)); + Mem_Free(olditems); + } + } + sz_address = strlen(address) + 1; + sz_name = strlen(name) + 1; + cl_iplog_items[cl_iplog_numitems].address = (char *) Mem_Alloc(cls.permanentmempool, sz_address); + cl_iplog_items[cl_iplog_numitems].name = (char *) Mem_Alloc(cls.permanentmempool, sz_name); + strlcpy(cl_iplog_items[cl_iplog_numitems].address, address, sz_address); + // TODO: maybe it would be better to strip weird characters from name when + // copying it here rather than using a straight strcpy? + strlcpy(cl_iplog_items[cl_iplog_numitems].name, name, sz_name); + cl_iplog_numitems++; + if (addtofile) + { + // add it to the iplog.txt file + // TODO: this ought to open the one in the userpath version of the base + // gamedir, not the current gamedir + Log_Printf(cl_iplog_name.string, "%s %s\n", address, name); + if (developer_extra.integer) + Con_DPrintf("CL_IPLog_Add: appending this line to %s: %s %s\n", cl_iplog_name.string, address, name); + } +} + +static void CL_IPLog_Load(void) +{ + int i, len, linenumber; + char *text, *textend; + unsigned char *filedata; + fs_offset_t filesize; + char line[MAX_INPUTLINE]; + char address[MAX_INPUTLINE]; + cl_iplog_loaded = true; + // TODO: this ought to open the one in the userpath version of the base + // gamedir, not the current gamedir + filedata = FS_LoadFile(cl_iplog_name.string, tempmempool, true, &filesize); + if (!filedata) + return; + text = (char *)filedata; + textend = text + filesize; + for (linenumber = 1;text < textend;linenumber++) + { + for (len = 0;text < textend && *text != '\r' && *text != '\n';text++) + if (len < (int)sizeof(line) - 1) + line[len++] = *text; + line[len] = 0; + if (text < textend && *text == '\r' && text[1] == '\n') + text++; + if (text < textend && *text == '\n') + text++; + if (line[0] == '/' && line[1] == '/') + continue; // skip comments if anyone happens to add them + for (i = 0;i < len && !ISWHITESPACE(line[i]);i++) + address[i] = line[i]; + address[i] = 0; + // skip exactly one space character + i++; + // address contains the address with termination, + // line + i contains the name with termination + if (address[0] && line[i]) + CL_IPLog_Add(address, line + i, false, false); + else + Con_Printf("%s:%i: could not parse address and name:\n%s\n", cl_iplog_name.string, linenumber, line); + } +} + +static void CL_IPLog_List_f(void) +{ + int i, j; + const char *addressprefix; + if (Cmd_Argc() > 2) + { + Con_Printf("usage: %s 123.456.789.\n", Cmd_Argv(0)); + return; + } + addressprefix = ""; + if (Cmd_Argc() >= 2) + addressprefix = Cmd_Argv(1); + if (!cl_iplog_loaded) + CL_IPLog_Load(); + if (addressprefix && addressprefix[0]) + Con_Printf("Listing iplog addresses beginning with %s\n", addressprefix); + else + Con_Printf("Listing all iplog entries\n"); + Con_Printf("address name\n"); + for (i = 0;i < cl_iplog_numitems;i++) + { + if (addressprefix && addressprefix[0]) + { + for (j = 0;addressprefix[j];j++) + if (addressprefix[j] != cl_iplog_items[i].address[j]) + break; + // if this address does not begin with the addressprefix string + // simply omit it from the output + if (addressprefix[j]) + continue; + } + // if name is less than 15 characters, left justify it and pad + // if name is more than 15 characters, print all of it, not worrying + // about the fact it will misalign the columns + if (strlen(cl_iplog_items[i].address) < 15) + Con_Printf("%-15s %s\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + else + Con_Printf("%5s %s\n", cl_iplog_items[i].address, cl_iplog_items[i].name); + } +} + +// look for anything interesting like player IP addresses or ping reports +static qboolean CL_ExaminePrintString(const char *text) +{ + int len; + const char *t; + char temp[MAX_INPUTLINE]; + if (!strcmp(text, "Client ping times:\n")) + { + cl.parsingtextmode = CL_PARSETEXTMODE_PING; + // hide ping reports in demos + if (cls.demoplayback) + cl.parsingtextexpectingpingforscores = 1; + for(cl.parsingtextplayerindex = 0; cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0]; cl.parsingtextplayerindex++) + ; + if (cl.parsingtextplayerindex >= cl.maxclients) // should never happen, since the client itself should be in cl.scores + { + Con_Printf("ping reply but empty scoreboard?!?\n"); + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; + cl.parsingtextexpectingpingforscores = 0; + } + cl.parsingtextexpectingpingforscores = cl.parsingtextexpectingpingforscores ? 2 : 0; + return !cl.parsingtextexpectingpingforscores; + } + if (!strncmp(text, "host: ", 9)) + { + // cl.parsingtextexpectingpingforscores = false; // really? + cl.parsingtextmode = CL_PARSETEXTMODE_STATUS; + cl.parsingtextplayerindex = 0; + return true; + } + if (cl.parsingtextmode == CL_PARSETEXTMODE_PING) + { + // if anything goes wrong, we'll assume this is not a ping report + qboolean expected = cl.parsingtextexpectingpingforscores != 0; + cl.parsingtextexpectingpingforscores = 0; + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; + t = text; + while (*t == ' ') + t++; + if ((*t >= '0' && *t <= '9') || *t == '-') + { + int ping = atoi(t); + while ((*t >= '0' && *t <= '9') || *t == '-') + t++; + if (*t == ' ') + { + int charindex = 0; + t++; + if(cl.parsingtextplayerindex < cl.maxclients) + { + for (charindex = 0;cl.scores[cl.parsingtextplayerindex].name[charindex] == t[charindex];charindex++) + ; + // note: the matching algorithm stops at the end of the player name because some servers append text such as " READY" after the player name in the scoreboard but not in the ping report + //if (cl.scores[cl.parsingtextplayerindex].name[charindex] == 0 && t[charindex] == '\n') + if (t[charindex] == '\n') + { + cl.scores[cl.parsingtextplayerindex].qw_ping = bound(0, ping, 9999); + for (cl.parsingtextplayerindex++;cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0];cl.parsingtextplayerindex++) + ; + //if (cl.parsingtextplayerindex < cl.maxclients) // we could still get unconnecteds! + { + // we parsed a valid ping entry, so expect another to follow + cl.parsingtextmode = CL_PARSETEXTMODE_PING; + cl.parsingtextexpectingpingforscores = expected; + } + return !expected; + } + } + if (!strncmp(t, "unconnected\n", 12)) + { + // just ignore + cl.parsingtextmode = CL_PARSETEXTMODE_PING; + cl.parsingtextexpectingpingforscores = expected; + return !expected; + } + else + Con_DPrintf("player names '%s' and '%s' didn't match\n", cl.scores[cl.parsingtextplayerindex].name, t); + } + } + } + if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS) + { + if (!strncmp(text, "players: ", 9)) + { + cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID; + cl.parsingtextplayerindex = 0; + return true; + } + else if (!strstr(text, ": ")) + { + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; // status report ended + return true; + } + } + if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERID) + { + // if anything goes wrong, we'll assume this is not a status report + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; + if (text[0] == '#' && text[1] >= '0' && text[1] <= '9') + { + t = text + 1; + cl.parsingtextplayerindex = atoi(t) - 1; + while (*t >= '0' && *t <= '9') + t++; + if (*t == ' ') + { + cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERIP; + return true; + } + // the player name follows here, along with frags and time + } + } + if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERIP) + { + // if anything goes wrong, we'll assume this is not a status report + cl.parsingtextmode = CL_PARSETEXTMODE_NONE; + if (text[0] == ' ') + { + t = text; + while (*t == ' ') + t++; + for (len = 0;*t && *t != '\n';t++) + if (len < (int)sizeof(temp) - 1) + temp[len++] = *t; + temp[len] = 0; + // botclient is perfectly valid, but we don't care about bots + // also don't try to look up the name of an invalid player index + if (strcmp(temp, "botclient") + && cl.parsingtextplayerindex >= 0 + && cl.parsingtextplayerindex < cl.maxclients + && cl.scores[cl.parsingtextplayerindex].name[0]) + { + // log the player name and IP address string + // (this operates entirely on strings to avoid issues with the + // nature of a network address) + CL_IPLog_Add(temp, cl.scores[cl.parsingtextplayerindex].name, true, true); + } + cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID; + return true; + } + } + return true; +} + +extern cvar_t slowmo; +extern cvar_t cl_lerpexcess; +static void CL_NetworkTimeReceived(double newtime) +{ + double timehigh; + cl.mtime[1] = cl.mtime[0]; + cl.mtime[0] = newtime; + if (cl_nolerp.integer || cls.timedemo || (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) || cl.mtime[1] == cl.mtime[0] || cls.signon < SIGNONS) + cl.time = cl.mtime[1] = newtime; + else if (cls.demoplayback) + { + // when time falls behind during demo playback it means the cl.mtime[1] was altered + // due to a large time gap, so treat it as an instant change in time + // (this can also happen during heavy packet loss in the demo) + if (cl.time < newtime - 0.1) + cl.mtime[1] = cl.time = newtime; + } + else if (cls.protocol != PROTOCOL_QUAKEWORLD) + { + cl.mtime[1] = max(cl.mtime[1], cl.mtime[0] - 0.1); + if (developer_extra.integer && vid_activewindow) + { + if (cl.time < cl.mtime[1] - (cl.mtime[0] - cl.mtime[1])) + Con_DPrintf("--- cl.time < cl.mtime[1] (%f < %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); + else if (cl.time > cl.mtime[0] + (cl.mtime[0] - cl.mtime[1])) + Con_DPrintf("--- cl.time > cl.mtime[0] (%f > %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); + } + cl.time += (cl.mtime[1] - cl.time) * bound(0, cl_nettimesyncfactor.value, 1); + timehigh = cl.mtime[1] + (cl.mtime[0] - cl.mtime[1]) * cl_nettimesyncboundtolerance.value; + if (cl_nettimesyncboundmode.integer == 1) + cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); + else if (cl_nettimesyncboundmode.integer == 2) + { + if (cl.time < cl.mtime[1] || cl.time > timehigh) + cl.time = cl.mtime[1]; + } + else if (cl_nettimesyncboundmode.integer == 3) + { + if ((cl.time < cl.mtime[1] && cl.oldtime < cl.mtime[1]) || (cl.time > timehigh && cl.oldtime > timehigh)) + cl.time = cl.mtime[1]; + } + else if (cl_nettimesyncboundmode.integer == 4) + { + if (fabs(cl.time - cl.mtime[1]) > 0.5) + cl.time = cl.mtime[1]; // reset + else if (fabs(cl.time - cl.mtime[1]) > 0.1) + cl.time += 0.5 * (cl.mtime[1] - cl.time); // fast + else if (cl.time > cl.mtime[1]) + cl.time -= 0.002 * cl.movevars_timescale; // fall into the past by 2ms + else + cl.time += 0.001 * cl.movevars_timescale; // creep forward 1ms + } + else if (cl_nettimesyncboundmode.integer == 5) + { + if (fabs(cl.time - cl.mtime[1]) > 0.5) + cl.time = cl.mtime[1]; // reset + else if (fabs(cl.time - cl.mtime[1]) > 0.1) + cl.time += 0.5 * (cl.mtime[1] - cl.time); // fast + else + cl.time = bound(cl.time - 0.002 * cl.movevars_timescale, cl.mtime[1], cl.time + 0.001 * cl.movevars_timescale); + } + else if (cl_nettimesyncboundmode.integer == 6) + { + cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); + cl.time = bound(cl.time - 0.002 * cl.movevars_timescale, cl.mtime[1], cl.time + 0.001 * cl.movevars_timescale); + } + } + // this packet probably contains a player entity update, so we will need + // to update the prediction + cl.movement_replay = true; + // this may get updated later in parsing by svc_clientdata + cl.onground = false; + // if true the cl.viewangles are interpolated from cl.mviewangles[] + // during this frame + // (makes spectating players much smoother and prevents mouse movement from turning) + cl.fixangle[1] = cl.fixangle[0]; + cl.fixangle[0] = false; + if (!cls.demoplayback) + VectorCopy(cl.mviewangles[0], cl.mviewangles[1]); + // update the csqc's server timestamps, critical for proper sync + CSQC_UpdateNetworkTimes(cl.mtime[0], cl.mtime[1]); + + if (cl.mtime[0] > cl.mtime[1]) + World_Physics_Frame(&cl.world, cl.mtime[0] - cl.mtime[1], cl.movevars_gravity); + + // only lerp entities that also get an update in this frame, when lerp excess is used + if(cl_lerpexcess.value > 0) + { + int i; + for (i = 1;i < cl.num_entities;i++) + { + if (cl.entities_active[i]) + { + entity_t *ent = cl.entities + i; + ent->persistent.lerpdeltatime = 0; + } + } + } +} + +#define SHOWNET(x) if(cl_shownet.integer==2)Con_Printf("%3i:%s(%i)\n", cl_message.readcount-1, x, cmd); + +/* +===================== +CL_ParseServerMessage +===================== +*/ +int parsingerror = false; +void CL_ParseServerMessage(void) +{ + int cmd; + int i; + protocolversion_t protocol; + unsigned char cmdlog[32]; + const char *cmdlogname[32], *temp; + int cmdindex, cmdcount = 0; + qboolean qwplayerupdatereceived; + qboolean strip_pqc; + char vabuf[1024]; + + // LordHavoc: moved demo message writing from before the packet parse to + // after the packet parse so that CL_Stop_f can be called by cl_autodemo + // code in CL_ParseServerinfo + //if (cls.demorecording) + // CL_WriteDemoMessage (&cl_message); + + cl.last_received_message = realtime; + + CL_KeepaliveMessage(false); + +// +// if recording demos, copy the message out +// + if (cl_shownet.integer == 1) + Con_Printf("%f %i\n", realtime, cl_message.cursize); + else if (cl_shownet.integer == 2) + Con_Print("------------------\n"); + +// +// parse the message +// + //MSG_BeginReading (); + + parsingerror = true; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + CL_NetworkTimeReceived(realtime); // qw has no clock + + // kill all qw nails + cl.qw_num_nails = 0; + + // fade weapon view kick + cl.qw_weaponkick = min(cl.qw_weaponkick + 10 * bound(0, cl.time - cl.oldtime, 0.1), 0); + + cls.servermovesequence = cls.netcon->qw.incoming_sequence; + + qwplayerupdatereceived = false; + + while (1) + { + if (cl_message.badread) + Host_Error ("CL_ParseServerMessage: Bad QW server message"); + + cmd = MSG_ReadByte(&cl_message); + + if (cmd == -1) + { + SHOWNET("END OF MESSAGE"); + break; // end of message + } + + cmdindex = cmdcount & 31; + cmdcount++; + cmdlog[cmdindex] = cmd; + + SHOWNET(qw_svc_strings[cmd]); + cmdlogname[cmdindex] = qw_svc_strings[cmd]; + if (!cmdlogname[cmdindex]) + { + // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) + temp = ""; + cmdlogname[cmdindex] = temp; + } + + // other commands + switch (cmd) + { + default: + { + char description[32*64], temp[64]; + int count; + strlcpy(description, "packet dump: ", sizeof(description)); + i = cmdcount - 32; + if (i < 0) + i = 0; + count = cmdcount - i; + i &= 31; + while(count > 0) + { + dpsnprintf(temp, sizeof(temp), "%3i:%s ", cmdlog[i], cmdlogname[i]); + strlcat(description, temp, sizeof(description)); + count--; + i++; + i &= 31; + } + description[strlen(description)-1] = '\n'; // replace the last space with a newline + Con_Print(description); + Host_Error("CL_ParseServerMessage: Illegible server message"); + } + break; + + case qw_svc_nop: + //Con_Printf("qw_svc_nop\n"); + break; + + case qw_svc_disconnect: + Con_Printf("Server disconnected\n"); + if (cls.demonum != -1) + CL_NextDemo(); + else + CL_Disconnect(); + return; + + case qw_svc_print: + i = MSG_ReadByte(&cl_message); + temp = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports + { + if (i == 3) // chat + CSQC_AddPrintText(va(vabuf, sizeof(vabuf), "\1%s", temp)); //[515]: csqc + else + CSQC_AddPrintText(temp); + } + break; + + case qw_svc_centerprint: + CL_VM_Parse_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); //[515]: csqc + break; + + case qw_svc_stufftext: + CL_VM_Parse_StuffCmd(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); //[515]: csqc + break; + + case qw_svc_damage: + // svc_damage protocol is identical to nq + V_ParseDamage (); + break; + + case qw_svc_serverdata: + //Cbuf_Execute(); // make sure any stuffed commands are done + CL_ParseServerInfo(); + break; + + case qw_svc_setangle: + for (i=0 ; i<3 ; i++) + cl.viewangles[i] = MSG_ReadAngle(&cl_message, cls.protocol); + if (!cls.demoplayback) + { + cl.fixangle[0] = true; + VectorCopy(cl.viewangles, cl.mviewangles[0]); + // disable interpolation if this is new + if (!cl.fixangle[1]) + VectorCopy(cl.viewangles, cl.mviewangles[1]); + } + break; + + case qw_svc_lightstyle: + i = MSG_ReadByte(&cl_message); + if (i >= cl.max_lightstyle) + { + Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); + break; + } + strlcpy (cl.lightstyle[i].map, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (cl.lightstyle[i].map)); + cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; + cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); + break; + + case qw_svc_sound: + CL_ParseStartSoundPacket(false); + break; + + case qw_svc_stopsound: + i = (unsigned short) MSG_ReadShort(&cl_message); + S_StopSound(i>>3, i&7); + break; + + case qw_svc_updatefrags: + i = MSG_ReadByte(&cl_message); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); + cl.scores[i].frags = (signed short) MSG_ReadShort(&cl_message); + break; + + case qw_svc_updateping: + i = MSG_ReadByte(&cl_message); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updateping >= cl.maxclients"); + cl.scores[i].qw_ping = MSG_ReadShort(&cl_message); + break; + + case qw_svc_updatepl: + i = MSG_ReadByte(&cl_message); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatepl >= cl.maxclients"); + cl.scores[i].qw_packetloss = MSG_ReadByte(&cl_message); + break; + + case qw_svc_updateentertime: + i = MSG_ReadByte(&cl_message); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updateentertime >= cl.maxclients"); + // seconds ago + cl.scores[i].qw_entertime = cl.time - MSG_ReadFloat(&cl_message); + break; + + case qw_svc_spawnbaseline: + i = (unsigned short) MSG_ReadShort(&cl_message); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl.max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline(cl.entities + i, false); + break; + case qw_svc_spawnstatic: + CL_ParseStatic(false); + break; + case qw_svc_temp_entity: + if(!CL_VM_Parse_TempEntity()) + CL_ParseTempEntity (); + break; + + case qw_svc_killedmonster: + cl.stats[STAT_MONSTERS]++; + break; + + case qw_svc_foundsecret: + cl.stats[STAT_SECRETS]++; + break; + + case qw_svc_updatestat: + i = MSG_ReadByte(&cl_message); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadByte(&cl_message); + break; + + case qw_svc_updatestatlong: + i = MSG_ReadByte(&cl_message); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestatlong: %i is invalid", i); + cl.stats[i] = MSG_ReadLong(&cl_message); + break; + + case qw_svc_spawnstaticsound: + CL_ParseStaticSound (false); + break; + + case qw_svc_cdtrack: + cl.cdtrack = cl.looptrack = MSG_ReadByte(&cl_message); + if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) + CDAudio_Play ((unsigned char)cls.forcetrack, true); + else + CDAudio_Play ((unsigned char)cl.cdtrack, true); + break; + + case qw_svc_intermission: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 1; + MSG_ReadVector(&cl_message, cl.qw_intermission_origin, cls.protocol); + for (i = 0;i < 3;i++) + cl.qw_intermission_angles[i] = MSG_ReadAngle(&cl_message, cls.protocol); + break; + + case qw_svc_finale: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 2; + SCR_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); + break; + + case qw_svc_sellscreen: + Cmd_ExecuteString ("help", src_command, true); + break; + + case qw_svc_smallkick: + cl.qw_weaponkick = -2; + break; + case qw_svc_bigkick: + cl.qw_weaponkick = -4; + break; + + case qw_svc_muzzleflash: + i = (unsigned short) MSG_ReadShort(&cl_message); + // NOTE: in QW this only worked on clients + if (i < 0 || i >= MAX_EDICTS) + Host_Error("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl.max_entities) + CL_ExpandEntities(i); + cl.entities[i].persistent.muzzleflash = 1.0f; + break; + + case qw_svc_updateuserinfo: + QW_CL_UpdateUserInfo(); + break; + + case qw_svc_setinfo: + QW_CL_SetInfo(); + break; + + case qw_svc_serverinfo: + QW_CL_ServerInfo(); + break; + + case qw_svc_download: + QW_CL_ParseDownload(); + break; + + case qw_svc_playerinfo: + // slightly kill qw player entities now that we know there is + // an update of player entities this frame... + if (!qwplayerupdatereceived) + { + qwplayerupdatereceived = true; + for (i = 1;i < cl.maxclients;i++) + cl.entities_active[i] = false; + } + EntityStateQW_ReadPlayerUpdate(); + break; + + case qw_svc_nails: + QW_CL_ParseNails(); + break; + + case qw_svc_chokecount: + (void) MSG_ReadByte(&cl_message); + // FIXME: apply to netgraph + //for (j = 0;j < i;j++) + // cl.frames[(cls.netcon->qw.incoming_acknowledged-1-j)&QW_UPDATE_MASK].receivedtime = -2; + break; + + case qw_svc_modellist: + QW_CL_ParseModelList(); + break; + + case qw_svc_soundlist: + QW_CL_ParseSoundList(); + break; + + case qw_svc_packetentities: + EntityFrameQW_CL_ReadFrame(false); + // first update is the final signon stage + if (cls.signon == SIGNONS - 1) + { + cls.signon = SIGNONS; + CL_SignonReply (); + } + break; + + case qw_svc_deltapacketentities: + EntityFrameQW_CL_ReadFrame(true); + // first update is the final signon stage + if (cls.signon == SIGNONS - 1) + { + cls.signon = SIGNONS; + CL_SignonReply (); + } + break; + + case qw_svc_maxspeed: + cl.movevars_maxspeed = MSG_ReadFloat(&cl_message); + break; + + case qw_svc_entgravity: + cl.movevars_entgravity = MSG_ReadFloat(&cl_message); + if (!cl.movevars_entgravity) + cl.movevars_entgravity = 1.0f; + break; + + case qw_svc_setpause: + cl.paused = MSG_ReadByte(&cl_message) != 0; + if (cl.paused) + CDAudio_Pause (); + else + CDAudio_Resume (); + S_PauseGameSounds (cl.paused); + break; + } + } + + if (qwplayerupdatereceived) + { + // fully kill any player entities that were not updated this frame + for (i = 1;i <= cl.maxclients;i++) + if (!cl.entities_active[i]) + cl.entities[i].state_current.active = false; + } + } + else + { + while (1) + { + if (cl_message.badread) + Host_Error ("CL_ParseServerMessage: Bad server message"); + + cmd = MSG_ReadByte(&cl_message); + + if (cmd == -1) + { +// R_TimeReport("END OF MESSAGE"); + SHOWNET("END OF MESSAGE"); + break; // end of message + } + + cmdindex = cmdcount & 31; + cmdcount++; + cmdlog[cmdindex] = cmd; + + // if the high bit of the command byte is set, it is a fast update + if (cmd & 128) + { + // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) + temp = "entity"; + cmdlogname[cmdindex] = temp; + SHOWNET("fast update"); + if (cls.signon == SIGNONS - 1) + { + // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } + EntityFrameQuake_ReadEntity (cmd&127); + continue; + } + + SHOWNET(svc_strings[cmd]); + cmdlogname[cmdindex] = svc_strings[cmd]; + if (!cmdlogname[cmdindex]) + { + // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) + temp = ""; + cmdlogname[cmdindex] = temp; + } + + // other commands + switch (cmd) + { + default: + { + char description[32*64], temp[64]; + int count; + strlcpy (description, "packet dump: ", sizeof(description)); + i = cmdcount - 32; + if (i < 0) + i = 0; + count = cmdcount - i; + i &= 31; + while(count > 0) + { + dpsnprintf (temp, sizeof (temp), "%3i:%s ", cmdlog[i], cmdlogname[i]); + strlcat (description, temp, sizeof (description)); + count--; + i++; + i &= 31; + } + description[strlen(description)-1] = '\n'; // replace the last space with a newline + Con_Print(description); + Host_Error ("CL_ParseServerMessage: Illegible server message"); + } + break; + + case svc_nop: + if (cls.signon < SIGNONS) + Con_Print("<-- server to client keepalive\n"); + break; + + case svc_time: + CL_NetworkTimeReceived(MSG_ReadFloat(&cl_message)); + break; + + case svc_clientdata: + CL_ParseClientdata(); + break; + + case svc_version: + i = MSG_ReadLong(&cl_message); + protocol = Protocol_EnumForNumber(i); + if (protocol == PROTOCOL_UNKNOWN) + Host_Error("CL_ParseServerMessage: Server is unrecognized protocol number (%i)", i); + // hack for unmarked Nehahra movie demos which had a custom protocol + if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && gamemode == GAME_NEHAHRA) + protocol = PROTOCOL_NEHAHRAMOVIE; + cls.protocol = protocol; + break; + + case svc_disconnect: + Con_Printf ("Server disconnected\n"); + if (cls.demonum != -1) + CL_NextDemo (); + else + CL_Disconnect (); + break; + + case svc_print: + temp = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports + CSQC_AddPrintText(temp); //[515]: csqc + break; + + case svc_centerprint: + CL_VM_Parse_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); //[515]: csqc + break; + + case svc_stufftext: + temp = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + /* if(utf8_enable.integer) + { + strip_pqc = true; + // we can safely strip and even + // interpret these in utf8 mode + } + else */ switch(cls.protocol) + { + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + // maybe add other protocols if + // so desired, but not DP7 + strip_pqc = true; + break; + case PROTOCOL_DARKPLACES7: + default: + // ProQuake does not support + // these protocols + strip_pqc = false; + break; + } + if(strip_pqc) + { + // skip over ProQuake messages, + // TODO actually interpret them + // (they are sbar team score + // updates), see proquake cl_parse.c + if(*temp == 0x01) + { + ++temp; + while(*temp >= 0x01 && *temp <= 0x1F) + ++temp; + } + } + CL_VM_Parse_StuffCmd(temp); //[515]: csqc + break; + + case svc_damage: + V_ParseDamage (); + break; + + case svc_serverinfo: + CL_ParseServerInfo (); + break; + + case svc_setangle: + for (i=0 ; i<3 ; i++) + cl.viewangles[i] = MSG_ReadAngle(&cl_message, cls.protocol); + if (!cls.demoplayback) + { + cl.fixangle[0] = true; + VectorCopy(cl.viewangles, cl.mviewangles[0]); + // disable interpolation if this is new + if (!cl.fixangle[1]) + VectorCopy(cl.viewangles, cl.mviewangles[1]); + } + break; + + case svc_setview: + cl.viewentity = (unsigned short)MSG_ReadShort(&cl_message); + if (cl.viewentity >= MAX_EDICTS) + Host_Error("svc_setview >= MAX_EDICTS"); + if (cl.viewentity >= cl.max_entities) + CL_ExpandEntities(cl.viewentity); + // LordHavoc: assume first setview recieved is the real player entity + if (!cl.realplayerentity) + cl.realplayerentity = cl.viewentity; + // update cl.playerentity to this one if it is a valid player + if (cl.viewentity >= 1 && cl.viewentity <= cl.maxclients) + cl.playerentity = cl.viewentity; + break; + + case svc_lightstyle: + i = MSG_ReadByte(&cl_message); + if (i >= cl.max_lightstyle) + { + Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); + break; + } + strlcpy (cl.lightstyle[i].map, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (cl.lightstyle[i].map)); + cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; + cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); + break; + + case svc_sound: + CL_ParseStartSoundPacket(false); + break; + + case svc_precache: + if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) + { + // was svc_sound2 in protocols 1, 2, 3, removed in 4, 5, changed to svc_precache in 6 + CL_ParseStartSoundPacket(true); + } + else + { + int i = (unsigned short)MSG_ReadShort(&cl_message); + char *s = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + if (i < 32768) + { + if (i >= 1 && i < MAX_MODELS) + { + dp_model_t *model = Mod_ForName(s, false, false, s[0] == '*' ? cl.model_name[1] : NULL); + if (!model) + Con_DPrintf("svc_precache: Mod_ForName(\"%s\") failed\n", s); + cl.model_precache[i] = model; + } + else + Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_MODELS); + } + else + { + i -= 32768; + if (i >= 1 && i < MAX_SOUNDS) + { + sfx_t *sfx = S_PrecacheSound (s, true, true); + if (!sfx && snd_initialized.integer) + Con_DPrintf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); + cl.sound_precache[i] = sfx; + } + else + Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_SOUNDS); + } + } + break; + + case svc_stopsound: + i = (unsigned short) MSG_ReadShort(&cl_message); + S_StopSound(i>>3, i&7); + break; + + case svc_updatename: + i = MSG_ReadByte(&cl_message); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatename >= cl.maxclients"); + strlcpy (cl.scores[i].name, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (cl.scores[i].name)); + break; + + case svc_updatefrags: + i = MSG_ReadByte(&cl_message); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); + cl.scores[i].frags = (signed short) MSG_ReadShort(&cl_message); + break; + + case svc_updatecolors: + i = MSG_ReadByte(&cl_message); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatecolors >= cl.maxclients"); + cl.scores[i].colors = MSG_ReadByte(&cl_message); + break; + + case svc_particle: + CL_ParseParticleEffect (); + break; + + case svc_effect: + CL_ParseEffect (); + break; + + case svc_effect2: + CL_ParseEffect2 (); + break; + + case svc_spawnbaseline: + i = (unsigned short) MSG_ReadShort(&cl_message); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl.max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline (cl.entities + i, false); + break; + case svc_spawnbaseline2: + i = (unsigned short) MSG_ReadShort(&cl_message); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline2: invalid entity number %i", i); + if (i >= cl.max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline (cl.entities + i, true); + break; + case svc_spawnstatic: + CL_ParseStatic (false); + break; + case svc_spawnstatic2: + CL_ParseStatic (true); + break; + case svc_temp_entity: + if(!CL_VM_Parse_TempEntity()) + CL_ParseTempEntity (); + break; + + case svc_setpause: + cl.paused = MSG_ReadByte(&cl_message) != 0; + if (cl.paused) + CDAudio_Pause (); + else + CDAudio_Resume (); + S_PauseGameSounds (cl.paused); + break; + + case svc_signonnum: + i = MSG_ReadByte(&cl_message); + // LordHavoc: it's rude to kick off the client if they missed the + // reconnect somehow, so allow signon 1 even if at signon 1 + if (i <= cls.signon && i != 1) + Host_Error ("Received signon %i when at %i", i, cls.signon); + cls.signon = i; + CL_SignonReply (); + break; + + case svc_killedmonster: + cl.stats[STAT_MONSTERS]++; + break; + + case svc_foundsecret: + cl.stats[STAT_SECRETS]++; + break; + + case svc_updatestat: + i = MSG_ReadByte(&cl_message); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadLong(&cl_message); + break; + + case svc_updatestatubyte: + i = MSG_ReadByte(&cl_message); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadByte(&cl_message); + break; + + case svc_spawnstaticsound: + CL_ParseStaticSound (false); + break; + + case svc_spawnstaticsound2: + CL_ParseStaticSound (true); + break; + + case svc_cdtrack: + cl.cdtrack = MSG_ReadByte(&cl_message); + cl.looptrack = MSG_ReadByte(&cl_message); + if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) + CDAudio_Play ((unsigned char)cls.forcetrack, true); + else + CDAudio_Play ((unsigned char)cl.cdtrack, true); + break; + + case svc_intermission: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 1; + CL_VM_UpdateIntermissionState(cl.intermission); + break; + + case svc_finale: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 2; + CL_VM_UpdateIntermissionState(cl.intermission); + SCR_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); + break; + + case svc_cutscene: + if(!cl.intermission) + cl.completed_time = cl.time; + cl.intermission = 3; + CL_VM_UpdateIntermissionState(cl.intermission); + SCR_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); + break; + + case svc_sellscreen: + Cmd_ExecuteString ("help", src_command, true); + break; + case svc_hidelmp: + if (gamemode == GAME_TENEBRAE) + { + // repeating particle effect + MSG_ReadCoord(&cl_message, cls.protocol); + MSG_ReadCoord(&cl_message, cls.protocol); + MSG_ReadCoord(&cl_message, cls.protocol); + MSG_ReadCoord(&cl_message, cls.protocol); + MSG_ReadCoord(&cl_message, cls.protocol); + MSG_ReadCoord(&cl_message, cls.protocol); + (void) MSG_ReadByte(&cl_message); + MSG_ReadLong(&cl_message); + MSG_ReadLong(&cl_message); + MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + } + else + SHOWLMP_decodehide(); + break; + case svc_showlmp: + if (gamemode == GAME_TENEBRAE) + { + // particle effect + MSG_ReadCoord(&cl_message, cls.protocol); + MSG_ReadCoord(&cl_message, cls.protocol); + MSG_ReadCoord(&cl_message, cls.protocol); + (void) MSG_ReadByte(&cl_message); + MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + } + else + SHOWLMP_decodeshow(); + break; + case svc_skybox: + R_SetSkyBox(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); + break; + case svc_entities: + if (cls.signon == SIGNONS - 1) + { + // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } + if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) + EntityFrame_CL_ReadFrame(); + else if (cls.protocol == PROTOCOL_DARKPLACES4) + EntityFrame4_CL_ReadFrame(); + else + EntityFrame5_CL_ReadFrame(); + break; + case svc_csqcentities: + CSQC_ReadEntities(); + break; + case svc_downloaddata: + CL_ParseDownload(); + break; + case svc_trailparticles: + CL_ParseTrailParticles(); + break; + case svc_pointparticles: + CL_ParsePointParticles(); + break; + case svc_pointparticles1: + CL_ParsePointParticles1(); + break; + } +// R_TimeReport(svc_strings[cmd]); + } + } + + if (cls.signon == SIGNONS) + CL_UpdateItemsAndWeapon(); +// R_TimeReport("UpdateItems"); + + EntityFrameQuake_ISeeDeadEntities(); +// R_TimeReport("ISeeDeadEntities"); + + CL_UpdateMoveVars(); +// R_TimeReport("UpdateMoveVars"); + + parsingerror = false; + + // LordHavoc: this was at the start of the function before cl_autodemo was + // implemented + if (cls.demorecording) + { + CL_WriteDemoMessage (&cl_message); +// R_TimeReport("WriteDemo"); + } +} + +void CL_Parse_DumpPacket(void) +{ + if (!parsingerror) + return; + Con_Print("Packet dump:\n"); + SZ_HexDumpToConsole(&cl_message); + parsingerror = false; +} + +void CL_Parse_ErrorCleanUp(void) +{ + CL_StopDownload(0, 0); + QW_CL_StopUpload(); +} + +void CL_Parse_Init(void) +{ + Cvar_RegisterVariable(&cl_worldmessage); + Cvar_RegisterVariable(&cl_worldname); + Cvar_RegisterVariable(&cl_worldnamenoextension); + Cvar_RegisterVariable(&cl_worldbasename); + + Cvar_RegisterVariable(&developer_networkentities); + Cvar_RegisterVariable(&cl_gameplayfix_soundsmovewithentities); + + Cvar_RegisterVariable(&cl_sound_wizardhit); + Cvar_RegisterVariable(&cl_sound_hknighthit); + Cvar_RegisterVariable(&cl_sound_tink1); + Cvar_RegisterVariable(&cl_sound_ric1); + Cvar_RegisterVariable(&cl_sound_ric2); + Cvar_RegisterVariable(&cl_sound_ric3); + Cvar_RegisterVariable(&cl_sound_ric_gunshot); + Cvar_RegisterVariable(&cl_sound_r_exp3); + + Cvar_RegisterVariable(&cl_joinbeforedownloadsfinish); + + // server extension cvars set by commands issued from the server during connect + Cvar_RegisterVariable(&cl_serverextension_download); + + Cvar_RegisterVariable(&cl_nettimesyncfactor); + Cvar_RegisterVariable(&cl_nettimesyncboundmode); + Cvar_RegisterVariable(&cl_nettimesyncboundtolerance); + Cvar_RegisterVariable(&cl_iplog_name); + Cvar_RegisterVariable(&cl_readpicture_force); + + Cmd_AddCommand("nextul", QW_CL_NextUpload, "sends next fragment of current upload buffer (screenshot for example)"); + Cmd_AddCommand("stopul", QW_CL_StopUpload, "aborts current upload (screenshot for example)"); + Cmd_AddCommand("skins", QW_CL_Skins_f, "downloads missing qw skins from server"); + Cmd_AddCommand("changing", QW_CL_Changing_f, "sent by qw servers to tell client to wait for level change"); + Cmd_AddCommand("cl_begindownloads", CL_BeginDownloads_f, "used internally by qvr client while connecting (causes loading of models and sounds or triggers downloads for missing ones)"); + Cmd_AddCommand("cl_downloadbegin", CL_DownloadBegin_f, "(networking) informs client of download file information, client replies with sv_startsoundload to begin the transfer"); + Cmd_AddCommand("stopdownload", CL_StopDownload_f, "terminates a download"); + Cmd_AddCommand("cl_downloadfinished", CL_DownloadFinished_f, "signals that a download has finished and provides the client with file size and crc to check its integrity"); + Cmd_AddCommand("iplog_list", CL_IPLog_List_f, "lists names of players whose IP address begins with the supplied text (example: iplog_list 123.456.789)"); +} + +void CL_Parse_Shutdown(void) +{ +} diff --git a/app/jni/cl_particles.c b/app/jni/cl_particles.c new file mode 100644 index 0000000..1a7e446 --- /dev/null +++ b/app/jni/cl_particles.c @@ -0,0 +1,3101 @@ +/* +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 "cl_collision.h" +#include "image.h" +#include "r_shadow.h" + +// must match ptype_t values +particletype_t particletype[pt_total] = +{ + {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen) + {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic + {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static + {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark + {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam + {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain + {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal + {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow + {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble + {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood + {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke + {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal + {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle +}; + +#define PARTICLEEFFECT_UNDERWATER 1 +#define PARTICLEEFFECT_NOTUNDERWATER 2 + +typedef struct particleeffectinfo_s +{ + int effectnameindex; // which effect this belongs to + // PARTICLEEFFECT_* bits + int flags; + // blood effects may spawn very few particles, so proper fraction-overflow + // handling is very important, this variable keeps track of the fraction + double particleaccumulator; + // the math is: countabsolute + requestedcount * countmultiplier * quality + // absolute number of particles to spawn, often used for decals + // (unaffected by quality and requestedcount) + float countabsolute; + // multiplier for the number of particles CL_ParticleEffect was told to + // spawn, most effects do not really have a count and hence use 1, so + // this is often the actual count to spawn, not merely a multiplier + float countmultiplier; + // if > 0 this causes the particle to spawn in an evenly spaced line from + // originmins to originmaxs (causing them to describe a trail, not a box) + float trailspacing; + // type of particle to spawn (defines some aspects of behavior) + ptype_t particletype; + // blending mode used on this particle type + pblend_t blendmode; + // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc) + porientation_t orientation; + // range of colors to choose from in hex RRGGBB (like HTML color tags), + // randomly interpolated at spawn + unsigned int color[2]; + // a random texture is chosen in this range (note the second value is one + // past the last choosable, so for example 8,16 chooses any from 8 up and + // including 15) + // if start and end of the range are the same, no randomization is done + int tex[2]; + // range of size values randomly chosen when spawning, plus size increase over time + float size[3]; + // range of alpha values randomly chosen when spawning, plus alpha fade + float alpha[3]; + // how long the particle should live (note it is also removed if alpha drops to 0) + float time[2]; + // how much gravity affects this particle (negative makes it fly up!) + float gravity; + // how much bounce the particle has when it hits a surface + // if negative the particle is removed on impact + float bounce; + // if in air this friction is applied + // if negative the particle accelerates + float airfriction; + // if in liquid (water/slime/lava) this friction is applied + // if negative the particle accelerates + float liquidfriction; + // these offsets are added to the values given to particleeffect(), and + // then an ellipsoid-shaped jitter is added as defined by these + // (they are the 3 radii) + float stretchfactor; + // stretch velocity factor (used for sparks) + float originoffset[3]; + float relativeoriginoffset[3]; + float velocityoffset[3]; + float relativevelocityoffset[3]; + float originjitter[3]; + float velocityjitter[3]; + float velocitymultiplier; + // an effect can also spawn a dlight + float lightradiusstart; + float lightradiusfade; + float lighttime; + float lightcolor[3]; + qboolean lightshadow; + int lightcubemapnum; + float lightcorona[2]; + unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color! + int staintex[2]; + float stainalpha[2]; + float stainsize[2]; + // other parameters + float rotate[4]; // min/max base angle, min/max rotation over time +} +particleeffectinfo_t; + +char particleeffectname[MAX_PARTICLEEFFECTNAME][64]; + +int numparticleeffectinfo; +particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO]; + +static int particlepalette[256]; +/* + 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7 + 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15 + 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23 + 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31 + 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39 + 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47 + 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55 + 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63 + 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71 + 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79 + 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87 + 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95 + 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103 + 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111 + 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119 + 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127 + 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135 + 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143 + 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151 + 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159 + 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167 + 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175 + 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183 + 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191 + 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199 + 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207 + 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215 + 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223 + 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231 + 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239 + 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247 + 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255 +*/ + +int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61}; +int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66}; +int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3}; + +//static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff}; + +// particletexture_t is a rectangle in the particlefonttexture +typedef struct particletexture_s +{ + rtexture_t *texture; + float s1, t1, s2, t2; +} +particletexture_t; + +static rtexturepool_t *particletexturepool; +static rtexture_t *particlefonttexture; +static particletexture_t particletexture[MAX_PARTICLETEXTURES]; +skinframe_t *decalskinframe; + +// texture numbers in particle font +static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7}; +static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15}; +static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23}; +static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31}; +static const int tex_rainsplash = 32; +static const int tex_particle = 63; +static const int tex_bubble = 62; +static const int tex_raindrop = 61; +static const int tex_beam = 60; + +particleeffectinfo_t baselineparticleeffectinfo = +{ + 0, //int effectnameindex; // which effect this belongs to + // PARTICLEEFFECT_* bits + 0, //int flags; + // blood effects may spawn very few particles, so proper fraction-overflow + // handling is very important, this variable keeps track of the fraction + 0.0, //double particleaccumulator; + // the math is: countabsolute + requestedcount * countmultiplier * quality + // absolute number of particles to spawn, often used for decals + // (unaffected by quality and requestedcount) + 0.0f, //float countabsolute; + // multiplier for the number of particles CL_ParticleEffect was told to + // spawn, most effects do not really have a count and hence use 1, so + // this is often the actual count to spawn, not merely a multiplier + 0.0f, //float countmultiplier; + // if > 0 this causes the particle to spawn in an evenly spaced line from + // originmins to originmaxs (causing them to describe a trail, not a box) + 0.0f, //float trailspacing; + // type of particle to spawn (defines some aspects of behavior) + pt_alphastatic, //ptype_t particletype; + // blending mode used on this particle type + PBLEND_ALPHA, //pblend_t blendmode; + // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc) + PARTICLE_BILLBOARD, //porientation_t orientation; + // range of colors to choose from in hex RRGGBB (like HTML color tags), + // randomly interpolated at spawn + {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2]; + // a random texture is chosen in this range (note the second value is one + // past the last choosable, so for example 8,16 chooses any from 8 up and + // including 15) + // if start and end of the range are the same, no randomization is done + {63, 63 /* tex_particle */}, //int tex[2]; + // range of size values randomly chosen when spawning, plus size increase over time + {1, 1, 0.0f}, //float size[3]; + // range of alpha values randomly chosen when spawning, plus alpha fade + {0.0f, 256.0f, 256.0f}, //float alpha[3]; + // how long the particle should live (note it is also removed if alpha drops to 0) + {16777216.0f, 16777216.0f}, //float time[2]; + // how much gravity affects this particle (negative makes it fly up!) + 0.0f, //float gravity; + // how much bounce the particle has when it hits a surface + // if negative the particle is removed on impact + 0.0f, //float bounce; + // if in air this friction is applied + // if negative the particle accelerates + 0.0f, //float airfriction; + // if in liquid (water/slime/lava) this friction is applied + // if negative the particle accelerates + 0.0f, //float liquidfriction; + // these offsets are added to the values given to particleeffect(), and + // then an ellipsoid-shaped jitter is added as defined by these + // (they are the 3 radii) + 1.0f, //float stretchfactor; + // stretch velocity factor (used for sparks) + {0.0f, 0.0f, 0.0f}, //float originoffset[3]; + {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3]; + {0.0f, 0.0f, 0.0f}, //float velocityoffset[3]; + {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3]; + {0.0f, 0.0f, 0.0f}, //float originjitter[3]; + {0.0f, 0.0f, 0.0f}, //float velocityjitter[3]; + 0.0f, //float velocitymultiplier; + // an effect can also spawn a dlight + 0.0f, //float lightradiusstart; + 0.0f, //float lightradiusfade; + 16777216.0f, //float lighttime; + {1.0f, 1.0f, 1.0f}, //float lightcolor[3]; + true, //qboolean lightshadow; + 0, //int lightcubemapnum; + {1.0f, 0.25f}, //float lightcorona[2]; + {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color! + {-1, -1}, //int staintex[2]; + {1.0f, 1.0f}, //float stainalpha[2]; + {2.0f, 2.0f}, //float stainsize[2]; + // other parameters + {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time +}; + +cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"}; +cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"}; +cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"}; +cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"}; +cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"}; +cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"}; +cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"}; +cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"}; +cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"}; +cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"}; +cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"}; +cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"}; +cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"}; +cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"}; +cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"}; +cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"}; +cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"}; +cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"}; +cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"}; +cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"}; +cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"}; +cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"}; +cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"}; +cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "0", "enables decals (bullet holes, blood, etc)"}; +cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"}; +cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"}; +cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"}; +cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"}; +cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"}; +cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"}; +cvar_t cl_decals_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"}; +cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"}; +cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"}; +cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"}; + + +static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename) +{ + int arrayindex; + int argc; + int linenumber; + particleeffectinfo_t *info = NULL; + const char *text = textstart; + char argv[16][1024]; + for (linenumber = 1;;linenumber++) + { + argc = 0; + for (arrayindex = 0;arrayindex < 16;arrayindex++) + argv[arrayindex][0] = 0; + for (;;) + { + if (!COM_ParseToken_Simple(&text, true, false, true)) + return; + if (!strcmp(com_token, "\n")) + break; + if (argc < 16) + { + strlcpy(argv[argc], com_token, sizeof(argv[argc])); + argc++; + } + } + if (argc < 1) + continue; +#define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;} +#define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0) +#define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex]) +#define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0) +#define readfloat(var) checkparms(2);var = atof(argv[1]) +#define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0 + if (!strcmp(argv[0], "effect")) + { + int effectnameindex; + checkparms(2); + if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO) + { + Con_Printf("%s:%i: too many effects!\n", filename, linenumber); + break; + } + for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++) + { + if (particleeffectname[effectnameindex][0]) + { + if (!strcmp(particleeffectname[effectnameindex], argv[1])) + break; + } + else + { + strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex])); + break; + } + } + // if we run out of names, abort + if (effectnameindex == MAX_PARTICLEEFFECTNAME) + { + Con_Printf("%s:%i: too many effects!\n", filename, linenumber); + break; + } + info = particleeffectinfo + numparticleeffectinfo++; + // copy entire info from baseline, then fix up the nameindex + *info = baselineparticleeffectinfo; + info->effectnameindex = effectnameindex; + } + else if (info == NULL) + { + Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]); + break; + } + else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);} + else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);} + else if (!strcmp(argv[0], "type")) + { + checkparms(2); + if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic; + else if (!strcmp(argv[1], "static")) info->particletype = pt_static; + else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark; + else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam; + else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain; + else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal; + else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow; + else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble; + else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;} + else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke; + else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal; + else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle; + else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]); + info->blendmode = particletype[info->particletype].blendmode; + info->orientation = particletype[info->particletype].orientation; + } + else if (!strcmp(argv[0], "blend")) + { + checkparms(2); + if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA; + else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD; + else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD; + else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]); + } + else if (!strcmp(argv[0], "orientation")) + { + checkparms(2); + if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD; + else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK; + else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED; + else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM; + else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]); + } + else if (!strcmp(argv[0], "color")) {readints(info->color, 2);} + else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);} + else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);} + else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);} + else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);} + else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);} + else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);} + else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);} + else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);} + else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);} + else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);} + else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);} + else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);} + else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);} + else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);} + else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);} + else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);} + else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);} + else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);} + else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);} + else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);} + else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);} + else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);} + else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);} + else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;} + else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;} + else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;} + else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);} + else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);} + else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);} + else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);} + else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);} + else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; } + else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);} + else + Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]); +#undef checkparms +#undef readints +#undef readfloats +#undef readint +#undef readfloat + } +} + +int CL_ParticleEffectIndexForName(const char *name) +{ + int i; + for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++) + if (!strcmp(particleeffectname[i], name)) + return i; + return 0; +} + +const char *CL_ParticleEffectNameForIndex(int i) +{ + if (i < 1 || i >= MAX_PARTICLEEFFECTNAME) + return NULL; + return particleeffectname[i]; +} + +// MUST match effectnameindex_t in client.h +static const char *standardeffectnames[EFFECT_TOTAL] = +{ + "", + "TE_GUNSHOT", + "TE_GUNSHOTQUAD", + "TE_SPIKE", + "TE_SPIKEQUAD", + "TE_SUPERSPIKE", + "TE_SUPERSPIKEQUAD", + "TE_WIZSPIKE", + "TE_KNIGHTSPIKE", + "TE_EXPLOSION", + "TE_EXPLOSIONQUAD", + "TE_TAREXPLOSION", + "TE_TELEPORT", + "TE_LAVASPLASH", + "TE_SMALLFLASH", + "TE_FLAMEJET", + "EF_FLAME", + "TE_BLOOD", + "TE_SPARK", + "TE_PLASMABURN", + "TE_TEI_G3", + "TE_TEI_SMOKE", + "TE_TEI_BIGEXPLOSION", + "TE_TEI_PLASMAHIT", + "EF_STARDUST", + "TR_ROCKET", + "TR_GRENADE", + "TR_BLOOD", + "TR_WIZSPIKE", + "TR_SLIGHTBLOOD", + "TR_KNIGHTSPIKE", + "TR_VORESPIKE", + "TR_NEHAHRASMOKE", + "TR_NEXUIZPLASMA", + "TR_GLOWTRAIL", + "SVC_PARTICLE" +}; + +static void CL_Particles_LoadEffectInfo(const char *customfile) +{ + int i; + int filepass; + unsigned char *filedata; + fs_offset_t filesize; + char filename[MAX_QPATH]; + numparticleeffectinfo = 0; + memset(particleeffectinfo, 0, sizeof(particleeffectinfo)); + memset(particleeffectname, 0, sizeof(particleeffectname)); + for (i = 0;i < EFFECT_TOTAL;i++) + strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i])); + for (filepass = 0;;filepass++) + { + if (filepass == 0) + { + if (customfile) + strlcpy(filename, customfile, sizeof(filename)); + else + strlcpy(filename, "effectinfo.txt", sizeof(filename)); + } + else if (filepass == 1) + { + if (!cl.worldbasename[0] || customfile) + continue; + dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension); + } + else + break; + filedata = FS_LoadFile(filename, tempmempool, true, &filesize); + if (!filedata) + continue; + CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename); + Mem_Free(filedata); + } +} + +static void CL_Particles_LoadEffectInfo_f(void) +{ + CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL); +} + +/* +=============== +CL_InitParticles +=============== +*/ +void CL_ReadPointFile_f (void); +void CL_Particles_Init (void) +{ + Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)"); + Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)"); + + Cvar_RegisterVariable (&cl_particles); + Cvar_RegisterVariable (&cl_particles_quality); + Cvar_RegisterVariable (&cl_particles_alpha); + Cvar_RegisterVariable (&cl_particles_size); + Cvar_RegisterVariable (&cl_particles_quake); + Cvar_RegisterVariable (&cl_particles_blood); + Cvar_RegisterVariable (&cl_particles_blood_alpha); + Cvar_RegisterVariable (&cl_particles_blood_decal_alpha); + Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin); + Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax); + Cvar_RegisterVariable (&cl_particles_blood_bloodhack); + Cvar_RegisterVariable (&cl_particles_explosions_sparks); + Cvar_RegisterVariable (&cl_particles_explosions_shell); + Cvar_RegisterVariable (&cl_particles_bulletimpacts); + Cvar_RegisterVariable (&cl_particles_rain); + Cvar_RegisterVariable (&cl_particles_snow); + Cvar_RegisterVariable (&cl_particles_smoke); + Cvar_RegisterVariable (&cl_particles_smoke_alpha); + Cvar_RegisterVariable (&cl_particles_smoke_alphafade); + Cvar_RegisterVariable (&cl_particles_sparks); + Cvar_RegisterVariable (&cl_particles_bubbles); + Cvar_RegisterVariable (&cl_particles_visculling); + Cvar_RegisterVariable (&cl_particles_collisions); + Cvar_RegisterVariable (&cl_decals); + Cvar_RegisterVariable (&cl_decals_visculling); + Cvar_RegisterVariable (&cl_decals_time); + Cvar_RegisterVariable (&cl_decals_fadetime); + Cvar_RegisterVariable (&cl_decals_newsystem); + Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier); + Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain); + Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears); + Cvar_RegisterVariable (&cl_decals_models); + Cvar_RegisterVariable (&cl_decals_bias); + Cvar_RegisterVariable (&cl_decals_max); +} + +void CL_Particles_Shutdown (void) +{ +} + +void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha); +void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2); + +// list of all 26 parameters: +// ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file +// pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color +// ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle +// psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM) +// palpha - opacity of particle as 0-255 (can be more than 255) +// palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second) +// ptime - how long the particle can live (note it is also removed if alpha drops to nothing) +// pgravity - how much effect gravity has on the particle (0-1) +// pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions +// px,py,pz - starting origin of particle +// pvx,pvy,pvz - starting velocity of particle +// pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1) +// blendmode - one of the PBLEND_ values +// orientation - one of the PARTICLE_ values +// staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none) +// staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none) +// stainalpha: opacity of the stain as factor for alpha +// stainsize: size of the stain as factor for palpha +// angle: base rotation of the particle geometry around its center normal +// spin: rotation speed of the particle geometry around its center normal +particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4]) +{ + int l1, l2, r, g, b; + particle_t *part; + vec3_t v; + if (!cl_particles.integer) + return NULL; + for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++); + if (cl.free_particle >= cl.max_particles) + return NULL; + if (!lifetime) + lifetime = palpha / min(1, palphafade); + part = &cl.particles[cl.free_particle++]; + if (cl.num_particles < cl.free_particle) + cl.num_particles = cl.free_particle; + memset(part, 0, sizeof(*part)); + VectorCopy(sortorigin, part->sortorigin); + part->typeindex = ptypeindex; + part->blendmode = blendmode; + if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM) + { + particletexture_t *tex = &particletexture[ptex]; + if(tex->t1 == 0 && tex->t2 == 1) // full height of texture? + part->orientation = PARTICLE_VBEAM; + else + part->orientation = PARTICLE_HBEAM; + } + else + part->orientation = orientation; + l2 = (int)lhrandom(0.5, 256.5); + l1 = 256 - l2; + part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF; + part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF; + part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF; + if (vid.sRGB3D) + { + part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f); + part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f); + part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f); + } + part->alpha = palpha; + part->alphafade = palphafade; + part->staintexnum = staintex; + if(staincolor1 >= 0 && staincolor2 >= 0) + { + l2 = (int)lhrandom(0.5, 256.5); + l1 = 256 - l2; + if(blendmode == PBLEND_INVMOD) + { + r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant + g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000; + b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000; + } + else + { + r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant + g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000; + b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000; + } + if(r > 0xFF) r = 0xFF; + if(g > 0xFF) g = 0xFF; + if(b > 0xFF) b = 0xFF; + } + else + { + r = part->color[0]; // -1 is shorthand for stain = particle color + g = part->color[1]; + b = part->color[2]; + } + part->staincolor[0] = r; + part->staincolor[1] = g; + part->staincolor[2] = b; + part->stainalpha = palpha * stainalpha; + part->stainsize = psize * stainsize; + if(tint) + { + if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting + { + part->color[0] *= tint[0]; + part->color[1] *= tint[1]; + part->color[2] *= tint[2]; + } + part->alpha *= tint[3]; + part->alphafade *= tint[3]; + part->stainalpha *= tint[3]; + } + part->texnum = ptex; + part->size = psize; + part->sizeincrease = psizeincrease; + part->gravity = pgravity; + part->bounce = pbounce; + part->stretch = stretch; + VectorRandom(v); + part->org[0] = px + originjitter * v[0]; + part->org[1] = py + originjitter * v[1]; + part->org[2] = pz + originjitter * v[2]; + part->vel[0] = pvx + velocityjitter * v[0]; + part->vel[1] = pvy + velocityjitter * v[1]; + part->vel[2] = pvz + velocityjitter * v[2]; + part->time2 = 0; + part->airfriction = pairfriction; + part->liquidfriction = pliquidfriction; + part->die = cl.time + lifetime; + part->delayedspawn = cl.time; +// part->delayedcollisions = 0; + part->qualityreduction = pqualityreduction; + part->angle = angle; + part->spin = spin; + // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance + if (part->typeindex == pt_rain) + { + int i; + particle_t *part2; + float lifetime = part->die - cl.time; + vec3_t endvec; + trace_t trace; + // turn raindrop into simple spark and create delayedspawn splash effect + part->typeindex = pt_spark; + part->bounce = 0; + VectorMA(part->org, lifetime, part->vel, endvec); + trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false, false); + part->die = cl.time + lifetime * trace.fraction; + part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL); + if (part2) + { + part2->delayedspawn = part->die; + part2->die += part->die - cl.time; + for (i = rand() & 7;i < 10;i++) + { + part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + if (part2) + { + part2->delayedspawn = part->die; + part2->die += part->die - cl.time; + } + } + } + } +#if 0 + else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow) + { + float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1); + vec3_t endvec; + trace_t trace; + VectorMA(part->org, lifetime, part->vel, endvec); + trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false); + part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1; + } +#endif + + return part; +} + +static void CL_ImmediateBloodStain(particle_t *part) +{ + vec3_t v; + int staintex; + + // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot + if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer) + { + VectorCopy(part->vel, v); + VectorNormalize(v); + staintex = part->staintexnum; + R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize); + } + + // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot + if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer) + { + VectorCopy(part->vel, v); + VectorNormalize(v); + staintex = tex_blooddecal[rand()&7]; + R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2); + } +} + +void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha) +{ + int l1, l2; + decal_t *decal; + entity_render_t *ent = &cl.entities[hitent].render; + unsigned char color[3]; + if (!cl_decals.integer) + return; + if (!ent->allowdecals) + return; + + l2 = (int)lhrandom(0.5, 256.5); + l1 = 256 - l2; + color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF; + color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF; + color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF; + + if (cl_decals_newsystem.integer) + { + if (vid.sRGB3D) + R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size); + else + R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size); + return; + } + + for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++); + if (cl.free_decal >= cl.max_decals) + return; + decal = &cl.decals[cl.free_decal++]; + if (cl.num_decals < cl.free_decal) + cl.num_decals = cl.free_decal; + memset(decal, 0, sizeof(*decal)); + decal->decalsequence = cl.decalsequence++; + decal->typeindex = pt_decal; + decal->texnum = texnum; + VectorMA(org, cl_decals_bias.value, normal, decal->org); + VectorCopy(normal, decal->normal); + decal->size = size; + decal->alpha = alpha; + decal->time2 = cl.time; + decal->color[0] = color[0]; + decal->color[1] = color[1]; + decal->color[2] = color[2]; + if (vid.sRGB3D) + { + decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f); + decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f); + decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f); + } + decal->owner = hitent; + decal->clusterindex = -1000; // no vis culling unless we're sure + if (hitent) + { + // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0) + decal->ownermodel = cl.entities[decal->owner].render.model; + Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin); + Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal); + } + else + { + if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) + { + mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org); + if(leaf) + decal->clusterindex = leaf->clusterindex; + } + } +} + +void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2) +{ + int i; + vec_t bestfrac; + vec3_t bestorg; + vec3_t bestnormal; + vec3_t org2; + int besthitent = 0, hitent; + trace_t trace; + bestfrac = 10; + for (i = 0;i < 32;i++) + { + VectorRandom(org2); + VectorMA(org, maxdist, org2, org2); + trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false, true); + // take the closest trace result that doesn't end up hitting a NOMARKS + // surface (sky for example) + if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)) + { + bestfrac = trace.fraction; + besthitent = hitent; + VectorCopy(trace.endpos, bestorg); + VectorCopy(trace.plane.normal, bestnormal); + } + } + if (bestfrac < 1) + CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha); +} + +static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount); +static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount); +static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles) +{ + vec3_t center; + matrix4x4_t tempmatrix; + particle_t *part; + + VectorLerp(originmins, 0.5, originmaxs, center); + Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]); + if (effectnameindex == EFFECT_SVC_PARTICLE) + { + if (cl_particles.integer) + { + // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead + if (count == 1024) + CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225)) + CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + else + { + count *= cl_particles_quality.value; + for (;count > 0;count--) + { + int k = particlepalette[(palettecolor & ~7) + (rand()&7)]; + CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + } + else if (effectnameindex == EFFECT_TE_WIZSPIKE) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20); + else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226); + else if (effectnameindex == EFFECT_TE_SPIKE) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + { + if (cl_particles_smoke.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + } + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + } + else if (effectnameindex == EFFECT_TE_SPIKEQUAD) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + { + if (cl_particles_smoke.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + } + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_SUPERSPIKE) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + { + if (cl_particles_smoke.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + } + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + } + else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + { + if (cl_particles_smoke.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + } + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_BLOOD) + { + if (!cl_particles_blood.integer) + return; + if (cl_particles_quake.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73); + else + { + static double bloodaccumulator = 0; + qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1); + //CL_NewParticle(center, pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, NULL); + bloodaccumulator += count * 0.333 * cl_particles_quality.value; + for (;bloodaccumulator > 0;bloodaccumulator--) + { + part = CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + if (immediatebloodstain && part) + { + immediatebloodstain = false; + CL_ImmediateBloodStain(part); + } + } + } + } + else if (effectnameindex == EFFECT_TE_SPARK) + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count); + else if (effectnameindex == EFFECT_TE_PLASMABURN) + { + // plasma scorch mark + R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_GUNSHOT) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + } + else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD) + { + if (cl_particles_bulletimpacts.integer) + { + if (cl_particles_quake.integer) + CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0); + else + { + CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); + CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count); + CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + // bullet hole + R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_EXPLOSION) + { + CL_ParticleExplosion(center); + CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD) + { + CL_ParticleExplosion(center); + CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_TAREXPLOSION) + { + if (cl_particles_quake.integer) + { + int i; + for (i = 0;i < 1024 * cl_particles_quality.value;i++) + { + if (i & 1) + CL_NewParticle(center, pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(center, pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else + CL_ParticleExplosion(center); + CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_SMALLFLASH) + CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + else if (effectnameindex == EFFECT_TE_FLAMEJET) + { + count *= cl_particles_quality.value; + while (count-- > 0) + CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (effectnameindex == EFFECT_TE_LAVASPLASH) + { + float i, j, inc, vel; + vec3_t dir, org; + + inc = 8 / cl_particles_quality.value; + for (i = -128;i < 128;i += inc) + { + for (j = -128;j < 128;j += inc) + { + dir[0] = j + lhrandom(0, inc); + dir[1] = i + lhrandom(0, inc); + dir[2] = 256; + org[0] = center[0] + dir[0]; + org[1] = center[1] + dir[1]; + org[2] = center[2] + lhrandom(0, 64); + vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale + CL_NewParticle(center, pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + else if (effectnameindex == EFFECT_TE_TELEPORT) + { + float i, j, k, inc, vel; + vec3_t dir; + + if (cl_particles_quake.integer) + inc = 4 / cl_particles_quality.value; + else + inc = 8 / cl_particles_quality.value; + for (i = -16;i < 16;i += inc) + { + for (j = -16;j < 16;j += inc) + { + for (k = -24;k < 32;k += inc) + { + VectorSet(dir, i*8, j*8, k*8); + VectorNormalize(dir); + vel = lhrandom(50, 113); + if (cl_particles_quake.integer) + CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + if (!cl_particles_quake.integer) + CL_NewParticle(center, pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_TEI_G3) + CL_NewParticle(center, pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); + else if (effectnameindex == EFFECT_TE_TEI_SMOKE) + { + if (cl_particles_smoke.integer) + { + count *= 0.25f * cl_particles_quality.value; + while (count-- > 0) + CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION) + { + CL_ParticleExplosion(center); + CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT) + { + float f; + R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + if (cl_particles_smoke.integer) + for (f = 0;f < count;f += 4.0f / cl_particles_quality.value) + CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + if (cl_particles_sparks.integer) + for (f = 0;f < count;f += 1.0f / cl_particles_quality.value) + CL_NewParticle(center, pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_EF_FLAME) + { + if (!spawnparticles) + count = 0; + count *= 300 * cl_particles_quality.value; + while (count-- > 0) + CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (effectnameindex == EFFECT_EF_STARDUST) + { + if (!spawnparticles) + count = 0; + count *= 200 * cl_particles_quality.value; + while (count-- > 0) + CL_NewParticle(center, pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3)) + { + vec3_t dir, pos; + float len, dec, qd; + int smoke, blood, bubbles, r, color; + + if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS) + { + vec4_t light; + Vector4Set(light, 0, 0, 0, 0); + + if (effectnameindex == EFFECT_TR_ROCKET) + Vector4Set(light, 3.0f, 1.5f, 0.5f, 200); + else if (effectnameindex == EFFECT_TR_VORESPIKE) + { + if (gamemode == GAME_PRYDON && !cl_particles_quake.integer) + Vector4Set(light, 0.3f, 0.6f, 1.2f, 100); + else + Vector4Set(light, 1.2f, 0.5f, 1.0f, 200); + } + else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA) + Vector4Set(light, 0.75f, 1.5f, 3.0f, 200); + + if (light[3]) + { + matrix4x4_t tempmatrix; + Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]); + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + } + + if (!spawnparticles) + return; + + if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2]) + return; + + VectorSubtract(originmaxs, originmins, dir); + len = VectorNormalizeLength(dir); + if (ent) + { + dec = -ent->persistent.trail_time; + ent->persistent.trail_time += len; + if (ent->persistent.trail_time < 0.01f) + return; + + // if we skip out, leave it reset + ent->persistent.trail_time = 0.0f; + } + else + dec = 0; + + // advance into this frame to reach the first puff location + VectorMA(originmins, dec, dir, pos); + len -= dec; + + smoke = cl_particles.integer && cl_particles_smoke.integer; + blood = cl_particles.integer && cl_particles_blood.integer; + bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)); + qd = 1.0f / cl_particles_quality.value; + + while (len >= 0) + { + dec = 3; + if (blood) + { + if (effectnameindex == EFFECT_TR_BLOOD) + { + if (cl_particles_quake.integer) + { + color = particlepalette[67 + (rand()&3)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + dec = 16; + CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD) + { + if (cl_particles_quake.integer) + { + dec = 6; + color = particlepalette[67 + (rand()&3)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + dec = 32; + CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + if (smoke) + { + if (effectnameindex == EFFECT_TR_ROCKET) + { + if (cl_particles_quake.integer) + { + r = rand()&3; + color = particlepalette[ramp3[r]]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(center, pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_GRENADE) + { + if (cl_particles_quake.integer) + { + r = 2 + (rand()%5); + color = particlepalette[ramp3[r]]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_WIZSPIKE) + { + if (cl_particles_quake.integer) + { + dec = 6; + color = particlepalette[52 + (rand()&7)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (gamemode == GAME_GOODVSBAD2) + { + dec = 6; + CL_NewParticle(center, pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + color = particlepalette[20 + (rand()&7)]; + CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE) + { + if (cl_particles_quake.integer) + { + dec = 6; + color = particlepalette[230 + (rand()&7)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + color = particlepalette[226 + (rand()&7)]; + CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + else if (effectnameindex == EFFECT_TR_VORESPIKE) + { + if (cl_particles_quake.integer) + { + color = particlepalette[152 + (rand()&3)]; + CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (gamemode == GAME_GOODVSBAD2) + { + dec = 6; + CL_NewParticle(center, pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (gamemode == GAME_PRYDON) + { + dec = 6; + CL_NewParticle(center, pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + CL_NewParticle(center, pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE) + { + dec = 7; + CL_NewParticle(center, pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA) + { + dec = 4; + CL_NewParticle(center, pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else if (effectnameindex == EFFECT_TR_GLOWTRAIL) + CL_NewParticle(center, pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + if (bubbles) + { + if (effectnameindex == EFFECT_TR_ROCKET) + CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else if (effectnameindex == EFFECT_TR_GRENADE) + CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + // advance to next time and position + dec *= qd; + len -= dec; + VectorMA (pos, dec, dir, pos); + } + if (ent) + ent->persistent.trail_time = len; + } + else + Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]); +} + +// this is also called on point effects with spawndlight = true and +// spawnparticles = true +// it is called CL_ParticleTrail because most code does not want to supply +// these parameters, only trail handling does +void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4]) +{ + qboolean found = false; + char vabuf[1024]; + if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0]) + { + Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex); + return; // no such effect + } + if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex) + { + int effectinfoindex; + int supercontents; + int tex, staintex; + particleeffectinfo_t *info; + vec3_t center; + vec3_t traildir; + vec3_t trailpos; + vec3_t rvec; + vec3_t angles; + vec3_t velocity; + vec3_t forward; + vec3_t right; + vec3_t up; + vec_t traillen; + vec_t trailstep; + qboolean underwater; + qboolean immediatebloodstain; + particle_t *part; + float avgtint[4], tint[4], tintlerp; + // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one + VectorLerp(originmins, 0.5, originmaxs, center); + supercontents = CL_PointSuperContents(center); + underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0; + VectorSubtract(originmaxs, originmins, traildir); + traillen = VectorLength(traildir); + VectorNormalize(traildir); + if(tintmins) + { + Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint); + } + else + { + Vector4Set(avgtint, 1, 1, 1, 1); + } + for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++) + { + if (info->effectnameindex == effectnameindex) + { + found = true; + if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater) + continue; + if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater) + continue; + + // spawn a dlight if requested + if (info->lightradiusstart > 0 && spawndlight) + { + matrix4x4_t tempmatrix; + if (info->trailspacing > 0) + Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]); + else + Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]); + if (info->lighttime > 0 && info->lightradiusfade > 0) + { + // light flash (explosion, etc) + // called when effect starts + CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + } + else if (r_refdef.scene.numlights < MAX_DLIGHTS) + { + // glowing entity + // called by CL_LinkNetworkEntity + Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1); + rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3]; + rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3]; + rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3]; + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + } + } + + if (!spawnparticles) + continue; + + // spawn particles + tex = info->tex[0]; + if (info->tex[1] > info->tex[0]) + { + tex = (int)lhrandom(info->tex[0], info->tex[1]); + tex = min(tex, info->tex[1] - 1); + } + if(info->staintex[0] < 0) + staintex = info->staintex[0]; + else + { + staintex = (int)lhrandom(info->staintex[0], info->staintex[1]); + staintex = min(staintex, info->staintex[1] - 1); + } + if (info->particletype == pt_decal) + { + VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity); + AnglesFromVectors(angles, velocity, NULL, false); + AngleVectors(angles, forward, right, up); + VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos); + + CL_SpawnDecalParticleForPoint(trailpos, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]); + } + else if (info->orientation == PARTICLE_HBEAM) + { + AnglesFromVectors(angles, traildir, NULL, false); + AngleVectors(angles, forward, right, up); + VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos); + + CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0] + trailpos[0], originmins[1] + trailpos[1], originmins[2] + trailpos[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL); + } + else + { + if (!cl_particles.integer) + continue; + switch (info->particletype) + { + case pt_smoke: if (!cl_particles_smoke.integer) continue;break; + case pt_spark: if (!cl_particles_sparks.integer) continue;break; + case pt_bubble: if (!cl_particles_bubbles.integer) continue;break; + case pt_blood: if (!cl_particles_blood.integer) continue;break; + case pt_rain: if (!cl_particles_rain.integer) continue;break; + case pt_snow: if (!cl_particles_snow.integer) continue;break; + default: break; + } + VectorCopy(originmins, trailpos); + if (info->trailspacing > 0) + { + info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value; + trailstep = info->trailspacing / cl_particles_quality.value; + immediatebloodstain = false; + + AnglesFromVectors(angles, traildir, NULL, false); + AngleVectors(angles, forward, right, up); + VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos); + VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity); + } + else + { + info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value; + trailstep = 0; + immediatebloodstain = + ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood)) + || + ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex); + + VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity); + AnglesFromVectors(angles, velocity, NULL, false); + AngleVectors(angles, forward, right, up); + VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], traildir, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos); + VectorMAMAM(info->relativevelocityoffset[0], traildir, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity); + } + info->particleaccumulator = bound(0, info->particleaccumulator, 16384); + for (;info->particleaccumulator >= 1;info->particleaccumulator--) + { + if (info->tex[1] > info->tex[0]) + { + tex = (int)lhrandom(info->tex[0], info->tex[1]); + tex = min(tex, info->tex[1] - 1); + } + if (!trailstep) + { + trailpos[0] = lhrandom(originmins[0], originmaxs[0]); + trailpos[1] = lhrandom(originmins[1], originmaxs[1]); + trailpos[2] = lhrandom(originmins[2], originmaxs[2]); + } + if(tintmins) + { + tintlerp = lhrandom(0, 1); + Vector4Lerp(tintmins, tintlerp, tintmaxs, tint); + } + VectorRandom(rvec); + part = CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0] + velocity[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1] + velocity[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2] + velocity[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL); + if (immediatebloodstain && part) + { + immediatebloodstain = false; + CL_ImmediateBloodStain(part); + } + if (trailstep) + VectorMA(trailpos, trailstep, traildir, trailpos); + } + } + } + } + } + if (!found) + CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles); +} + +void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor) +{ + CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL); +} + +/* +=============== +CL_EntityParticles +=============== +*/ +void CL_EntityParticles (const entity_t *ent) +{ + int i, j; + vec_t pitch, yaw, dist = 64, beamlength = 16; + vec3_t org, v; + static vec3_t avelocities[NUMVERTEXNORMALS]; + if (!cl_particles.integer) return; + if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused + + Matrix4x4_OriginFromMatrix(&ent->render.matrix, org); + + if (!avelocities[0][0]) + for (i = 0;i < NUMVERTEXNORMALS;i++) + for (j = 0;j < 3;j++) + avelocities[i][j] = lhrandom(0, 2.55); + + for (i = 0;i < NUMVERTEXNORMALS;i++) + { + yaw = cl.time * avelocities[i][0]; + pitch = cl.time * avelocities[i][1]; + v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength; + v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength; + v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength; + CL_NewParticle(org, pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + + +void CL_ReadPointFile_f (void) +{ + double org[3], leakorg[3]; + vec3_t vecorg; + int r, c, s; + char *pointfile = NULL, *pointfilepos, *t, tchar; + char name[MAX_QPATH]; + + if (!cl.worldmodel) + return; + + dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension); + pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL); + if (!pointfile) + { + Con_Printf("Could not open %s\n", name); + return; + } + + Con_Printf("Reading %s...\n", name); + VectorClear(leakorg); + c = 0; + s = 0; + pointfilepos = pointfile; + while (*pointfilepos) + { + while (*pointfilepos == '\n' || *pointfilepos == '\r') + pointfilepos++; + if (!*pointfilepos) + break; + t = pointfilepos; + while (*t && *t != '\n' && *t != '\r') + t++; + tchar = *t; + *t = 0; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]); + VectorCopy(org, vecorg); + *t = tchar; + pointfilepos = t; + if (r != 3) + break; + if (c == 0) + VectorCopy(org, leakorg); + c++; + + if (cl.num_particles < cl.max_particles - 3) + { + s++; + CL_NewParticle(vecorg, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + Mem_Free(pointfile); + VectorCopy(leakorg, vecorg); + Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]); + + CL_NewParticle(vecorg, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(vecorg, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); + CL_NewParticle(vecorg, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); +} + +/* +=============== +CL_ParseParticleEffect + +Parse an effect out of the server message +=============== +*/ +void CL_ParseParticleEffect (void) +{ + vec3_t org, dir; + int i, count, msgcount, color; + + MSG_ReadVector(&cl_message, org, cls.protocol); + for (i=0 ; i<3 ; i++) + dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0); + msgcount = MSG_ReadByte(&cl_message); + color = MSG_ReadByte(&cl_message); + + if (msgcount == 255) + count = 1024; + else + count = msgcount; + + CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color); +} + +/* +=============== +CL_ParticleExplosion + +=============== +*/ +void CL_ParticleExplosion (const vec3_t org) +{ + int i; + trace_t trace; + //vec3_t v; + //vec3_t v2; + R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64); + CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); + + if (cl_particles_quake.integer) + { + for (i = 0;i < 1024;i++) + { + int r, color; + r = rand()&3; + if (i & 1) + { + color = particlepalette[ramp1[r]]; + CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + color = particlepalette[ramp2[r]]; + CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + else + { + i = CL_PointSuperContents(org); + if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER)) + { + if (cl_particles.integer && cl_particles_bubbles.integer) + for (i = 0;i < 128 * cl_particles_quality.value;i++) + CL_NewParticle(org, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + else + { + if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer) + { + for (i = 0;i < 512 * cl_particles_quality.value;i++) + { + int k = 0; + vec3_t v, v2; + do + { + VectorRandom(v2); + VectorMA(org, 128, v2, v); + trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false); + } + while (k < 16 && trace.fraction < 0.1f); + VectorSubtract(trace.endpos, org, v2); + VectorScale(v2, 2.0f, v2); + CL_NewParticle(org, pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + } + } + } + } + + if (cl_particles_explosions_shell.integer) + R_NewExplosion(org); +} + +/* +=============== +CL_ParticleExplosion2 + +=============== +*/ +void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength) +{ + int i, k; + if (!cl_particles.integer) return; + + for (i = 0;i < 512 * cl_particles_quality.value;i++) + { + k = particlepalette[colorStart + (i % colorLength)]; + if (cl_particles_quake.integer) + CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + +static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount) +{ + vec3_t center; + VectorMAM(0.5f, originmins, 0.5f, originmaxs, center); + if (cl_particles_sparks.integer) + { + sparkcount *= cl_particles_quality.value; + while(sparkcount-- > 0) + CL_NewParticle(center, pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + +static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount) +{ + vec3_t center; + VectorMAM(0.5f, originmins, 0.5f, originmaxs, center); + if (cl_particles_smoke.integer) + { + smokecount *= cl_particles_quality.value; + while(smokecount-- > 0) + CL_NewParticle(center, pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + +void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel) +{ + vec3_t center; + int k; + if (!cl_particles.integer) return; + VectorMAM(0.5f, mins, 0.5f, maxs, center); + + count = (int)(count * cl_particles_quality.value); + while (count--) + { + k = particlepalette[colorbase + (rand()&3)]; + CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } +} + +void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type) +{ + int k; + float minz, maxz, lifetime = 30; + vec3_t org; + if (!cl_particles.integer) return; + if (dir[2] < 0) // falling + { + minz = maxs[2] + dir[2] * 0.1; + maxz = maxs[2]; + if (cl.worldmodel) + lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]); + } + else // rising?? + { + minz = mins[2]; + maxz = maxs[2] + dir[2] * 0.1; + if (cl.worldmodel) + lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]); + } + + count = (int)(count * cl_particles_quality.value); + + switch(type) + { + case 0: + if (!cl_particles_rain.integer) break; + count *= 4; // ick, this should be in the mod or maps? + + while(count--) + { + k = particlepalette[colorbase + (rand()&3)]; + VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz)); + if (gamemode == GAME_GOODVSBAD2) + CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); + } + break; + case 1: + if (!cl_particles_snow.integer) break; + while(count--) + { + k = particlepalette[colorbase + (rand()&3)]; + VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz)); + if (gamemode == GAME_GOODVSBAD2) + CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + else + CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); + } + break; + default: + Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type); + } +} + +cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"}; +static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"}; +static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"}; +static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"}; +cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"}; +static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"}; + +#define PARTICLETEXTURESIZE 64 +#define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8) + +static unsigned char shadebubble(float dx, float dy, vec3_t light) +{ + float dz, f, dot; + vec3_t normal; + dz = 1 - (dx*dx+dy*dy); + if (dz > 0) // it does hit the sphere + { + f = 0; + // back side + normal[0] = dx;normal[1] = dy;normal[2] = dz; + VectorNormalize(normal); + dot = DotProduct(normal, light); + if (dot > 0.5) // interior reflection + f += ((dot * 2) - 1); + else if (dot < -0.5) // exterior reflection + f += ((dot * -2) - 1); + // front side + normal[0] = dx;normal[1] = dy;normal[2] = -dz; + VectorNormalize(normal); + dot = DotProduct(normal, light); + if (dot > 0.5) // interior reflection + f += ((dot * 2) - 1); + else if (dot < -0.5) // exterior reflection + f += ((dot * -2) - 1); + f *= 128; + f += 16; // just to give it a haze so you can see the outline + f = bound(0, f, 255); + return (unsigned char) f; + } + else + return 0; +} + +int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols; +static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height) +{ + *basex = (texnum % particlefontcols) * particlefontcellwidth; + *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight; + *width = particlefontcellwidth; + *height = particlefontcellheight; +} + +static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata) +{ + int basex, basey, w, h, y; + CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h); + if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE) + Sys_Error("invalid particle texture size for autogenerating"); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4); +} + +static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha) +{ + int x, y; + float cx, cy, dx, dy, f, iradius; + unsigned char *d; + cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f; + cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f; + iradius = 1.0f / radius; + alpha *= (1.0f / 255.0f); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - cx); + dy = (y - cy); + f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha; + if (f > 0) + { + if (f > 1) + f = 1; + d = data + (y * PARTICLETEXTURESIZE + x) * 4; + d[0] += (int)(f * (blue - d[0])); + d[1] += (int)(f * (green - d[1])); + d[2] += (int)(f * (red - d[2])); + } + } + } +} + +#if 0 +static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb) +{ + int i; + for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4) + { + data[0] = bound(minb, data[0], maxb); + data[1] = bound(ming, data[1], maxg); + data[2] = bound(minr, data[2], maxr); + } +} +#endif + +static void particletextureinvert(unsigned char *data) +{ + int i; + for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4) + { + data[0] = 255 - data[0]; + data[1] = 255 - data[1]; + data[2] = 255 - data[2]; + } +} + +// Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC +static void R_InitBloodTextures (unsigned char *particletexturedata) +{ + int i, j, k, m; + size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4; + unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize); + + // blood particles + for (i = 0;i < 8;i++) + { + memset(data, 255, datasize); + for (k = 0;k < 24;k++) + particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160); + //particletextureclamp(data, 32, 32, 32, 255, 255, 255); + particletextureinvert(data); + setuptex(tex_bloodparticle[i], data, particletexturedata); + } + + // blood decals + for (i = 0;i < 8;i++) + { + memset(data, 255, datasize); + m = 8; + for (j = 1;j < 10;j++) + for (k = min(j, m - 1);k < m;k++) + particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8); + //particletextureclamp(data, 32, 32, 32, 255, 255, 255); + particletextureinvert(data); + setuptex(tex_blooddecal[i], data, particletexturedata); + } + + Mem_Free(data); +} + +//uncomment this to make engine save out particle font to a tga file when run +//#define DUMPPARTICLEFONT + +static void R_InitParticleTexture (void) +{ + int x, y, d, i, k, m; + int basex, basey, w, h; + float dx, dy, f, s1, t1, s2, t2; + vec3_t light; + char *buf; + fs_offset_t filesize; + char texturename[MAX_QPATH]; + skinframe_t *sf; + + // a note: decals need to modulate (multiply) the background color to + // properly darken it (stain), and they need to be able to alpha fade, + // this is a very difficult challenge because it means fading to white + // (no change to background) rather than black (darkening everything + // behind the whole decal polygon), and to accomplish this the texture is + // inverted (dark red blood on white background becomes brilliant cyan + // and white on black background) so we can alpha fade it to black, then + // we invert it again during the blendfunc to make it work... + +#ifndef DUMPPARTICLEFONT + decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false); + if (decalskinframe) + { + particlefonttexture = decalskinframe->base; + // TODO maybe allow custom grid size? + particlefontwidth = image_width; + particlefontheight = image_height; + particlefontcellwidth = image_width / 8; + particlefontcellheight = image_height / 8; + particlefontcols = 8; + particlefontrows = 8; + } + else +#endif + { + unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4); + size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4; + unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize); + unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2); + unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2); + + particlefontwidth = particlefontheight = PARTICLEFONTSIZE; + particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE; + particlefontcols = 8; + particlefontrows = 8; + + memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4); + + // smoke + for (i = 0;i < 8;i++) + { + memset(data, 255, datasize); + do + { + fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8); + fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4); + m = 0; + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192; + if (d > 0) + d = (int)(d * (1-(dx*dx+dy*dy))); + d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7; + d = bound(0, d, 255); + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d; + if (m < d) + m = d; + } + } + } + while (m < 224); + setuptex(tex_smoke[i], data, particletexturedata); + } + + // rain splash + memset(data, 255, datasize); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy))); + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f)); + } + } + setuptex(tex_rainsplash, data, particletexturedata); + + // normal particle + memset(data, 255, datasize); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + d = (int)(256 * (1 - (dx*dx+dy*dy))); + d = bound(0, d, 255); + data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d; + } + } + setuptex(tex_particle, data, particletexturedata); + + // rain + memset(data, 255, datasize); + light[0] = 1;light[1] = 1;light[2] = 1; + VectorNormalize(light); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + // stretch upper half of bubble by +50% and shrink lower half by -50% + // (this gives an elongated teardrop shape) + if (dy > 0.5f) + dy = (dy - 0.5f) * 2.0f; + else + dy = (dy - 0.5f) / 1.5f; + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + // shrink bubble width to half + dx *= 2.0f; + data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light); + } + } + setuptex(tex_raindrop, data, particletexturedata); + + // bubble + memset(data, 255, datasize); + light[0] = 1;light[1] = 1;light[2] = 1; + VectorNormalize(light); + for (y = 0;y < PARTICLETEXTURESIZE;y++) + { + dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + for (x = 0;x < PARTICLETEXTURESIZE;x++) + { + dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); + data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light); + } + } + setuptex(tex_bubble, data, particletexturedata); + + // Blood particles and blood decals + R_InitBloodTextures (particletexturedata); + + // bullet decals + for (i = 0;i < 8;i++) + { + memset(data, 255, datasize); + for (k = 0;k < 12;k++) + particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128); + for (k = 0;k < 3;k++) + particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160); + //particletextureclamp(data, 64, 64, 64, 255, 255, 255); + particletextureinvert(data); + setuptex(tex_bulletdecal[i], data, particletexturedata); + } + +#ifdef DUMPPARTICLEFONT + Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata); +#endif + + decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false); + particlefonttexture = decalskinframe->base; + + Mem_Free(particletexturedata); + Mem_Free(data); + Mem_Free(noise1); + Mem_Free(noise2); + } + for (i = 0;i < MAX_PARTICLETEXTURES;i++) + { + CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h); + particletexture[i].texture = particlefonttexture; + particletexture[i].s1 = (basex + 1) / (float)particlefontwidth; + particletexture[i].t1 = (basey + 1) / (float)particlefontheight; + particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth; + particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight; + } + +#ifndef DUMPPARTICLEFONT + particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D); + if (!particletexture[tex_beam].texture) +#endif + { + unsigned char noise3[64][64], data2[64][16][4]; + // nexbeam + fractalnoise(&noise3[0][0], 64, 4); + m = 0; + for (y = 0;y < 64;y++) + { + dy = (y - 0.5f*64) / (64*0.5f-1); + for (x = 0;x < 16;x++) + { + dx = (x - 0.5f*16) / (16*0.5f-2); + d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]); + data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255); + data2[y][x][3] = 255; + } + } + +#ifdef DUMPPARTICLEFONT + Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]); +#endif + particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL); + } + particletexture[tex_beam].s1 = 0; + particletexture[tex_beam].t1 = 0; + particletexture[tex_beam].s2 = 1; + particletexture[tex_beam].t2 = 1; + + // now load an texcoord/texture override file + buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize); + if(buf) + { + const char *bufptr; + bufptr = buf; + for(;;) + { + if(!COM_ParseToken_Simple(&bufptr, true, false, true)) + break; + if(!strcmp(com_token, "\n")) + continue; // empty line + i = atoi(com_token); + + texturename[0] = 0; + s1 = 0; + t1 = 0; + s2 = 1; + t2 = 1; + + if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) + { + strlcpy(texturename, com_token, sizeof(texturename)); + s1 = atof(com_token); + if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) + { + texturename[0] = 0; + t1 = atof(com_token); + if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) + { + s2 = atof(com_token); + if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) + { + t2 = atof(com_token); + strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename)); + if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) + strlcpy(texturename, com_token, sizeof(texturename)); + } + } + } + else + s1 = 0; + } + if (!texturename[0]) + { + Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n"); + continue; + } + if (i < 0 || i >= MAX_PARTICLETEXTURES) + { + Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES); + continue; + } + sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active! + if(!sf) + { + // R_SkinFrame_LoadExternal already complained + continue; + } + particletexture[i].texture = sf->base; + particletexture[i].s1 = s1; + particletexture[i].t1 = t1; + particletexture[i].s2 = s2; + particletexture[i].t2 = t2; + } + Mem_Free(buf); + } +} + +static void r_part_start(void) +{ + int i; + // generate particlepalette for convenience from the main one + for (i = 0;i < 256;i++) + particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2]; + particletexturepool = R_AllocTexturePool(); + R_InitParticleTexture (); + CL_Particles_LoadEffectInfo(NULL); +} + +static void r_part_shutdown(void) +{ + R_FreeTexturePool(&particletexturepool); +} + +static void r_part_newmap(void) +{ + if (decalskinframe) + R_SkinFrame_MarkUsed(decalskinframe); + CL_Particles_LoadEffectInfo(NULL); +} + +unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6]; +float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16]; + +void R_Particles_Init (void) +{ + int i; + for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++) + { + particle_elements[i*6+0] = i*4+0; + particle_elements[i*6+1] = i*4+1; + particle_elements[i*6+2] = i*4+2; + particle_elements[i*6+3] = i*4+0; + particle_elements[i*6+4] = i*4+2; + particle_elements[i*6+5] = i*4+3; + } + + Cvar_RegisterVariable(&r_drawparticles); + Cvar_RegisterVariable(&r_drawparticles_drawdistance); + Cvar_RegisterVariable(&r_drawparticles_nearclip_min); + Cvar_RegisterVariable(&r_drawparticles_nearclip_max); + Cvar_RegisterVariable(&r_drawdecals); + Cvar_RegisterVariable(&r_drawdecals_drawdistance); + R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL); +} + +static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex; + const decal_t *d; + float *v3f, *t2f, *c4f; + particletexture_t *tex; + vec_t right[3], up[3], size, ca; + float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value; + + RSurf_ActiveWorldEntity(); + + r_refdef.stats[r_stat_drawndecals] += numsurfaces; +// R_Mesh_ResetTextureState(); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + + // generate all the vertices at once + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + d = cl.decals + surfacelist[surfacelistindex]; + + // calculate color + c4f = particle_color4f + 16*surfacelistindex; + ca = d->alpha * alphascale; + // ensure alpha multiplier saturates properly + if (ca > 1.0f / 256.0f) + ca = 1.0f / 256.0f; + if (r_refdef.fogenabled) + ca *= RSurf_FogVertex(d->org); + Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1); + Vector4Copy(c4f, c4f + 4); + Vector4Copy(c4f, c4f + 8); + Vector4Copy(c4f, c4f + 12); + + // calculate vertex positions + size = d->size * cl_particles_size.value; + VectorVectors(d->normal, right, up); + VectorScale(right, size, right); + VectorScale(up, size, up); + v3f = particle_vertex3f + 12*surfacelistindex; + v3f[ 0] = d->org[0] - right[0] - up[0]; + v3f[ 1] = d->org[1] - right[1] - up[1]; + v3f[ 2] = d->org[2] - right[2] - up[2]; + v3f[ 3] = d->org[0] - right[0] + up[0]; + v3f[ 4] = d->org[1] - right[1] + up[1]; + v3f[ 5] = d->org[2] - right[2] + up[2]; + v3f[ 6] = d->org[0] + right[0] + up[0]; + v3f[ 7] = d->org[1] + right[1] + up[1]; + v3f[ 8] = d->org[2] + right[2] + up[2]; + v3f[ 9] = d->org[0] + right[0] - up[0]; + v3f[10] = d->org[1] + right[1] - up[1]; + v3f[11] = d->org[2] + right[2] - up[2]; + + // calculate texcoords + tex = &particletexture[d->texnum]; + t2f = particle_texcoord2f + 8*surfacelistindex; + t2f[0] = tex->s1;t2f[1] = tex->t2; + t2f[2] = tex->s1;t2f[3] = tex->t1; + t2f[4] = tex->s2;t2f[5] = tex->t1; + t2f[6] = tex->s2;t2f[7] = tex->t2; + } + + // now render the decals all at once + // (this assumes they all use one particle font texture!) + GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true); + R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f); + R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0); +} + +void R_DrawDecals (void) +{ + int i; + int drawdecals = r_drawdecals.integer; + decal_t *decal; + float frametime; + float decalfade; + float drawdist2; + int killsequence = cl.decalsequence - max(0, cl_decals_max.integer); + + frametime = bound(0, cl.time - cl.decals_updatetime, 1); + cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1); + + // LordHavoc: early out conditions + if (!cl.num_decals) + return; + + decalfade = frametime * 256 / cl_decals_fadetime.value; + drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality; + drawdist2 = drawdist2*drawdist2; + + for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++) + { + if (!decal->typeindex) + continue; + + if (killsequence - decal->decalsequence > 0) + goto killdecal; + + if (cl.time > decal->time2 + cl_decals_time.value) + { + decal->alpha -= decalfade; + if (decal->alpha <= 0) + goto killdecal; + } + + if (decal->owner) + { + if (cl.entities[decal->owner].render.model == decal->ownermodel) + { + Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org); + Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal); + } + else + goto killdecal; + } + + if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex)) + continue; + + if (!drawdecals) + continue; + + if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size)) + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL); + continue; +killdecal: + decal->typeindex = 0; + if (cl.free_decal > i) + cl.free_decal = i; + } + + // reduce cl.num_decals if possible + while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0) + cl.num_decals--; + + if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS) + { + decal_t *olddecals = cl.decals; + cl.max_decals = min(cl.max_decals * 2, MAX_DECALS); + cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t)); + memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t)); + Mem_Free(olddecals); + } + + r_refdef.stats[r_stat_totaldecals] = cl.num_decals; +} + +static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + vec3_t vecorg, vecvel, baseright, baseup; + int surfacelistindex; + int batchstart, batchcount; + const particle_t *p; + pblend_t blendmode; + rtexture_t *texture; + float *v3f, *t2f, *c4f; + particletexture_t *tex; + float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha; +// float ambient[3], diffuse[3], diffusenormal[3]; + float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4; + vec4_t colormultiplier; + float minparticledist_start, minparticledist_end; + qboolean dofade; + + RSurf_ActiveWorldEntity(); + + Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f)); + + r_refdef.stats[r_stat_particles] += numsurfaces; +// R_Mesh_ResetTextureState(); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + + spintime = r_refdef.scene.time; + + minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value; + minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value; + dofade = (minparticledist_start < minparticledist_end); + + // first generate all the vertices at once + for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4) + { + p = cl.particles + surfacelist[surfacelistindex]; + + blendmode = (pblend_t)p->blendmode; + palpha = p->alpha; + if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM) + palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start)); + alpha = palpha * colormultiplier[3]; + // ensure alpha multiplier saturates properly + if (alpha > 1.0f) + alpha = 1.0f; + + switch (blendmode) + { + case PBLEND_INVALID: + case PBLEND_INVMOD: + // additive and modulate can just fade out in fog (this is correct) + if (r_refdef.fogenabled) + alpha *= RSurf_FogVertex(p->org); + // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures) + alpha *= 1.0f / 256.0f; + c4f[0] = p->color[0] * alpha; + c4f[1] = p->color[1] * alpha; + c4f[2] = p->color[2] * alpha; + c4f[3] = 0; + break; + case PBLEND_ADD: + // additive and modulate can just fade out in fog (this is correct) + if (r_refdef.fogenabled) + alpha *= RSurf_FogVertex(p->org); + // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures) + c4f[0] = p->color[0] * colormultiplier[0] * alpha; + c4f[1] = p->color[1] * colormultiplier[1] * alpha; + c4f[2] = p->color[2] * colormultiplier[2] * alpha; + c4f[3] = 0; + break; + case PBLEND_ALPHA: + c4f[0] = p->color[0] * colormultiplier[0]; + c4f[1] = p->color[1] * colormultiplier[1]; + c4f[2] = p->color[2] * colormultiplier[2]; + c4f[3] = alpha; + // note: lighting is not cheap! + if (particletype[p->typeindex].lighting) + { + vecorg[0] = p->org[0]; + vecorg[1] = p->org[1]; + vecorg[2] = p->org[2]; + R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); + } + // mix in the fog color + if (r_refdef.fogenabled) + { + fog = RSurf_FogVertex(p->org); + ifog = 1 - fog; + c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog; + c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog; + c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog; + } + // for premultiplied alpha we have to apply the alpha to the color (after fog of course) + VectorScale(c4f, alpha, c4f); + break; + } + // copy the color into the other three vertices + Vector4Copy(c4f, c4f + 4); + Vector4Copy(c4f, c4f + 8); + Vector4Copy(c4f, c4f + 12); + + size = p->size * cl_particles_size.value; + tex = &particletexture[p->texnum]; + switch(p->orientation) + { +// case PARTICLE_INVALID: + case PARTICLE_BILLBOARD: + if (p->angle + p->spin) + { + spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f); + spinsin = sin(spinrad) * size; + spincos = cos(spinrad) * size; + spinm1 = -p->stretch * spincos; + spinm2 = -spinsin; + spinm3 = spinsin; + spinm4 = -p->stretch * spincos; + VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right); + VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up); + } + else + { + VectorScale(r_refdef.view.left, -size * p->stretch, right); + VectorScale(r_refdef.view.up, size, up); + } + + v3f[ 0] = p->org[0] - right[0] - up[0]; + v3f[ 1] = p->org[1] - right[1] - up[1]; + v3f[ 2] = p->org[2] - right[2] - up[2]; + v3f[ 3] = p->org[0] - right[0] + up[0]; + v3f[ 4] = p->org[1] - right[1] + up[1]; + v3f[ 5] = p->org[2] - right[2] + up[2]; + v3f[ 6] = p->org[0] + right[0] + up[0]; + v3f[ 7] = p->org[1] + right[1] + up[1]; + v3f[ 8] = p->org[2] + right[2] + up[2]; + v3f[ 9] = p->org[0] + right[0] - up[0]; + v3f[10] = p->org[1] + right[1] - up[1]; + v3f[11] = p->org[2] + right[2] - up[2]; + t2f[0] = tex->s1;t2f[1] = tex->t2; + t2f[2] = tex->s1;t2f[3] = tex->t1; + t2f[4] = tex->s2;t2f[5] = tex->t1; + t2f[6] = tex->s2;t2f[7] = tex->t2; + break; + case PARTICLE_ORIENTED_DOUBLESIDED: + vecvel[0] = p->vel[0]; + vecvel[1] = p->vel[1]; + vecvel[2] = p->vel[2]; + VectorVectors(vecvel, baseright, baseup); + if (p->angle + p->spin) + { + spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f); + spinsin = sin(spinrad) * size; + spincos = cos(spinrad) * size; + spinm1 = p->stretch * spincos; + spinm2 = -spinsin; + spinm3 = spinsin; + spinm4 = p->stretch * spincos; + VectorMAM(spinm1, baseright, spinm2, baseup, right); + VectorMAM(spinm3, baseright, spinm4, baseup, up); + } + else + { + VectorScale(baseright, size * p->stretch, right); + VectorScale(baseup, size, up); + } + v3f[ 0] = p->org[0] - right[0] - up[0]; + v3f[ 1] = p->org[1] - right[1] - up[1]; + v3f[ 2] = p->org[2] - right[2] - up[2]; + v3f[ 3] = p->org[0] - right[0] + up[0]; + v3f[ 4] = p->org[1] - right[1] + up[1]; + v3f[ 5] = p->org[2] - right[2] + up[2]; + v3f[ 6] = p->org[0] + right[0] + up[0]; + v3f[ 7] = p->org[1] + right[1] + up[1]; + v3f[ 8] = p->org[2] + right[2] + up[2]; + v3f[ 9] = p->org[0] + right[0] - up[0]; + v3f[10] = p->org[1] + right[1] - up[1]; + v3f[11] = p->org[2] + right[2] - up[2]; + t2f[0] = tex->s1;t2f[1] = tex->t2; + t2f[2] = tex->s1;t2f[3] = tex->t1; + t2f[4] = tex->s2;t2f[5] = tex->t1; + t2f[6] = tex->s2;t2f[7] = tex->t2; + break; + case PARTICLE_SPARK: + len = VectorLength(p->vel); + VectorNormalize2(p->vel, up); + lenfactor = p->stretch * 0.04 * len; + if(lenfactor < size * 0.5) + lenfactor = size * 0.5; + VectorMA(p->org, -lenfactor, up, v); + VectorMA(p->org, lenfactor, up, up2); + R_CalcBeam_Vertex3f(v3f, v, up2, size); + t2f[0] = tex->s1;t2f[1] = tex->t2; + t2f[2] = tex->s1;t2f[3] = tex->t1; + t2f[4] = tex->s2;t2f[5] = tex->t1; + t2f[6] = tex->s2;t2f[7] = tex->t2; + break; + case PARTICLE_VBEAM: + R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size); + VectorSubtract(p->vel, p->org, up); + VectorNormalize(up); + v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch; + v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch; + t2f[0] = tex->s2;t2f[1] = v[0]; + t2f[2] = tex->s1;t2f[3] = v[0]; + t2f[4] = tex->s1;t2f[5] = v[1]; + t2f[6] = tex->s2;t2f[7] = v[1]; + break; + case PARTICLE_HBEAM: + R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size); + VectorSubtract(p->vel, p->org, up); + VectorNormalize(up); + v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch; + v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch; + t2f[0] = v[0];t2f[1] = tex->t1; + t2f[2] = v[0];t2f[3] = tex->t2; + t2f[4] = v[1];t2f[5] = tex->t2; + t2f[6] = v[1];t2f[7] = tex->t1; + break; + } + } + + // now render batches of particles based on blendmode and texture + blendmode = PBLEND_INVALID; + texture = NULL; + batchstart = 0; + batchcount = 0; + R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;) + { + p = cl.particles + surfacelist[surfacelistindex]; + + if (texture != particletexture[p->texnum].texture) + { + texture = particletexture[p->texnum].texture; + R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false); + } + + if (p->blendmode == PBLEND_INVMOD) + { + // inverse modulate blend - group these + GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + // iterate until we find a change in settings + batchstart = surfacelistindex++; + for (;surfacelistindex < numsurfaces;surfacelistindex++) + { + p = cl.particles + surfacelist[surfacelistindex]; + if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture) + break; + } + } + else + { + // additive or alpha blend - group these + // (we can group these because we premultiplied the texture alpha) + GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + // iterate until we find a change in settings + batchstart = surfacelistindex++; + for (;surfacelistindex < numsurfaces;surfacelistindex++) + { + p = cl.particles + surfacelist[surfacelistindex]; + if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture) + break; + } + } + + batchcount = surfacelistindex - batchstart; + R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0); + } +} + +void R_DrawParticles (void) +{ + int i, a; + int drawparticles = r_drawparticles.integer; + float minparticledist_start; + particle_t *p; + float gravity, frametime, f, dist, oldorg[3], decaldir[3]; + float drawdist2; + int hitent; + trace_t trace; + qboolean update; + + frametime = bound(0, cl.time - cl.particles_updatetime, 1); + cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1); + + // LordHavoc: early out conditions + if (!cl.num_particles) + return; + + minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value; + gravity = frametime * cl.movevars_gravity; + update = frametime > 0; + drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality; + drawdist2 = drawdist2*drawdist2; + + for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++) + { + if (!p->typeindex) + { + if (cl.free_particle > i) + cl.free_particle = i; + continue; + } + + if (update) + { + if (p->delayedspawn > cl.time) + continue; + + p->size += p->sizeincrease * frametime; + p->alpha -= p->alphafade * frametime; + + if (p->alpha <= 0 || p->die <= cl.time) + goto killparticle; + + if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0) + { + if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)) + { + if (p->typeindex == pt_blood) + p->size += frametime * 8; + else + p->vel[2] -= p->gravity * gravity; + f = 1.0f - min(p->liquidfriction * frametime, 1); + VectorScale(p->vel, f, p->vel); + } + else + { + p->vel[2] -= p->gravity * gravity; + if (p->airfriction) + { + f = 1.0f - min(p->airfriction * frametime, 1); + VectorScale(p->vel, f, p->vel); + } + } + + VectorCopy(p->org, oldorg); + VectorMA(p->org, frametime, p->vel, p->org); +// if (p->bounce && cl.time >= p->delayedcollisions) + if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel)) + { + trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false, false); + // if the trace started in or hit something of SUPERCONTENTS_NODROP + // or if the trace hit something flagged as NOIMPACT + // then remove the particle + if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID)) + goto killparticle; + VectorCopy(trace.endpos, p->org); + // react if the particle hit something + if (trace.fraction < 1) + { + VectorCopy(trace.endpos, p->org); + + if (p->staintexnum >= 0) + { + // blood - splash on solid + if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)) + { + R_Stain(p->org, 16, + p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)), + p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f))); + if (cl_decals.integer) + { + // create a decal for the blood splat + a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]); + if (cl_decals_newsystem_bloodsmears.integer) + { + VectorCopy(p->vel, decaldir); + VectorNormalize(decaldir); + } + else + VectorCopy(trace.plane.normal, decaldir); + CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals! + } + } + } + + if (p->typeindex == pt_blood) + { + // blood - splash on solid + if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS) + goto killparticle; + if(p->staintexnum == -1) // staintex < -1 means no stains at all + { + R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f))); + if (cl_decals.integer) + { + // create a decal for the blood splat + if (cl_decals_newsystem_bloodsmears.integer) + { + VectorCopy(p->vel, decaldir); + VectorNormalize(decaldir); + } + else + VectorCopy(trace.plane.normal, decaldir); + CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768); + } + } + goto killparticle; + } + else if (p->bounce < 0) + { + // bounce -1 means remove on impact + goto killparticle; + } + else + { + // anything else - bounce off solid + dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce; + VectorMA(p->vel, dist, trace.plane.normal, p->vel); + } + } + } + + if (VectorLength2(p->vel) < 0.03) + { + if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off + goto killparticle; + VectorClear(p->vel); + } + } + + if (p->typeindex != pt_static) + { + switch (p->typeindex) + { + case pt_entityparticle: + // particle that removes itself after one rendered frame + if (p->time2) + goto killparticle; + else + p->time2 = 1; + break; + case pt_blood: + a = CL_PointSuperContents(p->org); + if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP)) + goto killparticle; + break; + case pt_bubble: + a = CL_PointSuperContents(p->org); + if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))) + goto killparticle; + break; + case pt_rain: + a = CL_PointSuperContents(p->org); + if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK)) + goto killparticle; + break; + case pt_snow: + if (cl.time > p->time2) + { + // snow flutter + p->time2 = cl.time + (rand() & 3) * 0.1; + p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32); + p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32); + } + a = CL_PointSuperContents(p->org); + if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK)) + goto killparticle; + break; + default: + break; + } + } + } + else if (p->delayedspawn > cl.time) + continue; + if (!drawparticles) + continue; + // don't render particles too close to the view (they chew fillrate) + // also don't render particles behind the view (useless) + // further checks to cull to the frustum would be too slow here + switch(p->typeindex) + { + case pt_beam: + // beams have no culling + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL); + break; + default: + if(cl_particles_visculling.integer) + if (!r_refdef.viewcache.world_novis) + if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) + { + mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org); + if(leaf) + if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex)) + continue; + } + // anything else just has to be in front of the viewer and visible at this distance + if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size)) + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL); + break; + } + + continue; +killparticle: + p->typeindex = 0; + if (cl.free_particle > i) + cl.free_particle = i; + } + + // reduce cl.num_particles if possible + while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0) + cl.num_particles--; + + if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES) + { + particle_t *oldparticles = cl.particles; + cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES); + cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t)); + memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t)); + Mem_Free(oldparticles); + } +} diff --git a/app/jni/cl_screen.c b/app/jni/cl_screen.c new file mode 100644 index 0000000..fcda851 --- /dev/null +++ b/app/jni/cl_screen.c @@ -0,0 +1,2788 @@ + +#include "quakedef.h" +#include "cl_video.h" +#include "image.h" +#include "jpeg.h" +#include "image_png.h" +#include "cl_collision.h" +#include "libcurl.h" +#include "csprogs.h" +#include "cap_avi.h" +#include "cap_ogg.h" + +// we have to include snd_main.h here only to get access to snd_renderbuffer->format.speed when writing the AVI headers +#include "snd_main.h" + +cvar_t scr_viewsize = {CVAR_SAVE, "viewsize","100", "how large the view should be, 110 disables inventory bar, 120 disables status bar"}; +cvar_t scr_fov = {CVAR_SAVE, "fov","110", "field of vision, 1-170 degrees, default 110, some players use 110-130"}; +cvar_t scr_conalpha = {CVAR_SAVE, "scr_conalpha", "1", "opacity of console background gfx/conback"}; +cvar_t scr_conalphafactor = {CVAR_SAVE, "scr_conalphafactor", "1", "opacity of console background gfx/conback relative to scr_conalpha; when 0, gfx/conback is not drawn"}; +cvar_t scr_conalpha2factor = {CVAR_SAVE, "scr_conalpha2factor", "0", "opacity of console background gfx/conback2 relative to scr_conalpha; when 0, gfx/conback2 is not drawn"}; +cvar_t scr_conalpha3factor = {CVAR_SAVE, "scr_conalpha3factor", "0", "opacity of console background gfx/conback3 relative to scr_conalpha; when 0, gfx/conback3 is not drawn"}; +cvar_t scr_conbrightness = {CVAR_SAVE, "scr_conbrightness", "1", "brightness of console background (0 = black, 1 = image)"}; +cvar_t scr_conforcewhiledisconnected = {0, "scr_conforcewhiledisconnected", "1", "forces fullscreen console while disconnected"}; +cvar_t scr_conscroll_x = {CVAR_SAVE, "scr_conscroll_x", "0", "scroll speed of gfx/conback in x direction"}; +cvar_t scr_conscroll_y = {CVAR_SAVE, "scr_conscroll_y", "0", "scroll speed of gfx/conback in y direction"}; +cvar_t scr_conscroll2_x = {CVAR_SAVE, "scr_conscroll2_x", "0", "scroll speed of gfx/conback2 in x direction"}; +cvar_t scr_conscroll2_y = {CVAR_SAVE, "scr_conscroll2_y", "0", "scroll speed of gfx/conback2 in y direction"}; +cvar_t scr_conscroll3_x = {CVAR_SAVE, "scr_conscroll3_x", "0", "scroll speed of gfx/conback3 in x direction"}; +cvar_t scr_conscroll3_y = {CVAR_SAVE, "scr_conscroll3_y", "0", "scroll speed of gfx/conback3 in y direction"}; +cvar_t scr_menuforcewhiledisconnected = {0, "scr_menuforcewhiledisconnected", "0", "forces menu while disconnected"}; +cvar_t scr_centertime = {0, "scr_centertime","3", "how long centerprint messages show"}; +cvar_t scr_showram = {CVAR_SAVE, "showram","1", "show ram icon if low on surface cache memory (not used)"}; +cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0", "show turtle icon when framerate is too low"}; +cvar_t scr_showpause = {CVAR_SAVE, "showpause","1", "show pause icon when game is paused"}; +cvar_t scr_showbrand = {0, "showbrand","0", "shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered)"}; +cvar_t scr_printspeed = {0, "scr_printspeed","0", "speed of intermission printing (episode end texts), a value of 0 disables the slow printing"}; +cvar_t scr_loadingscreen_background = {0, "scr_loadingscreen_background","0", "show the last visible background during loading screen (costs one screenful of video memory)"}; +cvar_t scr_loadingscreen_scale = {0, "scr_loadingscreen_scale","1", "scale factor of the background"}; +cvar_t scr_loadingscreen_scale_base = {0, "scr_loadingscreen_scale_base","0", "0 = console pixels, 1 = video pixels"}; +cvar_t scr_loadingscreen_scale_limit = {0, "scr_loadingscreen_scale_limit","0", "0 = no limit, 1 = until first edge hits screen edge, 2 = until last edge hits screen edge, 3 = until width hits screen width, 4 = until height hits screen height"}; +cvar_t scr_loadingscreen_picture = {CVAR_SAVE, "scr_loadingscreen_picture", "gfx/loading", "picture shown during loading"}; +cvar_t scr_loadingscreen_count = {0, "scr_loadingscreen_count","1", "number of loading screen files to use randomly (named loading.tga, loading2.tga, loading3.tga, ...)"}; +cvar_t scr_loadingscreen_firstforstartup = {0, "scr_loadingscreen_firstforstartup","0", "remove loading.tga from random scr_loadingscreen_count selection and only display it on client startup, 0 = normal, 1 = firstforstartup"}; +cvar_t scr_loadingscreen_barcolor = {0, "scr_loadingscreen_barcolor", "0 0 1", "rgb color of loadingscreen progress bar"}; +cvar_t scr_loadingscreen_barheight = {0, "scr_loadingscreen_barheight", "8", "the height of the loadingscreen progress bar"}; +cvar_t scr_loadingscreen_maxfps = {0, "scr_loadingscreen_maxfps", "10", "restrict maximal FPS for loading screen so it will not update very often (this will make lesser loading times on a maps loading large number of models)"}; +cvar_t scr_infobar_height = {0, "scr_infobar_height", "8", "the height of the infobar items"}; +cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640", "virtual width of 2D graphics system"}; +cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480", "virtual height of 2D graphics system"}; +cvar_t vid_pixelheight = {CVAR_SAVE, "vid_pixelheight", "1", "adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example)"}; +cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","1", "save jpeg instead of targa"}; +cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9", "image quality of saved jpeg"}; +cvar_t scr_screenshot_png = {CVAR_SAVE, "scr_screenshot_png","0", "save png instead of targa"}; +cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1", "gamma correction on saved screenshots and videos, 1.0 saves unmodified images"}; +cvar_t scr_screenshot_hwgamma = {CVAR_SAVE, "scr_screenshot_hwgamma","1", "apply the video gamma ramp to saved screenshots and videos"}; +cvar_t scr_screenshot_alpha = {CVAR_SAVE, "scr_screenshot_alpha","0", "try to write an alpha channel to screenshots (debugging feature)"}; +cvar_t scr_screenshot_timestamp = {CVAR_SAVE, "scr_screenshot_timestamp", "1", "use a timestamp based number of the type YYYYMMDDHHMMSSsss instead of sequential numbering"}; +// scr_screenshot_name is defined in fs.c +cvar_t cl_capturevideo = {0, "cl_capturevideo", "0", "enables saving of video to a .avi file using uncompressed I420 colorspace and PCM audio, note that scr_screenshot_gammaboost affects the brightness of the output)"}; +cvar_t cl_capturevideo_demo_stop = {CVAR_SAVE, "cl_capturevideo_demo_stop", "1", "automatically stops video recording when demo ends"}; +cvar_t cl_capturevideo_printfps = {CVAR_SAVE, "cl_capturevideo_printfps", "1", "prints the frames per second captured in capturevideo (is only written to the log file, not to the console, as that would be visible on the video)"}; +cvar_t cl_capturevideo_width = {CVAR_SAVE, "cl_capturevideo_width", "0", "scales all frames to this resolution before saving the video"}; +cvar_t cl_capturevideo_height = {CVAR_SAVE, "cl_capturevideo_height", "0", "scales all frames to this resolution before saving the video"}; +cvar_t cl_capturevideo_realtime = {0, "cl_capturevideo_realtime", "0", "causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over a minute"}; +cvar_t cl_capturevideo_fps = {CVAR_SAVE, "cl_capturevideo_fps", "30", "how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful)"}; +cvar_t cl_capturevideo_nameformat = {CVAR_SAVE, "cl_capturevideo_nameformat", "dpvideo", "prefix for saved videos (the date is encoded using strftime escapes)"}; +cvar_t cl_capturevideo_number = {CVAR_SAVE, "cl_capturevideo_number", "1", "number to append to video filename, incremented each time a capture begins"}; +cvar_t cl_capturevideo_ogg = {CVAR_SAVE, "cl_capturevideo_ogg", "1", "save captured video data as Ogg/Vorbis/Theora streams"}; +cvar_t cl_capturevideo_framestep = {CVAR_SAVE, "cl_capturevideo_framestep", "1", "when set to n >= 1, render n frames to capture one (useful for motion blur like effects)"}; +cvar_t r_letterbox = {0, "r_letterbox", "0", "reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes)"}; +cvar_t r_stereo_angle = {0, "r_stereo_angle", "0", "separation angle of eyes (makes the views look different directions, as an example, 90 gives a 90 degree separation where the views are 45 degrees left and 45 degrees right)"}; +cvar_t scr_stipple = {0, "scr_stipple", "0", "interlacing-like stippling of the display"}; +cvar_t scr_refresh = {0, "scr_refresh", "1", "allows you to completely shut off rendering for benchmarking purposes"}; +cvar_t scr_screenshot_name_in_mapdir = {CVAR_SAVE, "scr_screenshot_name_in_mapdir", "0", "if set to 1, screenshots are placed in a subdirectory named like the map they are from"}; +cvar_t shownetgraph = {CVAR_SAVE, "shownetgraph", "0", "shows a graph of packet sizes and other information, 0 = off, 1 = show client netgraph, 2 = show client and server netgraphs (when hosting a server)"}; +cvar_t cl_demo_mousegrab = {0, "cl_demo_mousegrab", "0", "Allows reading the mouse input while playing demos. Useful for camera mods developed in csqc. (0: never, 1: always)"}; +cvar_t timedemo_screenshotframelist = {0, "timedemo_screenshotframelist", "", "when performing a timedemo, take screenshots of each frame in this space-separated list - example: 1 201 401"}; +cvar_t vid_touchscreen_outlinealpha = {0, "vid_touchscreen_outlinealpha", "0.25", "opacity of touchscreen area outlines"}; +cvar_t vid_touchscreen_overlayalpha = {0, "vid_touchscreen_overlayalpha", "0.25", "opacity of touchscreen area icons"}; +cvar_t r_speeds_graph = {CVAR_SAVE, "r_speeds_graph", "0", "display a graph of renderer statistics "}; +cvar_t r_speeds_graph_filter[8] = +{ + {CVAR_SAVE, "r_speeds_graph_filter_r", "timedelta", "Red - display the specified renderer statistic"}, + {CVAR_SAVE, "r_speeds_graph_filter_g", "batch_batches", "Green - display the specified renderer statistic"}, + {CVAR_SAVE, "r_speeds_graph_filter_b", "batch_triangles", "Blue - display the specified renderer statistic"}, + {CVAR_SAVE, "r_speeds_graph_filter_y", "fast_triangles", "Yellow - display the specified renderer statistic"}, + {CVAR_SAVE, "r_speeds_graph_filter_c", "copytriangles_triangles", "Cyan - display the specified renderer statistic"}, + {CVAR_SAVE, "r_speeds_graph_filter_m", "dynamic_triangles", "Magenta - display the specified renderer statistic"}, + {CVAR_SAVE, "r_speeds_graph_filter_w", "animcache_shade_vertices", "White - display the specified renderer statistic"}, + {CVAR_SAVE, "r_speeds_graph_filter_o", "animcache_shape_vertices", "Orange - display the specified renderer statistic"}, +}; +cvar_t r_speeds_graph_length = {CVAR_SAVE, "r_speeds_graph_length", "1024", "number of frames in statistics graph, can be from 4 to 8192"}; +cvar_t r_speeds_graph_seconds = {CVAR_SAVE, "r_speeds_graph_seconds", "2", "number of seconds in graph, can be from 0.1 to 120"}; +cvar_t r_speeds_graph_x = {CVAR_SAVE, "r_speeds_graph_x", "0", "position of graph"}; +cvar_t r_speeds_graph_y = {CVAR_SAVE, "r_speeds_graph_y", "0", "position of graph"}; +cvar_t r_speeds_graph_width = {CVAR_SAVE, "r_speeds_graph_width", "256", "size of graph"}; +cvar_t r_speeds_graph_height = {CVAR_SAVE, "r_speeds_graph_height", "128", "size of graph"}; + + + +extern cvar_t v_glslgamma; +extern cvar_t sbar_info_pos; +extern cvar_t r_fog_clear; +#define WANT_SCREENSHOT_HWGAMMA (scr_screenshot_hwgamma.integer && vid_usinghwgamma) + +int jpeg_supported = false; + +qboolean scr_initialized; // ready to draw + +float scr_con_current; +int scr_con_margin_bottom; + +extern int con_vislines; + +extern void jni_BigScreenMode(int mode); + + +static void SCR_ScreenShot_f (void); +static void R_Envmap_f (void); + +// backend +void R_ClearScreen(qboolean fogcolor); + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + +char scr_centerstring[MAX_INPUTLINE]; +float scr_centertime_start; // for slow victory printing +float scr_centertime_off; +int scr_center_lines; +int scr_erase_lines; +int scr_erase_center; +char scr_infobarstring[MAX_INPUTLINE]; +float scr_infobartime_off; + +/* +============== +SCR_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void SCR_CenterPrint(const char *str) +{ + //Check to see if this is the shareware message, if so, replace with a more up to date + //relevant one + if (strstr(str, "1-800")) + { + char tempstr[] = "This episode isn't availble in the Shareware version\n" + "You can buy the full game of Quake for $10 on Steam:\n" + "http://store.steampowered.com/app/2310/"; + strlcpy(scr_centerstring, tempstr, sizeof(scr_centerstring)); + } + else { + strlcpy(scr_centerstring, str, sizeof(scr_centerstring)); + } + + scr_centertime_off = scr_centertime.value; + scr_centertime_start = cl.time; + +// count the number of lines for centering + scr_center_lines = 1; + while (*str) + { + if (*str == '\n') + scr_center_lines++; + str++; + } +} + + +static void SCR_DrawCenterString (void) +{ + char *start; + int x, y; + int remaining; + int color; + + if(cl.intermission == 2) // in finale, + if(sb_showscores) // make TAB hide the finale message (sb_showscores overrides finale in sbar.c) + return; + + if(scr_centertime.value <= 0 && !cl.intermission) + return; + +// the finale prints the characters one at a time, except if printspeed is an absurdly high value + if (cl.intermission && scr_printspeed.value > 0 && scr_printspeed.value < 1000000) + remaining = (int)(scr_printspeed.value * (cl.time - scr_centertime_start)); + else + remaining = 9999; + + scr_erase_center = 0; + start = scr_centerstring; + + if (remaining < 1) + return; + + //Lowered to be visible in the GVR + y = (int)(vid_conheight.integer*0.5); + + color = -1; + do + { + // scan the number of characters on the line, not counting color codes + char *newline = strchr(start, '\n'); + int l = newline ? (newline - start) : (int)strlen(start); + float width = DrawQ_TextWidth(start, l, 8, 8, false, FONT_CENTERPRINT); + + x = (int) (vid_conwidth.integer - width)/2 + (r_stereo_side == 0 ? 10 : -10); + if (l > 0) + { + if (remaining < l) + l = remaining; + DrawQ_String(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color, false, FONT_CENTERPRINT); + remaining -= l; + if (remaining <= 0) + return; + } + y += 8; + + if (!newline) + break; + start = newline + 1; // skip the \n + } while (1); +} + +static void SCR_CheckDrawCenterString (void) +{ + if (scr_center_lines > scr_erase_lines) + scr_erase_lines = scr_center_lines; + + if (cl.time > cl.oldtime) + scr_centertime_off -= cl.time - cl.oldtime; + + // don't draw if this is a normal stats-screen intermission, + // only if it is not an intermission, or a finale intermission + if (cl.intermission == 1) + return; + if (scr_centertime_off <= 0 && !cl.intermission) + return; + if (key_dest != key_game) + return; + + SCR_DrawCenterString (); +} + +static void SCR_DrawNetGraph_DrawGraph (int graphx, int graphy, int graphwidth, int graphheight, float graphscale, const char *label, float textsize, int packetcounter, netgraphitem_t *netgraph) +{ + netgraphitem_t *graph; + int j, x, y, numlines; + int totalbytes = 0; + char bytesstring[128]; + float g[NETGRAPH_PACKETS][6]; + float *a; + float *b; + r_vertexgeneric_t vertex[(NETGRAPH_PACKETS+2)*5*2]; + r_vertexgeneric_t *v; + DrawQ_Fill(graphx, graphy, graphwidth, graphheight + textsize * 2, 0, 0, 0, 0.5, 0); + // draw the bar graph itself + memset(g, 0, sizeof(g)); + for (j = 0;j < NETGRAPH_PACKETS;j++) + { + graph = netgraph + j; + g[j][0] = 1.0f - 0.25f * (realtime - graph->time); + g[j][1] = 1.0f; + g[j][2] = 1.0f; + g[j][3] = 1.0f; + g[j][4] = 1.0f; + g[j][5] = 1.0f; + if (graph->unreliablebytes == NETGRAPH_LOSTPACKET) + g[j][1] = 0.00f; + else if (graph->unreliablebytes == NETGRAPH_CHOKEDPACKET) + g[j][2] = 0.96f; + else + { + g[j][3] = 1.0f - graph->unreliablebytes * graphscale; + g[j][4] = g[j][3] - graph->reliablebytes * graphscale; + g[j][5] = g[j][4] - graph->ackbytes * graphscale; + // count bytes in the last second + if (realtime - graph->time < 1.0f) + totalbytes += graph->unreliablebytes + graph->reliablebytes + graph->ackbytes; + } + g[j][1] = bound(0.0f, g[j][1], 1.0f); + g[j][2] = bound(0.0f, g[j][2], 1.0f); + g[j][3] = bound(0.0f, g[j][3], 1.0f); + g[j][4] = bound(0.0f, g[j][4], 1.0f); + g[j][5] = bound(0.0f, g[j][5], 1.0f); + } + // render the lines for the graph + numlines = 0; + v = vertex; + for (j = 0;j < NETGRAPH_PACKETS;j++) + { + a = g[j]; + b = g[(j+1)%NETGRAPH_PACKETS]; + if (a[0] < 0.0f || b[0] > 1.0f || b[0] < a[0]) + continue; + VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[2], 0.0f);Vector4Set(v->color4f, 1.0f, 1.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[2], 0.0f);Vector4Set(v->color4f, 1.0f, 1.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + + VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[1], 0.0f);Vector4Set(v->color4f, 1.0f, 0.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[1], 0.0f);Vector4Set(v->color4f, 1.0f, 0.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + + VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[5], 0.0f);Vector4Set(v->color4f, 0.0f, 1.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[5], 0.0f);Vector4Set(v->color4f, 0.0f, 1.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + + VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[4], 0.0f);Vector4Set(v->color4f, 1.0f, 1.0f, 1.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[4], 0.0f);Vector4Set(v->color4f, 1.0f, 1.0f, 1.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + + VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[3], 0.0f);Vector4Set(v->color4f, 1.0f, 0.5f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[3], 0.0f);Vector4Set(v->color4f, 1.0f, 0.5f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; + + numlines += 5; + } + if (numlines > 0) + { + R_Mesh_PrepareVertices_Generic(numlines*2, vertex, NULL, 0); + DrawQ_Lines(0.0f, numlines, 0, false); + } + x = graphx; + y = graphy + graphheight; + dpsnprintf(bytesstring, sizeof(bytesstring), "%i", totalbytes); + DrawQ_String(x, y, label , 0, textsize, textsize, 1.0f, 1.0f, 1.0f, 1.0f, 0, NULL, false, FONT_DEFAULT);y += textsize; + DrawQ_String(x, y, bytesstring, 0, textsize, textsize, 1.0f, 1.0f, 1.0f, 1.0f, 0, NULL, false, FONT_DEFAULT);y += textsize; +} + +/* +============== +SCR_DrawNetGraph +============== +*/ +static void SCR_DrawNetGraph (void) +{ + int i, separator1, separator2, graphwidth, graphheight, netgraph_x, netgraph_y, textsize, index, netgraphsperrow; + float graphscale; + netconn_t *c; + char vabuf[1024]; + + if (cls.state != ca_connected) + return; + if (!cls.netcon) + return; + if (!shownetgraph.integer) + return; + + separator1 = 2; + separator2 = 4; + textsize = 8; + graphwidth = 120; + graphheight = 70; + graphscale = 1.0f / 1500.0f; + + netgraphsperrow = (vid_conwidth.integer + separator2) / (graphwidth * 2 + separator1 + separator2); + netgraphsperrow = max(netgraphsperrow, 1); + + index = 0; + netgraph_x = (vid_conwidth.integer + separator2) - (1 + (index % netgraphsperrow)) * (graphwidth * 2 + separator1 + separator2); + netgraph_y = (vid_conheight.integer - 48 - sbar_info_pos.integer + separator2) - (1 + (index / netgraphsperrow)) * (graphheight + textsize + separator2); + c = cls.netcon; + SCR_DrawNetGraph_DrawGraph(netgraph_x , netgraph_y, graphwidth, graphheight, graphscale, "incoming", textsize, c->incoming_packetcounter, c->incoming_netgraph); + SCR_DrawNetGraph_DrawGraph(netgraph_x + graphwidth + separator1, netgraph_y, graphwidth, graphheight, graphscale, "outgoing", textsize, c->outgoing_packetcounter, c->outgoing_netgraph); + index++; + + if (sv.active && shownetgraph.integer >= 2) + { + for (i = 0;i < svs.maxclients;i++) + { + c = svs.clients[i].netconnection; + if (!c) + continue; + netgraph_x = (vid_conwidth.integer + separator2) - (1 + (index % netgraphsperrow)) * (graphwidth * 2 + separator1 + separator2); + netgraph_y = (vid_conheight.integer - 48 + separator2) - (1 + (index / netgraphsperrow)) * (graphheight + textsize + separator2); + SCR_DrawNetGraph_DrawGraph(netgraph_x , netgraph_y, graphwidth, graphheight, graphscale, va(vabuf, sizeof(vabuf), "%s", svs.clients[i].name), textsize, c->outgoing_packetcounter, c->outgoing_netgraph); + SCR_DrawNetGraph_DrawGraph(netgraph_x + graphwidth + separator1, netgraph_y, graphwidth, graphheight, graphscale, "" , textsize, c->incoming_packetcounter, c->incoming_netgraph); + index++; + } + } +} + +/* +============== +SCR_DrawTurtle +============== +*/ +static void SCR_DrawTurtle (void) +{ + static int count; + + if (cls.state != ca_connected) + return; + + if (!scr_showturtle.integer) + return; + + if (cl.realframetime < 0.1) + { + count = 0; + return; + } + + count++; + if (count < 3) + return; + + DrawQ_Pic (0, 0, Draw_CachePic ("gfx/turtle"), 0, 0, 1, 1, 1, 1, 0); +} + +/* +============== +SCR_DrawNet +============== +*/ +static void SCR_DrawNet (void) +{ + if (cls.state != ca_connected) + return; + if (realtime - cl.last_received_message < 0.3) + return; + if (cls.demoplayback) + return; + + DrawQ_Pic (64, 0, Draw_CachePic ("gfx/net"), 0, 0, 1, 1, 1, 1, 0); +} + +/* +============== +DrawPause +============== +*/ +static void SCR_DrawPause (void) +{ + cachepic_t *pic; + + if (cls.state != ca_connected) + return; + + if (!scr_showpause.integer) // turn off for screenshots + return; + + if (!cl.paused) + return; + + pic = Draw_CachePic ("gfx/pause"); + DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, pic, 0, 0, 1, 1, 1, 1, 0); +} + +/* +============== +SCR_DrawBrand +============== +*/ +static void SCR_DrawBrand (void) +{ + cachepic_t *pic; + float x, y; + + if (!scr_showbrand.value) + return; + + pic = Draw_CachePic ("gfx/brand"); + + switch ((int)scr_showbrand.value) + { + case 1: // bottom left + x = 0; + y = vid_conheight.integer - pic->height; + break; + case 2: // bottom centre + x = (vid_conwidth.integer - pic->width) / 2; + y = vid_conheight.integer - pic->height; + break; + case 3: // bottom right + x = vid_conwidth.integer - pic->width; + y = vid_conheight.integer - pic->height; + break; + case 4: // centre right + x = vid_conwidth.integer - pic->width; + y = (vid_conheight.integer - pic->height) / 2; + break; + case 5: // top right + x = vid_conwidth.integer - pic->width; + y = 0; + break; + case 6: // top centre + x = (vid_conwidth.integer - pic->width) / 2; + y = 0; + break; + case 7: // top left + x = 0; + y = 0; + break; + case 8: // centre left + x = 0; + y = (vid_conheight.integer - pic->height) / 2; + break; + default: + return; + } + + DrawQ_Pic (x, y, pic, 0, 0, 1, 1, 1, 1, 0); +} + +/* +============== +SCR_DrawQWDownload +============== +*/ +static int SCR_DrawQWDownload(int offset) +{ + // sync with SCR_InfobarHeight + int len; + float x, y; + float size = scr_infobar_height.value; + char temp[256]; + + if (!cls.qw_downloadname[0]) + { + cls.qw_downloadspeedrate = 0; + cls.qw_downloadspeedtime = realtime; + cls.qw_downloadspeedcount = 0; + return 0; + } + if (realtime >= cls.qw_downloadspeedtime + 1) + { + cls.qw_downloadspeedrate = cls.qw_downloadspeedcount; + cls.qw_downloadspeedtime = realtime; + cls.qw_downloadspeedcount = 0; + } + if (cls.protocol == PROTOCOL_QUAKEWORLD) + dpsnprintf(temp, sizeof(temp), "Downloading %s %3i%% (%i) at %i bytes/s", cls.qw_downloadname, cls.qw_downloadpercent, cls.qw_downloadmemorycursize, cls.qw_downloadspeedrate); + else + dpsnprintf(temp, sizeof(temp), "Downloading %s %3i%% (%i/%i) at %i bytes/s", cls.qw_downloadname, cls.qw_downloadpercent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize, cls.qw_downloadspeedrate); + len = (int)strlen(temp); + x = (vid_conwidth.integer - DrawQ_TextWidth(temp, len, size, size, true, FONT_INFOBAR)) / 2; + y = vid_conheight.integer - size - offset; + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); + DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + return size; +} +/* +============== +SCR_DrawInfobarString +============== +*/ +static int SCR_DrawInfobarString(int offset) +{ + int len; + float x, y; + float size = scr_infobar_height.value; + + len = (int)strlen(scr_infobarstring); + x = (vid_conwidth.integer - DrawQ_TextWidth(scr_infobarstring, len, size, size, false, FONT_INFOBAR)) / 2; + y = vid_conheight.integer - size - offset; + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); + DrawQ_String(x, y, scr_infobarstring, len, size, size, 1, 1, 1, 1, 0, NULL, false, FONT_INFOBAR); + return size; +} + +/* +============== +SCR_DrawCurlDownload +============== +*/ +static int SCR_DrawCurlDownload(int offset) +{ + // sync with SCR_InfobarHeight + int len; + int nDownloads; + int i; + float x, y; + float size = scr_infobar_height.value; + Curl_downloadinfo_t *downinfo; + char temp[256]; + char addinfobuf[128]; + const char *addinfo; + + downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo, addinfobuf, sizeof(addinfobuf)); + if(!downinfo) + return 0; + + y = vid_conheight.integer - size * nDownloads - offset; + + if(addinfo) + { + len = (int)strlen(addinfo); + x = (vid_conwidth.integer - DrawQ_TextWidth(addinfo, len, size, size, true, FONT_INFOBAR)) / 2; + DrawQ_Fill(0, y - size, vid_conwidth.integer, size, 1, 1, 1, cls.signon == SIGNONS ? 0.8 : 1, 0); + DrawQ_String(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0, NULL, true, FONT_INFOBAR); + } + + for(i = 0; i != nDownloads; ++i) + { + if(downinfo[i].queued) + dpsnprintf(temp, sizeof(temp), "Still in queue: %s", downinfo[i].filename); + else if(downinfo[i].progress <= 0) + dpsnprintf(temp, sizeof(temp), "Downloading %s ... ???.?%% @ %.1f KiB/s", downinfo[i].filename, downinfo[i].speed / 1024.0); + else + dpsnprintf(temp, sizeof(temp), "Downloading %s ... %5.1f%% @ %.1f KiB/s", downinfo[i].filename, 100.0 * downinfo[i].progress, downinfo[i].speed / 1024.0); + len = (int)strlen(temp); + x = (vid_conwidth.integer - DrawQ_TextWidth(temp, len, size, size, true, FONT_INFOBAR)) / 2; + DrawQ_Fill(0, y + i * size, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); + DrawQ_String(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + } + + Z_Free(downinfo); + + return size * (nDownloads + (addinfo ? 1 : 0)); +} + +/* +============== +SCR_DrawInfobar +============== +*/ +static void SCR_DrawInfobar(void) +{ + int offset = 0; + offset += SCR_DrawQWDownload(offset); + offset += SCR_DrawCurlDownload(offset); + if(scr_infobartime_off > 0) + offset += SCR_DrawInfobarString(offset); + if(offset != scr_con_margin_bottom) + Con_DPrintf("broken console margin calculation: %d != %d\n", offset, scr_con_margin_bottom); +} + +static int SCR_InfobarHeight(void) +{ + int offset = 0; + Curl_downloadinfo_t *downinfo; + const char *addinfo; + int nDownloads; + char addinfobuf[128]; + + if (cl.time > cl.oldtime) + scr_infobartime_off -= cl.time - cl.oldtime; + if(scr_infobartime_off > 0) + offset += 1; + if(cls.qw_downloadname[0]) + offset += 1; + + downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo, addinfobuf, sizeof(addinfobuf)); + if(downinfo) + { + offset += (nDownloads + (addinfo ? 1 : 0)); + Z_Free(downinfo); + } + offset *= scr_infobar_height.value; + + return offset; +} + +/* +============== +SCR_InfoBar_f +============== +*/ +static void SCR_InfoBar_f(void) +{ + if(Cmd_Argc() == 3) + { + scr_infobartime_off = atof(Cmd_Argv(1)); + strlcpy(scr_infobarstring, Cmd_Argv(2), sizeof(scr_infobarstring)); + } + else + { + Con_Printf("usage:\ninfobar expiretime \"string\"\n"); + } +} +//============================================================================= + +/* +================== +SCR_SetUpToDrawConsole +================== +*/ +static void SCR_SetUpToDrawConsole (void) +{ + // lines of console to display + float conlines; + static int framecounter = 0; + + Con_CheckResize (); + + if (scr_menuforcewhiledisconnected.integer && key_dest == key_game && cls.state == ca_disconnected) + { + if (framecounter >= 2) + MR_ToggleMenu(1); + else + framecounter++; + } + else + framecounter = 0; + + if (scr_conforcewhiledisconnected.integer && key_dest == key_game && cls.signon != SIGNONS) + key_consoleactive |= KEY_CONSOLEACTIVE_FORCED; + else + key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED; + +// decide on the height of the console + if (key_consoleactive & KEY_CONSOLEACTIVE_USER) + conlines = vid_conheight.integer/2; // half screen + else + conlines = 0; // none visible + + scr_con_current = conlines; +} + +/* +================== +SCR_DrawConsole +================== +*/ +void SCR_DrawConsole (void) +{ + scr_con_margin_bottom = SCR_InfobarHeight(); + if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED) + { + // full screen + Con_DrawConsole (vid_conheight.integer - scr_con_margin_bottom); + jni_BigScreenMode(1); + } + else if (scr_con_current) { + Con_DrawConsole(min((int) scr_con_current, vid_conheight.integer - scr_con_margin_bottom)); + jni_BigScreenMode(1); + } + else { + con_vislines = 0; + } +} + +/* +=============== +SCR_BeginLoadingPlaque + +================ +*/ +void SCR_BeginLoadingPlaque (qboolean startup) +{ + // save console log up to this point to log_file if it was set by configs + Log_Start(); + + Host_StartVideo(); + SCR_UpdateLoadingScreen(false, startup); +} + +//============================================================================= + +const char *r_stat_name[r_stat_count] = +{ + "timedelta", + "quality", + "renders", + "entities", + "entities_surfaces", + "entities_triangles", + "world_leafs", + "world_portals", + "world_surfaces", + "world_triangles", + "lightmapupdates", + "lightmapupdatepixels", + "particles", + "drawndecals", + "totaldecals", + "draws", + "draws_vertices", + "draws_elements", + "lights", + "lights_clears", + "lights_scissored", + "lights_lighttriangles", + "lights_shadowtriangles", + "lights_dynamicshadowtriangles", + "bouncegrid_lights", + "bouncegrid_particles", + "bouncegrid_traces", + "bouncegrid_hits", + "bouncegrid_splats", + "bouncegrid_bounces", + "photoncache_animated", + "photoncache_cached", + "photoncache_traced", + "bloom", + "bloom_copypixels", + "bloom_drawpixels", + "indexbufferuploadcount", + "indexbufferuploadsize", + "vertexbufferuploadcount", + "vertexbufferuploadsize", + "framedatacurrent", + "framedatasize", + "bufferdatacurrent_vertex", // R_BUFFERDATA_ types are added to this index + "bufferdatacurrent_index16", + "bufferdatacurrent_index32", + "bufferdatacurrent_uniform", + "bufferdatasize_vertex", // R_BUFFERDATA_ types are added to this index + "bufferdatasize_index16", + "bufferdatasize_index32", + "bufferdatasize_uniform", + "animcache_vertexmesh_count", + "animcache_vertexmesh_vertices", + "animcache_vertexmesh_maxvertices", + "animcache_skeletal_count", + "animcache_skeletal_bones", + "animcache_skeletal_maxbones", + "animcache_shade_count", + "animcache_shade_vertices", + "animcache_shade_maxvertices", + "animcache_shape_count", + "animcache_shape_vertices", + "animcache_shape_maxvertices", + "batch_batches", + "batch_withgaps", + "batch_surfaces", + "batch_vertices", + "batch_triangles", + "fast_batches", + "fast_surfaces", + "fast_vertices", + "fast_triangles", + "copytriangles_batches", + "copytriangles_surfaces", + "copytriangles_vertices", + "copytriangles_triangles", + "dynamic_batches", + "dynamic_surfaces", + "dynamic_vertices", + "dynamic_triangles", + "dynamicskeletal_batches", + "dynamicskeletal_surfaces", + "dynamicskeletal_vertices", + "dynamicskeletal_triangles", + "dynamic_batches_because_cvar", + "dynamic_surfaces_because_cvar", + "dynamic_vertices_because_cvar", + "dynamic_triangles_because_cvar", + "dynamic_batches_because_lightmapvertex", + "dynamic_surfaces_because_lightmapvertex", + "dynamic_vertices_because_lightmapvertex", + "dynamic_triangles_because_lightmapvertex", + "dynamic_batches_because_deformvertexes_autosprite", + "dynamic_surfaces_because_deformvertexes_autosprite", + "dynamic_vertices_because_deformvertexes_autosprite", + "dynamic_triangles_because_deformvertexes_autosprite", + "dynamic_batches_because_deformvertexes_autosprite2", + "dynamic_surfaces_because_deformvertexes_autosprite2", + "dynamic_vertices_because_deformvertexes_autosprite2", + "dynamic_triangles_because_deformvertexes_autosprite2", + "dynamic_batches_because_deformvertexes_normal", + "dynamic_surfaces_because_deformvertexes_normal", + "dynamic_vertices_because_deformvertexes_normal", + "dynamic_triangles_because_deformvertexes_normal", + "dynamic_batches_because_deformvertexes_wave", + "dynamic_surfaces_because_deformvertexes_wave", + "dynamic_vertices_because_deformvertexes_wave", + "dynamic_triangles_because_deformvertexes_wave", + "dynamic_batches_because_deformvertexes_bulge", + "dynamic_surfaces_because_deformvertexes_bulge", + "dynamic_vertices_because_deformvertexes_bulge", + "dynamic_triangles_because_deformvertexes_bulge", + "dynamic_batches_because_deformvertexes_move", + "dynamic_surfaces_because_deformvertexes_move", + "dynamic_vertices_because_deformvertexes_move", + "dynamic_triangles_because_deformvertexes_move", + "dynamic_batches_because_tcgen_lightmap", + "dynamic_surfaces_because_tcgen_lightmap", + "dynamic_vertices_because_tcgen_lightmap", + "dynamic_triangles_because_tcgen_lightmap", + "dynamic_batches_because_tcgen_vector", + "dynamic_surfaces_because_tcgen_vector", + "dynamic_vertices_because_tcgen_vector", + "dynamic_triangles_because_tcgen_vector", + "dynamic_batches_because_tcgen_environment", + "dynamic_surfaces_because_tcgen_environment", + "dynamic_vertices_because_tcgen_environment", + "dynamic_triangles_because_tcgen_environment", + "dynamic_batches_because_tcmod_turbulent", + "dynamic_surfaces_because_tcmod_turbulent", + "dynamic_vertices_because_tcmod_turbulent", + "dynamic_triangles_because_tcmod_turbulent", + "dynamic_batches_because_interleavedarrays", + "dynamic_surfaces_because_interleavedarrays", + "dynamic_vertices_because_interleavedarrays", + "dynamic_triangles_because_interleavedarrays", + "dynamic_batches_because_nogaps", + "dynamic_surfaces_because_nogaps", + "dynamic_vertices_because_nogaps", + "dynamic_triangles_because_nogaps", + "dynamic_batches_because_derived", + "dynamic_surfaces_because_derived", + "dynamic_vertices_because_derived", + "dynamic_triangles_because_derived", + "entitycache_count", + "entitycache_surfaces", + "entitycache_vertices", + "entitycache_triangles", + "entityanimate_count", + "entityanimate_surfaces", + "entityanimate_vertices", + "entityanimate_triangles", + "entityskeletal_count", + "entityskeletal_surfaces", + "entityskeletal_vertices", + "entityskeletal_triangles", + "entitystatic_count", + "entitystatic_surfaces", + "entitystatic_vertices", + "entitystatic_triangles", + "entitycustom_count", + "entitycustom_surfaces", + "entitycustom_vertices", + "entitycustom_triangles", +}; + +char r_speeds_timestring[4096]; +int speedstringcount, r_timereport_active; +double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0; +int r_speeds_longestitem = 0; + +void R_TimeReport(const char *desc) +{ + char tempbuf[256]; + int length; + int t; + + if (r_speeds.integer < 2 || !r_timereport_active) + return; + + CHECKGLERROR + if (r_speeds.integer == 2) + GL_Finish(); + CHECKGLERROR + r_timereport_temp = r_timereport_current; + r_timereport_current = Sys_DirtyTime(); + t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0 + 0.5); + + length = dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %s", t, desc); + length = min(length, (int)sizeof(tempbuf) - 1); + if (r_speeds_longestitem < length) + r_speeds_longestitem = length; + for (;length < r_speeds_longestitem;length++) + tempbuf[length] = ' '; + tempbuf[length] = 0; + + if (speedstringcount + length > (vid_conwidth.integer / 8)) + { + strlcat(r_speeds_timestring, "\n", sizeof(r_speeds_timestring)); + speedstringcount = 0; + } + strlcat(r_speeds_timestring, tempbuf, sizeof(r_speeds_timestring)); + speedstringcount += length; +} + +static void R_TimeReport_BeginFrame(void) +{ + speedstringcount = 0; + r_speeds_timestring[0] = 0; + r_timereport_active = false; + memset(&r_refdef.stats, 0, sizeof(r_refdef.stats)); + + if (r_speeds.integer >= 2) + { + r_timereport_active = true; + r_timereport_start = r_timereport_current = Sys_DirtyTime(); + } +} + +static int R_CountLeafTriangles(const dp_model_t *model, const mleaf_t *leaf) +{ + int i, triangles = 0; + for (i = 0;i < leaf->numleafsurfaces;i++) + triangles += model->data_surfaces[leaf->firstleafsurface[i]].num_triangles; + return triangles; +} + +#define R_SPEEDS_GRAPH_COLORS 8 +#define R_SPEEDS_GRAPH_TEXTLENGTH 64 +static float r_speeds_graph_colors[R_SPEEDS_GRAPH_COLORS][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}, {0, 1, 1, 1}, {1, 0, 1, 1}, {1, 1, 1, 1}, {1, 0.5f, 0, 1}}; + +extern cvar_t r_viewscale; +extern float viewscalefpsadjusted; +static void R_TimeReport_EndFrame(void) +{ + int i, j, lines, y; + cl_locnode_t *loc; + char string[1024+4096]; + mleaf_t *viewleaf; + static double oldtime = 0; + + r_refdef.stats[r_stat_timedelta] = (int)((realtime - oldtime) * 1000000.0); + oldtime = realtime; + r_refdef.stats[r_stat_quality] = (int)(100 * r_refdef.view.quality); + + string[0] = 0; + if (r_speeds.integer) + { + // put the location name in the r_speeds display as it greatly helps + // when creating loc files + loc = CL_Locs_FindNearest(cl.movement_origin); + viewleaf = (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) ? r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, r_refdef.view.origin) : NULL; + dpsnprintf(string, sizeof(string), +"%6ius time delta %s%s %.3f cl.time%2.4f brightness\n" +"%3i renders org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n" +"%5i viewleaf%5i cluster%3i area%4i brushes%4i surfaces(%7i triangles)\n" +"%7i surfaces%7i triangles %5i entities (%7i surfaces%7i triangles)\n" +"%5i leafs%5i portals%6i/%6i particles%6i/%6i decals %3i%% quality\n" +"%7i lightmap updates (%7i pixels)%8i/%8i framedata\n" +"%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n" +"bouncegrid:%4i lights%6i particles%6i traces%6i hits%6i splats%6i bounces\n" +"photon cache efficiency:%6i cached%6i traced%6ianimated\n" +"%6i draws%8i vertices%8i triangles bloompixels%8i copied%8i drawn\n" +"updated%5i indexbuffers%8i bytes%5i vertexbuffers%8i bytes\n" +"animcache%5ib gpuskeletal%7i vertices (%7i with normals)\n" +"fastbatch%5i count%5i surfaces%7i vertices %7i triangles\n" +"copytris%5i count%5i surfaces%7i vertices %7i triangles\n" +"dynamic%5i count%5i surfaces%7i vertices%7i triangles\n" +"%s" +, r_refdef.stats[r_stat_timedelta], loc ? "Location: " : "", loc ? loc->name : "", cl.time, r_refdef.view.colorscale +, r_refdef.stats[r_stat_renders], r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], r_refdef.view.forward[0], r_refdef.view.forward[1], r_refdef.view.forward[2] +, viewleaf ? (int)(viewleaf - r_refdef.scene.worldmodel->brush.data_leafs) : -1, viewleaf ? viewleaf->clusterindex : -1, viewleaf ? viewleaf->areaindex : -1, viewleaf ? viewleaf->numleafbrushes : 0, viewleaf ? viewleaf->numleafsurfaces : 0, viewleaf ? R_CountLeafTriangles(r_refdef.scene.worldmodel, viewleaf) : 0 +, r_refdef.stats[r_stat_world_surfaces], r_refdef.stats[r_stat_world_triangles], r_refdef.stats[r_stat_entities], r_refdef.stats[r_stat_entities_surfaces], r_refdef.stats[r_stat_entities_triangles] +, r_refdef.stats[r_stat_world_leafs], r_refdef.stats[r_stat_world_portals], r_refdef.stats[r_stat_particles], cl.num_particles, r_refdef.stats[r_stat_drawndecals], r_refdef.stats[r_stat_totaldecals], r_refdef.stats[r_stat_quality] +, r_refdef.stats[r_stat_lightmapupdates], r_refdef.stats[r_stat_lightmapupdatepixels], r_refdef.stats[r_stat_framedatacurrent], r_refdef.stats[r_stat_framedatasize] +, r_refdef.stats[r_stat_lights], r_refdef.stats[r_stat_lights_clears], r_refdef.stats[r_stat_lights_scissored], r_refdef.stats[r_stat_lights_lighttriangles], r_refdef.stats[r_stat_lights_shadowtriangles], r_refdef.stats[r_stat_lights_dynamicshadowtriangles] +, r_refdef.stats[r_stat_bouncegrid_lights], r_refdef.stats[r_stat_bouncegrid_particles], r_refdef.stats[r_stat_bouncegrid_traces], r_refdef.stats[r_stat_bouncegrid_hits], r_refdef.stats[r_stat_bouncegrid_splats], r_refdef.stats[r_stat_bouncegrid_bounces] +, r_refdef.stats[r_stat_photoncache_cached], r_refdef.stats[r_stat_photoncache_traced], r_refdef.stats[r_stat_photoncache_animated] +, r_refdef.stats[r_stat_draws], r_refdef.stats[r_stat_draws_vertices], r_refdef.stats[r_stat_draws_elements] / 3, r_refdef.stats[r_stat_bloom_copypixels], r_refdef.stats[r_stat_bloom_drawpixels] +, r_refdef.stats[r_stat_indexbufferuploadcount], r_refdef.stats[r_stat_indexbufferuploadsize], r_refdef.stats[r_stat_vertexbufferuploadcount], r_refdef.stats[r_stat_vertexbufferuploadsize] +, r_refdef.stats[r_stat_animcache_skeletal_bones], r_refdef.stats[r_stat_animcache_shape_vertices], r_refdef.stats[r_stat_animcache_shade_vertices] +, r_refdef.stats[r_stat_batch_fast_batches], r_refdef.stats[r_stat_batch_fast_surfaces], r_refdef.stats[r_stat_batch_fast_vertices], r_refdef.stats[r_stat_batch_fast_triangles] +, r_refdef.stats[r_stat_batch_copytriangles_batches], r_refdef.stats[r_stat_batch_copytriangles_surfaces], r_refdef.stats[r_stat_batch_copytriangles_vertices], r_refdef.stats[r_stat_batch_copytriangles_triangles] +, r_refdef.stats[r_stat_batch_dynamic_batches], r_refdef.stats[r_stat_batch_dynamic_surfaces], r_refdef.stats[r_stat_batch_dynamic_vertices], r_refdef.stats[r_stat_batch_dynamic_triangles] +, r_speeds_timestring); + } + + speedstringcount = 0; + r_speeds_timestring[0] = 0; + r_timereport_active = false; + + if (r_speeds.integer >= 2) + { + r_timereport_active = true; + r_timereport_start = r_timereport_current = Sys_DirtyTime(); + } + + if (string[0]) + { + if (string[strlen(string)-1] == '\n') + string[strlen(string)-1] = 0; + lines = 1; + for (i = 0;string[i];i++) + if (string[i] == '\n') + lines++; + y = vid_conheight.integer - sb_lines - lines * 8; + i = j = 0; + r_draw2d_force = true; + DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0); + while (string[i]) + { + j = i; + while (string[i] && string[i] != '\n') + i++; + if (i - j > 0) + DrawQ_String(0, y, string + j, i - j, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT); + if (string[i] == '\n') + i++; + y += 8; + } + r_draw2d_force = false; + } + + if (r_speeds_graph_length.integer != bound(4, r_speeds_graph_length.integer, 8192)) + Cvar_SetValueQuick(&r_speeds_graph_length, bound(4, r_speeds_graph_length.integer, 8192)); + if (fabs(r_speeds_graph_seconds.value - bound(0.1f, r_speeds_graph_seconds.value, 120.0f)) > 0.01f) + Cvar_SetValueQuick(&r_speeds_graph_seconds, bound(0.1f, r_speeds_graph_seconds.value, 120.0f)); + if (r_speeds_graph.integer) + { + // if we currently have no graph data, reset the graph data entirely + if (!cls.r_speeds_graph_data) + for (i = 0;i < r_stat_count;i++) + cls.r_speeds_graph_datamin[i] = cls.r_speeds_graph_datamax[i] = r_refdef.stats[i]; + if (cls.r_speeds_graph_length != r_speeds_graph_length.integer) + { + int i, stat, index, d, graph_length, *graph_data; + cls.r_speeds_graph_length = r_speeds_graph_length.integer; + cls.r_speeds_graph_current = 0; + if (cls.r_speeds_graph_data) + Mem_Free(cls.r_speeds_graph_data); + cls.r_speeds_graph_data = (int *)Mem_Alloc(cls.permanentmempool, cls.r_speeds_graph_length * sizeof(r_refdef.stats)); + // initialize the graph to have the current values throughout history + graph_data = cls.r_speeds_graph_data; + graph_length = cls.r_speeds_graph_length; + index = 0; + for (stat = 0;stat < r_stat_count;stat++) + { + d = r_refdef.stats[stat]; + if (stat == r_stat_timedelta) + d = 0; + for (i = 0;i < graph_length;i++) + graph_data[index++] = d; + } + } + } + else + { + if (cls.r_speeds_graph_length) + { + cls.r_speeds_graph_length = 0; + Mem_Free(cls.r_speeds_graph_data); + cls.r_speeds_graph_data = NULL; + cls.r_speeds_graph_current = 0; + } + } + + if (cls.r_speeds_graph_length) + { + char legend[128]; + r_vertexgeneric_t *v; + int numlines; + const int *data; + float x, y, width, height, scalex, scaley; + int color, stat, stats, index, range_min, range_max; + int graph_current, graph_length, *graph_data; + int statindex[R_SPEEDS_GRAPH_COLORS]; + int sum; + + // add current stats to the graph_data + cls.r_speeds_graph_current++; + if (cls.r_speeds_graph_current >= cls.r_speeds_graph_length) + cls.r_speeds_graph_current = 0; + // poke each new stat into the current offset of its graph + graph_data = cls.r_speeds_graph_data; + graph_current = cls.r_speeds_graph_current; + graph_length = cls.r_speeds_graph_length; + for (stat = 0;stat < r_stat_count;stat++) + graph_data[stat * graph_length + graph_current] = r_refdef.stats[stat]; + + // update the graph ranges + for (stat = 0;stat < r_stat_count;stat++) + { + if (cls.r_speeds_graph_datamin[stat] > r_refdef.stats[stat]) + cls.r_speeds_graph_datamin[stat] = r_refdef.stats[stat]; + if (cls.r_speeds_graph_datamax[stat] < r_refdef.stats[stat]) + cls.r_speeds_graph_datamax[stat] = r_refdef.stats[stat]; + } + + // force 2D drawing to occur even if r_render is 0 + r_draw2d_force = true; + + // position the graph + width = r_speeds_graph_width.value; + height = r_speeds_graph_height.value; + x = bound(0, r_speeds_graph_x.value, vid_conwidth.value - width); + y = bound(0, r_speeds_graph_y.value, vid_conheight.value - height); + + // fill background with a pattern of gray and black at one second intervals + scalex = (float)width / (float)r_speeds_graph_seconds.value; + for (i = 0;i < r_speeds_graph_seconds.integer + 1;i++) + { + float x1 = x + width - (i + 1) * scalex; + float x2 = x + width - i * scalex; + if (x1 < x) + x1 = x; + if (i & 1) + DrawQ_Fill(x1, y, x2 - x1, height, 0.0f, 0.0f, 0.0f, 0.5f, 0); + else + DrawQ_Fill(x1, y, x2 - x1, height, 0.2f, 0.2f, 0.2f, 0.5f, 0); + } + + // count how many stats match our pattern + stats = 0; + color = 0; + for (color = 0;color < R_SPEEDS_GRAPH_COLORS;color++) + { + // look at all stat names and find ones matching the filter + statindex[color] = -1; + if (!r_speeds_graph_filter[color].string) + continue; + for (stat = 0;stat < r_stat_count;stat++) + if (!strcmp(r_stat_name[stat], r_speeds_graph_filter[color].string)) + break; + if (stat >= r_stat_count) + continue; + // record that this color is this stat for the line drawing loop + statindex[color] = stat; + // draw the legend text in the background of the graph + dpsnprintf(legend, sizeof(legend), "%10i :%s", graph_data[stat * graph_length + graph_current], r_stat_name[stat]); + DrawQ_String(x, y + stats * 8, legend, 0, 8, 8, r_speeds_graph_colors[color][0], r_speeds_graph_colors[color][1], r_speeds_graph_colors[color][2], r_speeds_graph_colors[color][3] * 1.00f, 0, NULL, true, FONT_DEFAULT); + // count how many stats we need to graph in vertex buffer + stats++; + } + + if (stats) + { + // legend text is drawn after the graphs + // render the graph lines, we'll go back and render the legend text later + scalex = (float)width / (1000000.0 * r_speeds_graph_seconds.value); + // get space in a vertex buffer to draw this + numlines = stats * (graph_length - 1); + v = R_Mesh_PrepareVertices_Generic_Lock(numlines * 2); + stats = 0; + for (color = 0;color < R_SPEEDS_GRAPH_COLORS;color++) + { + // look at all stat names and find ones matching the filter + stat = statindex[color]; + if (stat < 0) + continue; + // prefer to graph stats with 0 base, but if they are + // negative we have no choice + range_min = min(cls.r_speeds_graph_datamin[stat], 0); + range_max = cls.r_speeds_graph_datamax[stat]; + // some stats we specifically override the graph scale on + if (stat == r_stat_timedelta) + range_max = 100000; + if (range_max == range_min) + range_max++; + scaley = height / (range_max - range_min); + // generate lines (2 vertices each) + // to deal with incomplete data we walk right to left + data = graph_data + stat * graph_length; + index = graph_current; + sum = 0; + for (i = 0;i < graph_length - 1;) + { + v->vertex3f[0] = x + width - sum * scalex; + if (v->vertex3f[0] < x) + v->vertex3f[0] = x; + v->vertex3f[1] = y + height - (data[index] - range_min) * scaley; + v->vertex3f[2] = 0; + v->color4f[0] = r_speeds_graph_colors[color][0]; + v->color4f[1] = r_speeds_graph_colors[color][1]; + v->color4f[2] = r_speeds_graph_colors[color][2]; + v->color4f[3] = r_speeds_graph_colors[color][3]; + v->texcoord2f[0] = 0; + v->texcoord2f[1] = 0; + v++; + sum += graph_data[r_stat_timedelta * graph_length + index]; + index--; + if (index < 0) + index = graph_length - 1; + i++; + v->vertex3f[0] = x + width - sum * scalex; + if (v->vertex3f[0] < x) + v->vertex3f[0] = x; + v->vertex3f[1] = y + height - (data[index] - range_min) * scaley; + v->vertex3f[2] = 0; + v->color4f[0] = r_speeds_graph_colors[color][0]; + v->color4f[1] = r_speeds_graph_colors[color][1]; + v->color4f[2] = r_speeds_graph_colors[color][2]; + v->color4f[3] = r_speeds_graph_colors[color][3]; + v->texcoord2f[0] = 0; + v->texcoord2f[1] = 0; + v++; + } + } + R_Mesh_PrepareVertices_Generic_Unlock(); + DrawQ_Lines(0.0f, numlines, 0, false); + } + + // return to not drawing anything if r_render is 0 + r_draw2d_force = false; + } + + memset(&r_refdef.stats, 0, sizeof(r_refdef.stats)); +} + +/* +================= +SCR_SizeUp_f + +Keybinding command +================= +*/ +static void SCR_SizeUp_f (void) +{ + Cvar_SetValue ("viewsize",scr_viewsize.value+10); +} + + +/* +================= +SCR_SizeDown_f + +Keybinding command +================= +*/ +static void SCR_SizeDown_f (void) +{ + Cvar_SetValue ("viewsize",scr_viewsize.value-10); +} + +void SCR_CaptureVideo_EndVideo(void); +void CL_Screen_Shutdown(void) +{ + SCR_CaptureVideo_EndVideo(); +} + +void CL_Screen_Init(void) +{ + int i; + Cvar_RegisterVariable (&scr_fov); + Cvar_RegisterVariable (&scr_viewsize); + Cvar_RegisterVariable (&scr_conalpha); + Cvar_RegisterVariable (&scr_conalphafactor); + Cvar_RegisterVariable (&scr_conalpha2factor); + Cvar_RegisterVariable (&scr_conalpha3factor); + Cvar_RegisterVariable (&scr_conscroll_x); + Cvar_RegisterVariable (&scr_conscroll_y); + Cvar_RegisterVariable (&scr_conscroll2_x); + Cvar_RegisterVariable (&scr_conscroll2_y); + Cvar_RegisterVariable (&scr_conscroll3_x); + Cvar_RegisterVariable (&scr_conscroll3_y); + Cvar_RegisterVariable (&scr_conbrightness); + Cvar_RegisterVariable (&scr_conforcewhiledisconnected); + Cvar_RegisterVariable (&scr_menuforcewhiledisconnected); + Cvar_RegisterVariable (&scr_loadingscreen_background); + Cvar_RegisterVariable (&scr_loadingscreen_scale); + Cvar_RegisterVariable (&scr_loadingscreen_scale_base); + Cvar_RegisterVariable (&scr_loadingscreen_scale_limit); + Cvar_RegisterVariable (&scr_loadingscreen_picture); + Cvar_RegisterVariable (&scr_loadingscreen_count); + Cvar_RegisterVariable (&scr_loadingscreen_firstforstartup); + Cvar_RegisterVariable (&scr_loadingscreen_barcolor); + Cvar_RegisterVariable (&scr_loadingscreen_barheight); + Cvar_RegisterVariable (&scr_loadingscreen_maxfps); + Cvar_RegisterVariable (&scr_infobar_height); + Cvar_RegisterVariable (&scr_showram); + Cvar_RegisterVariable (&scr_showturtle); + Cvar_RegisterVariable (&scr_showpause); + Cvar_RegisterVariable (&scr_showbrand); + Cvar_RegisterVariable (&scr_centertime); + Cvar_RegisterVariable (&scr_printspeed); + Cvar_RegisterVariable (&vid_conwidth); + Cvar_RegisterVariable (&vid_conheight); + Cvar_RegisterVariable (&vid_pixelheight); + Cvar_RegisterVariable (&scr_screenshot_jpeg); + Cvar_RegisterVariable (&scr_screenshot_jpeg_quality); + Cvar_RegisterVariable (&scr_screenshot_png); + Cvar_RegisterVariable (&scr_screenshot_gammaboost); + Cvar_RegisterVariable (&scr_screenshot_hwgamma); + Cvar_RegisterVariable (&scr_screenshot_name_in_mapdir); + Cvar_RegisterVariable (&scr_screenshot_alpha); + Cvar_RegisterVariable (&scr_screenshot_timestamp); + Cvar_RegisterVariable (&cl_capturevideo); + Cvar_RegisterVariable (&cl_capturevideo_demo_stop); + Cvar_RegisterVariable (&cl_capturevideo_printfps); + Cvar_RegisterVariable (&cl_capturevideo_width); + Cvar_RegisterVariable (&cl_capturevideo_height); + Cvar_RegisterVariable (&cl_capturevideo_realtime); + Cvar_RegisterVariable (&cl_capturevideo_fps); + Cvar_RegisterVariable (&cl_capturevideo_nameformat); + Cvar_RegisterVariable (&cl_capturevideo_number); + Cvar_RegisterVariable (&cl_capturevideo_ogg); + Cvar_RegisterVariable (&cl_capturevideo_framestep); + Cvar_RegisterVariable (&r_letterbox); + Cvar_RegisterVariable(&r_stereo_angle); + Cvar_RegisterVariable(&scr_stipple); + Cvar_RegisterVariable(&scr_refresh); + Cvar_RegisterVariable(&shownetgraph); + Cvar_RegisterVariable(&cl_demo_mousegrab); + Cvar_RegisterVariable(&timedemo_screenshotframelist); + Cvar_RegisterVariable(&vid_touchscreen_outlinealpha); + Cvar_RegisterVariable(&vid_touchscreen_overlayalpha); + Cvar_RegisterVariable(&r_speeds_graph); + for (i = 0;i < (int)(sizeof(r_speeds_graph_filter)/sizeof(r_speeds_graph_filter[0]));i++) + Cvar_RegisterVariable(&r_speeds_graph_filter[i]); + Cvar_RegisterVariable(&r_speeds_graph_length); + Cvar_RegisterVariable(&r_speeds_graph_seconds); + Cvar_RegisterVariable(&r_speeds_graph_x); + Cvar_RegisterVariable(&r_speeds_graph_y); + Cvar_RegisterVariable(&r_speeds_graph_width); + Cvar_RegisterVariable(&r_speeds_graph_height); + + // if we want no console, turn it off here too + if (COM_CheckParm ("-noconsole")) + Cvar_SetQuick(&scr_conforcewhiledisconnected, "0"); + + Cmd_AddCommand ("sizeup",SCR_SizeUp_f, "increase view size (increases viewsize cvar)"); + Cmd_AddCommand ("sizedown",SCR_SizeDown_f, "decrease view size (decreases viewsize cvar)"); + Cmd_AddCommand ("screenshot",SCR_ScreenShot_f, "takes a screenshot of the next rendered frame"); + Cmd_AddCommand ("envmap", R_Envmap_f, "render a cubemap (skybox) of the current scene"); + Cmd_AddCommand ("infobar", SCR_InfoBar_f, "display a text in the infobar (usage: infobar expiretime string)"); + + SCR_CaptureVideo_Ogg_Init(); + + scr_initialized = true; +} + +/* +================== +SCR_ScreenShot_f +================== +*/ +void SCR_ScreenShot_f (void) +{ + static int shotnumber; + static char old_prefix_name[MAX_QPATH]; + char prefix_name[MAX_QPATH]; + char filename[MAX_QPATH]; + unsigned char *buffer1; + unsigned char *buffer2; + qboolean jpeg = (scr_screenshot_jpeg.integer != 0); + qboolean png = (scr_screenshot_png.integer != 0) && !jpeg; + char vabuf[1024]; + + if (Cmd_Argc() == 2) + { + const char *ext; + strlcpy(filename, Cmd_Argv(1), sizeof(filename)); + ext = FS_FileExtension(filename); + if (!strcasecmp(ext, "jpg")) + { + jpeg = true; + png = false; + } + else if (!strcasecmp(ext, "tga")) + { + jpeg = false; + png = false; + } + else if (!strcasecmp(ext, "png")) + { + jpeg = false; + png = true; + } + else + { + Con_Printf("screenshot: supplied filename must end in .jpg or .tga or .png\n"); + return; + } + } + else if (scr_screenshot_timestamp.integer) + { + int shotnumber100; + + // TODO maybe make capturevideo and screenshot use similar name patterns? + if (scr_screenshot_name_in_mapdir.integer && cl.worldbasename[0]) + dpsnprintf(prefix_name, sizeof(prefix_name), "%s/%s%s", cl.worldbasename, scr_screenshot_name.string, Sys_TimeString("%Y%m%d%H%M%S")); + else + dpsnprintf(prefix_name, sizeof(prefix_name), "%s%s", scr_screenshot_name.string, Sys_TimeString("%Y%m%d%H%M%S")); + + // find a file name to save it to + for (shotnumber100 = 0;shotnumber100 < 100;shotnumber100++) + if (!FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s-%02d.tga", fs_gamedir, prefix_name, shotnumber100)) + && !FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s-%02d.jpg", fs_gamedir, prefix_name, shotnumber100)) + && !FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s-%02d.png", fs_gamedir, prefix_name, shotnumber100))) + break; + if (shotnumber100 >= 100) + { + Con_Print("Couldn't create the image file - already 100 shots taken this second!\n"); + return; + } + + dpsnprintf(filename, sizeof(filename), "screenshots/%s-%02d.%s", prefix_name, shotnumber100, jpeg ? "jpg" : png ? "png" : "tga"); + } + else + { + // TODO maybe make capturevideo and screenshot use similar name patterns? + if (scr_screenshot_name_in_mapdir.integer && cl.worldbasename[0]) + dpsnprintf(prefix_name, sizeof(prefix_name), "%s/%s", cl.worldbasename, Sys_TimeString(scr_screenshot_name.string)); + else + dpsnprintf(prefix_name, sizeof(prefix_name), "%s", Sys_TimeString(scr_screenshot_name.string)); + + // if prefix changed, gamedir or map changed, reset the shotnumber so + // we scan again + // FIXME: should probably do this whenever FS_Rescan or something like that occurs? + if (strcmp(old_prefix_name, prefix_name)) + { + dpsnprintf(old_prefix_name, sizeof(old_prefix_name), "%s", prefix_name ); + shotnumber = 0; + } + + // find a file name to save it to + for (;shotnumber < 1000000;shotnumber++) + if (!FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s%06d.tga", fs_gamedir, prefix_name, shotnumber)) + && !FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s%06d.jpg", fs_gamedir, prefix_name, shotnumber)) + && !FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s%06d.png", fs_gamedir, prefix_name, shotnumber))) + break; + if (shotnumber >= 1000000) + { + Con_Print("Couldn't create the image file - you already have 1000000 screenshots!\n"); + return; + } + + dpsnprintf(filename, sizeof(filename), "screenshots/%s%06d.%s", prefix_name, shotnumber, jpeg ? "jpg" : png ? "png" : "tga"); + + shotnumber++; + } + + buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); + buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * (scr_screenshot_alpha.integer ? 4 : 3)); + + if (SCR_ScreenShot (filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, jpeg, png, true, scr_screenshot_alpha.integer != 0)) + Con_Printf("Wrote %s\n", filename); + else + { + Con_Printf("Unable to write %s\n", filename); + if(jpeg || png) + { + if(SCR_ScreenShot (filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, false, false, true, scr_screenshot_alpha.integer != 0)) + { + strlcpy(filename + strlen(filename) - 3, "tga", 4); + Con_Printf("Wrote %s\n", filename); + } + } + } + + Mem_Free (buffer1); + Mem_Free (buffer2); +} + +static void SCR_CaptureVideo_BeginVideo(void) +{ + double r, g, b; + unsigned int i; + int width = cl_capturevideo_width.integer, height = cl_capturevideo_height.integer; + if (cls.capturevideo.active) + return; + memset(&cls.capturevideo, 0, sizeof(cls.capturevideo)); + // soundrate is figured out on the first SoundFrame + + if(width == 0 && height != 0) + width = (int) (height * (double)vid.width / ((double)vid.height * vid_pixelheight.value)); // keep aspect + if(width != 0 && height == 0) + height = (int) (width * ((double)vid.height * vid_pixelheight.value) / (double)vid.width); // keep aspect + + if(width < 2 || width > vid.width) // can't scale up + width = vid.width; + if(height < 2 || height > vid.height) // can't scale up + height = vid.height; + + // ensure it's all even; if not, scale down a little + if(width % 1) + --width; + if(height % 1) + --height; + + cls.capturevideo.width = width; + cls.capturevideo.height = height; + cls.capturevideo.active = true; + cls.capturevideo.framerate = bound(1, cl_capturevideo_fps.value, 1001) * bound(1, cl_capturevideo_framestep.integer, 64); + cls.capturevideo.framestep = cl_capturevideo_framestep.integer; + cls.capturevideo.soundrate = S_GetSoundRate(); + cls.capturevideo.soundchannels = S_GetSoundChannels(); + cls.capturevideo.startrealtime = realtime; + cls.capturevideo.frame = cls.capturevideo.lastfpsframe = 0; + cls.capturevideo.starttime = cls.capturevideo.lastfpstime = realtime; + cls.capturevideo.soundsampleframe = 0; + cls.capturevideo.realtime = cl_capturevideo_realtime.integer != 0; + cls.capturevideo.screenbuffer = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); + cls.capturevideo.outbuffer = (unsigned char *)Mem_Alloc(tempmempool, width * height * (4+4) + 18); + dpsnprintf(cls.capturevideo.basename, sizeof(cls.capturevideo.basename), "video/%s%03i", Sys_TimeString(cl_capturevideo_nameformat.string), cl_capturevideo_number.integer); + Cvar_SetValueQuick(&cl_capturevideo_number, cl_capturevideo_number.integer + 1); + + /* + for (i = 0;i < 256;i++) + { + unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255); + cls.capturevideo.rgbgammatable[0][i] = j; + cls.capturevideo.rgbgammatable[1][i] = j; + cls.capturevideo.rgbgammatable[2][i] = j; + } + */ +/* +R = Y + 1.4075 * (Cr - 128); +G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128); +B = Y + 1.7790 * (Cb - 128); +Y = R * .299 + G * .587 + B * .114; +Cb = R * -.169 + G * -.332 + B * .500 + 128.; +Cr = R * .500 + G * -.419 + B * -.0813 + 128.; +*/ + + if(WANT_SCREENSHOT_HWGAMMA) + { + VID_BuildGammaTables(&cls.capturevideo.vidramp[0], 256); + } + else + { + // identity gamma table + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp, 256); + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp + 256, 256); + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp + 256*2, 256); + } + if(scr_screenshot_gammaboost.value != 1) + { + double igamma = 1 / scr_screenshot_gammaboost.value; + for (i = 0;i < 256 * 3;i++) + cls.capturevideo.vidramp[i] = (unsigned short) (0.5 + pow(cls.capturevideo.vidramp[i] * (1.0 / 65535.0), igamma) * 65535.0); + } + + for (i = 0;i < 256;i++) + { + r = 255*cls.capturevideo.vidramp[i]/65535.0; + g = 255*cls.capturevideo.vidramp[i+256]/65535.0; + b = 255*cls.capturevideo.vidramp[i+512]/65535.0; + // NOTE: we have to round DOWN here, or integer overflows happen. Sorry for slightly wrong looking colors sometimes... + // Y weights from RGB + cls.capturevideo.rgbtoyuvscaletable[0][0][i] = (short)(r * 0.299); + cls.capturevideo.rgbtoyuvscaletable[0][1][i] = (short)(g * 0.587); + cls.capturevideo.rgbtoyuvscaletable[0][2][i] = (short)(b * 0.114); + // Cb weights from RGB + cls.capturevideo.rgbtoyuvscaletable[1][0][i] = (short)(r * -0.169); + cls.capturevideo.rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332); + cls.capturevideo.rgbtoyuvscaletable[1][2][i] = (short)(b * 0.500); + // Cr weights from RGB + cls.capturevideo.rgbtoyuvscaletable[2][0][i] = (short)(r * 0.500); + cls.capturevideo.rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419); + cls.capturevideo.rgbtoyuvscaletable[2][2][i] = (short)(b * -0.0813); + // range reduction of YCbCr to valid signal range + cls.capturevideo.yuvnormalizetable[0][i] = 16 + i * (236-16) / 256; + cls.capturevideo.yuvnormalizetable[1][i] = 16 + i * (240-16) / 256; + cls.capturevideo.yuvnormalizetable[2][i] = 16 + i * (240-16) / 256; + } + + if (cl_capturevideo_ogg.integer) + { + if(SCR_CaptureVideo_Ogg_Available()) + { + SCR_CaptureVideo_Ogg_BeginVideo(); + return; + } + else + Con_Print("cl_capturevideo_ogg: libraries not available. Capturing in AVI instead.\n"); + } + + SCR_CaptureVideo_Avi_BeginVideo(); +} + +void SCR_CaptureVideo_EndVideo(void) +{ + if (!cls.capturevideo.active) + return; + cls.capturevideo.active = false; + + Con_Printf("Finishing capture of %s.%s (%d frames, %d audio frames)\n", cls.capturevideo.basename, cls.capturevideo.formatextension, cls.capturevideo.frame, cls.capturevideo.soundsampleframe); + + if (cls.capturevideo.videofile) + { + cls.capturevideo.endvideo(); + } + + if (cls.capturevideo.screenbuffer) + { + Mem_Free (cls.capturevideo.screenbuffer); + cls.capturevideo.screenbuffer = NULL; + } + + if (cls.capturevideo.outbuffer) + { + Mem_Free (cls.capturevideo.outbuffer); + cls.capturevideo.outbuffer = NULL; + } + + memset(&cls.capturevideo, 0, sizeof(cls.capturevideo)); +} + +static void SCR_ScaleDownBGRA(unsigned char *in, int inw, int inh, unsigned char *out, int outw, int outh) +{ + // TODO optimize this function + + int x, y; + float area; + + // memcpy is faster than me + if(inw == outw && inh == outh) + { + memcpy(out, in, 4 * inw * inh); + return; + } + + // otherwise: a box filter + area = (float)outw * (float)outh / (float)inw / (float)inh; + for(y = 0; y < outh; ++y) + { + float iny0 = y / (float)outh * inh; int iny0_i = (int) floor(iny0); + float iny1 = (y+1) / (float)outh * inh; int iny1_i = (int) ceil(iny1); + for(x = 0; x < outw; ++x) + { + float inx0 = x / (float)outw * inw; int inx0_i = (int) floor(inx0); + float inx1 = (x+1) / (float)outw * inw; int inx1_i = (int) ceil(inx1); + float r = 0, g = 0, b = 0, alpha = 0; + int xx, yy; + + for(yy = iny0_i; yy < iny1_i; ++yy) + { + float ya = min(yy+1, iny1) - max(iny0, yy); + for(xx = inx0_i; xx < inx1_i; ++xx) + { + float a = ya * (min(xx+1, inx1) - max(inx0, xx)); + r += a * in[4*(xx + inw * yy)+0]; + g += a * in[4*(xx + inw * yy)+1]; + b += a * in[4*(xx + inw * yy)+2]; + alpha += a * in[4*(xx + inw * yy)+3]; + } + } + + out[4*(x + outw * y)+0] = (unsigned char) (r * area); + out[4*(x + outw * y)+1] = (unsigned char) (g * area); + out[4*(x + outw * y)+2] = (unsigned char) (b * area); + out[4*(x + outw * y)+3] = (unsigned char) (alpha * area); + } + } +} + +static void SCR_CaptureVideo_VideoFrame(int newframestepframenum) +{ + int x = 0, y = 0; + int width = cls.capturevideo.width, height = cls.capturevideo.height; + + if(newframestepframenum == cls.capturevideo.framestepframe) + return; + + CHECKGLERROR + // speed is critical here, so do saving as directly as possible + + GL_ReadPixelsBGRA(x, y, vid.width, vid.height, cls.capturevideo.screenbuffer); + + SCR_ScaleDownBGRA (cls.capturevideo.screenbuffer, vid.width, vid.height, cls.capturevideo.outbuffer, width, height); + + cls.capturevideo.videoframes(newframestepframenum - cls.capturevideo.framestepframe); + cls.capturevideo.framestepframe = newframestepframenum; + + if(cl_capturevideo_printfps.integer) + { + char buf[80]; + double t = realtime; + if(t > cls.capturevideo.lastfpstime + 1) + { + double fps1 = (cls.capturevideo.frame - cls.capturevideo.lastfpsframe) / (t - cls.capturevideo.lastfpstime + 0.0000001); + double fps = (cls.capturevideo.frame ) / (t - cls.capturevideo.starttime + 0.0000001); + dpsnprintf(buf, sizeof(buf), "capturevideo: (%.1fs) last second %.3ffps, total %.3ffps\n", cls.capturevideo.frame / cls.capturevideo.framerate, fps1, fps); + Sys_PrintToTerminal(buf); + cls.capturevideo.lastfpstime = t; + cls.capturevideo.lastfpsframe = cls.capturevideo.frame; + } + } +} + +void SCR_CaptureVideo_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length) +{ + cls.capturevideo.soundsampleframe += length; + cls.capturevideo.soundframe(paintbuffer, length); +} + +static void SCR_CaptureVideo(void) +{ + int newframenum; + if (cl_capturevideo.integer) + { + if (!cls.capturevideo.active) + SCR_CaptureVideo_BeginVideo(); + if (cls.capturevideo.framerate != cl_capturevideo_fps.value * cl_capturevideo_framestep.integer) + { + Con_Printf("You can not change the video framerate while recording a video.\n"); + Cvar_SetValueQuick(&cl_capturevideo_fps, cls.capturevideo.framerate / (double) cl_capturevideo_framestep.integer); + } + // for AVI saving we have to make sure that sound is saved before video + if (cls.capturevideo.soundrate && !cls.capturevideo.soundsampleframe) + return; + if (cls.capturevideo.realtime) + { + // preserve sound sync by duplicating frames when running slow + newframenum = (int)((realtime - cls.capturevideo.startrealtime) * cls.capturevideo.framerate); + } + else + newframenum = cls.capturevideo.frame + 1; + // if falling behind more than one second, stop + if (newframenum - cls.capturevideo.frame > 60 * (int)ceil(cls.capturevideo.framerate)) + { + Cvar_SetValueQuick(&cl_capturevideo, 0); + Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo.frame); + SCR_CaptureVideo_EndVideo(); + return; + } + // write frames + SCR_CaptureVideo_VideoFrame(newframenum / cls.capturevideo.framestep); + cls.capturevideo.frame = newframenum; + if (cls.capturevideo.error) + { + Cvar_SetValueQuick(&cl_capturevideo, 0); + Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo.frame); + SCR_CaptureVideo_EndVideo(); + } + } + else if (cls.capturevideo.active) + SCR_CaptureVideo_EndVideo(); +} + +/* +=============== +R_Envmap_f + +Grab six views for environment mapping tests +=============== +*/ +struct envmapinfo_s +{ + float angles[3]; + const char *name; + qboolean flipx, flipy, flipdiagonaly; +} +envmapinfo[12] = +{ + {{ 0, 0, 0}, "rt", false, false, false}, + {{ 0, 270, 0}, "ft", false, false, false}, + {{ 0, 180, 0}, "lf", false, false, false}, + {{ 0, 90, 0}, "bk", false, false, false}, + {{-90, 180, 0}, "up", true, true, false}, + {{ 90, 180, 0}, "dn", true, true, false}, + + {{ 0, 0, 0}, "px", true, true, true}, + {{ 0, 90, 0}, "py", false, true, false}, + {{ 0, 180, 0}, "nx", false, false, true}, + {{ 0, 270, 0}, "ny", true, false, false}, + {{-90, 180, 0}, "pz", false, false, true}, + {{ 90, 180, 0}, "nz", false, false, true} +}; + +static void R_Envmap_f (void) +{ + int j, size; + char filename[MAX_QPATH], basename[MAX_QPATH]; + unsigned char *buffer1; + unsigned char *buffer2; + + if (Cmd_Argc() != 3) + { + Con_Print("envmap : save out 6 cubic environment map images, usable with loadsky, note that size must one of 128, 256, 512, or 1024 and can't be bigger than your current resolution\n"); + return; + } + + strlcpy (basename, Cmd_Argv(1), sizeof (basename)); + size = atoi(Cmd_Argv(2)); + if (size != 128 && size != 256 && size != 512 && size != 1024) + { + Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n"); + return; + } + if (size > vid.width || size > vid.height) + { + Con_Print("envmap: your resolution is not big enough to render that size\n"); + return; + } + + r_refdef.envmap = true; + + R_UpdateVariables(); + + r_refdef.view.x = 0; + r_refdef.view.y = 0; + r_refdef.view.z = 0; + r_refdef.view.width = size; + r_refdef.view.height = size; + r_refdef.view.depth = 1; + r_refdef.view.useperspective = true; + r_refdef.view.isoverlay = false; + + r_refdef.view.frustum_x = 1; // tan(45 * M_PI / 180.0); + r_refdef.view.frustum_y = 1; // tan(45 * M_PI / 180.0); + r_refdef.view.ortho_x = 90; // abused as angle by VM_CL_R_SetView + r_refdef.view.ortho_y = 90; // abused as angle by VM_CL_R_SetView + + buffer1 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 4); + buffer2 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3); + + for (j = 0;j < 12;j++) + { + dpsnprintf(filename, sizeof(filename), "env/%s%s.tga", basename, envmapinfo[j].name); + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], envmapinfo[j].angles[0], envmapinfo[j].angles[1], envmapinfo[j].angles[2], 1); + r_refdef.view.quality = 1; + r_refdef.view.clear = true; + R_Mesh_Start(); + R_RenderView(0.0); + R_Mesh_Finish(); + SCR_ScreenShot(filename, buffer1, buffer2, 0, vid.height - (r_refdef.view.y + r_refdef.view.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false, false, false); + } + + Mem_Free (buffer1); + Mem_Free (buffer2); + + r_refdef.envmap = false; +} + +//============================================================================= + +void SHOWLMP_decodehide(void) +{ + int i; + char *lmplabel; + lmplabel = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + for (i = 0;i < cl.num_showlmps;i++) + if (cl.showlmps[i].isactive && strcmp(cl.showlmps[i].label, lmplabel) == 0) + { + cl.showlmps[i].isactive = false; + return; + } +} + +void SHOWLMP_decodeshow(void) +{ + int k; + char lmplabel[256], picname[256]; + float x, y; + strlcpy (lmplabel,MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (lmplabel)); + strlcpy (picname, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (picname)); + if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk + { + x = MSG_ReadByte(&cl_message); + y = MSG_ReadByte(&cl_message); + } + else + { + x = MSG_ReadShort(&cl_message); + y = MSG_ReadShort(&cl_message); + } + if (!cl.showlmps || cl.num_showlmps >= cl.max_showlmps) + { + showlmp_t *oldshowlmps = cl.showlmps; + cl.max_showlmps += 16; + cl.showlmps = (showlmp_t *) Mem_Alloc(cls.levelmempool, cl.max_showlmps * sizeof(showlmp_t)); + if (cl.num_showlmps) + memcpy(cl.showlmps, oldshowlmps, cl.num_showlmps * sizeof(showlmp_t)); + if (oldshowlmps) + Mem_Free(oldshowlmps); + } + for (k = 0;k < cl.max_showlmps;k++) + if (cl.showlmps[k].isactive && !strcmp(cl.showlmps[k].label, lmplabel)) + break; + if (k == cl.max_showlmps) + for (k = 0;k < cl.max_showlmps;k++) + if (!cl.showlmps[k].isactive) + break; + cl.showlmps[k].isactive = true; + strlcpy (cl.showlmps[k].label, lmplabel, sizeof (cl.showlmps[k].label)); + strlcpy (cl.showlmps[k].pic, picname, sizeof (cl.showlmps[k].pic)); + cl.showlmps[k].x = x; + cl.showlmps[k].y = y; + cl.num_showlmps = max(cl.num_showlmps, k + 1); +} + +void SHOWLMP_drawall(void) +{ + int i; + for (i = 0;i < cl.num_showlmps;i++) + if (cl.showlmps[i].isactive) + DrawQ_Pic(cl.showlmps[i].x, cl.showlmps[i].y, Draw_CachePic_Flags (cl.showlmps[i].pic, CACHEPICFLAG_NOTPERSISTENT), 0, 0, 1, 1, 1, 1, 0); +} + +/* +============================================================================== + + SCREEN SHOTS + +============================================================================== +*/ + +// buffer1: 4*w*h +// buffer2: 3*w*h (or 4*w*h if screenshotting alpha too) +qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean png, qboolean gammacorrect, qboolean keep_alpha) +{ + int indices[4] = {0,1,2,3}; // BGRA + qboolean ret; + + GL_ReadPixelsBGRA(x, y, width, height, buffer1); + + if(gammacorrect && (scr_screenshot_gammaboost.value != 1 || WANT_SCREENSHOT_HWGAMMA)) + { + int i; + double igamma = 1.0 / scr_screenshot_gammaboost.value; + unsigned short vidramp[256 * 3]; + if(WANT_SCREENSHOT_HWGAMMA) + { + VID_BuildGammaTables(&vidramp[0], 256); + } + else + { + // identity gamma table + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp, 256); + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp + 256, 256); + BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp + 256*2, 256); + } + if(scr_screenshot_gammaboost.value != 1) + { + for (i = 0;i < 256 * 3;i++) + vidramp[i] = (unsigned short) (0.5 + pow(vidramp[i] * (1.0 / 65535.0), igamma) * 65535.0); + } + for (i = 0;i < width*height*4;i += 4) + { + buffer1[i] = (unsigned char) (vidramp[buffer1[i] + 512] * 255.0 / 65535.0 + 0.5); // B + buffer1[i+1] = (unsigned char) (vidramp[buffer1[i+1] + 256] * 255.0 / 65535.0 + 0.5); // G + buffer1[i+2] = (unsigned char) (vidramp[buffer1[i+2]] * 255.0 / 65535.0 + 0.5); // R + // A + } + } + + if(keep_alpha && !jpeg) + { + if(!png) + flipy = !flipy; // TGA: not preflipped + Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 4, 4, indices); + if (png) + ret = PNG_SaveImage_preflipped (filename, width, height, true, buffer2); + else + ret = Image_WriteTGABGRA(filename, width, height, buffer2); + } + else + { + if(jpeg) + { + indices[0] = 2; + indices[2] = 0; // RGB + } + Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 3, 4, indices); + if (jpeg) + ret = JPEG_SaveImage_preflipped (filename, width, height, buffer2); + else if (png) + ret = PNG_SaveImage_preflipped (filename, width, height, false, buffer2); + else + ret = Image_WriteTGABGR_preflipped (filename, width, height, buffer2); + } + + return ret; +} + +//============================================================================= + +int scr_numtouchscreenareas; +scr_touchscreenarea_t scr_touchscreenareas[16]; + +static void SCR_DrawTouchscreenOverlay(void) +{ + int i; + scr_touchscreenarea_t *a; + cachepic_t *pic; + for (i = 0, a = scr_touchscreenareas;i < scr_numtouchscreenareas;i++, a++) + { + if (vid_touchscreen_outlinealpha.value > 0 && a->rect[0] >= 0 && a->rect[1] >= 0 && a->rect[2] >= 4 && a->rect[3] >= 4) + { + DrawQ_Fill(a->rect[0] + 2, a->rect[1] , a->rect[2] - 4, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] + 1, a->rect[1] + 1, a->rect[2] - 2, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] , a->rect[1] + 2, 2 , a->rect[3] - 2, 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] + a->rect[2] - 2, a->rect[1] + 2, 2 , a->rect[3] - 2, 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] + 1, a->rect[1] + a->rect[3] - 2, a->rect[2] - 2, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + DrawQ_Fill(a->rect[0] + 2, a->rect[1] + a->rect[3] - 1, a->rect[2] - 4, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); + } + pic = a->pic ? Draw_CachePic(a->pic) : NULL; + if (pic && pic->tex != r_texture_notexture) + DrawQ_Pic(a->rect[0], a->rect[1], Draw_CachePic(a->pic), a->rect[2], a->rect[3], 1, 1, 1, vid_touchscreen_overlayalpha.value * (0.5f + 0.5f * a->active), 0); + } +} + +void R_ClearScreen(qboolean fogcolor) +{ + float clearcolor[4]; + // clear to black + Vector4Clear(clearcolor); + if (fogcolor && r_fog_clear.integer) + { + R_UpdateFog(); + VectorCopy(r_refdef.fogcolor, clearcolor); + } + // clear depth is 1.0 + // LordHavoc: we use a stencil centered around 128 instead of 0, + // to avoid clamping interfering with strange shadow volume + // drawing orders + // clear the screen + GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (vid.stencil ? GL_STENCIL_BUFFER_BIT : 0), clearcolor, 1.0f, 128); +} + +extern int r_stereo_side; + +/*static*/ void SCR_DrawScreen (int x, int y) +{ + Draw_Frame(); + + R_Mesh_Start(); + + R_UpdateVariables(); + + // Quake uses clockwise winding, so these are swapped + r_refdef.view.cullface_front = GL_BACK; + r_refdef.view.cullface_back = GL_FRONT; + + if (cls.signon == SIGNONS) + { + float size; + + size = scr_viewsize.value * (1.0 / 100.0); + size = min(size, 1); + + r_refdef.view.width = (int)(vid.width * size); + r_refdef.view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100)); + r_refdef.view.depth = 1; + r_refdef.view.x = (int)((vid.width - r_refdef.view.width)/2) + x; + r_refdef.view.y = (int)((vid.height - r_refdef.view.height)/2) + y; + r_refdef.view.z = 0; + + // LordHavoc: viewzoom (zoom in for sniper rifles, etc) + // LordHavoc: this is designed to produce widescreen fov values + // when the screen is wider than 4/3 width/height aspect, to do + // this it simply assumes the requested fov is the vertical fov + // for a 4x3 display, if the ratio is not 4x3 this makes the fov + // higher/lower according to the ratio + r_refdef.view.useperspective = true; + r_refdef.view.frustum_y = tan(scr_fov.value * M_PI / 360.0) * (3.0/4.0) * cl.viewzoom; + r_refdef.view.frustum_x = r_refdef.view.frustum_y * (float)r_refdef.view.width / (float)r_refdef.view.height / vid_pixelheight.value; + + r_refdef.view.frustum_x *= r_refdef.frustumscale_x; + r_refdef.view.frustum_y *= r_refdef.frustumscale_y; + r_refdef.view.ortho_x = atan(r_refdef.view.frustum_x) * (360.0 / M_PI); // abused as angle by VM_CL_R_SetView + r_refdef.view.ortho_y = atan(r_refdef.view.frustum_y) * (360.0 / M_PI); // abused as angle by VM_CL_R_SetView + + if(!CL_VM_UpdateView(r_stereo_side ? 0.0 : max(0.0, cl.time - cl.oldtime))) + R_RenderView(); + } + + r_refdef.view.width = vid.width; + r_refdef.view.height = vid.height; + r_refdef.view.depth = 1; + r_refdef.view.x = x; + r_refdef.view.y = y; + r_refdef.view.z = 0; + r_refdef.view.useperspective = false; + + if (cls.timedemo && cls.td_frames > 0 && timedemo_screenshotframelist.string && timedemo_screenshotframelist.string[0]) + { + const char *t; + int framenum; + t = timedemo_screenshotframelist.string; + while (*t) + { + while (*t == ' ') + t++; + if (!*t) + break; + framenum = atof(t); + if (framenum == cls.td_frames) + break; + while (*t && *t != ' ') + t++; + } + if (*t) + { + // we need to take a screenshot of this frame... + char filename[MAX_QPATH]; + unsigned char *buffer1; + unsigned char *buffer2; + dpsnprintf(filename, sizeof(filename), "timedemoscreenshots/%s%06d.tga", cls.demoname, cls.td_frames); + buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); + buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3); + SCR_ScreenShot(filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, false, false, true, false); + Mem_Free(buffer1); + Mem_Free(buffer2); + } + } + + // draw 2D stuff + if(!scr_con_current && !(key_consoleactive & KEY_CONSOLEACTIVE_FORCED)) + if ((key_dest == key_game || key_dest == key_message) && !r_letterbox.value) + Con_DrawNotify (); // only draw notify in game + + if (cls.signon == SIGNONS) + { + SCR_DrawNet (); + SCR_DrawTurtle (); + SCR_DrawPause (); + if (!r_letterbox.value) + Sbar_Draw(); + SHOWLMP_drawall(); + SCR_CheckDrawCenterString(); + } + SCR_DrawNetGraph (); + MR_Draw(); + CL_DrawVideo(); + R_Shadow_EditLights_DrawSelectedLightProperties(); + + SCR_DrawConsole(); + + SCR_DrawBrand(); + + SCR_DrawInfobar(); + + // No need for this + //SCR_DrawTouchscreenOverlay(); + + if (r_timereport_active) + R_TimeReport("2d"); + + R_TimeReport_EndFrame(); + R_TimeReport_BeginFrame(); + Sbar_ShowFPS(); + + DrawQ_Finish(); + + R_DrawGamma(); + + R_Mesh_Finish(); +} + +typedef struct loadingscreenstack_s +{ + struct loadingscreenstack_s *prev; + char msg[MAX_QPATH]; + float absolute_loading_amount_min; // this corresponds to relative completion 0 of this item + float absolute_loading_amount_len; // this corresponds to relative completion 1 of this item + float relative_completion; // 0 .. 1 +} +loadingscreenstack_t; +static loadingscreenstack_t *loadingscreenstack = NULL; +static qboolean loadingscreendone = false; +static qboolean loadingscreencleared = false; +static float loadingscreenheight = 0; +rtexture_t *loadingscreentexture = NULL; +static float loadingscreentexture_vertex3f[12]; +static float loadingscreentexture_texcoord2f[8]; +static int loadingscreenpic_number = 0; + +static void SCR_ClearLoadingScreenTexture(void) +{ + if(loadingscreentexture) + R_FreeTexture(loadingscreentexture); + loadingscreentexture = NULL; +} + +extern rtexturepool_t *r_main_texturepool; +static void SCR_SetLoadingScreenTexture(void) +{ + int w, h; + float loadingscreentexture_w; + float loadingscreentexture_h; + + SCR_ClearLoadingScreenTexture(); + + if (vid.support.arb_texture_non_power_of_two) + { + w = vid.width; h = vid.height; + loadingscreentexture_w = loadingscreentexture_h = 1; + } + else + { + w = CeilPowerOf2(vid.width); h = CeilPowerOf2(vid.height); + loadingscreentexture_w = vid.width / (float) w; + loadingscreentexture_h = vid.height / (float) h; + } + + loadingscreentexture = R_LoadTexture2D(r_main_texturepool, "loadingscreentexture", w, h, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCENEAREST | TEXF_CLAMP, -1, NULL); + R_Mesh_CopyToTexture(loadingscreentexture, 0, 0, 0, 0, vid.width, vid.height); + + loadingscreentexture_vertex3f[2] = loadingscreentexture_vertex3f[5] = loadingscreentexture_vertex3f[8] = loadingscreentexture_vertex3f[11] = 0; + loadingscreentexture_vertex3f[0] = loadingscreentexture_vertex3f[9] = 0; + loadingscreentexture_vertex3f[1] = loadingscreentexture_vertex3f[4] = 0; + loadingscreentexture_vertex3f[3] = loadingscreentexture_vertex3f[6] = vid_conwidth.integer; + loadingscreentexture_vertex3f[7] = loadingscreentexture_vertex3f[10] = vid_conheight.integer; + loadingscreentexture_texcoord2f[0] = 0;loadingscreentexture_texcoord2f[1] = loadingscreentexture_h; + loadingscreentexture_texcoord2f[2] = loadingscreentexture_w;loadingscreentexture_texcoord2f[3] = loadingscreentexture_h; + loadingscreentexture_texcoord2f[4] = loadingscreentexture_w;loadingscreentexture_texcoord2f[5] = 0; + loadingscreentexture_texcoord2f[6] = 0;loadingscreentexture_texcoord2f[7] = 0; +} + +void SCR_UpdateLoadingScreenIfShown(void) +{ + if(loadingscreendone) + SCR_UpdateLoadingScreen(loadingscreencleared, false); +} + +void SCR_PushLoadingScreen (qboolean redraw, const char *msg, float len_in_parent) +{ + loadingscreenstack_t *s = (loadingscreenstack_t *) Z_Malloc(sizeof(loadingscreenstack_t)); + s->prev = loadingscreenstack; + loadingscreenstack = s; + + strlcpy(s->msg, msg, sizeof(s->msg)); + s->relative_completion = 0; + + if(s->prev) + { + s->absolute_loading_amount_min = s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len * s->prev->relative_completion; + s->absolute_loading_amount_len = s->prev->absolute_loading_amount_len * len_in_parent; + if(s->absolute_loading_amount_len > s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len - s->absolute_loading_amount_min) + s->absolute_loading_amount_len = s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len - s->absolute_loading_amount_min; + } + else + { + s->absolute_loading_amount_min = 0; + s->absolute_loading_amount_len = 1; + } + + if(redraw) + SCR_UpdateLoadingScreenIfShown(); +} + +void SCR_PopLoadingScreen (qboolean redraw) +{ + loadingscreenstack_t *s = loadingscreenstack; + + if(!s) + { + Con_DPrintf("Popping a loading screen item from an empty stack!\n"); + return; + } + + loadingscreenstack = s->prev; + if(s->prev) + s->prev->relative_completion = (s->absolute_loading_amount_min + s->absolute_loading_amount_len - s->prev->absolute_loading_amount_min) / s->prev->absolute_loading_amount_len; + Z_Free(s); + + if(redraw) + SCR_UpdateLoadingScreenIfShown(); +} + +void SCR_ClearLoadingScreen (qboolean redraw) +{ + while(loadingscreenstack) + SCR_PopLoadingScreen(redraw && !loadingscreenstack->prev); +} + +static float SCR_DrawLoadingStack_r(loadingscreenstack_t *s, float y, float size) +{ + float x; + size_t len; + float total; + + total = 0; +#if 0 + if(s) + { + total += SCR_DrawLoadingStack_r(s->prev, y, 8); + y -= total; + if(!s->prev || strcmp(s->msg, s->prev->msg)) + { + len = strlen(s->msg); + x = (vid_conwidth.integer - DrawQ_TextWidth(s->msg, len, size, size, true, FONT_INFOBAR)) / 2; + y -= size; + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 1, 0); + DrawQ_String(x, y, s->msg, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + total += size; + } + } +#else + if(s) + { + len = strlen(s->msg); + x = (vid_conwidth.integer - DrawQ_TextWidth(s->msg, len, size, size, true, FONT_INFOBAR)) / 2; + y -= size; + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 1, 0); + DrawQ_String(x, y, s->msg, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + total += size; + } +#endif + return total; +} + +static void SCR_DrawLoadingStack(void) +{ + float verts[12]; + float colors[16]; + + loadingscreenheight = SCR_DrawLoadingStack_r(loadingscreenstack, vid_conheight.integer, scr_loadingscreen_barheight.value); + if(loadingscreenstack) + { + // height = 32; // sorry, using the actual one is ugly + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(false); +// R_Mesh_ResetTextureState(); + verts[2] = verts[5] = verts[8] = verts[11] = 0; + verts[0] = verts[9] = 0; + verts[1] = verts[4] = vid_conheight.integer - scr_loadingscreen_barheight.value; + verts[3] = verts[6] = vid_conwidth.integer * loadingscreenstack->absolute_loading_amount_min; + verts[7] = verts[10] = vid_conheight.integer; + +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + // ^^^^^^^^^^ blue component + // ^^^^^^ bottom row + // ^^^^^^^^^^^^ alpha is always on + colors[0] = 0; colors[1] = 0; colors[2] = 0; colors[3] = 1; + colors[4] = 0; colors[5] = 0; colors[6] = 0; colors[7] = 1; + sscanf(scr_loadingscreen_barcolor.string, "%f %f %f", &colors[8], &colors[9], &colors[10]); colors[11] = 1; + sscanf(scr_loadingscreen_barcolor.string, "%f %f %f", &colors[12], &colors[13], &colors[14]); colors[15] = 1; + + R_Mesh_PrepareVertices_Generic_Arrays(4, verts, colors, NULL); + R_SetupShader_Generic_NoTexture(true, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + + // make sure everything is cleared, including the progress indicator + if(loadingscreenheight < 8) + loadingscreenheight = 8; + } +} + +static cachepic_t *loadingscreenpic; +static float loadingscreenpic_vertex3f[12]; +static float loadingscreenpic_texcoord2f[8]; + +static void SCR_DrawLoadingScreen_SharedSetup (qboolean clear) +{ + r_viewport_t viewport; + float x, y, w, h, sw, sh, f; + char vabuf[1024]; + // release mouse grab while loading + if (!vid.fullscreen) + VID_SetMouse(false, false, false); +// CHECKGLERROR + r_refdef.draw2dstage = true; + R_Viewport_InitOrtho(&viewport, &identitymatrix, 0, 0, vid.width, vid.height, 0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100, NULL); + R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); + R_SetViewport(&viewport); + GL_ColorMask(1,1,1,1); + // when starting up a new video mode, make sure the screen is cleared to black + if (clear || loadingscreentexture) + GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 0); + R_Textures_Frame(); + R_Mesh_Start(); + R_EntityMatrix(&identitymatrix); + // draw the loading plaque + loadingscreenpic = Draw_CachePic_Flags (loadingscreenpic_number ? va(vabuf, sizeof(vabuf), "%s%d", scr_loadingscreen_picture.string, loadingscreenpic_number+1) : scr_loadingscreen_picture.string, loadingscreenpic_number ? CACHEPICFLAG_NOTPERSISTENT : 0); + + w = loadingscreenpic->width; + h = loadingscreenpic->height; + + // apply scale + w *= scr_loadingscreen_scale.value; + h *= scr_loadingscreen_scale.value; + + // apply scale base + if(scr_loadingscreen_scale_base.integer) + { + w *= vid_conwidth.integer / (float) vid.width; + h *= vid_conheight.integer / (float) vid.height; + } + + // apply scale limit + sw = w / vid_conwidth.integer; + sh = h / vid_conheight.integer; + f = 1; + switch(scr_loadingscreen_scale_limit.integer) + { + case 1: + f = max(sw, sh); + break; + case 2: + f = min(sw, sh); + break; + case 3: + f = sw; + break; + case 4: + f = sh; + break; + } + if(f > 1) + { + w /= f; + h /= f; + } + + x = (vid_conwidth.integer - w)/2; + y = (vid_conheight.integer - h)/2; + loadingscreenpic_vertex3f[2] = loadingscreenpic_vertex3f[5] = loadingscreenpic_vertex3f[8] = loadingscreenpic_vertex3f[11] = 0; + loadingscreenpic_vertex3f[0] = loadingscreenpic_vertex3f[9] = x; + loadingscreenpic_vertex3f[1] = loadingscreenpic_vertex3f[4] = y; + loadingscreenpic_vertex3f[3] = loadingscreenpic_vertex3f[6] = x + w; + loadingscreenpic_vertex3f[7] = loadingscreenpic_vertex3f[10] = y + h; + loadingscreenpic_texcoord2f[0] = 0;loadingscreenpic_texcoord2f[1] = 0; + loadingscreenpic_texcoord2f[2] = 1;loadingscreenpic_texcoord2f[3] = 0; + loadingscreenpic_texcoord2f[4] = 1;loadingscreenpic_texcoord2f[5] = 1; + loadingscreenpic_texcoord2f[6] = 0;loadingscreenpic_texcoord2f[7] = 1; +} + +static void SCR_DrawLoadingScreen (qboolean clear) +{ + // we only need to draw the image if it isn't already there + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(false); +// R_Mesh_ResetTextureState(); + GL_Color(1,1,1,1); + if(loadingscreentexture) + { + R_Mesh_PrepareVertices_Generic_Arrays(4, loadingscreentexture_vertex3f, NULL, loadingscreentexture_texcoord2f); + R_SetupShader_Generic(loadingscreentexture, NULL, GL_MODULATE, 1, true, true, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } + R_Mesh_PrepareVertices_Generic_Arrays(4, loadingscreenpic_vertex3f, NULL, loadingscreenpic_texcoord2f); + R_SetupShader_Generic(Draw_GetPicTexture(loadingscreenpic), NULL, GL_MODULATE, 1, true, true, false); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + SCR_DrawLoadingStack(); +} + +static void SCR_DrawLoadingScreen_SharedFinish (qboolean clear) +{ + R_Mesh_Finish(); + // refresh + VID_Finish(); +} + +static double loadingscreen_lastupdate; + +void SCR_UpdateLoadingScreen (qboolean clear, qboolean startup) +{ + keydest_t old_key_dest; + int old_key_consoleactive; + + // don't do anything if not initialized yet + if (vid_hidden || cls.state == ca_dedicated) + return; + + // limit update rate + if (scr_loadingscreen_maxfps.value) + { + double t = Sys_DirtyTime(); + if ((t - loadingscreen_lastupdate) < 1.0f/scr_loadingscreen_maxfps.value) + return; + loadingscreen_lastupdate = t; + } + + if(!scr_loadingscreen_background.integer) + clear = true; + + if(loadingscreendone) + clear |= loadingscreencleared; + + if(!loadingscreendone) + { + if(startup && scr_loadingscreen_firstforstartup.integer) + loadingscreenpic_number = 0; + else if(scr_loadingscreen_firstforstartup.integer) + if(scr_loadingscreen_count.integer > 1) + loadingscreenpic_number = rand() % (scr_loadingscreen_count.integer - 1) + 1; + else + loadingscreenpic_number = 0; + else + loadingscreenpic_number = rand() % (scr_loadingscreen_count.integer > 1 ? scr_loadingscreen_count.integer : 1); + } + + if(clear) + SCR_ClearLoadingScreenTexture(); + else if(!loadingscreendone) + SCR_SetLoadingScreenTexture(); + + if(!loadingscreendone) + { + loadingscreendone = true; + loadingscreenheight = 0; + } + loadingscreencleared = clear; + +#ifdef USE_GLES2 + SCR_DrawLoadingScreen_SharedSetup(clear); + SCR_DrawLoadingScreen(clear); +#else + if (qglDrawBuffer) + qglDrawBuffer(GL_BACK); + SCR_DrawLoadingScreen_SharedSetup(clear); + if (vid.stereobuffer && qglDrawBuffer) + { + qglDrawBuffer(GL_BACK_LEFT); + SCR_DrawLoadingScreen(clear); + qglDrawBuffer(GL_BACK_RIGHT); + SCR_DrawLoadingScreen(clear); + } + else + { + if (qglDrawBuffer) + qglDrawBuffer(GL_BACK); + SCR_DrawLoadingScreen(clear); + } +#endif + SCR_DrawLoadingScreen_SharedFinish(clear); + + // this goes into the event loop, and should prevent unresponsive cursor on vista + old_key_dest = key_dest; + old_key_consoleactive = key_consoleactive; + key_dest = key_void; + key_consoleactive = false; + Key_EventQueue_Block(); Sys_SendKeyEvents(); + key_dest = old_key_dest; + key_consoleactive = old_key_consoleactive; +} + +extern cvar_t cl_minfps; +extern cvar_t cl_minfps_fade; +extern cvar_t cl_minfps_qualitymax; +extern cvar_t cl_minfps_qualitymin; +extern cvar_t cl_minfps_qualitymultiply; +extern cvar_t cl_minfps_qualityhysteresis; +extern cvar_t cl_minfps_qualitystepmax; +extern cvar_t cl_minfps_force; +static double cl_updatescreen_quality = 1; +void CL_BeginUpdateScreen() +{ + vec3_t vieworigin; + static double drawscreenstart = 0.0; + double drawscreendelta; + float conwidth, conheight; + r_viewport_t viewport; + + if(drawscreenstart) + { + drawscreendelta = Sys_DirtyTime() - drawscreenstart; + if (cl_minfps.value > 0 && (cl_minfps_force.integer || !(cls.timedemo || (cls.capturevideo.active && !cls.capturevideo.realtime))) && drawscreendelta >= 0 && drawscreendelta < 60) + { + // quality adjustment according to render time + double actualframetime; + double targetframetime; + double adjust; + double f; + double h; + + // fade lastdrawscreentime + r_refdef.lastdrawscreentime += (drawscreendelta - r_refdef.lastdrawscreentime) * cl_minfps_fade.value; + + // find actual and target frame times + actualframetime = r_refdef.lastdrawscreentime; + targetframetime = (1.0 / cl_minfps.value); + + // we scale hysteresis by quality + h = cl_updatescreen_quality * cl_minfps_qualityhysteresis.value; + + // calculate adjustment assuming linearity + f = cl_updatescreen_quality / actualframetime * cl_minfps_qualitymultiply.value; + adjust = (targetframetime - actualframetime) * f; + + // one sided hysteresis + if(adjust > 0) + adjust = max(0, adjust - h); + + // adjust > 0 if: + // (targetframetime - actualframetime) * f > h + // ((1.0 / cl_minfps.value) - actualframetime) * (cl_updatescreen_quality / actualframetime * cl_minfps_qualitymultiply.value) > (cl_updatescreen_quality * cl_minfps_qualityhysteresis.value) + // ((1.0 / cl_minfps.value) - actualframetime) * (cl_minfps_qualitymultiply.value / actualframetime) > cl_minfps_qualityhysteresis.value + // (1.0 / cl_minfps.value) * (cl_minfps_qualitymultiply.value / actualframetime) - cl_minfps_qualitymultiply.value > cl_minfps_qualityhysteresis.value + // (1.0 / cl_minfps.value) * (cl_minfps_qualitymultiply.value / actualframetime) > cl_minfps_qualityhysteresis.value + cl_minfps_qualitymultiply.value + // (1.0 / cl_minfps.value) / actualframetime > (cl_minfps_qualityhysteresis.value + cl_minfps_qualitymultiply.value) / cl_minfps_qualitymultiply.value + // (1.0 / cl_minfps.value) / actualframetime > 1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value + // cl_minfps.value * actualframetime < 1.0 / (1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value) + // actualframetime < 1.0 / cl_minfps.value / (1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value) + // actualfps > cl_minfps.value * (1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value) + + // adjust < 0 if: + // (targetframetime - actualframetime) * f < 0 + // ((1.0 / cl_minfps.value) - actualframetime) * (cl_updatescreen_quality / actualframetime * cl_minfps_qualitymultiply.value) < 0 + // ((1.0 / cl_minfps.value) - actualframetime) < 0 + // -actualframetime) < -(1.0 / cl_minfps.value) + // actualfps < cl_minfps.value + + /* + Con_Printf("adjust UP if fps > %f, adjust DOWN if fps < %f\n", + cl_minfps.value * (1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value), + cl_minfps.value); + */ + + // don't adjust too much at once + adjust = bound(-cl_minfps_qualitystepmax.value, adjust, cl_minfps_qualitystepmax.value); + + // adjust! + cl_updatescreen_quality += adjust; + cl_updatescreen_quality = bound(max(0.01, cl_minfps_qualitymin.value), cl_updatescreen_quality, cl_minfps_qualitymax.value); + } + else + { + cl_updatescreen_quality = 1; + r_refdef.lastdrawscreentime = 0; + } + } + + drawscreenstart = Sys_DirtyTime(); + + Sbar_ShowFPS_Update(); + + if (!scr_initialized || !con_initialized || !scr_refresh.integer) + return; // not initialized yet + + loadingscreendone = false; + + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + // play a bit with the palette (experimental) + palette_rgb_pantscolormap[15][0] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 0.0f*M_PI/3.0f)); + palette_rgb_pantscolormap[15][1] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 2.0f*M_PI/3.0f)); + palette_rgb_pantscolormap[15][2] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 4.0f*M_PI/3.0f)); + palette_rgb_shirtcolormap[15][0] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 5.0f*M_PI/3.0f)); + palette_rgb_shirtcolormap[15][1] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 3.0f*M_PI/3.0f)); + palette_rgb_shirtcolormap[15][2] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 1.0f*M_PI/3.0f)); + memcpy(palette_rgb_pantsscoreboard[15], palette_rgb_pantscolormap[15], sizeof(*palette_rgb_pantscolormap)); + memcpy(palette_rgb_shirtscoreboard[15], palette_rgb_shirtcolormap[15], sizeof(*palette_rgb_shirtcolormap)); + } + + if (vid_hidden) + { + VID_Finish(); + return; + } + + conwidth = bound(160, vid_conwidth.value, 32768); + conheight = bound(90, vid_conheight.value, 24576); + if (vid_conwidth.value != conwidth) + Cvar_SetValue("vid_conwidth", conwidth); + if (vid_conheight.value != conheight) + Cvar_SetValue("vid_conheight", conheight); + + // bound viewsize + if (scr_viewsize.value < 30) + Cvar_Set ("viewsize","30"); + if (scr_viewsize.value > 120) + Cvar_Set ("viewsize","120"); + + // bound field of view + if (scr_fov.value < 1) + Cvar_Set ("fov","1"); + if (scr_fov.value > 170) + Cvar_Set ("fov","170"); + + // intermission is always full screen + if (cl.intermission) + sb_lines = 0; + else + { + if (scr_viewsize.value >= 120) + sb_lines = 0; // no status bar at all + else if (scr_viewsize.value >= 110) + sb_lines = 24; // no inventory + else + sb_lines = 24+16+8; + } + + R_FrameData_NewFrame(); + R_BufferData_NewFrame(); + + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, vieworigin); + R_HDR_UpdateIrisAdaptation(vieworigin); + + r_refdef.view.colormask[0] = 1; + r_refdef.view.colormask[1] = 1; + r_refdef.view.colormask[2] = 1; + + SCR_SetUpToDrawConsole(); + + R_Viewport_InitOrtho(&viewport, &identitymatrix, 0, 0, vid.width, vid.height, 0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100, NULL); + R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); + R_SetViewport(&viewport); + GL_ScissorTest(false); + GL_ColorMask(1,1,1,1); + GL_DepthMask(true); + + R_ClearScreen(false); + r_refdef.view.clear = false; + r_refdef.view.isoverlay = false; + + // calculate r_refdef.view.quality + r_refdef.view.quality = cl_updatescreen_quality; +} + + +void CL_EndUpdateScreen() +{ + SCR_CaptureVideo(); + +// if (qglFlush) +// qglFlush(); // FIXME: should we really be using qglFlush here? + + if (!vid_activewindow) + VID_SetMouse(false, false, false); + else if (key_consoleactive) + VID_SetMouse(vid.fullscreen, false, false); + else if (key_dest == key_menu_grabbed) + VID_SetMouse(true, vid_mouse.integer && !in_client_mouse && !vid_touchscreen.integer, !vid_touchscreen.integer); + else if (key_dest == key_menu) + VID_SetMouse(vid.fullscreen, vid_mouse.integer && !in_client_mouse && !vid_touchscreen.integer, !vid_touchscreen.integer); + else + VID_SetMouse(vid.fullscreen, vid_mouse.integer && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0 && (!cls.demoplayback || cl_demo_mousegrab.integer) && !vid_touchscreen.integer, !vid_touchscreen.integer); + + VID_Finish(); +} + +void CL_Screen_NewMap(void) +{ +} diff --git a/app/jni/cl_screen.h b/app/jni/cl_screen.h new file mode 100644 index 0000000..ddf611f --- /dev/null +++ b/app/jni/cl_screen.h @@ -0,0 +1,30 @@ + +#ifndef CL_SCREEN_H +#define CL_SCREEN_H + +void SHOWLMP_decodehide(void); +void SHOWLMP_decodeshow(void); +void SHOWLMP_drawall(void); + +extern cvar_t vid_conwidth; +extern cvar_t vid_conheight; +extern cvar_t vid_pixelheight; +extern cvar_t scr_screenshot_jpeg; +extern cvar_t scr_screenshot_jpeg_quality; +extern cvar_t scr_screenshot_png; +extern cvar_t scr_screenshot_gammaboost; +extern cvar_t scr_screenshot_name; + +void CL_Screen_NewMap(void); +void CL_Screen_Init(void); +void CL_Screen_Shutdown(void); +void CL_BeginUpdateScreen(); +void CL_EndUpdateScreen(); + +void SCR_DrawScreen (); + +qboolean R_Stereo_Active(void); +qboolean R_Stereo_ColorMasking(void); + +#endif + diff --git a/app/jni/cl_video.c b/app/jni/cl_video.c new file mode 100644 index 0000000..1089b75 --- /dev/null +++ b/app/jni/cl_video.c @@ -0,0 +1,743 @@ + +#include "quakedef.h" +#include "cl_dyntexture.h" +#include "cl_video.h" + +// cvars +cvar_t cl_video_subtitles = {CVAR_SAVE, "cl_video_subtitles", "0", "show subtitles for videos (if they are present)"}; +cvar_t cl_video_subtitles_lines = {CVAR_SAVE, "cl_video_subtitles_lines", "4", "how many lines to occupy for subtitles"}; +cvar_t cl_video_subtitles_textsize = {CVAR_SAVE, "cl_video_subtitles_textsize", "16", "textsize for subtitles"}; +cvar_t cl_video_scale = {CVAR_SAVE, "cl_video_scale", "1", "scale of video, 1 = fullscreen, 0.75 - 3/4 of screen etc."}; +cvar_t cl_video_scale_vpos = {CVAR_SAVE, "cl_video_scale_vpos", "0", "vertical align of scaled video, -1 is top, 1 is bottom"}; +cvar_t cl_video_stipple = {CVAR_SAVE, "cl_video_stipple", "0", "draw interlacing-like effect on videos, similar to scr_stipple but static and used only with video playing."}; +cvar_t cl_video_brightness = {CVAR_SAVE, "cl_video_brightness", "1", "brightness of video, 1 = fullbright, 0.75 - 3/4 etc."}; +cvar_t cl_video_keepaspectratio = {CVAR_SAVE, "cl_video_keepaspectratio", "0", "keeps aspect ratio of fullscreen videos, leaving black color on unfilled areas, a value of 2 let video to be stretched horizontally with top & bottom being sliced out"}; +cvar_t cl_video_fadein = {CVAR_SAVE, "cl_video_fadein", "0", "fading-from-black effect once video is started, in seconds"}; +cvar_t cl_video_fadeout = {CVAR_SAVE, "cl_video_fadeout", "0", "fading-to-black effect once video is ended, in seconds"}; + +cvar_t v_glslgamma_video = {CVAR_SAVE, "v_glslgamma_video", "1", "applies GLSL gamma to played video, could be a fraction, requires r_glslgamma_2d 1."}; + +// DPV stream decoder +#include "dpvsimpledecode.h" + +// VorteX: libavcodec implementation +#include "cl_video_libavw.c" + +// JAM video decoder used by Blood Omnicide +#ifdef JAMVIDEO +#include "cl_video_jamdecode.c" +#endif + +// constants (and semi-constants) +static int cl_videormask; +static int cl_videobmask; +static int cl_videogmask; +static int cl_videobytesperpixel; + +static int cl_num_videos; +static clvideo_t cl_videos[ MAXCLVIDEOS ]; +static rtexturepool_t *cl_videotexturepool; + +static clvideo_t *FindUnusedVid( void ) +{ + int i; + for( i = 1 ; i < MAXCLVIDEOS ; i++ ) + if( cl_videos[ i ].state == CLVIDEO_UNUSED ) + return &cl_videos[ i ]; + return NULL; +} + +static qboolean OpenStream( clvideo_t * video ) +{ + const char *errorstring; + + video->stream = dpvsimpledecode_open( video, video->filename, &errorstring); + if (video->stream) + return true; + +#ifdef JAMVIDEO + video->stream = jam_open( video, video->filename, &errorstring); + if (video->stream) + return true; +#endif + + video->stream = LibAvW_OpenVideo( video, video->filename, &errorstring); + if (video->stream) + return true; + + Con_Printf("unable to open \"%s\", error: %s\n", video->filename, errorstring); + return false; +} + +static void VideoUpdateCallback(rtexture_t *rt, void *data) +{ + clvideo_t *video = (clvideo_t *) data; + R_UpdateTexture( video->cpif.tex, (unsigned char *)video->imagedata, 0, 0, 0, video->cpif.width, video->cpif.height, 1 ); +} + +static void LinkVideoTexture( clvideo_t *video ) +{ + video->cpif.tex = R_LoadTexture2D( cl_videotexturepool, video->cpif.name, video->cpif.width, video->cpif.height, NULL, TEXTYPE_BGRA, TEXF_PERSISTENT | TEXF_CLAMP, -1, NULL ); + R_MakeTextureDynamic( video->cpif.tex, VideoUpdateCallback, video ); + CL_LinkDynTexture( video->cpif.name, video->cpif.tex ); +} + +static void UnlinkVideoTexture( clvideo_t *video ) +{ + CL_UnlinkDynTexture( video->cpif.name ); + // free the texture + R_FreeTexture( video->cpif.tex ); + video->cpif.tex = NULL; + // free the image data + Mem_Free( video->imagedata ); +} + +static void SuspendVideo( clvideo_t * video ) +{ + if (video->suspended) + return; + video->suspended = true; + UnlinkVideoTexture(video); + // if we are in firstframe mode, also close the stream + if (video->state == CLVIDEO_FIRSTFRAME) + { + if (video->stream) + video->close(video->stream); + video->stream = NULL; + } +} + +static qboolean WakeVideo( clvideo_t * video ) +{ + if( !video->suspended ) + return true; + video->suspended = false; + + if( video->state == CLVIDEO_FIRSTFRAME ) + if( !OpenStream( video ) ) { + video->state = CLVIDEO_UNUSED; + return false; + } + + video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel ); + LinkVideoTexture( video ); + + // update starttime + video->starttime += realtime - video->lasttime; + + return true; +} + +static void LoadSubtitles( clvideo_t *video, const char *subtitlesfile ) +{ + char *subtitle_text; + const char *data; + float subtime, sublen; + int numsubs = 0; + + if (gamemode == GAME_BLOODOMNICIDE) + { + char overridename[MAX_QPATH]; + cvar_t *langcvar; + + langcvar = Cvar_FindVar("language"); + subtitle_text = NULL; + if (langcvar) + { + dpsnprintf(overridename, sizeof(overridename), "locale/%s/%s", langcvar->string, subtitlesfile); + subtitle_text = (char *)FS_LoadFile(overridename, cls.permanentmempool, false, NULL); + } + if (!subtitle_text) + subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL); + } + else + { + subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL); + } + if (!subtitle_text) + { + Con_DPrintf( "LoadSubtitles: can't open subtitle file '%s'!\n", subtitlesfile ); + return; + } + + // parse subtitle_text + // line is: x y "text" where + // x - start time + // y - seconds last (if 0 - last thru next sub, if negative - last to next sub - this amount of seconds) + + data = subtitle_text; + for (;;) + { + if (!COM_ParseToken_QuakeC(&data, false)) + break; + subtime = atof( com_token ); + if (!COM_ParseToken_QuakeC(&data, false)) + break; + sublen = atof( com_token ); + if (!COM_ParseToken_QuakeC(&data, false)) + break; + if (!com_token[0]) + continue; + // check limits + if (video->subtitles == CLVIDEO_MAX_SUBTITLES) + { + Con_Printf("WARNING: CLVIDEO_MAX_SUBTITLES = %i reached when reading subtitles from '%s'\n", CLVIDEO_MAX_SUBTITLES, subtitlesfile); + break; + } + // add a sub + video->subtitle_text[numsubs] = (char *) Mem_Alloc(cls.permanentmempool, strlen(com_token) + 1); + memcpy(video->subtitle_text[numsubs], com_token, strlen(com_token) + 1); + video->subtitle_start[numsubs] = subtime; + video->subtitle_end[numsubs] = sublen; + if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles + { + if (video->subtitle_end[numsubs-1] <= 0) + video->subtitle_end[numsubs-1] = max(video->subtitle_start[numsubs-1], video->subtitle_start[numsubs] + video->subtitle_end[numsubs-1]); + else + video->subtitle_end[numsubs-1] = min(video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1], video->subtitle_start[numsubs]); + } + numsubs++; + // todo: check timing for consistency? + } + if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles + { + if (video->subtitle_end[numsubs-1] <= 0) + video->subtitle_end[numsubs-1] = 99999999; // fixme: make it end when video ends? + else + video->subtitle_end[numsubs-1] = video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1]; + } + Z_Free( subtitle_text ); + video->subtitles = numsubs; +/* + Con_Printf( "video->subtitles: %i\n", video->subtitles ); + for (numsubs = 0; numsubs < video->subtitles; numsubs++) + Con_Printf( " %03.2f %03.2f : %s\n", video->subtitle_start[numsubs], video->subtitle_end[numsubs], video->subtitle_text[numsubs] ); +*/ +} + +static clvideo_t* OpenVideo( clvideo_t *video, const char *filename, const char *name, int owner, const char *subtitlesfile ) +{ + strlcpy( video->filename, filename, sizeof(video->filename) ); + video->ownertag = owner; + if( strncmp( name, CLVIDEOPREFIX, sizeof( CLVIDEOPREFIX ) - 1 ) ) + return NULL; + strlcpy( video->cpif.name, name, sizeof(video->cpif.name) ); + + if( !OpenStream( video ) ) + return NULL; + + video->state = CLVIDEO_FIRSTFRAME; + video->framenum = -1; + video->framerate = video->getframerate( video->stream ); + video->lasttime = realtime; + video->subtitles = 0; + + video->cpif.width = video->getwidth( video->stream ); + video->cpif.height = video->getheight( video->stream ); + video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel ); + LinkVideoTexture( video ); + + // VorteX: load simple subtitle_text file + if (subtitlesfile[0]) + LoadSubtitles( video, subtitlesfile ); + + return video; +} + +clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile ) +{ + clvideo_t *video; + // sanity check + if( !name || !*name || strncmp( name, CLVIDEOPREFIX, sizeof( CLVIDEOPREFIX ) - 1 ) != 0 ) { + Con_DPrintf( "CL_OpenVideo: Bad video texture name '%s'!\n", name ); + return NULL; + } + + video = FindUnusedVid(); + if( !video ) { + Con_Printf( "CL_OpenVideo: unable to open video \"%s\" - video limit reached\n", filename ); + return NULL; + } + video = OpenVideo( video, filename, name, owner, subtitlesfile ); + // expand the active range to include the new entry + if (video) { + cl_num_videos = max(cl_num_videos, (int)(video - cl_videos) + 1); + } + return video; +} + +static clvideo_t* CL_GetVideoBySlot( int slot ) +{ + clvideo_t *video = &cl_videos[ slot ]; + + if( video->suspended ) + { + if( !WakeVideo( video ) ) + return NULL; + else if( video->state == CLVIDEO_RESETONWAKEUP ) + video->framenum = -1; + } + + video->lasttime = realtime; + + return video; +} + +clvideo_t *CL_GetVideoByName( const char *name ) +{ + int i; + + for( i = 0 ; i < cl_num_videos ; i++ ) + if( cl_videos[ i ].state != CLVIDEO_UNUSED + && !strcmp( cl_videos[ i ].cpif.name , name ) ) + break; + if( i != cl_num_videos ) + return CL_GetVideoBySlot( i ); + else + return NULL; +} + +void CL_SetVideoState(clvideo_t *video, clvideostate_t state) +{ + if (!video) + return; + + video->lasttime = realtime; + video->state = state; + if (state == CLVIDEO_FIRSTFRAME) + CL_RestartVideo(video); +} + +void CL_RestartVideo(clvideo_t *video) +{ + if (!video) + return; + + // reset time + video->starttime = video->lasttime = realtime; + video->framenum = -1; + + // reopen stream + if (video->stream) + video->close(video->stream); + video->stream = NULL; + if (!OpenStream(video)) + video->state = CLVIDEO_UNUSED; +} + +// close video +void CL_CloseVideo(clvideo_t * video) +{ + int i; + + if (!video || video->state == CLVIDEO_UNUSED) + return; + + // close stream + if (!video->suspended || video->state != CLVIDEO_FIRSTFRAME) + { + if (video->stream) + video->close(video->stream); + video->stream = NULL; + } + // unlink texture + if (!video->suspended) + UnlinkVideoTexture(video); + // purge subtitles + if (video->subtitles) + { + for (i = 0; i < video->subtitles; i++) + Z_Free( video->subtitle_text[i] ); + video->subtitles = 0; + } + video->state = CLVIDEO_UNUSED; +} + +// update all videos +void CL_Video_Frame(void) +{ + clvideo_t *video; + int destframe; + int i; + + if (!cl_num_videos) + return; + for (video = cl_videos, i = 0 ; i < cl_num_videos ; video++, i++) + { + if (video->state != CLVIDEO_UNUSED && !video->suspended) + { + if (realtime - video->lasttime > CLTHRESHOLD) + { + SuspendVideo(video); + continue; + } + if (video->state == CLVIDEO_PAUSE) + { + video->starttime = realtime - video->framenum * video->framerate; + continue; + } + // read video frame from stream if time has come + if (video->state == CLVIDEO_FIRSTFRAME ) + destframe = 0; + else + destframe = (int)((realtime - video->starttime) * video->framerate); + if (destframe < 0) + destframe = 0; + if (video->framenum < destframe) + { + do { + video->framenum++; + if (video->decodeframe(video->stream, video->imagedata, cl_videormask, cl_videogmask, cl_videobmask, cl_videobytesperpixel, cl_videobytesperpixel * video->cpif.width)) + { + // finished? + CL_RestartVideo(video); + if (video->state == CLVIDEO_PLAY) + video->state = CLVIDEO_FIRSTFRAME; + return; + } + } while(video->framenum < destframe); + R_MarkDirtyTexture(video->cpif.tex); + } + } + } + + // stop main video + if (cl_videos->state == CLVIDEO_FIRSTFRAME) + CL_VideoStop(); + + // reduce range to exclude unnecessary entries + while(cl_num_videos > 0 && cl_videos[cl_num_videos-1].state == CLVIDEO_UNUSED) + cl_num_videos--; +} + +void CL_PurgeOwner( int owner ) +{ + int i; + + for (i = 0 ; i < cl_num_videos ; i++) + if (cl_videos[i].ownertag == owner) + CL_CloseVideo(&cl_videos[i]); +} + +typedef struct +{ + dp_font_t *font; + float x; + float y; + float width; + float height; + float alignment; // 0 = left, 0.5 = center, 1 = right + float fontsize; + float textalpha; +} +cl_video_subtitle_info_t; + +static float CL_DrawVideo_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth) +{ + cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough; + + if(w == NULL) + return si->fontsize * si->font->maxwidth; + if(maxWidth >= 0) + return DrawQ_TextWidth_UntilWidth(w, length, si->fontsize, si->fontsize, false, si->font, -maxWidth); // -maxWidth: we want at least one char + else if(maxWidth == -1) + return DrawQ_TextWidth(w, *length, si->fontsize, si->fontsize, false, si->font); + else + return 0; +} + +static int CL_DrawVideo_DisplaySubtitleLine(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) +{ + cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough; + + int x = (int) (si->x + (si->width - width) * si->alignment); + if (length > 0) + DrawQ_String(x, si->y, line, length, si->fontsize, si->fontsize, 1.0, 1.0, 1.0, si->textalpha, 0, NULL, false, si->font); + si->y += si->fontsize; + return 1; +} + +int cl_videoplaying = false; // old, but still supported + +void CL_DrawVideo(void) +{ + clvideo_t *video; + float videotime, px, py, sx, sy, st[8], b; + cl_video_subtitle_info_t si; + int i; + + if (!cl_videoplaying) + return; + + video = CL_GetVideoBySlot( 0 ); + + // fix cvars + if (cl_video_scale.value <= 0 || cl_video_scale.value > 1) + Cvar_SetValueQuick( &cl_video_scale, 1); + if (cl_video_brightness.value <= 0 || cl_video_brightness.value > 10) + Cvar_SetValueQuick( &cl_video_brightness, 1); + + // calc video proportions + px = 0; + py = 0; + sx = vid_conwidth.integer; + sy = vid_conheight.integer; + st[0] = 0.0; st[1] = 0.0; + st[2] = 1.0; st[3] = 0.0; + st[4] = 0.0; st[5] = 1.0; + st[6] = 1.0; st[7] = 1.0; + if (cl_video_keepaspectratio.integer) + { + float a = video->getaspectratio(video->stream) / ((float)vid.width / (float)vid.height); + if (cl_video_keepaspectratio.integer >= 2) + { + // clip instead of scale + if (a < 1.0) // clip horizontally + { + st[1] = st[3] = (1 - a)*0.5; + st[5] = st[7] = 1 - (1 - a)*0.5; + } + else if (a > 1.0) // clip vertically + { + st[0] = st[4] = (1 - 1/a)*0.5; + st[2] = st[6] = (1/a)*0.5; + } + } + else if (a < 1.0) // scale horizontally + { + px += sx * (1 - a) * 0.5; + sx *= a; + } + else if (a > 1.0) // scale vertically + { + a = 1 / a; + py += sy * (1 - a); + sy *= a; + } + } + + if (cl_video_scale.value != 1) + { + px += sx * (1 - cl_video_scale.value) * 0.5; + py += sy * (1 - cl_video_scale.value) * ((bound(-1, cl_video_scale_vpos.value, 1) + 1) / 2); + sx *= cl_video_scale.value; + sy *= cl_video_scale.value; + } + + // calc brightness for fadein and fadeout effects + b = cl_video_brightness.value; + if (cl_video_fadein.value && (realtime - video->starttime) < cl_video_fadein.value) + b = pow((realtime - video->starttime)/cl_video_fadein.value, 2); + else if (cl_video_fadeout.value && ((video->starttime + video->framenum * video->framerate) - realtime) < cl_video_fadeout.value) + b = pow(((video->starttime + video->framenum * video->framerate) - realtime)/cl_video_fadeout.value, 2); + + // draw black bg in case stipple is active or video is scaled + if (cl_video_stipple.integer || px != 0 || py != 0 || sx != vid_conwidth.integer || sy != vid_conheight.integer) + DrawQ_Fill(0, 0, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, 1, 0); + +#ifndef USE_GLES2 + // enable video-only polygon stipple (of global stipple is not active) + if (qglPolygonStipple && !scr_stipple.integer && cl_video_stipple.integer) + { + GLubyte stipple[128]; + int i, s, width, parts; + + s = cl_video_stipple.integer; + parts = (s & 007); + width = (s & 070) >> 3; + qglEnable(GL_POLYGON_STIPPLE);CHECKGLERROR // 0x0B42 + for(i = 0; i < 128; ++i) + { + int line = i/4; + stipple[i] = ((line >> width) & ((1 << parts) - 1)) ? 0x00 : 0xFF; + } + qglPolygonStipple(stipple);CHECKGLERROR + } +#endif + + // draw video + if (v_glslgamma_video.value >= 1) + DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, 1, st[2], st[3], b, b, b, 1, st[4], st[5], b, b, b, 1, st[6], st[7], b, b, b, 1, 0); + else + { + DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, 1, st[2], st[3], b, b, b, 1, st[4], st[5], b, b, b, 1, st[6], st[7], b, b, b, 1, DRAWFLAG_NOGAMMA); + if (v_glslgamma_video.value > 0.0) + DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, v_glslgamma_video.value, st[2], st[3], b, b, b, v_glslgamma_video.value, st[4], st[5], b, b, b, v_glslgamma_video.value, st[6], st[7], b, b, b, v_glslgamma_video.value, 0); + } + +#ifndef USE_GLES2 + // disable video-only stipple + if (qglPolygonStipple && !scr_stipple.integer && cl_video_stipple.integer) + qglDisable(GL_POLYGON_STIPPLE);CHECKGLERROR +#endif + + // VorteX: draw subtitle_text + if (!video->subtitles || !cl_video_subtitles.integer) + return; + + // find current subtitle + videotime = realtime - video->starttime; + for (i = 0; i < video->subtitles; i++) + { + if (videotime >= video->subtitle_start[i] && videotime <= video->subtitle_end[i]) + { + // found, draw it + si.font = FONT_NOTIFY; + si.x = vid_conwidth.integer * 0.1; + si.y = vid_conheight.integer - (max(1, cl_video_subtitles_lines.value) * cl_video_subtitles_textsize.value); + si.width = vid_conwidth.integer * 0.8; + si.height = max(1, cl_video_subtitles_lines.integer) * cl_video_subtitles_textsize.value; + si.alignment = 0.5; + si.fontsize = cl_video_subtitles_textsize.value; + si.textalpha = min(1, (videotime - video->subtitle_start[i])/0.5) * min(1, ((video->subtitle_end[i] - videotime)/0.3)); // fade in and fade out + COM_Wordwrap(video->subtitle_text[i], strlen(video->subtitle_text[i]), 0, si.width, CL_DrawVideo_WordWidthFunc, &si, CL_DrawVideo_DisplaySubtitleLine, &si); + break; + } + } +} + +void CL_VideoStart(char *filename, const char *subtitlesfile) +{ + char vabuf[1024]; + Host_StartVideo(); + + if( cl_videos->state != CLVIDEO_UNUSED ) + CL_CloseVideo( cl_videos ); + // already contains video/ + if( !OpenVideo( cl_videos, filename, va(vabuf, sizeof(vabuf), CLDYNTEXTUREPREFIX "%s", filename ), 0, subtitlesfile ) ) + return; + // expand the active range to include the new entry + cl_num_videos = max(cl_num_videos, 1); + + cl_videoplaying = true; + + CL_SetVideoState( cl_videos, CLVIDEO_PLAY ); + CL_RestartVideo( cl_videos ); +} + +void CL_Video_KeyEvent( int key, int ascii, qboolean down ) +{ + // only react to up events, to allow the user to delay the abortion point if it suddenly becomes interesting.. + if( !down ) { + if( key == K_ESCAPE || key == K_ENTER || key == K_MOUSE1 || key == K_SPACE ) { + CL_VideoStop(); + } + } +} + +void CL_VideoStop(void) +{ + cl_videoplaying = false; + + CL_CloseVideo( cl_videos ); +} + +static void CL_PlayVideo_f(void) +{ + char name[MAX_QPATH], subtitlesfile[MAX_QPATH]; + const char *extension; + + Host_StartVideo(); + + if (COM_CheckParm("-benchmark")) + return; + + if (Cmd_Argc() < 2) + { + Con_Print("usage: playvideo [custom_subtitles_file]\nplays video named video/.dpv\nif custom subtitles file is not presented\nit tries video/.sub"); + return; + } + + extension = FS_FileExtension(Cmd_Argv(1)); + if (extension[0]) + dpsnprintf(name, sizeof(name), "video/%s", Cmd_Argv(1)); + else + dpsnprintf(name, sizeof(name), "video/%s.dpv", Cmd_Argv(1)); + if ( Cmd_Argc() > 2) + CL_VideoStart(name, Cmd_Argv(2)); + else + { + dpsnprintf(subtitlesfile, sizeof(subtitlesfile), "video/%s.dpsubs", Cmd_Argv(1)); + CL_VideoStart(name, subtitlesfile); + } +} + +static void CL_StopVideo_f(void) +{ + CL_VideoStop(); +} + +static void cl_video_start( void ) +{ + int i; + clvideo_t *video; + + cl_videotexturepool = R_AllocTexturePool(); + + for( video = cl_videos, i = 0 ; i < cl_num_videos ; i++, video++ ) + if( video->state != CLVIDEO_UNUSED && !video->suspended ) + LinkVideoTexture( video ); +} + +static void cl_video_shutdown( void ) +{ + int i; + clvideo_t *video; + + for( video = cl_videos, i = 0 ; i < cl_num_videos ; i++, video++ ) + if( video->state != CLVIDEO_UNUSED && !video->suspended ) + SuspendVideo( video ); + R_FreeTexturePool( &cl_videotexturepool ); +} + +static void cl_video_newmap( void ) +{ +} + +void CL_Video_Init( void ) +{ + union + { + unsigned char b[4]; + unsigned int i; + } + bgra; + + cl_num_videos = 0; + cl_videobytesperpixel = 4; + + // set masks in an endian-independent way (as they really represent bytes) + bgra.i = 0;bgra.b[0] = 0xFF;cl_videobmask = bgra.i; + bgra.i = 0;bgra.b[1] = 0xFF;cl_videogmask = bgra.i; + bgra.i = 0;bgra.b[2] = 0xFF;cl_videormask = bgra.i; + + Cmd_AddCommand( "playvideo", CL_PlayVideo_f, "play a .dpv video file" ); + Cmd_AddCommand( "stopvideo", CL_StopVideo_f, "stop playing a .dpv video file" ); + + Cvar_RegisterVariable(&cl_video_subtitles); + Cvar_RegisterVariable(&cl_video_subtitles_lines); + Cvar_RegisterVariable(&cl_video_subtitles_textsize); + Cvar_RegisterVariable(&cl_video_scale); + Cvar_RegisterVariable(&cl_video_scale_vpos); + Cvar_RegisterVariable(&cl_video_brightness); + Cvar_RegisterVariable(&cl_video_stipple); + Cvar_RegisterVariable(&cl_video_keepaspectratio); + Cvar_RegisterVariable(&cl_video_fadein); + Cvar_RegisterVariable(&cl_video_fadeout); + + Cvar_RegisterVariable(&v_glslgamma_video); + + R_RegisterModule( "CL_Video", cl_video_start, cl_video_shutdown, cl_video_newmap, NULL, NULL ); + + LibAvW_OpenLibrary(); +} + +void CL_Video_Shutdown( void ) +{ + int i; + + for (i = 0 ; i < cl_num_videos ; i++) + CL_CloseVideo(&cl_videos[ i ]); + + LibAvW_CloseLibrary(); +} diff --git a/app/jni/cl_video.h b/app/jni/cl_video.h new file mode 100644 index 0000000..97960b8 --- /dev/null +++ b/app/jni/cl_video.h @@ -0,0 +1,97 @@ + +#ifndef CL_VIDEO_H +#define CL_VIDEO_H + +#include "cl_dyntexture.h" + +// yields DYNAMIC_TEXTURE_PATH_PREFIX CLVIDEOPREFIX video name for a path +#define CLVIDEOPREFIX CLDYNTEXTUREPREFIX "video/" +#define CLTHRESHOLD 2.0 + +#define MENUOWNER 1 + +typedef enum clvideostate_e +{ + CLVIDEO_UNUSED, + CLVIDEO_PLAY, + CLVIDEO_LOOP, + CLVIDEO_PAUSE, + CLVIDEO_FIRSTFRAME, + CLVIDEO_RESETONWAKEUP, + CLVIDEO_STATECOUNT +} clvideostate_t; + +#define CLVIDEO_MAX_SUBTITLES 512 + +extern cvar_t cl_video_subtitles; +extern cvar_t cl_video_subtitles_lines; +extern cvar_t cl_video_subtitles_textsize; +extern cvar_t cl_video_scale; +extern cvar_t cl_video_scale_vpos; +extern cvar_t cl_video_stipple; +extern cvar_t cl_video_brightness; +extern cvar_t cl_video_keepaspectratio; + +typedef struct clvideo_s +{ + int ownertag; + clvideostate_t state; + + // private stuff + void *stream; + + double starttime; + int framenum; + double framerate; + + void *imagedata; + + cachepic_t cpif; + + // VorteX: subtitles array + int subtitles; + char *subtitle_text[CLVIDEO_MAX_SUBTITLES]; + float subtitle_start[CLVIDEO_MAX_SUBTITLES]; + float subtitle_end[CLVIDEO_MAX_SUBTITLES]; + + // this functions gets filled by video format module + void (*close) (void *stream); + unsigned int (*getwidth) (void *stream); + unsigned int (*getheight) (void *stream); + double (*getframerate) (void *stream); + double (*getaspectratio) (void *stream); + int (*decodeframe) (void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); + + // if a video is suspended, it is automatically paused (else we'd still have to process the frames) + // used to determine whether the video's resources should be freed or not + double lasttime; + // when lasttime - realtime > THRESHOLD, all but the stream is freed + qboolean suspended; + + char filename[MAX_QPATH]; +} clvideo_t; + +clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile ); +clvideo_t* CL_GetVideoByName( const char *name ); +void CL_SetVideoState( clvideo_t *video, clvideostate_t state ); +void CL_RestartVideo( clvideo_t *video ); + +void CL_CloseVideo( clvideo_t * video ); +void CL_PurgeOwner( int owner ); + +void CL_Video_Frame( void ); // update all videos +void CL_Video_Init( void ); +void CL_Video_Shutdown( void ); + +// old interface +extern int cl_videoplaying; + +void CL_DrawVideo( void ); +void CL_VideoStart( char *filename, const char *subtitlesfile ); +void CL_VideoStop( void ); + +// new function used for fullscreen videos +// TODO: Andreas Kirsch: move this subsystem somewhere else (preferably host) since the cl_video system shouldnt do such work like managing key events.. +void CL_Video_KeyEvent( int key, int ascii, qboolean down ); + +#endif diff --git a/app/jni/cl_video_libavw.c b/app/jni/cl_video_libavw.c new file mode 100644 index 0000000..7924127 --- /dev/null +++ b/app/jni/cl_video_libavw.c @@ -0,0 +1,386 @@ +/* + Libavcodec integration for Darkplaces by Timofeyev Pavel + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +// LordHavoc: for some reason this is being #include'd rather than treated as its own file... +// LordHavoc: adapted to not require stdint.h as this is not available on MSVC++, using unsigned char instead of uint8_t and fs_offset_t instead of int64_t. + +// scaler type +#define LIBAVW_SCALER_BILINEAR 0 +#define LIBAVW_SCALER_BICUBIC 1 +#define LIBAVW_SCALER_X 2 +#define LIBAVW_SCALER_POINT 3 +#define LIBAVW_SCALER_AREA 4 +#define LIBAVW_SCALER_BICUBLIN 5 +#define LIBAVW_SCALER_GAUSS 6 +#define LIBAVW_SCALER_SINC 7 +#define LIBAVW_SCALER_LANCZOS 8 +#define LIBAVW_SCALER_SPLINE 9 +// output format +#define LIBAVW_PIXEL_FORMAT_BGR 0 +#define LIBAVW_PIXEL_FORMAT_BGRA 1 +// print levels +#define LIBAVW_PRINT_WARNING 1 +#define LIBAVW_PRINT_ERROR 2 +#define LIBAVW_PRINT_FATAL 3 +#define LIBAVW_PRINT_PANIC 4 +// exported callback functions: +typedef void avwCallbackPrint(int, const char *); +typedef int avwCallbackIoRead(void *, unsigned char *, int); +typedef fs_offset_t avwCallbackIoSeek(void *, fs_offset_t, int); +typedef fs_offset_t avwCallbackIoSeekSize(void *); +// exported functions: +int (*qLibAvW_Init)(avwCallbackPrint *printfunction); // init library, returns error code +const char *(*qLibAvW_ErrorString)(int errorcode); // get string for error code +const char *(*qLibAvW_AvcVersion)(void); // get a string containing libavcodec version wrapper was built for +float (*qLibAvW_Version)(void); // get wrapper version +int (*qLibAvW_CreateStream)(void **stream); // create stream, returns error code +void (*qLibAvW_RemoveStream)(void *stream); // flush and remove stream +int (*qLibAvW_StreamGetVideoWidth)(void *stream); // get video parameters of stream +int (*qLibAvW_StreamGetVideoHeight)(void *stream); +double (*qLibAvW_StreamGetFramerate)(void *stream); +int (*qLibAvW_StreamGetError)(void *stream); // get last function errorcode from stream +// simple API to play video +int (*qLibAvW_PlayVideo)(void *stream, void *file, avwCallbackIoRead *IoRead, avwCallbackIoSeek *IoSeek, avwCallbackIoSeekSize *IoSeekSize); +int (*qLibAvW_PlaySeekNextFrame)(void *stream); +int (*qLibAvW_PlayGetFrameImage)(void *stream, int pixel_format, void *imagedata, int imagewidth, int imageheight, int scaler); + +static dllfunction_t libavwfuncs[] = +{ + {"LibAvW_Init", (void **) &qLibAvW_Init }, + {"LibAvW_ErrorString", (void **) &qLibAvW_ErrorString }, + {"LibAvW_AvcVersion", (void **) &qLibAvW_AvcVersion }, + {"LibAvW_Version", (void **) &qLibAvW_Version }, + {"LibAvW_CreateStream", (void **) &qLibAvW_CreateStream }, + {"LibAvW_RemoveStream", (void **) &qLibAvW_RemoveStream }, + {"LibAvW_StreamGetVideoWidth", (void **) &qLibAvW_StreamGetVideoWidth }, + {"LibAvW_StreamGetVideoHeight",(void **) &qLibAvW_StreamGetVideoHeight }, + {"LibAvW_StreamGetFramerate", (void **) &qLibAvW_StreamGetFramerate }, + {"LibAvW_StreamGetError", (void **) &qLibAvW_StreamGetError }, + {"LibAvW_PlayVideo", (void **) &qLibAvW_PlayVideo }, + {"LibAvW_PlaySeekNextFrame", (void **) &qLibAvW_PlaySeekNextFrame }, + {"LibAvW_PlayGetFrameImage", (void **) &qLibAvW_PlayGetFrameImage }, + {NULL, NULL} +}; + +const char* dllnames_libavw[] = +{ +#if defined(WIN32) + "libavw.dll", +#elif defined(MACOSX) + "libavw.dylib", +#else + "libavw.so.1", + "libavw.so", +#endif + NULL +}; + +static dllhandle_t libavw_dll = NULL; + +// DP videostream +typedef struct libavwstream_s +{ + qfile_t *file; + double info_framerate; + unsigned int info_imagewidth; + unsigned int info_imageheight; + double info_aspectratio; + void *stream; + + // channel the sound file is being played on + sfx_t *sfx; + int sndchan; + int sndstarted; +} +libavwstream_t; + +cvar_t cl_video_libavw_minwidth = {CVAR_SAVE, "cl_video_libavw_minwidth", "0", "if videos width is lesser than minimal, thay will be upscaled"}; +cvar_t cl_video_libavw_minheight = {CVAR_SAVE, "cl_video_libavw_minheight", "0", "if videos height is lesser than minimal, thay will be upscaled"}; +cvar_t cl_video_libavw_scaler = {CVAR_SAVE, "cl_video_libavw_scaler", "1", "selects a scaler for libavcode played videos. Scalers are: 0 - bilinear, 1 - bicubic, 2 - x, 3 - point, 4 - area, 5 - bicublin, 6 - gauss, 7 - sinc, 8 - lanczos, 9 - spline."}; + +// video extensions +const char* libavw_extensions[] = +{ + "ogv", + "avi", + "mpg", + "mp4", + "mkv", + "webm", + "bik", + "roq", + "flv", + "wmv", + "mpeg", + "mjpeg", + "mpeg4", + NULL +}; + +/* +================================================================= + + Video decoding + a features that is not supported yet and likely to be done + - streaming audio from videofiles + - streaming subtitles + +================================================================= +*/ + +unsigned int libavw_getwidth(void *stream); +unsigned int libavw_getheight(void *stream); +double libavw_getframerate(void *stream); +double libavw_getaspectratio(void *stream); +void libavw_close(void *stream); + +static int libavw_decodeframe(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) +{ + int pixel_format = LIBAVW_PIXEL_FORMAT_BGR; + int errorcode; + + libavwstream_t *s = (libavwstream_t *)stream; + + // start sound + if (!s->sndstarted) + { + if (s->sfx != NULL) + s->sndchan = S_StartSound(-1, 0, s->sfx, vec3_origin, 1.0f, 0); + s->sndstarted = 1; + } + + // read frame + if (!qLibAvW_PlaySeekNextFrame(s->stream)) + { + // got error or file end + errorcode = qLibAvW_StreamGetError(s->stream); + if (errorcode) + Con_Printf("LibAvW: %s\n", qLibAvW_ErrorString(errorcode)); + return 1; + } + + // decode into bgr texture + if (bytesperpixel == 4) + pixel_format = LIBAVW_PIXEL_FORMAT_BGRA; + else if (bytesperpixel == 3) + pixel_format = LIBAVW_PIXEL_FORMAT_BGR; + else + { + Con_Printf("LibAvW: cannot determine pixel format for bpp %i\n", bytesperpixel); + return 1; + } + if (!qLibAvW_PlayGetFrameImage(s->stream, pixel_format, imagedata, s->info_imagewidth, s->info_imageheight, min(9, max(0, cl_video_libavw_scaler.integer)))) + Con_Printf("LibAvW: %s\n", qLibAvW_ErrorString(qLibAvW_StreamGetError(s->stream))); + return 0; +} + +// get stream info +unsigned int libavw_getwidth(void *stream) +{ + return ((libavwstream_t *)stream)->info_imagewidth; +} + +unsigned int libavw_getheight(void *stream) +{ + return ((libavwstream_t *)stream)->info_imageheight; +} + +double libavw_getframerate(void *stream) +{ + return ((libavwstream_t *)stream)->info_framerate; +} + +double libavw_getaspectratio(void *stream) +{ + return ((libavwstream_t *)stream)->info_aspectratio; +} + +// close stream +void libavw_close(void *stream) +{ + libavwstream_t *s = (libavwstream_t *)stream; + + if (s->stream) + qLibAvW_RemoveStream(s->stream); + s->stream = NULL; + if (s->file) + FS_Close(s->file); + s->file = NULL; + if (s->sndchan >= 0) + S_StopChannel(s->sndchan, true, true); + s->sndchan = -1; +} + +// IO wrapper +static int LibAvW_FS_Read(void *opaque, unsigned char *buf, int buf_size) +{ + return FS_Read((qfile_t *)opaque, buf, buf_size); +} +static fs_offset_t LibAvW_FS_Seek(void *opaque, fs_offset_t pos, int whence) +{ + return (fs_offset_t)FS_Seek((qfile_t *)opaque, pos, whence); +} +static fs_offset_t LibAvW_FS_SeekSize(void *opaque) +{ + return (fs_offset_t)FS_FileSize((qfile_t *)opaque); +} + +// open as DP video stream +static void *LibAvW_OpenVideo(clvideo_t *video, char *filename, const char **errorstring) +{ + libavwstream_t *s; + char filebase[MAX_OSPATH], check[MAX_OSPATH]; + unsigned int i; + int errorcode; + char *wavename; + size_t len; + + if (!libavw_dll) + return NULL; + + // allocate stream + s = (libavwstream_t *)Z_Malloc(sizeof(libavwstream_t)); + if (s == NULL) + { + *errorstring = "unable to allocate memory for stream info structure"; + return NULL; + } + memset(s, 0, sizeof(libavwstream_t)); + s->sndchan = -1; + + // open file + s->file = FS_OpenVirtualFile(filename, true); + if (!s->file) + { + FS_StripExtension(filename, filebase, sizeof(filebase)); + // we tried .dpv, try another extensions + for (i = 0; libavw_extensions[i] != NULL; i++) + { + dpsnprintf(check, sizeof(check), "%s.%s", filebase, libavw_extensions[i]); + s->file = FS_OpenVirtualFile(check, true); + if (s->file) + break; + } + if (!s->file) + { + *errorstring = "unable to open videofile"; + libavw_close(s); + Z_Free(s); + return NULL; + } + } + + // allocate libavw stream + if ((errorcode = qLibAvW_CreateStream(&s->stream))) + { + *errorstring = qLibAvW_ErrorString(errorcode); + libavw_close(s); + Z_Free(s); + return NULL; + } + + // open video for playing + if (!qLibAvW_PlayVideo(s->stream, s->file, &LibAvW_FS_Read, &LibAvW_FS_Seek, &LibAvW_FS_SeekSize)) + { + *errorstring = qLibAvW_ErrorString(qLibAvW_StreamGetError(s->stream)); + libavw_close(s); + Z_Free(s); + return NULL; + } + + // all right, start codec + s->info_imagewidth = qLibAvW_StreamGetVideoWidth(s->stream); + s->info_imageheight = qLibAvW_StreamGetVideoHeight(s->stream); + s->info_framerate = qLibAvW_StreamGetFramerate(s->stream); + s->info_aspectratio = (double)s->info_imagewidth / (double)s->info_imageheight; + video->close = libavw_close; + video->getwidth = libavw_getwidth; + video->getheight = libavw_getheight; + video->getframerate = libavw_getframerate; + video->decodeframe = libavw_decodeframe; + video->getaspectratio = libavw_getaspectratio; + + // apply min-width, min-height, keep aspect rate + if (cl_video_libavw_minwidth.integer > 0) + s->info_imagewidth = max(s->info_imagewidth, (unsigned int)cl_video_libavw_minwidth.integer); + if (cl_video_libavw_minheight.integer > 0) + s->info_imageheight = max(s->info_imageheight, (unsigned int)cl_video_libavw_minheight.integer); + + // provide sound in separate .wav + len = strlen(filename) + 10; + wavename = (char *)Z_Malloc(len); + if (wavename) + { + FS_StripExtension(filename, wavename, len-1); + strlcat(wavename, ".wav", len); + s->sfx = S_PrecacheSound(wavename, false, false); + s->sndchan = -1; + Z_Free(wavename); + } + return s; +} + +static void libavw_message(int level, const char *message) +{ + if (level == LIBAVW_PRINT_WARNING) + Con_Printf("LibAvcodec warning: %s\n", message); + else if (level == LIBAVW_PRINT_ERROR) + Con_Printf("LibAvcodec error: %s\n", message); + else if (level == LIBAVW_PRINT_FATAL) + Con_Printf("LibAvcodec fatal error: %s\n", message); + else + Con_Printf("LibAvcodec panic: %s\n", message); +} + +static qboolean LibAvW_OpenLibrary(void) +{ + int errorcode; + + // COMMANDLINEOPTION: Video: -nolibavw disables libavcodec wrapper support + if (COM_CheckParm("-nolibavw")) + return false; + + // load DLL's + Sys_LoadLibrary(dllnames_libavw, &libavw_dll, libavwfuncs); + if (!libavw_dll) + return false; + + // initialize libav wrapper + if ((errorcode = qLibAvW_Init(&libavw_message))) + { + Con_Printf("LibAvW failed to initialize: %s\n", qLibAvW_ErrorString(errorcode)); + Sys_UnloadLibrary(&libavw_dll); + } + + Cvar_RegisterVariable(&cl_video_libavw_minwidth); + Cvar_RegisterVariable(&cl_video_libavw_minheight); + Cvar_RegisterVariable(&cl_video_libavw_scaler); + + return true; +} + +static void LibAvW_CloseLibrary(void) +{ + Sys_UnloadLibrary(&libavw_dll); +} + diff --git a/app/jni/client.h b/app/jni/client.h new file mode 100644 index 0000000..8e4e74c --- /dev/null +++ b/app/jni/client.h @@ -0,0 +1,2052 @@ +/* +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. + +*/ +// client.h + +#ifndef CLIENT_H +#define CLIENT_H + +#include "matrixlib.h" +#include "snd_main.h" + +// NOTE: r_stat_name[] must match this indexing +typedef enum r_stat_e +{ + r_stat_timedelta, + r_stat_quality, + r_stat_renders, + r_stat_entities, + r_stat_entities_surfaces, + r_stat_entities_triangles, + r_stat_world_leafs, + r_stat_world_portals, + r_stat_world_surfaces, + r_stat_world_triangles, + r_stat_lightmapupdates, + r_stat_lightmapupdatepixels, + r_stat_particles, + r_stat_drawndecals, + r_stat_totaldecals, + r_stat_draws, + r_stat_draws_vertices, + r_stat_draws_elements, + r_stat_lights, + r_stat_lights_clears, + r_stat_lights_scissored, + r_stat_lights_lighttriangles, + r_stat_lights_shadowtriangles, + r_stat_lights_dynamicshadowtriangles, + r_stat_bouncegrid_lights, + r_stat_bouncegrid_particles, + r_stat_bouncegrid_traces, + r_stat_bouncegrid_hits, + r_stat_bouncegrid_splats, + r_stat_bouncegrid_bounces, + r_stat_photoncache_animated, + r_stat_photoncache_cached, + r_stat_photoncache_traced, + r_stat_bloom, + r_stat_bloom_copypixels, + r_stat_bloom_drawpixels, + r_stat_indexbufferuploadcount, + r_stat_indexbufferuploadsize, + r_stat_vertexbufferuploadcount, + r_stat_vertexbufferuploadsize, + r_stat_framedatacurrent, + r_stat_framedatasize, + r_stat_bufferdatacurrent_vertex, // R_BUFFERDATA_ types are added to this index + r_stat_bufferdatacurrent_index16, + r_stat_bufferdatacurrent_index32, + r_stat_bufferdatacurrent_uniform, + r_stat_bufferdatasize_vertex, // R_BUFFERDATA_ types are added to this index + r_stat_bufferdatasize_index16, + r_stat_bufferdatasize_index32, + r_stat_bufferdatasize_uniform, + r_stat_animcache_vertexmesh_count, + r_stat_animcache_vertexmesh_vertices, + r_stat_animcache_vertexmesh_maxvertices, + r_stat_animcache_skeletal_count, + r_stat_animcache_skeletal_bones, + r_stat_animcache_skeletal_maxbones, + r_stat_animcache_shade_count, + r_stat_animcache_shade_vertices, + r_stat_animcache_shade_maxvertices, + r_stat_animcache_shape_count, + r_stat_animcache_shape_vertices, + r_stat_animcache_shape_maxvertices, + r_stat_batch_batches, + r_stat_batch_withgaps, + r_stat_batch_surfaces, + r_stat_batch_vertices, + r_stat_batch_triangles, + r_stat_batch_fast_batches, + r_stat_batch_fast_surfaces, + r_stat_batch_fast_vertices, + r_stat_batch_fast_triangles, + r_stat_batch_copytriangles_batches, + r_stat_batch_copytriangles_surfaces, + r_stat_batch_copytriangles_vertices, + r_stat_batch_copytriangles_triangles, + r_stat_batch_dynamic_batches, + r_stat_batch_dynamic_surfaces, + r_stat_batch_dynamic_vertices, + r_stat_batch_dynamic_triangles, + r_stat_batch_dynamicskeletal_batches, + r_stat_batch_dynamicskeletal_surfaces, + r_stat_batch_dynamicskeletal_vertices, + r_stat_batch_dynamicskeletal_triangles, + r_stat_batch_dynamic_batches_because_cvar, + r_stat_batch_dynamic_surfaces_because_cvar, + r_stat_batch_dynamic_vertices_because_cvar, + r_stat_batch_dynamic_triangles_because_cvar, + r_stat_batch_dynamic_batches_because_lightmapvertex, + r_stat_batch_dynamic_surfaces_because_lightmapvertex, + r_stat_batch_dynamic_vertices_because_lightmapvertex, + r_stat_batch_dynamic_triangles_because_lightmapvertex, + r_stat_batch_dynamic_batches_because_deformvertexes_autosprite, + r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite, + r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite, + r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite, + r_stat_batch_dynamic_batches_because_deformvertexes_autosprite2, + r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite2, + r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite2, + r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite2, + r_stat_batch_dynamic_batches_because_deformvertexes_normal, + r_stat_batch_dynamic_surfaces_because_deformvertexes_normal, + r_stat_batch_dynamic_vertices_because_deformvertexes_normal, + r_stat_batch_dynamic_triangles_because_deformvertexes_normal, + r_stat_batch_dynamic_batches_because_deformvertexes_wave, + r_stat_batch_dynamic_surfaces_because_deformvertexes_wave, + r_stat_batch_dynamic_vertices_because_deformvertexes_wave, + r_stat_batch_dynamic_triangles_because_deformvertexes_wave, + r_stat_batch_dynamic_batches_because_deformvertexes_bulge, + r_stat_batch_dynamic_surfaces_because_deformvertexes_bulge, + r_stat_batch_dynamic_vertices_because_deformvertexes_bulge, + r_stat_batch_dynamic_triangles_because_deformvertexes_bulge, + r_stat_batch_dynamic_batches_because_deformvertexes_move, + r_stat_batch_dynamic_surfaces_because_deformvertexes_move, + r_stat_batch_dynamic_vertices_because_deformvertexes_move, + r_stat_batch_dynamic_triangles_because_deformvertexes_move, + r_stat_batch_dynamic_batches_because_tcgen_lightmap, + r_stat_batch_dynamic_surfaces_because_tcgen_lightmap, + r_stat_batch_dynamic_vertices_because_tcgen_lightmap, + r_stat_batch_dynamic_triangles_because_tcgen_lightmap, + r_stat_batch_dynamic_batches_because_tcgen_vector, + r_stat_batch_dynamic_surfaces_because_tcgen_vector, + r_stat_batch_dynamic_vertices_because_tcgen_vector, + r_stat_batch_dynamic_triangles_because_tcgen_vector, + r_stat_batch_dynamic_batches_because_tcgen_environment, + r_stat_batch_dynamic_surfaces_because_tcgen_environment, + r_stat_batch_dynamic_vertices_because_tcgen_environment, + r_stat_batch_dynamic_triangles_because_tcgen_environment, + r_stat_batch_dynamic_batches_because_tcmod_turbulent, + r_stat_batch_dynamic_surfaces_because_tcmod_turbulent, + r_stat_batch_dynamic_vertices_because_tcmod_turbulent, + r_stat_batch_dynamic_triangles_because_tcmod_turbulent, + r_stat_batch_dynamic_batches_because_interleavedarrays, + r_stat_batch_dynamic_surfaces_because_interleavedarrays, + r_stat_batch_dynamic_vertices_because_interleavedarrays, + r_stat_batch_dynamic_triangles_because_interleavedarrays, + r_stat_batch_dynamic_batches_because_nogaps, + r_stat_batch_dynamic_surfaces_because_nogaps, + r_stat_batch_dynamic_vertices_because_nogaps, + r_stat_batch_dynamic_triangles_because_nogaps, + r_stat_batch_dynamic_batches_because_derived, + r_stat_batch_dynamic_surfaces_because_derived, + r_stat_batch_dynamic_vertices_because_derived, + r_stat_batch_dynamic_triangles_because_derived, + r_stat_batch_entitycache_count, + r_stat_batch_entitycache_surfaces, + r_stat_batch_entitycache_vertices, + r_stat_batch_entitycache_triangles, + r_stat_batch_entityanimate_count, + r_stat_batch_entityanimate_surfaces, + r_stat_batch_entityanimate_vertices, + r_stat_batch_entityanimate_triangles, + r_stat_batch_entityskeletal_count, + r_stat_batch_entityskeletal_surfaces, + r_stat_batch_entityskeletal_vertices, + r_stat_batch_entityskeletal_triangles, + r_stat_batch_entitystatic_count, + r_stat_batch_entitystatic_surfaces, + r_stat_batch_entitystatic_vertices, + r_stat_batch_entitystatic_triangles, + r_stat_batch_entitycustom_count, + r_stat_batch_entitycustom_surfaces, + r_stat_batch_entitycustom_vertices, + r_stat_batch_entitycustom_triangles, + r_stat_count // size of array +} +r_stat_t; + +// flags for rtlight rendering +#define LIGHTFLAG_NORMALMODE 1 +#define LIGHTFLAG_REALTIMEMODE 2 + +typedef struct tridecal_s +{ + // color and initial alpha value + float texcoord2f[3][2]; + float vertex3f[3][3]; + float color4f[3][4]; + float plane[4]; // backface culling + // how long this decal has lived so far (the actual fade begins at cl_decals_time) + float lived; + // if >= 0 this indicates the decal should follow an animated triangle + int triangleindex; + // for visibility culling + int surfaceindex; + // old decals are killed to obey cl_decals_max + int decalsequence; +} +tridecal_t; + +typedef struct decalsystem_s +{ + dp_model_t *model; + double lastupdatetime; + int maxdecals; + int freedecal; + int numdecals; + tridecal_t *decals; + float *vertex3f; + float *texcoord2f; + float *color4f; + int *element3i; + unsigned short *element3s; +} +decalsystem_t; + +typedef struct effect_s +{ + int active; + vec3_t origin; + double starttime; + float framerate; + int modelindex; + int startframe; + int endframe; + // these are for interpolation + int frame; + double frame1time; + double frame2time; +} +cl_effect_t; + +typedef struct beam_s +{ + int entity; + // draw this as lightning polygons, or a model? + int lightning; + struct model_s *model; + float endtime; + vec3_t start, end; +} +beam_t; + +typedef struct rtlight_particle_s +{ + float origin[3]; + float color[3]; +} +rtlight_particle_t; + +typedef struct rtlight_s +{ + // shadow volumes are done entirely in model space, so there are no matrices for dealing with them... they just use the origin + + // note that the world to light matrices are inversely scaled (divided) by lightradius + + // core properties + /// matrix for transforming light filter coordinates to world coordinates + matrix4x4_t matrix_lighttoworld; + /// matrix for transforming world coordinates to light filter coordinates + matrix4x4_t matrix_worldtolight; + /// typically 1 1 1, can be lower (dim) or higher (overbright) + vec3_t color; + /// size of the light (remove?) + vec_t radius; + /// light filter + char cubemapname[64]; + /// light style to monitor for brightness + int style; + /// whether light should render shadows + int shadow; + /// intensity of corona to render + vec_t corona; + /// radius scale of corona to render (1.0 means same as light radius) + vec_t coronasizescale; + /// ambient intensity to render + vec_t ambientscale; + /// diffuse intensity to render + vec_t diffusescale; + /// specular intensity to render + vec_t specularscale; + /// LIGHTFLAG_* flags + int flags; + + // generated properties + /// used only for shadow volumes + vec3_t shadoworigin; + /// culling + vec3_t cullmins; + vec3_t cullmaxs; + // culling + //vec_t cullradius; + // squared cullradius + //vec_t cullradius2; + + // rendering properties, updated each time a light is rendered + // this is rtlight->color * d_lightstylevalue + vec3_t currentcolor; + /// used by corona updates, due to occlusion query + float corona_visibility; + unsigned int corona_queryindex_visiblepixels; + unsigned int corona_queryindex_allpixels; + /// this is R_GetCubemap(rtlight->cubemapname) + rtexture_t *currentcubemap; + /// set by R_Shadow_PrepareLight to decide whether R_Shadow_DrawLight should draw it + qboolean draw; + /// these fields are set by R_Shadow_PrepareLight for later drawing + int cached_numlightentities; + int cached_numlightentities_noselfshadow; + int cached_numshadowentities; + int cached_numshadowentities_noselfshadow; + int cached_numsurfaces; + struct entity_render_s **cached_lightentities; + struct entity_render_s **cached_lightentities_noselfshadow; + struct entity_render_s **cached_shadowentities; + struct entity_render_s **cached_shadowentities_noselfshadow; + unsigned char *cached_shadowtrispvs; + unsigned char *cached_lighttrispvs; + int *cached_surfacelist; + // reduced light cullbox from GetLightInfo + vec3_t cached_cullmins; + vec3_t cached_cullmaxs; + // current shadow-caster culling planes based on view + // (any geometry outside these planes can not contribute to the visible + // shadows in any way, and thus can be culled safely) + int cached_numfrustumplanes; + mplane_t cached_frustumplanes[5]; // see R_Shadow_ComputeShadowCasterCullingPlanes + + /// static light info + /// true if this light should be compiled as a static light + int isstatic; + /// true if this is a compiled world light, cleared if the light changes + int compiled; + /// the shadowing mode used to compile this light + int shadowmode; + /// premade shadow volumes to render for world entity + shadowmesh_t *static_meshchain_shadow_zpass; + shadowmesh_t *static_meshchain_shadow_zfail; + shadowmesh_t *static_meshchain_shadow_shadowmap; + /// used for visibility testing (more exact than bbox) + int static_numleafs; + int static_numleafpvsbytes; + int *static_leaflist; + unsigned char *static_leafpvs; + /// surfaces seen by light + int static_numsurfaces; + int *static_surfacelist; + /// flag bits indicating which triangles of the world model should cast + /// shadows, and which ones should be lit + /// + /// this avoids redundantly scanning the triangles in each surface twice + /// for whether they should cast shadows, once in culling and once in the + /// actual shadowmarklist production. + int static_numshadowtrispvsbytes; + unsigned char *static_shadowtrispvs; + /// this allows the lighting batch code to skip backfaces andother culled + /// triangles not relevant for lighting + /// (important on big surfaces such as terrain) + int static_numlighttrispvsbytes; + unsigned char *static_lighttrispvs; + /// masks of all shadowmap sides that have any potential static receivers or casters + int static_shadowmap_receivers; + int static_shadowmap_casters; + /// particle-tracing cache for global illumination + int particlecache_numparticles; + int particlecache_maxparticles; + int particlecache_updateparticle; + rtlight_particle_t *particlecache_particles; + + /// bouncegrid light info + float photoncolor[3]; + float photons; +} +rtlight_t; + +typedef struct dlight_s +{ + // destroy light after this time + // (dlight only) + vec_t die; + // the entity that owns this light (can be NULL) + // (dlight only) + struct entity_render_s *ent; + // location + // (worldlight: saved to .rtlights file) + vec3_t origin; + // worldlight orientation + // (worldlight only) + // (worldlight: saved to .rtlights file) + vec3_t angles; + // dlight orientation/scaling/location + // (dlight only) + matrix4x4_t matrix; + // color of light + // (worldlight: saved to .rtlights file) + vec3_t color; + // cubemap name to use on this light + // (worldlight: saved to .rtlights file) + char cubemapname[64]; + // make light flash while selected + // (worldlight only) + int selected; + // brightness (not really radius anymore) + // (worldlight: saved to .rtlights file) + vec_t radius; + // drop intensity this much each second + // (dlight only) + vec_t decay; + // intensity value which is dropped over time + // (dlight only) + vec_t intensity; + // initial values for intensity to modify + // (dlight only) + vec_t initialradius; + vec3_t initialcolor; + // light style which controls intensity of this light + // (worldlight: saved to .rtlights file) + int style; + // cast shadows + // (worldlight: saved to .rtlights file) + int shadow; + // corona intensity + // (worldlight: saved to .rtlights file) + vec_t corona; + // radius scale of corona to render (1.0 means same as light radius) + // (worldlight: saved to .rtlights file) + vec_t coronasizescale; + // ambient intensity to render + // (worldlight: saved to .rtlights file) + vec_t ambientscale; + // diffuse intensity to render + // (worldlight: saved to .rtlights file) + vec_t diffusescale; + // specular intensity to render + // (worldlight: saved to .rtlights file) + vec_t specularscale; + // LIGHTFLAG_* flags + // (worldlight: saved to .rtlights file) + int flags; + // linked list of world lights + // (worldlight only) + struct dlight_s *next; + // embedded rtlight struct for renderer + // (worldlight only) + rtlight_t rtlight; +} +dlight_t; + +// this is derived from processing of the framegroupblend array +// note: technically each framegroupblend can produce two of these, but that +// never happens in practice because no one blends between more than 2 +// framegroups at once +#define MAX_FRAMEBLENDS (MAX_FRAMEGROUPBLENDS * 2) +typedef struct frameblend_s +{ + int subframe; + float lerp; +} +frameblend_t; + +// LordHavoc: this struct is intended for the renderer but some fields are +// used by the client. +// +// The renderer should not rely on any changes to this struct to be persistent +// across multiple frames because temp entities are wiped every frame, but it +// is acceptable to cache things in this struct that are not critical. +// +// For example the r_cullentities_trace code does such caching. +typedef struct entity_render_s +{ + // location + //vec3_t origin; + // orientation + //vec3_t angles; + // transform matrix for model to world + matrix4x4_t matrix; + // transform matrix for world to model + matrix4x4_t inversematrix; + // opacity (alpha) of the model + float alpha; + // size the model is shown + float scale; + // transparent sorting offset + float transparent_offset; + + // NULL = no model + dp_model_t *model; + // number of the entity represents, or 0 for non-network entities + int entitynumber; + // literal colormap colors for renderer, if both are 0 0 0 it is not colormapped + vec3_t colormap_pantscolor; + vec3_t colormap_shirtcolor; + // light, particles, etc + int effects; + // qw CTF flags and other internal-use-only effect bits + int internaleffects; + // for Alias models + int skinnum; + // render flags + int flags; + + // colormod tinting of models + float colormod[3]; + float glowmod[3]; + + // interpolated animation - active framegroups and blend factors + framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; + + // time of last model change (for shader animations) + double shadertime; + + // calculated by the renderer (but not persistent) + + // calculated during R_AddModelEntities + vec3_t mins, maxs; + // subframe numbers (-1 if not used) and their blending scalers (0-1), if interpolation is not desired, use subframeblend[0].subframe + frameblend_t frameblend[MAX_FRAMEBLENDS]; + // skeletal animation data (if skeleton.relativetransforms is not NULL, it overrides frameblend) + skeleton_t *skeleton; + + // animation cache (pointers allocated using R_FrameData_Alloc) + // ONLY valid during R_RenderView! may be NULL (not cached) + float *animcache_vertex3f; + r_meshbuffer_t *animcache_vertex3f_vertexbuffer; + int animcache_vertex3f_bufferoffset; + float *animcache_normal3f; + r_meshbuffer_t *animcache_normal3f_vertexbuffer; + int animcache_normal3f_bufferoffset; + float *animcache_svector3f; + r_meshbuffer_t *animcache_svector3f_vertexbuffer; + int animcache_svector3f_bufferoffset; + float *animcache_tvector3f; + r_meshbuffer_t *animcache_tvector3f_vertexbuffer; + int animcache_tvector3f_bufferoffset; + // interleaved arrays for rendering and dynamic vertex buffers for them + r_vertexmesh_t *animcache_vertexmesh; + r_meshbuffer_t *animcache_vertexmesh_vertexbuffer; + int animcache_vertexmesh_bufferoffset; + // gpu-skinning shader needs transforms in a certain format, we have to + // upload this to a uniform buffer for the shader to use, and also keep a + // backup copy in system memory for the dynamic batch fallback code + // if this is not NULL, the other animcache variables are NULL + float *animcache_skeletaltransform3x4; + r_meshbuffer_t *animcache_skeletaltransform3x4buffer; + int animcache_skeletaltransform3x4offset; + int animcache_skeletaltransform3x4size; + + // current lighting from map (updated ONLY by client code, not renderer) + vec3_t modellight_ambient; + vec3_t modellight_diffuse; // q3bsp + vec3_t modellight_lightdir; // q3bsp + + // storage of decals on this entity + // (note: if allowdecals is set, be sure to call R_DecalSystem_Reset on removal!) + int allowdecals; + decalsystem_t decalsystem; + + // FIELDS UPDATED BY RENDERER: + // last time visible during trace culling + double last_trace_visibility; + + // user wavefunc parameters (from csqc) + vec_t userwavefunc_param[Q3WAVEFUNC_USER_COUNT]; +} +entity_render_t; + +typedef struct entity_persistent_s +{ + vec3_t trail_origin; // previous position for particle trail spawning + vec3_t oldorigin; // lerp + vec3_t oldangles; // lerp + vec3_t neworigin; // lerp + vec3_t newangles; // lerp + vec_t lerpstarttime; // lerp + vec_t lerpdeltatime; // lerp + float muzzleflash; // muzzleflash intensity, fades over time + float trail_time; // residual error accumulation for particle trail spawning (to keep spacing across frames) + qboolean trail_allowed; // set to false by teleports, true by update code, prevents bad lerps +} +entity_persistent_t; + +typedef struct entity_s +{ + // baseline state (default values) + entity_state_t state_baseline; + // previous state (interpolating from this) + entity_state_t state_previous; + // current state (interpolating to this) + entity_state_t state_current; + + // used for regenerating parts of render + entity_persistent_t persistent; + + // the only data the renderer should know about + entity_render_t render; +} +entity_t; + +typedef struct usercmd_s +{ + vec3_t viewangles; + +// intended velocities + float forwardmove; + float sidemove; + float upmove; + + vec3_t cursor_screen; + vec3_t cursor_start; + vec3_t cursor_end; + vec3_t cursor_impact; + vec3_t cursor_normal; + vec_t cursor_fraction; + int cursor_entitynumber; + + double time; // time the move is executed for (cl_movement: clienttime, non-cl_movement: receivetime) + double receivetime; // time the move was received at + double clienttime; // time to which server state the move corresponds to + int msec; // for predicted moves + int buttons; + int impulse; + int sequence; + qboolean applied; // if false we're still accumulating a move + qboolean predicted; // if true the sequence should be sent as 0 + + // derived properties + double frametime; + qboolean canjump; + qboolean jump; + qboolean crouch; +} usercmd_t; + +typedef struct lightstyle_s +{ + int length; + char map[MAX_STYLESTRING]; +} lightstyle_t; + +typedef struct scoreboard_s +{ + char name[MAX_SCOREBOARDNAME]; + int frags; + int colors; // two 4 bit fields + // QW fields: + int qw_userid; + char qw_userinfo[MAX_USERINFO_STRING]; + float qw_entertime; + int qw_ping; + int qw_packetloss; + int qw_movementloss; + int qw_spectator; + char qw_team[8]; + char qw_skin[MAX_QPATH]; +} scoreboard_t; + +typedef struct cshift_s +{ + float destcolor[3]; + float percent; // 0-255 + float alphafade; // (any speed) +} cshift_t; + +#define CSHIFT_CONTENTS 0 +#define CSHIFT_DAMAGE 1 +#define CSHIFT_BONUS 2 +#define CSHIFT_POWERUP 3 +#define CSHIFT_VCSHIFT 4 +#define NUM_CSHIFTS 5 + +#define NAME_LENGTH 64 + + +// +// client_state_t should hold all pieces of the client state +// + +#define SIGNONS 4 // signon messages to receive before connected + +typedef enum cactive_e +{ + ca_uninitialized, // during early startup + ca_dedicated, // a dedicated server with no ability to start a client + ca_disconnected, // full screen console with no connection + ca_connected // valid netcon, talking to a server +} +cactive_t; + +typedef enum qw_downloadtype_e +{ + dl_none, + dl_single, + dl_skin, + dl_model, + dl_sound +} +qw_downloadtype_t; + +typedef enum capturevideoformat_e +{ + CAPTUREVIDEOFORMAT_AVI_I420, + CAPTUREVIDEOFORMAT_OGG_VORBIS_THEORA +} +capturevideoformat_t; + +typedef struct capturevideostate_s +{ + double startrealtime; + double framerate; + int framestep; + int framestepframe; + qboolean active; + qboolean realtime; + qboolean error; + int soundrate; + int soundchannels; + int frame; + double starttime; + double lastfpstime; + int lastfpsframe; + int soundsampleframe; + unsigned char *screenbuffer; + unsigned char *outbuffer; + char basename[MAX_QPATH]; + int width, height; + + // precomputed RGB to YUV tables + // converts the RGB values to YUV (see cap_avi.c for how to use them) + short rgbtoyuvscaletable[3][3][256]; + unsigned char yuvnormalizetable[3][256]; + + // precomputed gamma ramp (only needed if the capturevideo module uses RGB output) + // note: to map from these values to RGB24, you have to multiply by 255.0/65535.0, then add 0.5, then cast to integer + unsigned short vidramp[256 * 3]; + + // stuff to be filled in by the video format module + capturevideoformat_t format; + const char *formatextension; + qfile_t *videofile; + // always use this: + // cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false); + void (*endvideo) (void); + void (*videoframes) (int num); + void (*soundframe) (const portable_sampleframe_t *paintbuffer, size_t length); + + // format specific data + void *formatspecific; +} +capturevideostate_t; + +#define CL_MAX_DOWNLOADACKS 4 + +typedef struct cl_downloadack_s +{ + int start, size; +} +cl_downloadack_t; + +typedef struct cl_soundstats_s +{ + int mixedsounds; + int totalsounds; + int latency_milliseconds; +} +cl_soundstats_t; + +// +// the client_static_t structure is persistent through an arbitrary number +// of server connections +// +typedef struct client_static_s +{ + cactive_t state; + + // all client memory allocations go in these pools + mempool_t *levelmempool; + mempool_t *permanentmempool; + +// demo loop control + // -1 = don't play demos + int demonum; + // list of demos in loop + char demos[MAX_DEMOS][MAX_DEMONAME]; + // the actively playing demo (set by CL_PlayDemo_f) + char demoname[MAX_QPATH]; + +// demo recording info must be here, because record is started before +// entering a map (and clearing client_state_t) + qboolean demorecording; + fs_offset_t demo_lastcsprogssize; + int demo_lastcsprogscrc; + qboolean demoplayback; + qboolean demostarting; // set if currently starting a demo, to stop -demo from quitting when switching to another demo + qboolean timedemo; + // -1 = use normal cd track + int forcetrack; + qfile_t *demofile; + // realtime at second frame of timedemo (LordHavoc: changed to double) + double td_starttime; + int td_frames; // total frames parsed + double td_onesecondnexttime; + double td_onesecondframes; + double td_onesecondrealtime; + double td_onesecondminfps; + double td_onesecondmaxfps; + double td_onesecondavgfps; + int td_onesecondavgcount; + // LordHavoc: pausedemo + qboolean demopaused; + + // sound mixer statistics for showsound display + cl_soundstats_t soundstats; + + qboolean connect_trying; + int connect_remainingtries; + double connect_nextsendtime; + lhnetsocket_t *connect_mysocket; + lhnetaddress_t connect_address; + // protocol version of the server we're connected to + // (kept outside client_state_t because it's used between levels) + protocolversion_t protocol; + +#define MAX_RCONS 16 + int rcon_trying; + lhnetaddress_t rcon_addresses[MAX_RCONS]; + char rcon_commands[MAX_RCONS][MAX_INPUTLINE]; + double rcon_timeout[MAX_RCONS]; + int rcon_ringpos; + +// connection information + // 0 to SIGNONS + int signon; + // network connection + netconn_t *netcon; + + // download information + // (note: qw_download variables are also used) + cl_downloadack_t dp_downloadack[CL_MAX_DOWNLOADACKS]; + + // input sequence numbers are not reset on level change, only connect + int movesequence; + int servermovesequence; + + // quakeworld stuff below + + // value of "qport" cvar at time of connection + int qw_qport; + // copied from cls.netcon->qw. variables every time they change, or set by demos (which have no cls.netcon) + int qw_incoming_sequence; + int qw_outgoing_sequence; + + // current file download buffer (only saved when file is completed) + char qw_downloadname[MAX_QPATH]; + unsigned char *qw_downloadmemory; + int qw_downloadmemorycursize; + int qw_downloadmemorymaxsize; + int qw_downloadnumber; + int qw_downloadpercent; + qw_downloadtype_t qw_downloadtype; + // transfer rate display + double qw_downloadspeedtime; + int qw_downloadspeedcount; + int qw_downloadspeedrate; + qboolean qw_download_deflate; + + // current file upload buffer (for uploading screenshots to server) + unsigned char *qw_uploaddata; + int qw_uploadsize; + int qw_uploadpos; + + // user infostring + // this normally contains the following keys in quakeworld: + // password spectator name team skin topcolor bottomcolor rate noaim msg *ver *ip + char userinfo[MAX_USERINFO_STRING]; + + // extra user info for the "connect" command + char connect_userinfo[MAX_USERINFO_STRING]; + + // video capture stuff + capturevideostate_t capturevideo; + + // crypto channel + crypto_t crypto; + + // ProQuake compatibility stuff + int proquake_servermod; // 0 = not proquake, 1 = proquake + int proquake_serverversion; // actual proquake server version * 10 (3.40 = 34, etc) + int proquake_serverflags; // 0 (PQF_CHEATFREE not supported) + + // don't write-then-read csprogs.dat (useful for demo playback) + unsigned char *caughtcsprogsdata; + fs_offset_t caughtcsprogsdatasize; + + int r_speeds_graph_length; + int r_speeds_graph_current; + int *r_speeds_graph_data; + + // graph scales + int r_speeds_graph_datamin[r_stat_count]; + int r_speeds_graph_datamax[r_stat_count]; +} +client_static_t; + +extern client_static_t cls; + +typedef struct client_movementqueue_s +{ + double time; + float frametime; + int sequence; + float viewangles[3]; + float move[3]; + qboolean jump; + qboolean crouch; + qboolean canjump; +} +client_movementqueue_t; + +//[515]: csqc +typedef struct +{ + qboolean drawworld; + qboolean drawenginesbar; + qboolean drawcrosshair; +}csqc_vidvars_t; + +typedef enum +{ + PARTICLE_BILLBOARD = 0, + PARTICLE_SPARK = 1, + PARTICLE_ORIENTED_DOUBLESIDED = 2, + PARTICLE_VBEAM = 3, + PARTICLE_HBEAM = 4, + PARTICLE_INVALID = -1 +} +porientation_t; + +typedef enum +{ + PBLEND_ALPHA = 0, + PBLEND_ADD = 1, + PBLEND_INVMOD = 2, + PBLEND_INVALID = -1 +} +pblend_t; + +typedef struct particletype_s +{ + pblend_t blendmode; + porientation_t orientation; + qboolean lighting; +} +particletype_t; + +typedef enum ptype_e +{ + pt_dead, pt_alphastatic, pt_static, pt_spark, pt_beam, pt_rain, pt_raindecal, pt_snow, pt_bubble, pt_blood, pt_smoke, pt_decal, pt_entityparticle, pt_total +} +ptype_t; + +typedef struct decal_s +{ + // fields used by rendering: (44 bytes) + unsigned short typeindex; + unsigned short texnum; + int decalsequence; + vec3_t org; + vec3_t normal; + float size; + float alpha; // 0-255 + unsigned char color[3]; + unsigned char unused1; + int clusterindex; // cheap culling by pvs + + // fields not used by rendering: (36 bytes in 32bit, 40 bytes in 64bit) + float time2; // used for decal fade + unsigned int owner; // decal stuck to this entity + dp_model_t *ownermodel; // model the decal is stuck to (used to make sure the entity is still alive) + vec3_t relativeorigin; // decal at this location in entity's coordinate space + vec3_t relativenormal; // decal oriented this way relative to entity's coordinate space +} +decal_t; + +typedef struct particle_s +{ + // for faster batch rendering, particles are rendered in groups by effect (resulting in less perfect sorting but far less state changes) + + // fields used by rendering: (48 bytes) + vec3_t sortorigin; // sort by this group origin, not particle org + vec3_t org; + vec3_t vel; // velocity of particle, or orientation of decal, or end point of beam + float size; + float alpha; // 0-255 + float stretch; // only for sparks + + // fields not used by rendering: (44 bytes) + float stainsize; + float stainalpha; + float sizeincrease; // rate of size change per second + float alphafade; // how much alpha reduces per second + float time2; // used for snow fluttering and decal fade + float bounce; // how much bounce-back from a surface the particle hits (0 = no physics, 1 = stop and slide, 2 = keep bouncing forever, 1.5 is typical) + float gravity; // how much gravity affects this particle (1.0 = normal gravity, 0.0 = none) + float airfriction; // how much air friction affects this object (objects with a low mass/size ratio tend to get more air friction) + float liquidfriction; // how much liquid friction affects this object (objects with a low mass/size ratio tend to get more liquid friction) +// float delayedcollisions; // time that p->bounce becomes active + float delayedspawn; // time that particle appears and begins moving + float die; // time when this particle should be removed, regardless of alpha + + // short variables grouped to save memory (4 bytes) + short angle; // base rotation of particle + short spin; // geometry rotation speed around the particle center normal + + // byte variables grouped to save memory (12 bytes) + unsigned char color[3]; + unsigned char qualityreduction; // enables skipping of this particle according to r_refdef.view.qualityreduction + unsigned char typeindex; + unsigned char blendmode; + unsigned char orientation; + unsigned char texnum; + unsigned char staincolor[3]; + signed char staintexnum; +} +particle_t; + +typedef enum cl_parsingtextmode_e +{ + CL_PARSETEXTMODE_NONE, + CL_PARSETEXTMODE_PING, + CL_PARSETEXTMODE_STATUS, + CL_PARSETEXTMODE_STATUS_PLAYERID, + CL_PARSETEXTMODE_STATUS_PLAYERIP +} +cl_parsingtextmode_t; + +typedef struct cl_locnode_s +{ + struct cl_locnode_s *next; + char *name; + vec3_t mins, maxs; +} +cl_locnode_t; + +typedef struct showlmp_s +{ + qboolean isactive; + float x; + float y; + char label[32]; + char pic[128]; +} +showlmp_t; + +// +// the client_state_t structure is wiped completely at every +// server signon +// +typedef struct client_state_s +{ + // true if playing in a local game and no one else is connected + int islocalgame; + + // send a clc_nop periodically until connected + float sendnoptime; + + // current input being accumulated by mouse/joystick/etc input + usercmd_t cmd; + // latest moves sent to the server that have not been confirmed yet + usercmd_t movecmd[CL_MAX_USERCMDS]; + +// information for local display + // health, etc + int stats[MAX_CL_STATS]; + float *statsf; // points to stats[] array + // last known inventory bit flags, for blinking + int olditems; + // cl.time of acquiring item, for blinking + float item_gettime[32]; + // last known STAT_ACTIVEWEAPON + int activeweapon; + // cl.time of changing STAT_ACTIVEWEAPON + float weapontime; + // use pain anim frame if cl.time < this + float faceanimtime; + // for stair smoothing + float stairsmoothz; + double stairsmoothtime; + + // color shifts for damage, powerups + cshift_t cshifts[NUM_CSHIFTS]; + // and content types + cshift_t prev_cshifts[NUM_CSHIFTS]; + +// the client maintains its own idea of view angles, which are +// sent to the server each frame. The server sets punchangle when +// the view is temporarily offset, and an angle reset commands at the start +// of each level and after teleporting. + + //The increments for comfort mode + int comfortInc; + + // mviewangles is read from demo + // viewangles is either client controlled or lerped from mviewangles + vec3_t mviewangles[2], viewangles; + // update by server, used by qc to do weapon recoil + vec3_t mpunchangle[2], punchangle; + // update by server, can be used by mods to kick view around + vec3_t mpunchvector[2], punchvector; + // update by server, used for lean+bob (0 is newest) + vec3_t mvelocity[2], velocity; + // update by server, can be used by mods for zooming + vec_t mviewzoom[2], viewzoom; + // if true interpolation the mviewangles and other interpolation of the + // player is disabled until the next network packet + // this is used primarily by teleporters, and when spectating players + // special checking of the old fixangle[1] is used to differentiate + // between teleporting and spectating + qboolean fixangle[2]; + + // client movement simulation + // these fields are only updated by CL_ClientMovement (called by CL_SendMove after parsing each network packet) + // set by CL_ClientMovement_Replay functions + qboolean movement_predicted; + // if true the CL_ClientMovement_Replay function will update origin, etc + qboolean movement_replay; + // simulated data (this is valid even if cl.movement is false) + vec3_t movement_origin; + vec3_t movement_velocity; + // whether the replay should allow a jump at the first sequence + qboolean movement_replay_canjump; + + // previous gun angles (for leaning effects) + vec3_t gunangles_prev; + vec3_t gunangles_highpass; + vec3_t gunangles_adjustment_lowpass; + vec3_t gunangles_adjustment_highpass; + // previous gun angles (for leaning effects) + vec3_t gunorg_prev; + vec3_t gunorg_highpass; + vec3_t gunorg_adjustment_lowpass; + vec3_t gunorg_adjustment_highpass; + +// pitch drifting vars + float idealpitch; + float pitchvel; + qboolean nodrift; + float driftmove; + double laststop; + +//[515]: added for csqc purposes + float sensitivityscale; + csqc_vidvars_t csqc_vidvars; //[515]: these parms must be set to true by default + qboolean csqc_wantsmousemove; + qboolean csqc_paused; // vortex: int because could be flags + struct model_s *csqc_model_precache[MAX_MODELS]; + + // local amount for smoothing stepups + //float crouch; + + // sent by server + qboolean paused; + qboolean onground; + qboolean inwater; + + // used by bob + qboolean oldonground; + double lastongroundtime; + double hitgroundtime; + float bob2_smooth; + float bobfall_speed; + float bobfall_swing; + double calcrefdef_prevtime; + + // don't change view angle, full screen, etc + int intermission; + // latched at intermission start + double completed_time; + + // the timestamp of the last two messages + double mtime[2]; + + // clients view of time, time should be between mtime[0] and mtime[1] to + // generate a lerp point for other data, oldtime is the previous frame's + // value of time, frametime is the difference between time and oldtime + // note: cl.time may be beyond cl.mtime[0] if packet loss is occuring, it + // is only forcefully limited when a packet is received + double time, oldtime; + // how long it has been since the previous client frame in real time + // (not game time, for that use cl.time - cl.oldtime) + double realframetime; + + // fade var for fading while dead + float deathfade; + + // motionblur alpha level variable + float motionbluralpha; + + // copy of realtime from last recieved message, for net trouble icon + float last_received_message; + +// information that is static for the entire time connected to a server + struct model_s *model_precache[MAX_MODELS]; + struct sfx_s *sound_precache[MAX_SOUNDS]; + + // FIXME: this is a lot of memory to be keeping around, this really should be dynamically allocated and freed somehow + char model_name[MAX_MODELS][MAX_QPATH]; + char sound_name[MAX_SOUNDS][MAX_QPATH]; + + // for display on solo scoreboard + char worldmessage[40]; // map title (not related to filename) + // variants of map name + char worldbasename[MAX_QPATH]; // %s + char worldname[MAX_QPATH]; // maps/%s.bsp + char worldnamenoextension[MAX_QPATH]; // maps/%s + // cl_entitites[cl.viewentity] = player + int viewentity; + // the real player entity (normally same as viewentity, + // different than viewentity if mod uses chasecam or other tricks) + int realplayerentity; + // this is updated to match cl.viewentity whenever it is in the clients + // range, basically this is used in preference to cl.realplayerentity for + // most purposes because when spectating another player it should show + // their information rather than yours + int playerentity; + // max players that can be in this game + int maxclients; + // type of game (deathmatch, coop, singleplayer) + int gametype; + + // models and sounds used by engine code (particularly cl_parse.c) + dp_model_t *model_bolt; + dp_model_t *model_bolt2; + dp_model_t *model_bolt3; + dp_model_t *model_beam; + sfx_t *sfx_wizhit; + sfx_t *sfx_knighthit; + sfx_t *sfx_tink1; + sfx_t *sfx_ric1; + sfx_t *sfx_ric2; + sfx_t *sfx_ric3; + sfx_t *sfx_r_exp3; + // indicates that the file "sound/misc/talk2.wav" was found (for use by team chat messages) + qboolean foundtalk2wav; + +// refresh related state + + // cl_entitites[0].model + struct model_s *worldmodel; + + // the gun model + entity_t viewent; + + // cd audio + int cdtrack, looptrack; + +// frag scoreboard + + // [cl.maxclients] + scoreboard_t *scores; + + // keep track of svc_print parsing state (analyzes ping reports and status reports) + cl_parsingtextmode_t parsingtextmode; + int parsingtextplayerindex; + // set by scoreboard code when sending ping command, this causes the next ping results to be hidden + // (which could eat the wrong ping report if the player issues one + // manually, but they would still see a ping report, just a later one + // caused by the scoreboard code rather than the one they intentionally + // issued) + int parsingtextexpectingpingforscores; + + // entity database stuff + // latest received entity frame numbers +#define LATESTFRAMENUMS 32 + int latestframenumsposition; + int latestframenums[LATESTFRAMENUMS]; + int latestsendnums[LATESTFRAMENUMS]; + entityframe_database_t *entitydatabase; + entityframe4_database_t *entitydatabase4; + entityframeqw_database_t *entitydatabaseqw; + + // keep track of quake entities because they need to be killed if they get stale + int lastquakeentity; + unsigned char isquakeentity[MAX_EDICTS]; + + // bounding boxes for clientside movement + vec3_t playerstandmins; + vec3_t playerstandmaxs; + vec3_t playercrouchmins; + vec3_t playercrouchmaxs; + + // old decals are killed based on this + int decalsequence; + + int max_entities; + int max_csqcrenderentities; + int max_static_entities; + int max_effects; + int max_beams; + int max_dlights; + int max_lightstyle; + int max_brushmodel_entities; + int max_particles; + int max_decals; + int max_showlmps; + + entity_t *entities; + entity_render_t *csqcrenderentities; + unsigned char *entities_active; + entity_t *static_entities; + cl_effect_t *effects; + beam_t *beams; + dlight_t *dlights; + lightstyle_t *lightstyle; + int *brushmodel_entities; + particle_t *particles; + decal_t *decals; + showlmp_t *showlmps; + + int num_entities; + int num_static_entities; + int num_brushmodel_entities; + int num_effects; + int num_beams; + int num_dlights; + int num_particles; + int num_decals; + int num_showlmps; + + double particles_updatetime; + double decals_updatetime; + int free_particle; + int free_decal; + + // cl_serverextension_download feature + int loadmodel_current; + int downloadmodel_current; + int loadmodel_total; + int loadsound_current; + int downloadsound_current; + int loadsound_total; + qboolean downloadcsqc; + qboolean loadcsqc; + qboolean loadbegun; + qboolean loadfinished; + + // quakeworld stuff + + // local copy of the server infostring + char qw_serverinfo[MAX_SERVERINFO_STRING]; + + // time of last qw "pings" command sent to server while showing scores + double last_ping_request; + + // used during connect + int qw_servercount; + + // updated from serverinfo + int qw_teamplay; + + // unused: indicates whether the player is spectating + // use cl.scores[cl.playerentity-1].qw_spectator instead + //qboolean qw_spectator; + + // last time an input packet was sent + double lastpackettime; + + // movement parameters for client prediction + unsigned int moveflags; + float movevars_wallfriction; + float movevars_waterfriction; + float movevars_friction; + float movevars_timescale; + float movevars_gravity; + float movevars_stopspeed; + float movevars_maxspeed; + float movevars_spectatormaxspeed; + float movevars_accelerate; + float movevars_airaccelerate; + float movevars_wateraccelerate; + float movevars_entgravity; + float movevars_jumpvelocity; + float movevars_edgefriction; + float movevars_maxairspeed; + float movevars_stepheight; + float movevars_airaccel_qw; + float movevars_airaccel_qw_stretchfactor; + float movevars_airaccel_sideways_friction; + float movevars_airstopaccelerate; + float movevars_airstrafeaccelerate; + float movevars_maxairstrafespeed; + float movevars_airstrafeaccel_qw; + float movevars_aircontrol; + float movevars_aircontrol_power; + float movevars_aircontrol_penalty; + float movevars_warsowbunny_airforwardaccel; + float movevars_warsowbunny_accel; + float movevars_warsowbunny_topspeed; + float movevars_warsowbunny_turnaccel; + float movevars_warsowbunny_backtosideratio; + float movevars_ticrate; + float movevars_airspeedlimit_nonqw; + + // models used by qw protocol + int qw_modelindex_spike; + int qw_modelindex_player; + int qw_modelindex_flag; + int qw_modelindex_s_explod; + + vec3_t qw_intermission_origin; + vec3_t qw_intermission_angles; + + // 255 is the most nails the QW protocol could send + int qw_num_nails; + vec_t qw_nails[255][6]; + + float qw_weaponkick; + + int qw_validsequence; + + int qw_deltasequence[QW_UPDATE_BACKUP]; + + // csqc stuff: + // server entity number corresponding to a clientside entity + unsigned short csqc_server2csqcentitynumber[MAX_EDICTS]; + qboolean csqc_loaded; + vec3_t csqc_vieworigin; + vec3_t csqc_viewangles; + vec3_t csqc_vieworiginfromengine; + vec3_t csqc_viewanglesfromengine; + matrix4x4_t csqc_viewmodelmatrixfromengine; + qboolean csqc_usecsqclistener; + matrix4x4_t csqc_listenermatrix; + char csqc_printtextbuf[MAX_INPUTLINE]; + + // collision culling data + world_t world; + + // loc file stuff (points and boxes describing locations in the level) + cl_locnode_t *locnodes; + // this is updated to cl.movement_origin whenever health is < 1 + // used by %d print in say/say_team messages if cl_locs_enable is on + vec3_t lastdeathorigin; + + // processing buffer used by R_BuildLightMap, reallocated as needed, + // freed on each level change + size_t buildlightmapmemorysize; + unsigned char *buildlightmapmemory; + + // used by EntityState5_ReadUpdate + skeleton_t *engineskeletonobjects; +} +client_state_t; + +// +// cvars +// +extern cvar_t cl_name; +extern cvar_t cl_color; +extern cvar_t cl_rate; +extern cvar_t cl_pmodel; +extern cvar_t cl_playermodel; +extern cvar_t cl_playerskin; + +extern cvar_t rcon_password; +extern cvar_t rcon_address; + +extern cvar_t cl_upspeed; +extern cvar_t cl_forwardspeed; +extern cvar_t cl_backspeed; +extern cvar_t cl_sidespeed; + +extern cvar_t cl_movespeedkey; + +extern cvar_t cl_yawmode; +extern cvar_t cl_pitchmode; +extern cvar_t cl_comfort; +extern cvar_t cl_yawspeed; +extern cvar_t cl_pitchspeed; +extern cvar_t cl_yawmult; +extern cvar_t cl_pitchmult; +extern qboolean headtracking; + +extern cvar_t cl_anglespeedkey; + +extern cvar_t cl_autofire; + +extern cvar_t cl_shownet; +extern cvar_t cl_nolerp; +extern cvar_t cl_nettimesyncfactor; +extern cvar_t cl_nettimesyncboundmode; +extern cvar_t cl_nettimesyncboundtolerance; + +extern cvar_t cl_pitchdriftspeed; +extern cvar_t lookspring; +extern cvar_t lookstrafe; +extern cvar_t sensitivity; + +extern cvar_t freelook; + +extern cvar_t m_pitch; +extern cvar_t m_yaw; +extern cvar_t m_forward; +extern cvar_t m_side; + +extern cvar_t cl_autodemo; +extern cvar_t cl_autodemo_nameformat; +extern cvar_t cl_autodemo_delete; + +extern cvar_t r_draweffects; + +extern cvar_t cl_explosions_alpha_start; +extern cvar_t cl_explosions_alpha_end; +extern cvar_t cl_explosions_size_start; +extern cvar_t cl_explosions_size_end; +extern cvar_t cl_explosions_lifetime; +extern cvar_t cl_stainmaps; +extern cvar_t cl_stainmaps_clearonload; + +extern cvar_t cl_prydoncursor; +extern cvar_t cl_prydoncursor_notrace; + +extern cvar_t cl_locs_enable; + +extern client_state_t cl; + +extern void CL_AllocLightFlash (entity_render_t *ent, matrix4x4_t *matrix, float radius, float red, float green, float blue, float decay, float lifetime, int cubemapnum, int style, int shadowenable, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags); + +cl_locnode_t *CL_Locs_FindNearest(const vec3_t point); +void CL_Locs_FindLocationName(char *buffer, size_t buffersize, vec3_t point); + +//============================================================================= + +// +// cl_main +// + +void CL_Shutdown (void); +void CL_Init (void); + +void CL_EstablishConnection(const char *host, int firstarg); + +void CL_Disconnect (void); +void CL_Disconnect_f (void); + +void CL_UpdateRenderEntity(entity_render_t *ent); +void CL_SetEntityColormapColors(entity_render_t *ent, int colormap); +void CL_UpdateViewEntities(void); + +// +// cl_input +// +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} +kbutton_t; + +extern kbutton_t in_mlook, in_klook; +extern kbutton_t in_strafe; +extern kbutton_t in_speed; + +void CL_InitInput (void); +void CL_SendMove (void); + +void CL_ValidateState(entity_state_t *s); +void CL_MoveLerpEntityStates(entity_t *ent); +void CL_LerpUpdate(entity_t *e); +void CL_ParseTEnt (void); +void CL_NewBeam (int ent, vec3_t start, vec3_t end, dp_model_t *m, int lightning); +void CL_RelinkBeams (void); +void CL_Beam_CalculatePositions (const beam_t *b, vec3_t start, vec3_t end); +void CL_ClientMovement_Replay(void); + +void CL_ClearTempEntities (void); +entity_render_t *CL_NewTempEntity (double shadertime); + +void CL_Effect(vec3_t org, int modelindex, int startframe, int framecount, float framerate); + +void CL_ClearState (void); +void CL_ExpandEntities(int num); +void CL_ExpandCSQCRenderEntities(int num); +void CL_SetInfo(const char *key, const char *value, qboolean send, qboolean allowstarkey, qboolean allowmodel, qboolean quiet); + + +void CL_UpdateWorld (void); +void CL_WriteToServer (void); +void CL_Input (void); +extern int cl_ignoremousemoves; + + +float CL_KeyState (kbutton_t *key); +const char *Key_KeynumToString (int keynum, char *buf, size_t buflength); +int Key_StringToKeynum (const char *str); + +// +// cl_demo.c +// +void CL_StopPlayback(void); +void CL_ReadDemoMessage(void); +void CL_WriteDemoMessage(sizebuf_t *mesage); + +void CL_CutDemo(unsigned char **buf, fs_offset_t *filesize); +void CL_PasteDemo(unsigned char **buf, fs_offset_t *filesize); + +void CL_NextDemo(void); +void CL_Stop_f(void); +void CL_Record_f(void); +void CL_PlayDemo_f(void); +void CL_TimeDemo_f(void); + +// +// cl_parse.c +// +void CL_Parse_Init(void); +void CL_Parse_Shutdown(void); +void CL_ParseServerMessage(void); +void CL_Parse_DumpPacket(void); +void CL_Parse_ErrorCleanUp(void); +void QW_CL_StartUpload(unsigned char *data, int size); +extern cvar_t qport; +void CL_KeepaliveMessage(qboolean readmessages); // call this during loading of large content + +// +// view +// +void V_StartPitchDrift (void); +void V_StopPitchDrift (void); + +void V_Init (void); +float V_CalcRoll (const vec3_t angles, const vec3_t velocity); +void V_UpdateBlends (void); +void V_ParseDamage (void); + +// +// cl_part +// + +extern cvar_t cl_particles; +extern cvar_t cl_particles_quality; +extern cvar_t cl_particles_size; +extern cvar_t cl_particles_quake; +extern cvar_t cl_particles_blood; +extern cvar_t cl_particles_blood_alpha; +extern cvar_t cl_particles_blood_decal_alpha; +extern cvar_t cl_particles_blood_decal_scalemin; +extern cvar_t cl_particles_blood_decal_scalemax; +extern cvar_t cl_particles_blood_bloodhack; +extern cvar_t cl_particles_bulletimpacts; +extern cvar_t cl_particles_explosions_sparks; +extern cvar_t cl_particles_explosions_shell; +extern cvar_t cl_particles_rain; +extern cvar_t cl_particles_snow; +extern cvar_t cl_particles_smoke; +extern cvar_t cl_particles_smoke_alpha; +extern cvar_t cl_particles_smoke_alphafade; +extern cvar_t cl_particles_sparks; +extern cvar_t cl_particles_bubbles; +extern cvar_t cl_decals; +extern cvar_t cl_decals_time; +extern cvar_t cl_decals_fadetime; + +void CL_Particles_Clear(void); +void CL_Particles_Init(void); +void CL_Particles_Shutdown(void); +particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4]); + +typedef enum effectnameindex_s +{ + EFFECT_NONE, + EFFECT_TE_GUNSHOT, + EFFECT_TE_GUNSHOTQUAD, + EFFECT_TE_SPIKE, + EFFECT_TE_SPIKEQUAD, + EFFECT_TE_SUPERSPIKE, + EFFECT_TE_SUPERSPIKEQUAD, + EFFECT_TE_WIZSPIKE, + EFFECT_TE_KNIGHTSPIKE, + EFFECT_TE_EXPLOSION, + EFFECT_TE_EXPLOSIONQUAD, + EFFECT_TE_TAREXPLOSION, + EFFECT_TE_TELEPORT, + EFFECT_TE_LAVASPLASH, + EFFECT_TE_SMALLFLASH, + EFFECT_TE_FLAMEJET, + EFFECT_EF_FLAME, + EFFECT_TE_BLOOD, + EFFECT_TE_SPARK, + EFFECT_TE_PLASMABURN, + EFFECT_TE_TEI_G3, + EFFECT_TE_TEI_SMOKE, + EFFECT_TE_TEI_BIGEXPLOSION, + EFFECT_TE_TEI_PLASMAHIT, + EFFECT_EF_STARDUST, + EFFECT_TR_ROCKET, + EFFECT_TR_GRENADE, + EFFECT_TR_BLOOD, + EFFECT_TR_WIZSPIKE, + EFFECT_TR_SLIGHTBLOOD, + EFFECT_TR_KNIGHTSPIKE, + EFFECT_TR_VORESPIKE, + EFFECT_TR_NEHAHRASMOKE, + EFFECT_TR_NEXUIZPLASMA, + EFFECT_TR_GLOWTRAIL, + EFFECT_SVC_PARTICLE, + EFFECT_TOTAL +} +effectnameindex_t; + +int CL_ParticleEffectIndexForName(const char *name); +const char *CL_ParticleEffectNameForIndex(int i); +void CL_ParticleEffect(int effectindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor); +void CL_ParticleTrail(int effectindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4]); +void CL_ParseParticleEffect (void); +void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel); +void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type); +void CL_EntityParticles (const entity_t *ent); +void CL_ParticleExplosion (const vec3_t org); +void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength); +void R_NewExplosion(const vec3_t org); + +void Debug_PolygonBegin(const char *picname, int flags); +void Debug_PolygonVertex(float x, float y, float z, float s, float t, float r, float g, float b, float a); +void Debug_PolygonEnd(void); + +#include "cl_screen.h" + +extern qboolean sb_showscores; + +float RSurf_FogVertex(const vec3_t p); +float RSurf_FogPoint(const vec3_t p); + +typedef enum r_viewport_type_e +{ + R_VIEWPORTTYPE_ORTHO, + R_VIEWPORTTYPE_PERSPECTIVE, + R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP, + R_VIEWPORTTYPE_PERSPECTIVECUBESIDE, + R_VIEWPORTTYPE_TOTAL +} +r_viewport_type_t; + +typedef struct r_viewport_s +{ + matrix4x4_t cameramatrix; // from entity (transforms from camera entity to world) + matrix4x4_t viewmatrix; // actual matrix for rendering (transforms to viewspace) + matrix4x4_t projectmatrix; // actual projection matrix (transforms from viewspace to screen) + int x; + int y; + int z; + int width; + int height; + int depth; + r_viewport_type_t type; + float screentodepth[2]; // used by deferred renderer to calculate linear depth from device depth coordinates +} +r_viewport_t; + +typedef struct r_refdef_view_s +{ + // view information (changes multiple times per frame) + // if any of these variables change then r_refdef.viewcache must be regenerated + // by calling R_View_Update + // (which also updates viewport, scissor, colormask) + + // it is safe and expected to copy this into a structure on the stack and + // call the renderer recursively, then restore from the stack afterward + // (as long as R_View_Update is called) + + // eye position information + matrix4x4_t matrix, inverse_matrix; + vec3_t origin; + vec3_t forward; + vec3_t left; + vec3_t right; + vec3_t up; + int numfrustumplanes; + mplane_t frustum[6]; + qboolean useclipplane; + qboolean usecustompvs; // uses r_refdef.viewcache.pvsbits as-is rather than computing it + mplane_t clipplane; + float frustum_x, frustum_y; + vec3_t frustumcorner[4]; + // if turned off it renders an ortho view + int useperspective; + float ortho_x, ortho_y; + + // screen area to render in + int x; + int y; + int z; + int width; + int height; + int depth; + r_viewport_t viewport; // note: if r_viewscale is used, the viewport.width and viewport.height may be less than width and height + + // which color components to allow (for anaglyph glasses) + int colormask[4]; + + // global RGB color multiplier for rendering + float colorscale; + + // whether to call R_ClearScreen before rendering stuff + qboolean clear; + // if true, don't clear or do any post process effects (bloom, etc) + qboolean isoverlay; + // if true, this is the MAIN view (which is, after CSQC, copied into the scene for use e.g. by r_speeds 1, showtex, prydon cursor) + qboolean ismain; + + // whether to draw r_showtris and such, this is only true for the main + // view render, all secondary renders (mirrors, portals, cameras, + // distortion effects, etc) omit such debugging information + qboolean showdebug; + + // these define which values to use in GL_CullFace calls to request frontface or backface culling + int cullface_front; + int cullface_back; + + // render quality (0 to 1) - affects r_drawparticles_drawdistance and others + float quality; +} +r_refdef_view_t; + +typedef struct r_refdef_viewcache_s +{ + // updated by gl_main_newmap() + int maxentities; + int world_numclusters; + int world_numclusterbytes; + int world_numleafs; + int world_numsurfaces; + + // these properties are generated by R_View_Update() + + // which entities are currently visible for this viewpoint + // (the used range is 0...r_refdef.scene.numentities) + unsigned char *entityvisible; + + // flag arrays used for visibility checking on world model + // (all other entities have no per-surface/per-leaf visibility checks) + unsigned char *world_pvsbits; + unsigned char *world_leafvisible; + unsigned char *world_surfacevisible; + // if true, the view is currently in a leaf without pvs data + qboolean world_novis; +} +r_refdef_viewcache_t; + +// TODO: really think about which fields should go into scene and which one should stay in refdef [1/7/2008 Black] +// maybe also refactor some of the functions to support different setting sources (ie. fogenabled, etc.) for different scenes +typedef struct r_refdef_scene_s { + // whether to call S_ExtraUpdate during render to reduce sound chop + qboolean extraupdate; + + // (client gameworld) time for rendering time based effects + double time; + + // the world + entity_render_t *worldentity; + + // same as worldentity->model + dp_model_t *worldmodel; + + // renderable entities (excluding world) + entity_render_t **entities; + int numentities; + int maxentities; + + // field of temporary entities that is reset each (client) frame + entity_render_t *tempentities; + int numtempentities; + int maxtempentities; + qboolean expandtempentities; + + // renderable dynamic lights + rtlight_t *lights[MAX_DLIGHTS]; + rtlight_t templights[MAX_DLIGHTS]; + int numlights; + + // intensities for light styles right now, controls rtlights + float rtlightstylevalue[MAX_LIGHTSTYLES]; // float fraction of base light value + // 8.8bit fixed point intensities for light styles + // controls intensity lightmap layers + unsigned short lightstylevalue[MAX_LIGHTSTYLES]; // 8.8 fraction of base light value + + float ambient; + + qboolean rtworld; + qboolean rtworldshadows; + qboolean rtdlight; + qboolean rtdlightshadows; +} r_refdef_scene_t; + +typedef struct r_refdef_s +{ + // these fields define the basic rendering information for the world + // but not the view, which could change multiple times in one rendered + // frame (for example when rendering textures for certain effects) + + // these are set for water warping before + // frustum_x/frustum_y are calculated + float frustumscale_x, frustumscale_y; + + // current view settings (these get reset a few times during rendering because of water rendering, reflections, etc) + r_refdef_view_t view; + r_refdef_viewcache_t viewcache; + + // minimum visible distance (pixels closer than this disappear) + double nearclip; + // maximum visible distance (pixels further than this disappear in 16bpp modes, + // in 32bpp an infinite-farclip matrix is used instead) + double farclip; + + // fullscreen color blend + float viewblend[4]; + + r_refdef_scene_t scene; + + float fogplane[4]; + float fogplaneviewdist; + qboolean fogplaneviewabove; + float fogheightfade; + float fogcolor[3]; + float fogrange; + float fograngerecip; + float fogmasktabledistmultiplier; +#define FOGMASKTABLEWIDTH 1024 + float fogmasktable[FOGMASKTABLEWIDTH]; + float fogmasktable_start, fogmasktable_alpha, fogmasktable_range, fogmasktable_density; + float fog_density; + float fog_red; + float fog_green; + float fog_blue; + float fog_alpha; + float fog_start; + float fog_end; + float fog_height; + float fog_fadedepth; + qboolean fogenabled; + qboolean oldgl_fogenable; + + // new flexible texture height fog (overrides normal fog) + char fog_height_texturename[64]; // note: must be 64 for the sscanf code + unsigned char *fog_height_table1d; + unsigned char *fog_height_table2d; + int fog_height_tablesize; // enable + float fog_height_tablescale; + float fog_height_texcoordscale; + char fogheighttexturename[64]; // detects changes to active fog height texture + + int draw2dstage; // 0 = no, 1 = yes, other value = needs setting up again + + // true during envmap command capture + qboolean envmap; + + // brightness of world lightmaps and related lighting + // (often reduced when world rtlights are enabled) + float lightmapintensity; + // whether to draw world lights realtime, dlights realtime, and their shadows + float polygonfactor; + float polygonoffset; + float shadowpolygonfactor; + float shadowpolygonoffset; + + // how long R_RenderView took on the previous frame + double lastdrawscreentime; + + // rendering stats for r_speeds display + // (these are incremented in many places) + int stats[r_stat_count]; +} +r_refdef_t; + +extern r_refdef_t r_refdef; + +typedef enum waterlevel_e +{ + WATERLEVEL_NONE, + WATERLEVEL_WETFEET, + WATERLEVEL_SWIMMING, + WATERLEVEL_SUBMERGED +} +waterlevel_t; + +typedef struct cl_clientmovement_state_s +{ + // entity to be ignored for movement + struct prvm_edict_s *self; + // position + vec3_t origin; + vec3_t velocity; + // current bounding box (different if crouched vs standing) + vec3_t mins; + vec3_t maxs; + // currently on the ground + qboolean onground; + // currently crouching + qboolean crouched; + // what kind of water (SUPERCONTENTS_LAVA for instance) + int watertype; + // how deep + waterlevel_t waterlevel; + // weird hacks when jumping out of water + // (this is in seconds and counts down to 0) + float waterjumptime; + + // user command + usercmd_t cmd; +} +cl_clientmovement_state_t; +void CL_ClientMovement_PlayerMove_Frame(cl_clientmovement_state_t *s); + +// warpzone prediction hack (CSQC builtin) +void CL_RotateMoves(const matrix4x4_t *m); + +void CL_NewFrameReceived(int num); +void CL_ParseEntityLump(char *entitystring); +void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius); +void CL_RelinkLightFlashes(void); +void Sbar_ShowFPS(void); +void Sbar_ShowFPS_Update(void); +void Host_SaveConfig(void); +void Host_LoadConfig_f(void); +void CL_UpdateMoveVars(void); +void SCR_CaptureVideo_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length); +void V_DriftPitch(void); +void V_FadeViewFlashs(void); +void V_CalcViewBlend(void); +void V_CalcRefdefUsing (const matrix4x4_t *entrendermatrix, const vec3_t clviewangles, qboolean teleported, qboolean clonground, qboolean clcmdjump, float clstatsviewheight, qboolean cldead, qboolean clintermission, const vec3_t clvelocity); +void V_CalcRefdef(void); +void CL_Locs_Reload_f(void); + +#endif + diff --git a/app/jni/clprogdefs.h b/app/jni/clprogdefs.h new file mode 100644 index 0000000..bee62ab --- /dev/null +++ b/app/jni/clprogdefs.h @@ -0,0 +1,98 @@ +/* file generated by qcc, do not modify */ + + +#ifndef CLPROGDEFS_H +#define CLPROGDEFS_H + +/* +typedef struct cl_globalvars_s +{ + int pad[28]; + int self; + int other; + int world; + float time; + float frametime; + float player_localentnum; + float player_localnum; + float maxclients; + float clientcommandframe; + float servercommandframe; + string_t mapname; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + int trace_ent; + float trace_inopen; + float trace_inwater; + func_t CSQC_Init; + func_t CSQC_Shutdown; + func_t CSQC_InputEvent; + func_t CSQC_UpdateView; + func_t CSQC_ConsoleCommand; + vec3_t pmove_org; + vec3_t pmove_vel; + vec3_t pmove_mins; + vec3_t pmove_maxs; + float input_timelength; + vec3_t input_angles; + vec3_t input_movevalues; + float input_buttons; + float movevar_gravity; + float movevar_stopspeed; + float movevar_maxspeed; + float movevar_spectatormaxspeed; + float movevar_accelerate; + float movevar_airaccelerate; + float movevar_wateraccelerate; + float movevar_friction; + float movevar_waterfriction; + float movevar_entgravity; +} cl_globalvars_t; + +typedef struct cl_entvars_s +{ + float modelindex; + vec3_t absmin; + vec3_t absmax; + float entnum; + float drawmask; + func_t predraw; + float movetype; + float solid; + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t angles; + vec3_t avelocity; + string_t classname; + string_t model; + float frame; + float skin; + float effects; + vec3_t mins; + vec3_t maxs; + vec3_t size; + func_t touch; + func_t use; + func_t think; + func_t blocked; + float nextthink; + int chain; + string_t netname; + int enemy; + float flags; + float colormap; + int owner; +} cl_entvars_t; + +#define CL_PROGHEADER_CRC 52195 +*/ + +#endif diff --git a/app/jni/clvm_cmds.c b/app/jni/clvm_cmds.c new file mode 100644 index 0000000..6ca2749 --- /dev/null +++ b/app/jni/clvm_cmds.c @@ -0,0 +1,5007 @@ +#include "quakedef.h" + +#include "prvm_cmds.h" +#include "csprogs.h" +#include "cl_collision.h" +#include "r_shadow.h" +#include "jpeg.h" +#include "image.h" + +//============================================================================ +// Client +//[515]: unsolved PROBLEMS +//- finish player physics code (cs_runplayerphysics) +//- EntWasFreed ? +//- RF_DEPTHHACK is not like it should be +//- add builtin that sets cl.viewangles instead of reading "input_angles" global +//- finish lines support for R_Polygon*** +//- insert selecttraceline into traceline somehow + +//4 feature darkplaces csqc: add builtin to clientside qc for reading triangles of model meshes (useful to orient a ui along a triangle of a model mesh) +//4 feature darkplaces csqc: add builtins to clientside qc for gl calls + +extern cvar_t v_flipped; +extern cvar_t r_equalize_entities_fullbright; + +r_refdef_view_t csqc_original_r_refdef_view; +r_refdef_view_t csqc_main_r_refdef_view; + +// #1 void(vector ang) makevectors +static void VM_CL_makevectors (prvm_prog_t *prog) +{ + vec3_t angles, forward, right, up; + VM_SAFEPARMCOUNT(1, VM_CL_makevectors); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), angles); + AngleVectors(angles, forward, right, up); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorCopy(right, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); +} + +// #2 void(entity e, vector o) setorigin +static void VM_CL_setorigin (prvm_prog_t *prog) +{ + prvm_edict_t *e; + prvm_vec_t *org; + VM_SAFEPARMCOUNT(2, VM_CL_setorigin); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning(prog, "setorigin: can not modify world entity\n"); + return; + } + if (e->priv.required->free) + { + VM_Warning(prog, "setorigin: can not modify free entity\n"); + return; + } + org = PRVM_G_VECTOR(OFS_PARM1); + VectorCopy (org, PRVM_clientedictvector(e, origin)); + if(e->priv.required->mark == PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN) + e->priv.required->mark = PRVM_EDICT_MARK_SETORIGIN_CAUGHT; + CL_LinkEdict(e); +} + +static void SetMinMaxSizePRVM (prvm_prog_t *prog, prvm_edict_t *e, prvm_vec_t *min, prvm_vec_t *max) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (min[i] > max[i]) + prog->error_cmd("SetMinMaxSize: backwards mins/maxs"); + + // set derived values + VectorCopy (min, PRVM_clientedictvector(e, mins)); + VectorCopy (max, PRVM_clientedictvector(e, maxs)); + VectorSubtract (max, min, PRVM_clientedictvector(e, size)); + + CL_LinkEdict (e); +} + +static void SetMinMaxSize (prvm_prog_t *prog, prvm_edict_t *e, const vec_t *min, const vec_t *max) +{ + prvm_vec3_t mins, maxs; + VectorCopy(min, mins); + VectorCopy(max, maxs); + SetMinMaxSizePRVM(prog, e, mins, maxs); +} + +// #3 void(entity e, string m) setmodel +static void VM_CL_setmodel (prvm_prog_t *prog) +{ + prvm_edict_t *e; + const char *m; + dp_model_t *mod; + int i; + + VM_SAFEPARMCOUNT(2, VM_CL_setmodel); + + e = PRVM_G_EDICT(OFS_PARM0); + PRVM_clientedictfloat(e, modelindex) = 0; + PRVM_clientedictstring(e, model) = 0; + + m = PRVM_G_STRING(OFS_PARM1); + mod = NULL; + for (i = 0;i < MAX_MODELS && cl.csqc_model_precache[i];i++) + { + if (!strcmp(cl.csqc_model_precache[i]->name, m)) + { + mod = cl.csqc_model_precache[i]; + PRVM_clientedictstring(e, model) = PRVM_SetEngineString(prog, mod->name); + PRVM_clientedictfloat(e, modelindex) = -(i+1); + break; + } + } + + if( !mod ) { + for (i = 0;i < MAX_MODELS;i++) + { + mod = cl.model_precache[i]; + if (mod && !strcmp(mod->name, m)) + { + PRVM_clientedictstring(e, model) = PRVM_SetEngineString(prog, mod->name); + PRVM_clientedictfloat(e, modelindex) = i; + break; + } + } + } + + if( mod ) { + // TODO: check if this breaks needed consistency and maybe add a cvar for it too?? [1/10/2008 Black] + // LordHavoc: erm you broke it by commenting this out - setmodel must do setsize or else the qc can't find out the model size, and ssqc does this by necessity, consistency. + SetMinMaxSize (prog, e, mod->normalmins, mod->normalmaxs); + } + else + { + SetMinMaxSize (prog, e, vec3_origin, vec3_origin); + VM_Warning(prog, "setmodel: model '%s' not precached\n", m); + } +} + +// #4 void(entity e, vector min, vector max) setsize +static void VM_CL_setsize (prvm_prog_t *prog) +{ + prvm_edict_t *e; + vec3_t mins, maxs; + VM_SAFEPARMCOUNT(3, VM_CL_setsize); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning(prog, "setsize: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning(prog, "setsize: can not modify free entity\n"); + return; + } + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), mins); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), maxs); + + SetMinMaxSize( prog, e, mins, maxs ); + + CL_LinkEdict(e); +} + +// #8 void(entity e, float chan, string samp, float volume, float atten[, float pitchchange[, float flags]]) sound +static void VM_CL_sound (prvm_prog_t *prog) +{ + const char *sample; + int channel; + prvm_edict_t *entity; + float volume; + float attenuation; + float pitchchange; + float startposition; + int flags; + vec3_t org; + + VM_SAFEPARMCOUNTRANGE(5, 7, VM_CL_sound); + + entity = PRVM_G_EDICT(OFS_PARM0); + channel = (int)PRVM_G_FLOAT(OFS_PARM1); + sample = PRVM_G_STRING(OFS_PARM2); + volume = PRVM_G_FLOAT(OFS_PARM3); + attenuation = PRVM_G_FLOAT(OFS_PARM4); + + if (volume < 0 || volume > 1) + { + VM_Warning(prog, "VM_CL_sound: volume must be in range 0-1\n"); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + VM_Warning(prog, "VM_CL_sound: attenuation must be in range 0-4\n"); + return; + } + + if (prog->argc < 6) + pitchchange = 0; + else + pitchchange = PRVM_G_FLOAT(OFS_PARM5); + + if (prog->argc < 7) + flags = 0; + else + { + // LordHavoc: we only let the qc set certain flags, others are off-limits + flags = (int)PRVM_G_FLOAT(OFS_PARM6) & (CHANNELFLAG_RELIABLE | CHANNELFLAG_FORCELOOP | CHANNELFLAG_PAUSED); + } + + // sound_starttime exists instead of sound_startposition because in a + // networking sense you might not know when something is being received, + // so making sounds match up in sync would be impossible if relative + // position was sent + if (PRVM_clientglobalfloat(sound_starttime)) + startposition = cl.time - PRVM_clientglobalfloat(sound_starttime); + else + startposition = 0; + + channel = CHAN_USER2ENGINE(channel); + + if (!IS_CHAN(channel)) + { + VM_Warning(prog, "VM_CL_sound: channel must be in range 0-127\n"); + return; + } + + CL_VM_GetEntitySoundOrigin(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), org); + S_StartSound_StartPosition_Flags(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), channel, S_FindName(sample), org, volume, attenuation, startposition, flags, pitchchange > 0.0f ? pitchchange * 0.01f : 1.0f); +} + +// #483 void(vector origin, string sample, float volume, float attenuation) pointsound +static void VM_CL_pointsound(prvm_prog_t *prog) +{ + const char *sample; + float volume; + float attenuation; + vec3_t org; + + VM_SAFEPARMCOUNT(4, VM_CL_pointsound); + + VectorCopy( PRVM_G_VECTOR(OFS_PARM0), org); + sample = PRVM_G_STRING(OFS_PARM1); + volume = PRVM_G_FLOAT(OFS_PARM2); + attenuation = PRVM_G_FLOAT(OFS_PARM3); + + if (volume < 0 || volume > 1) + { + VM_Warning(prog, "VM_CL_pointsound: volume must be in range 0-1\n"); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + VM_Warning(prog, "VM_CL_pointsound: attenuation must be in range 0-4\n"); + return; + } + + // Send World Entity as Entity to Play Sound (for CSQC, that is MAX_EDICTS) + S_StartSound(MAX_EDICTS, 0, S_FindName(sample), org, volume, attenuation); +} + +// #14 entity() spawn +static void VM_CL_spawn (prvm_prog_t *prog) +{ + prvm_edict_t *ed; + ed = PRVM_ED_Alloc(prog); + VM_RETURN_EDICT(ed); +} + +static void CL_VM_SetTraceGlobals(prvm_prog_t *prog, const trace_t *trace, int svent) +{ + VM_SetTraceGlobals(prog, trace); + PRVM_clientglobalfloat(trace_networkentity) = svent; +} + +#define CL_HitNetworkBrushModels(move) !((move) == MOVE_WORLDONLY) +#define CL_HitNetworkPlayers(move) !((move) == MOVE_WORLDONLY || (move) == MOVE_NOMONSTERS) + +// #16 void(vector v1, vector v2, float movetype, entity ignore) traceline +static void VM_CL_traceline (prvm_prog_t *prog) +{ + vec3_t v1, v2; + trace_t trace; + int move, svent; + prvm_edict_t *ent; + +// R_TimeReport("pretraceline"); + + VM_SAFEPARMCOUNTRANGE(4, 4, VM_CL_traceline); + + prog->xfunction->builtinsprofile += 30; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), v1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), v2); + move = (int)PRVM_G_FLOAT(OFS_PARM2); + ent = PRVM_G_EDICT(OFS_PARM3); + + if (VEC_IS_NAN(v1[0]) || VEC_IS_NAN(v1[1]) || VEC_IS_NAN(v1[2]) || VEC_IS_NAN(v2[0]) || VEC_IS_NAN(v2[1]) || VEC_IS_NAN(v2[2])) + prog->error_cmd("%s: NAN errors detected in traceline('%f %f %f', '%f %f %f', %i, entity %i)\n", prog->name, v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); + + trace = CL_TraceLine(v1, v2, move, ent, CL_GenericHitSuperContentsMask(ent), CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true, false); + + CL_VM_SetTraceGlobals(prog, &trace, svent); +// R_TimeReport("traceline"); +} + +/* +================= +VM_CL_tracebox + +Used for use tracing and shot targeting +Traces are blocked by bbox and exact bsp entityes, and also slide box entities +if the tryents flag is set. + +tracebox (vector1, vector mins, vector maxs, vector2, tryents) +================= +*/ +// LordHavoc: added this for my own use, VERY useful, similar to traceline +static void VM_CL_tracebox (prvm_prog_t *prog) +{ + vec3_t v1, v2, m1, m2; + trace_t trace; + int move, svent; + prvm_edict_t *ent; + +// R_TimeReport("pretracebox"); + VM_SAFEPARMCOUNTRANGE(6, 8, VM_CL_tracebox); // allow more parameters for future expansion + + prog->xfunction->builtinsprofile += 30; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), v1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), m1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), m2); + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), v2); + move = (int)PRVM_G_FLOAT(OFS_PARM4); + ent = PRVM_G_EDICT(OFS_PARM5); + + if (VEC_IS_NAN(v1[0]) || VEC_IS_NAN(v1[1]) || VEC_IS_NAN(v1[2]) || VEC_IS_NAN(v2[0]) || VEC_IS_NAN(v2[1]) || VEC_IS_NAN(v2[2])) + prog->error_cmd("%s: NAN errors detected in tracebox('%f %f %f', '%f %f %f', '%f %f %f', '%f %f %f', %i, entity %i)\n", prog->name, v1[0], v1[1], v1[2], m1[0], m1[1], m1[2], m2[0], m2[1], m2[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); + + trace = CL_TraceBox(v1, m1, m2, v2, move, ent, CL_GenericHitSuperContentsMask(ent), CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true); + + CL_VM_SetTraceGlobals(prog, &trace, svent); +// R_TimeReport("tracebox"); +} + +static trace_t CL_Trace_Toss (prvm_prog_t *prog, prvm_edict_t *tossent, prvm_edict_t *ignore, int *svent) +{ + int i; + float gravity; + vec3_t start, end, mins, maxs, move; + vec3_t original_origin; + vec3_t original_velocity; + vec3_t original_angles; + vec3_t original_avelocity; + trace_t trace; + + VectorCopy(PRVM_clientedictvector(tossent, origin) , original_origin ); + VectorCopy(PRVM_clientedictvector(tossent, velocity) , original_velocity ); + VectorCopy(PRVM_clientedictvector(tossent, angles) , original_angles ); + VectorCopy(PRVM_clientedictvector(tossent, avelocity), original_avelocity); + + gravity = PRVM_clientedictfloat(tossent, gravity); + if (!gravity) + gravity = 1.0f; + gravity *= cl.movevars_gravity * 0.05; + + for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds + { + PRVM_clientedictvector(tossent, velocity)[2] -= gravity; + VectorMA (PRVM_clientedictvector(tossent, angles), 0.05, PRVM_clientedictvector(tossent, avelocity), PRVM_clientedictvector(tossent, angles)); + VectorScale (PRVM_clientedictvector(tossent, velocity), 0.05, move); + VectorAdd (PRVM_clientedictvector(tossent, origin), move, end); + VectorCopy(PRVM_clientedictvector(tossent, origin), start); + VectorCopy(PRVM_clientedictvector(tossent, mins), mins); + VectorCopy(PRVM_clientedictvector(tossent, maxs), maxs); + trace = CL_TraceBox(start, mins, maxs, end, MOVE_NORMAL, tossent, CL_GenericHitSuperContentsMask(tossent), true, true, NULL, true); + VectorCopy (trace.endpos, PRVM_clientedictvector(tossent, origin)); + + if (trace.fraction < 1) + break; + } + + VectorCopy(original_origin , PRVM_clientedictvector(tossent, origin) ); + VectorCopy(original_velocity , PRVM_clientedictvector(tossent, velocity) ); + VectorCopy(original_angles , PRVM_clientedictvector(tossent, angles) ); + VectorCopy(original_avelocity, PRVM_clientedictvector(tossent, avelocity)); + + return trace; +} + +static void VM_CL_tracetoss (prvm_prog_t *prog) +{ + trace_t trace; + prvm_edict_t *ent; + prvm_edict_t *ignore; + int svent = 0; + + prog->xfunction->builtinsprofile += 600; + + VM_SAFEPARMCOUNT(2, VM_CL_tracetoss); + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning(prog, "tracetoss: can not use world entity\n"); + return; + } + ignore = PRVM_G_EDICT(OFS_PARM1); + + trace = CL_Trace_Toss (prog, ent, ignore, &svent); + + CL_VM_SetTraceGlobals(prog, &trace, svent); +} + + +// #20 void(string s) precache_model +static void VM_CL_precache_model (prvm_prog_t *prog) +{ + const char *name; + int i; + dp_model_t *m; + + VM_SAFEPARMCOUNT(1, VM_CL_precache_model); + + name = PRVM_G_STRING(OFS_PARM0); + for (i = 0;i < MAX_MODELS && cl.csqc_model_precache[i];i++) + { + if(!strcmp(cl.csqc_model_precache[i]->name, name)) + { + PRVM_G_FLOAT(OFS_RETURN) = -(i+1); + return; + } + } + PRVM_G_FLOAT(OFS_RETURN) = 0; + m = Mod_ForName(name, false, false, name[0] == '*' ? cl.model_name[1] : NULL); + if(m && m->loaded) + { + for (i = 0;i < MAX_MODELS;i++) + { + if (!cl.csqc_model_precache[i]) + { + cl.csqc_model_precache[i] = (dp_model_t*)m; + PRVM_G_FLOAT(OFS_RETURN) = -(i+1); + return; + } + } + VM_Warning(prog, "VM_CL_precache_model: no free models\n"); + return; + } + VM_Warning(prog, "VM_CL_precache_model: model \"%s\" not found\n", name); +} + +// #22 entity(vector org, float rad) findradius +static void VM_CL_findradius (prvm_prog_t *prog) +{ + prvm_edict_t *ent, *chain; + vec_t radius, radius2; + vec3_t org, eorg, mins, maxs; + int i, numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_findradius); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if(chainfield < 0) + prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); + + chain = (prvm_edict_t *)prog->edicts; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + radius = PRVM_G_FLOAT(OFS_PARM1); + radius2 = radius * radius; + + mins[0] = org[0] - (radius + 1); + mins[1] = org[1] - (radius + 1); + mins[2] = org[2] - (radius + 1); + maxs[0] = org[0] + (radius + 1); + maxs[1] = org[1] + (radius + 1); + maxs[2] = org[2] + (radius + 1); + numtouchedicts = World_EntitiesInBox(&cl.world, mins, maxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens //[515]: for what then ? + Con_Printf("CSQC_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + ent = touchedicts[i]; + // Quake did not return non-solid entities but darkplaces does + // (note: this is the reason you can't blow up fallen zombies) + if (PRVM_clientedictfloat(ent, solid) == SOLID_NOT && !sv_gameplayfix_blowupfallenzombies.integer) + continue; + // LordHavoc: compare against bounding box rather than center so it + // doesn't miss large objects, and use DotProduct instead of Length + // for a major speedup + VectorSubtract(org, PRVM_clientedictvector(ent, origin), eorg); + if (sv_gameplayfix_findradiusdistancetobox.integer) + { + eorg[0] -= bound(PRVM_clientedictvector(ent, mins)[0], eorg[0], PRVM_clientedictvector(ent, maxs)[0]); + eorg[1] -= bound(PRVM_clientedictvector(ent, mins)[1], eorg[1], PRVM_clientedictvector(ent, maxs)[1]); + eorg[2] -= bound(PRVM_clientedictvector(ent, mins)[2], eorg[2], PRVM_clientedictvector(ent, maxs)[2]); + } + else + VectorMAMAM(1, eorg, -0.5f, PRVM_clientedictvector(ent, mins), -0.5f, PRVM_clientedictvector(ent, maxs), eorg); + if (DotProduct(eorg, eorg) < radius2) + { + PRVM_EDICTFIELDEDICT(ent, chainfield) = PRVM_EDICT_TO_PROG(chain); + chain = ent; + } + } + + VM_RETURN_EDICT(chain); +} + +// #34 float() droptofloor +static void VM_CL_droptofloor (prvm_prog_t *prog) +{ + prvm_edict_t *ent; + vec3_t start, end, mins, maxs; + trace_t trace; + + VM_SAFEPARMCOUNTRANGE(0, 2, VM_CL_droptofloor); // allow 2 parameters because the id1 defs.qc had an incorrect prototype + + // assume failure if it returns early + PRVM_G_FLOAT(OFS_RETURN) = 0; + + ent = PRVM_PROG_TO_EDICT(PRVM_clientglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning(prog, "droptofloor: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "droptofloor: can not modify free entity\n"); + return; + } + + VectorCopy(PRVM_clientedictvector(ent, origin), start); + VectorCopy(PRVM_clientedictvector(ent, mins), mins); + VectorCopy(PRVM_clientedictvector(ent, maxs), maxs); + VectorCopy(PRVM_clientedictvector(ent, origin), end); + end[2] -= 256; + + trace = CL_TraceBox(start, mins, maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true); + + if (trace.fraction != 1) + { + VectorCopy (trace.endpos, PRVM_clientedictvector(ent, origin)); + PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) | FL_ONGROUND; + PRVM_clientedictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + PRVM_G_FLOAT(OFS_RETURN) = 1; + // if support is destroyed, keep suspended (gross hack for floating items in various maps) +// ent->priv.server->suspendedinairflag = true; + } +} + +// #35 void(float style, string value) lightstyle +static void VM_CL_lightstyle (prvm_prog_t *prog) +{ + int i; + const char *c; + + VM_SAFEPARMCOUNT(2, VM_CL_lightstyle); + + i = (int)PRVM_G_FLOAT(OFS_PARM0); + c = PRVM_G_STRING(OFS_PARM1); + if (i >= cl.max_lightstyle) + { + VM_Warning(prog, "VM_CL_lightstyle >= MAX_LIGHTSTYLES\n"); + return; + } + strlcpy (cl.lightstyle[i].map, c, sizeof (cl.lightstyle[i].map)); + cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; + cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); +} + +// #40 float(entity e) checkbottom +static void VM_CL_checkbottom (prvm_prog_t *prog) +{ + static int cs_yes, cs_no; + prvm_edict_t *ent; + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VM_SAFEPARMCOUNT(1, VM_CL_checkbottom); + ent = PRVM_G_EDICT(OFS_PARM0); + PRVM_G_FLOAT(OFS_RETURN) = 0; + + VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); + VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (!(CL_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) + goto realcheck; + } + + cs_yes++; + PRVM_G_FLOAT(OFS_RETURN) = true; + return; // we got out easy + +realcheck: + cs_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*sv_stepheight.value; + trace = CL_TraceLine(start, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true, false); + + if (trace.fraction == 1.0) + return; + + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = CL_TraceLine(start, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true, false); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) + return; + } + + cs_yes++; + PRVM_G_FLOAT(OFS_RETURN) = true; +} + +// #41 float(vector v) pointcontents +static void VM_CL_pointcontents (prvm_prog_t *prog) +{ + vec3_t point; + VM_SAFEPARMCOUNT(1, VM_CL_pointcontents); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), point); + PRVM_G_FLOAT(OFS_RETURN) = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, CL_PointSuperContents(point)); +} + +// #48 void(vector o, vector d, float color, float count) particle +static void VM_CL_particle (prvm_prog_t *prog) +{ + vec3_t org, dir; + int count; + unsigned char color; + VM_SAFEPARMCOUNT(4, VM_CL_particle); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), dir); + color = (int)PRVM_G_FLOAT(OFS_PARM2); + count = (int)PRVM_G_FLOAT(OFS_PARM3); + CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color); +} + +// #74 void(vector pos, string samp, float vol, float atten) ambientsound +static void VM_CL_ambientsound (prvm_prog_t *prog) +{ + vec3_t f; + sfx_t *s; + VM_SAFEPARMCOUNT(4, VM_CL_ambientsound); + s = S_FindName(PRVM_G_STRING(OFS_PARM0)); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f); + S_StaticSound (s, f, PRVM_G_FLOAT(OFS_PARM2), PRVM_G_FLOAT(OFS_PARM3)*64); +} + +// #92 vector(vector org[, float lpflag]) getlight (DP_QC_GETLIGHT) +static void VM_CL_getlight (prvm_prog_t *prog) +{ + vec3_t ambientcolor, diffusecolor, diffusenormal; + vec3_t p; + + VM_SAFEPARMCOUNTRANGE(1, 3, VM_CL_getlight); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), p); + VectorClear(ambientcolor); + VectorClear(diffusecolor); + VectorClear(diffusenormal); + if (prog->argc >= 2) + R_CompleteLightPoint(ambientcolor, diffusecolor, diffusenormal, p, PRVM_G_FLOAT(OFS_PARM1)); + else if (cl.worldmodel && cl.worldmodel->brush.LightPoint) + cl.worldmodel->brush.LightPoint(cl.worldmodel, p, ambientcolor, diffusecolor, diffusenormal); + VectorMA(ambientcolor, 0.5, diffusecolor, PRVM_G_VECTOR(OFS_RETURN)); + if (PRVM_clientglobalvector(getlight_ambient)) + VectorCopy(ambientcolor, PRVM_clientglobalvector(getlight_ambient)); + if (PRVM_clientglobalvector(getlight_diffuse)) + VectorCopy(diffusecolor, PRVM_clientglobalvector(getlight_diffuse)); + if (PRVM_clientglobalvector(getlight_dir)) + VectorCopy(diffusenormal, PRVM_clientglobalvector(getlight_dir)); +} + +//============================================================================ +//[515]: SCENE MANAGER builtins + +void CSQC_R_RecalcView (void) +{ + extern matrix4x4_t viewmodelmatrix_nobob; + extern matrix4x4_t viewmodelmatrix_withbob; + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, cl.csqc_vieworigin[0], cl.csqc_vieworigin[1], cl.csqc_vieworigin[2], cl.csqc_viewangles[0], cl.csqc_viewangles[1], cl.csqc_viewangles[2], 1); + Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); + Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); + Matrix4x4_Concat(&viewmodelmatrix_withbob, &r_refdef.view.matrix, &cl.csqc_viewmodelmatrixfromengine); +} + +//#300 void() clearscene (EXT_CSQC) +static void VM_CL_R_ClearScene (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_R_ClearScene); + // clear renderable entity and light lists + r_refdef.scene.numentities = 0; + r_refdef.scene.numlights = 0; + // restore the view settings to the values that VM_CL_UpdateView received from the client code + r_refdef.view = csqc_original_r_refdef_view; + VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin); + VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles); + cl.csqc_vidvars.drawworld = r_drawworld.integer != 0; + cl.csqc_vidvars.drawenginesbar = false; + cl.csqc_vidvars.drawcrosshair = false; + CSQC_R_RecalcView(); +} + +//#301 void(float mask) addentities (EXT_CSQC) +static void VM_CL_R_AddEntities (prvm_prog_t *prog) +{ + double t = Sys_DirtyTime(); + int i, drawmask; + prvm_edict_t *ed; + VM_SAFEPARMCOUNT(1, VM_CL_R_AddEntities); + drawmask = (int)PRVM_G_FLOAT(OFS_PARM0); + CSQC_RelinkAllEntities(drawmask); + CL_RelinkLightFlashes(); + + PRVM_clientglobalfloat(time) = cl.time; + for(i=1;inum_edicts;i++) + { + // so we can easily check if CSQC entity #edictnum is currently drawn + cl.csqcrenderentities[i].entitynumber = 0; + ed = &prog->edicts[i]; + if(ed->priv.required->free) + continue; + CSQC_Think(ed); + if(ed->priv.required->free) + continue; + // note that for RF_USEAXIS entities, Predraw sets v_forward/v_right/v_up globals that are read by CSQC_AddRenderEdict + CSQC_Predraw(ed); + if(ed->priv.required->free) + continue; + if(!((int)PRVM_clientedictfloat(ed, drawmask) & drawmask)) + continue; + CSQC_AddRenderEdict(ed, i); + } + + // callprofile fixing hack: do not include this time in what is counted for CSQC_UpdateView + t = Sys_DirtyTime() - t;if (t < 0 || t >= 1800) t = 0; + prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= t; +} + +//#302 void(entity ent) addentity (EXT_CSQC) +static void VM_CL_R_AddEntity (prvm_prog_t *prog) +{ + double t = Sys_DirtyTime(); + VM_SAFEPARMCOUNT(1, VM_CL_R_AddEntity); + CSQC_AddRenderEdict(PRVM_G_EDICT(OFS_PARM0), 0); + t = Sys_DirtyTime() - t;if (t < 0 || t >= 1800) t = 0; + prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= t; +} + +//#303 float(float property, ...) setproperty (EXT_CSQC) +//#303 float(float property) getproperty +//#303 vector(float property) getpropertyvec +//#309 float(float property) getproperty +//#309 vector(float property) getpropertyvec +// VorteX: make this function be able to return previously set property if new value is not given +static void VM_CL_R_SetView (prvm_prog_t *prog) +{ + int c; + prvm_vec_t *f; + float k; + + VM_SAFEPARMCOUNTRANGE(1, 3, VM_CL_R_SetView); + + c = (int)PRVM_G_FLOAT(OFS_PARM0); + + // return value? + if (prog->argc < 2) + { + switch(c) + { + case VF_MIN: + VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.x, r_refdef.view.y, 0); + break; + case VF_MIN_X: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.x; + break; + case VF_MIN_Y: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.y; + break; + case VF_SIZE: + VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.width, r_refdef.view.height, 0); + break; + case VF_SIZE_X: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.width; + break; + case VF_SIZE_Y: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.height; + break; + case VF_VIEWPORT: + VM_Warning(prog, "VM_CL_R_GetView : VF_VIEWPORT can't be retrieved, use VF_MIN/VF_SIZE instead\n"); + break; + case VF_FOV: + VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.ortho_x, r_refdef.view.ortho_y, 0); + break; + case VF_FOVX: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ortho_x; + break; + case VF_FOVY: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ortho_y; + break; + case VF_ORIGIN: + VectorCopy(cl.csqc_vieworigin, PRVM_G_VECTOR(OFS_RETURN)); + break; + case VF_ORIGIN_X: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[0]; + break; + case VF_ORIGIN_Y: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[1]; + break; + case VF_ORIGIN_Z: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[2]; + break; + case VF_ANGLES: + VectorCopy(cl.csqc_viewangles, PRVM_G_VECTOR(OFS_RETURN)); + break; + case VF_ANGLES_X: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[0]; + break; + case VF_ANGLES_Y: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[1]; + break; + case VF_ANGLES_Z: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[2]; + break; + case VF_DRAWWORLD: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawworld; + break; + case VF_DRAWENGINESBAR: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawenginesbar; + break; + case VF_DRAWCROSSHAIR: + PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawcrosshair; + break; + case VF_CL_VIEWANGLES: + VectorCopy(cl.viewangles, PRVM_G_VECTOR(OFS_RETURN));; + break; + case VF_CL_VIEWANGLES_X: + PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[0]; + break; + case VF_CL_VIEWANGLES_Y: + PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[1]; + break; + case VF_CL_VIEWANGLES_Z: + PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[2]; + break; + case VF_PERSPECTIVE: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.useperspective; + break; + case VF_CLEARSCREEN: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.isoverlay; + break; + case VF_MAINVIEW: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ismain; + break; + case VF_FOG_DENSITY: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_density; + break; + case VF_FOG_COLOR: + PRVM_G_VECTOR(OFS_RETURN)[0] = r_refdef.fog_red; + PRVM_G_VECTOR(OFS_RETURN)[1] = r_refdef.fog_green; + PRVM_G_VECTOR(OFS_RETURN)[2] = r_refdef.fog_blue; + break; + case VF_FOG_COLOR_R: + PRVM_G_VECTOR(OFS_RETURN)[0] = r_refdef.fog_red; + break; + case VF_FOG_COLOR_G: + PRVM_G_VECTOR(OFS_RETURN)[1] = r_refdef.fog_green; + break; + case VF_FOG_COLOR_B: + PRVM_G_VECTOR(OFS_RETURN)[2] = r_refdef.fog_blue; + break; + case VF_FOG_ALPHA: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_alpha; + break; + case VF_FOG_START: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_start; + break; + case VF_FOG_END: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_end; + break; + case VF_FOG_HEIGHT: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_height; + break; + case VF_FOG_FADEDEPTH: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_fadedepth; + break; + case VF_MINFPS_QUALITY: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.quality; + break; + default: + PRVM_G_FLOAT(OFS_RETURN) = 0; + VM_Warning(prog, "VM_CL_R_GetView : unknown parm %i\n", c); + return; + } + return; + } + + f = PRVM_G_VECTOR(OFS_PARM1); + k = PRVM_G_FLOAT(OFS_PARM1); + switch(c) + { + case VF_MIN: + r_refdef.view.x = (int)(f[0]); + r_refdef.view.y = (int)(f[1]); + DrawQ_RecalcView(); + break; + case VF_MIN_X: + r_refdef.view.x = (int)(k); + DrawQ_RecalcView(); + break; + case VF_MIN_Y: + r_refdef.view.y = (int)(k); + DrawQ_RecalcView(); + break; + case VF_SIZE: + r_refdef.view.width = (int)(f[0]); + r_refdef.view.height = (int)(f[1]); + DrawQ_RecalcView(); + break; + case VF_SIZE_X: + r_refdef.view.width = (int)(k); + DrawQ_RecalcView(); + break; + case VF_SIZE_Y: + r_refdef.view.height = (int)(k); + DrawQ_RecalcView(); + break; + case VF_VIEWPORT: + r_refdef.view.x = (int)(f[0]); + r_refdef.view.y = (int)(f[1]); + f = PRVM_G_VECTOR(OFS_PARM2); + r_refdef.view.width = (int)(f[0]); + r_refdef.view.height = (int)(f[1]); + DrawQ_RecalcView(); + break; + case VF_FOV: + r_refdef.view.frustum_x = tan(f[0] * M_PI / 360.0);r_refdef.view.ortho_x = f[0]; + r_refdef.view.frustum_y = tan(f[1] * M_PI / 360.0);r_refdef.view.ortho_y = f[1]; + break; + case VF_FOVX: + r_refdef.view.frustum_x = tan(k * M_PI / 360.0);r_refdef.view.ortho_x = k; + break; + case VF_FOVY: + r_refdef.view.frustum_y = tan(k * M_PI / 360.0);r_refdef.view.ortho_y = k; + break; + case VF_ORIGIN: + VectorCopy(f, cl.csqc_vieworigin); + CSQC_R_RecalcView(); + break; + case VF_ORIGIN_X: + cl.csqc_vieworigin[0] = k; + CSQC_R_RecalcView(); + break; + case VF_ORIGIN_Y: + cl.csqc_vieworigin[1] = k; + CSQC_R_RecalcView(); + break; + case VF_ORIGIN_Z: + cl.csqc_vieworigin[2] = k; + CSQC_R_RecalcView(); + break; + case VF_ANGLES: + VectorCopy(f, cl.csqc_viewangles); + CSQC_R_RecalcView(); + break; + case VF_ANGLES_X: + cl.csqc_viewangles[0] = k; + CSQC_R_RecalcView(); + break; + case VF_ANGLES_Y: + cl.csqc_viewangles[1] = k; + CSQC_R_RecalcView(); + break; + case VF_ANGLES_Z: + cl.csqc_viewangles[2] = k; + CSQC_R_RecalcView(); + break; + case VF_DRAWWORLD: + cl.csqc_vidvars.drawworld = ((k != 0) && r_drawworld.integer); + break; + case VF_DRAWENGINESBAR: + cl.csqc_vidvars.drawenginesbar = k != 0; + break; + case VF_DRAWCROSSHAIR: + cl.csqc_vidvars.drawcrosshair = k != 0; + break; + case VF_CL_VIEWANGLES: + VectorCopy(f, cl.viewangles); + break; + case VF_CL_VIEWANGLES_X: + cl.viewangles[0] = k; + break; + case VF_CL_VIEWANGLES_Y: + cl.viewangles[1] = k; + break; + case VF_CL_VIEWANGLES_Z: + cl.viewangles[2] = k; + break; + case VF_PERSPECTIVE: + r_refdef.view.useperspective = k != 0; + break; + case VF_CLEARSCREEN: + r_refdef.view.isoverlay = !k; + break; + case VF_MAINVIEW: + PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ismain; + break; + case VF_FOG_DENSITY: + r_refdef.fog_density = k; + break; + case VF_FOG_COLOR: + r_refdef.fog_red = f[0]; + r_refdef.fog_green = f[1]; + r_refdef.fog_blue = f[2]; + break; + case VF_FOG_COLOR_R: + r_refdef.fog_red = k; + break; + case VF_FOG_COLOR_G: + r_refdef.fog_green = k; + break; + case VF_FOG_COLOR_B: + r_refdef.fog_blue = k; + break; + case VF_FOG_ALPHA: + r_refdef.fog_alpha = k; + break; + case VF_FOG_START: + r_refdef.fog_start = k; + break; + case VF_FOG_END: + r_refdef.fog_end = k; + break; + case VF_FOG_HEIGHT: + r_refdef.fog_height = k; + break; + case VF_FOG_FADEDEPTH: + r_refdef.fog_fadedepth = k; + break; + case VF_MINFPS_QUALITY: + r_refdef.view.quality = k; + break; + default: + PRVM_G_FLOAT(OFS_RETURN) = 0; + VM_Warning(prog, "VM_CL_R_SetView : unknown parm %i\n", c); + return; + } + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +//#305 void(vector org, float radius, vector lightcolours[, float style, string cubemapname, float pflags]) adddynamiclight (EXT_CSQC) +static void VM_CL_R_AddDynamicLight (prvm_prog_t *prog) +{ + double t = Sys_DirtyTime(); + vec3_t org; + float radius = 300; + vec3_t col; + int style = -1; + const char *cubemapname = NULL; + int pflags = PFLAGS_CORONA | PFLAGS_FULLDYNAMIC; + float coronaintensity = 1; + float coronasizescale = 0.25; + qboolean castshadow = true; + float ambientscale = 0; + float diffusescale = 1; + float specularscale = 1; + matrix4x4_t matrix; + vec3_t forward, left, up; + VM_SAFEPARMCOUNTRANGE(3, 8, VM_CL_R_AddDynamicLight); + + // if we've run out of dlights, just return + if (r_refdef.scene.numlights >= MAX_DLIGHTS) + return; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + radius = PRVM_G_FLOAT(OFS_PARM1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), col); + if (prog->argc >= 4) + { + style = (int)PRVM_G_FLOAT(OFS_PARM3); + if (style >= MAX_LIGHTSTYLES) + { + Con_DPrintf("VM_CL_R_AddDynamicLight: out of bounds lightstyle index %i\n", style); + style = -1; + } + } + if (prog->argc >= 5) + cubemapname = PRVM_G_STRING(OFS_PARM4); + if (prog->argc >= 6) + pflags = (int)PRVM_G_FLOAT(OFS_PARM5); + coronaintensity = (pflags & PFLAGS_CORONA) != 0; + castshadow = (pflags & PFLAGS_NOSHADOW) == 0; + + VectorScale(PRVM_clientglobalvector(v_forward), radius, forward); + VectorScale(PRVM_clientglobalvector(v_right), -radius, left); + VectorScale(PRVM_clientglobalvector(v_up), radius, up); + Matrix4x4_FromVectors(&matrix, forward, left, up, org); + + R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &matrix, col, style, cubemapname, castshadow, coronaintensity, coronasizescale, ambientscale, diffusescale, specularscale, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; + t = Sys_DirtyTime() - t;if (t < 0 || t >= 1800) t = 0; + prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= t; +} + +//============================================================================ + +//#310 vector (vector v) cs_unproject (EXT_CSQC) +static void VM_CL_unproject (prvm_prog_t *prog) +{ + vec3_t f; + vec3_t temp; + vec3_t result; + + VM_SAFEPARMCOUNT(1, VM_CL_unproject); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), f); + VectorSet(temp, + f[2], + (-1.0 + 2.0 * (f[0] / vid_conwidth.integer)) * f[2] * -r_refdef.view.frustum_x, + (-1.0 + 2.0 * (f[1] / vid_conheight.integer)) * f[2] * -r_refdef.view.frustum_y); + if(v_flipped.integer) + temp[1] = -temp[1]; + Matrix4x4_Transform(&r_refdef.view.matrix, temp, result); + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); +} + +//#311 vector (vector v) cs_project (EXT_CSQC) +static void VM_CL_project (prvm_prog_t *prog) +{ + vec3_t f; + vec3_t v; + matrix4x4_t m; + + VM_SAFEPARMCOUNT(1, VM_CL_project); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), f); + Matrix4x4_Invert_Simple(&m, &r_refdef.view.matrix); + Matrix4x4_Transform(&m, f, v); + if(v_flipped.integer) + v[1] = -v[1]; + VectorSet(PRVM_G_VECTOR(OFS_RETURN), + vid_conwidth.integer * (0.5*(1.0+v[1]/v[0]/-r_refdef.view.frustum_x)), + vid_conheight.integer * (0.5*(1.0+v[2]/v[0]/-r_refdef.view.frustum_y)), + v[0]); + // explanation: + // after transforming, relative position to viewport (0..1) = 0.5 * (1 + v[2]/v[0]/-frustum_{x \or y}) + // as 2D drawing honors the viewport too, to get the same pixel, we simply multiply this by conwidth/height +} + +//#330 float(float stnum) getstatf (EXT_CSQC) +static void VM_CL_getstatf (prvm_prog_t *prog) +{ + int i; + union + { + float f; + int l; + }dat; + VM_SAFEPARMCOUNT(1, VM_CL_getstatf); + i = (int)PRVM_G_FLOAT(OFS_PARM0); + if(i < 0 || i >= MAX_CL_STATS) + { + VM_Warning(prog, "VM_CL_getstatf: index>=MAX_CL_STATS or index<0\n"); + return; + } + dat.l = cl.stats[i]; + PRVM_G_FLOAT(OFS_RETURN) = dat.f; +} + +//#331 float(float stnum) getstati (EXT_CSQC) +static void VM_CL_getstati (prvm_prog_t *prog) +{ + int i, index; + int firstbit, bitcount; + + VM_SAFEPARMCOUNTRANGE(1, 3, VM_CL_getstati); + + index = (int)PRVM_G_FLOAT(OFS_PARM0); + if (prog->argc > 1) + { + firstbit = (int)PRVM_G_FLOAT(OFS_PARM1); + if (prog->argc > 2) + bitcount = (int)PRVM_G_FLOAT(OFS_PARM2); + else + bitcount = 1; + } + else + { + firstbit = 0; + bitcount = 32; + } + + if(index < 0 || index >= MAX_CL_STATS) + { + VM_Warning(prog, "VM_CL_getstati: index>=MAX_CL_STATS or index<0\n"); + return; + } + i = cl.stats[index]; + if (bitcount != 32) //32 causes the mask to overflow, so there's nothing to subtract from. + i = (((unsigned int)i)&(((1<>firstbit; + PRVM_G_FLOAT(OFS_RETURN) = i; +} + +//#332 string(float firststnum) getstats (EXT_CSQC) +static void VM_CL_getstats (prvm_prog_t *prog) +{ + int i; + char t[17]; + VM_SAFEPARMCOUNT(1, VM_CL_getstats); + i = (int)PRVM_G_FLOAT(OFS_PARM0); + if(i < 0 || i > MAX_CL_STATS-4) + { + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + VM_Warning(prog, "VM_CL_getstats: index>MAX_CL_STATS-4 or index<0\n"); + return; + } + strlcpy(t, (char*)&cl.stats[i], sizeof(t)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, t); +} + +//#333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) +static void VM_CL_setmodelindex (prvm_prog_t *prog) +{ + int i; + prvm_edict_t *t; + struct model_s *model; + + VM_SAFEPARMCOUNT(2, VM_CL_setmodelindex); + + t = PRVM_G_EDICT(OFS_PARM0); + + i = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_clientedictstring(t, model) = 0; + PRVM_clientedictfloat(t, modelindex) = 0; + + if (!i) + return; + + model = CL_GetModelByIndex(i); + if (!model) + { + VM_Warning(prog, "VM_CL_setmodelindex: null model\n"); + return; + } + PRVM_clientedictstring(t, model) = PRVM_SetEngineString(prog, model->name); + PRVM_clientedictfloat(t, modelindex) = i; + + // TODO: check if this breaks needed consistency and maybe add a cvar for it too?? [1/10/2008 Black] + if (model) + { + SetMinMaxSize (prog, t, model->normalmins, model->normalmaxs); + } + else + SetMinMaxSize (prog, t, vec3_origin, vec3_origin); +} + +//#334 string(float mdlindex) modelnameforindex (EXT_CSQC) +static void VM_CL_modelnameforindex (prvm_prog_t *prog) +{ + dp_model_t *model; + + VM_SAFEPARMCOUNT(1, VM_CL_modelnameforindex); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + model = CL_GetModelByIndex((int)PRVM_G_FLOAT(OFS_PARM0)); + PRVM_G_INT(OFS_RETURN) = model ? PRVM_SetEngineString(prog, model->name) : 0; +} + +//#335 float(string effectname) particleeffectnum (EXT_CSQC) +static void VM_CL_particleeffectnum (prvm_prog_t *prog) +{ + int i; + VM_SAFEPARMCOUNT(1, VM_CL_particleeffectnum); + i = CL_ParticleEffectIndexForName(PRVM_G_STRING(OFS_PARM0)); + if (i == 0) + i = -1; + PRVM_G_FLOAT(OFS_RETURN) = i; +} + +// #336 void(entity ent, float effectnum, vector start, vector end[, float color]) trailparticles (EXT_CSQC) +static void VM_CL_trailparticles (prvm_prog_t *prog) +{ + int i; + vec3_t start, end, velocity; + prvm_edict_t *t; + VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_trailparticles); + + t = PRVM_G_EDICT(OFS_PARM0); + i = (int)PRVM_G_FLOAT(OFS_PARM1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), start); + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), end); + VectorCopy(PRVM_clientedictvector(t, velocity), velocity); + + if (i < 0) + return; + CL_ParticleEffect(i, 1, start, end, velocity, velocity, NULL, prog->argc >= 5 ? (int)PRVM_G_FLOAT(OFS_PARM4) : 0); +} + +//#337 void(float effectnum, vector origin, vector dir, float count[, float color]) pointparticles (EXT_CSQC) +static void VM_CL_pointparticles (prvm_prog_t *prog) +{ + int i; + float n; + vec3_t f, v; + VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_pointparticles); + i = (int)PRVM_G_FLOAT(OFS_PARM0); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), v); + n = PRVM_G_FLOAT(OFS_PARM3); + if (i < 0) + return; + CL_ParticleEffect(i, n, f, f, v, v, NULL, prog->argc >= 5 ? (int)PRVM_G_FLOAT(OFS_PARM4) : 0); +} + +//#502 void(float effectnum, entity own, vector origin_from, vector origin_to, vector dir_from, vector dir_to, float count, float extflags) boxparticles (DP_CSQC_BOXPARTICLES) +static void VM_CL_boxparticles (prvm_prog_t *prog) +{ + int effectnum; + // prvm_edict_t *own; + vec3_t origin_from, origin_to, dir_from, dir_to; + float count; + int flags; + float tintmins[4], tintmaxs[4]; + VM_SAFEPARMCOUNTRANGE(7, 8, VM_CL_boxparticles); + + effectnum = (int)PRVM_G_FLOAT(OFS_PARM0); + // own = PRVM_G_EDICT(OFS_PARM1); // TODO find use for this + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin_from); + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin_to ); + VectorCopy(PRVM_G_VECTOR(OFS_PARM4), dir_from ); + VectorCopy(PRVM_G_VECTOR(OFS_PARM5), dir_to ); + count = PRVM_G_FLOAT(OFS_PARM6); + if(prog->argc >= 8) + flags = PRVM_G_FLOAT(OFS_PARM7); + else + flags = 0; + Vector4Set(tintmins, 1, 1, 1, 1); + Vector4Set(tintmaxs, 1, 1, 1, 1); + if(flags & 1) // read alpha + { + tintmins[3] = PRVM_clientglobalfloat(particles_alphamin); + tintmaxs[3] = PRVM_clientglobalfloat(particles_alphamax); + } + if(flags & 2) // read color + { + VectorCopy(PRVM_clientglobalvector(particles_colormin), tintmins); + VectorCopy(PRVM_clientglobalvector(particles_colormax), tintmaxs); + } + if (effectnum < 0) + return; + CL_ParticleTrail(effectnum, count, origin_from, origin_to, dir_from, dir_to, NULL, 0, true, true, tintmins, tintmaxs); +} + +//#531 void(float pause) setpause +static void VM_CL_setpause(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_CL_setpause); + if ((int)PRVM_G_FLOAT(OFS_PARM0) != 0) + cl.csqc_paused = true; + else + cl.csqc_paused = false; +} + +//#343 void(float usecursor) setcursormode (DP_CSQC) +static void VM_CL_setcursormode (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_CL_setcursormode); + cl.csqc_wantsmousemove = PRVM_G_FLOAT(OFS_PARM0) != 0; + cl_ignoremousemoves = 2; +} + +//#344 vector() getmousepos (DP_CSQC) +static void VM_CL_getmousepos(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_CL_getmousepos); + + if (key_consoleactive || key_dest != key_game) + VectorSet(PRVM_G_VECTOR(OFS_RETURN), 0, 0, 0); + else if (cl.csqc_wantsmousemove) + VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_windowmouse_x * vid_conwidth.integer / vid.width, in_windowmouse_y * vid_conheight.integer / vid.height, 0); + else + VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height, 0); +} + +//#345 float(float framenum) getinputstate (EXT_CSQC) +static void VM_CL_getinputstate (prvm_prog_t *prog) +{ + int i, frame; + VM_SAFEPARMCOUNT(1, VM_CL_getinputstate); + frame = (int)PRVM_G_FLOAT(OFS_PARM0); + PRVM_G_FLOAT(OFS_RETURN) = false; + for (i = 0;i < CL_MAX_USERCMDS;i++) + { + if (cl.movecmd[i].sequence == frame) + { + VectorCopy(cl.movecmd[i].viewangles, PRVM_clientglobalvector(input_angles)); + PRVM_clientglobalfloat(input_buttons) = cl.movecmd[i].buttons; // FIXME: this should not be directly exposed to csqc (translation layer needed?) + PRVM_clientglobalvector(input_movevalues)[0] = cl.movecmd[i].forwardmove; + PRVM_clientglobalvector(input_movevalues)[1] = cl.movecmd[i].sidemove; + PRVM_clientglobalvector(input_movevalues)[2] = cl.movecmd[i].upmove; + PRVM_clientglobalfloat(input_timelength) = cl.movecmd[i].frametime; + // this probably shouldn't be here + if(cl.movecmd[i].crouch) + { + VectorCopy(cl.playercrouchmins, PRVM_clientglobalvector(pmove_mins)); + VectorCopy(cl.playercrouchmaxs, PRVM_clientglobalvector(pmove_maxs)); + } + else + { + VectorCopy(cl.playerstandmins, PRVM_clientglobalvector(pmove_mins)); + VectorCopy(cl.playerstandmaxs, PRVM_clientglobalvector(pmove_maxs)); + } + PRVM_G_FLOAT(OFS_RETURN) = true; + } + } +} + +//#346 void(float sens) setsensitivityscaler (EXT_CSQC) +static void VM_CL_setsensitivityscale (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_CL_setsensitivityscale); + cl.sensitivityscale = PRVM_G_FLOAT(OFS_PARM0); +} + +//#347 void() runstandardplayerphysics (EXT_CSQC) +#define PMF_JUMP_HELD 1 // matches FTEQW +#define PMF_LADDER 2 // not used by DP, FTEQW sets this in runplayerphysics but does not read it +#define PMF_DUCKED 4 // FIXME FTEQW doesn't have this for Q1 like movement because Q1 cannot crouch +#define PMF_ONGROUND 8 // FIXME FTEQW doesn't have this for Q1 like movement and expects CSQC code to do its own trace, this is stupid CPU waste +static void VM_CL_runplayerphysics (prvm_prog_t *prog) +{ + cl_clientmovement_state_t s; + prvm_edict_t *ent; + + memset(&s, 0, sizeof(s)); + + VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_runplayerphysics); + + ent = (prog->argc == 1 ? PRVM_G_EDICT(OFS_PARM0) : prog->edicts); + if(ent == prog->edicts) + { + // deprecated use + s.self = NULL; + VectorCopy(PRVM_clientglobalvector(pmove_org), s.origin); + VectorCopy(PRVM_clientglobalvector(pmove_vel), s.velocity); + VectorCopy(PRVM_clientglobalvector(pmove_mins), s.mins); + VectorCopy(PRVM_clientglobalvector(pmove_maxs), s.maxs); + s.crouched = 0; + s.waterjumptime = PRVM_clientglobalfloat(pmove_waterjumptime); + s.cmd.canjump = (int)PRVM_clientglobalfloat(pmove_jump_held) == 0; + } + else + { + // new use + s.self = ent; + VectorCopy(PRVM_clientedictvector(ent, origin), s.origin); + VectorCopy(PRVM_clientedictvector(ent, velocity), s.velocity); + VectorCopy(PRVM_clientedictvector(ent, mins), s.mins); + VectorCopy(PRVM_clientedictvector(ent, maxs), s.maxs); + s.crouched = ((int)PRVM_clientedictfloat(ent, pmove_flags) & PMF_DUCKED) != 0; + s.waterjumptime = 0; // FIXME where do we get this from? FTEQW lacks support for this too + s.cmd.canjump = ((int)PRVM_clientedictfloat(ent, pmove_flags) & PMF_JUMP_HELD) == 0; + } + + VectorCopy(PRVM_clientglobalvector(input_angles), s.cmd.viewangles); + s.cmd.forwardmove = PRVM_clientglobalvector(input_movevalues)[0]; + s.cmd.sidemove = PRVM_clientglobalvector(input_movevalues)[1]; + s.cmd.upmove = PRVM_clientglobalvector(input_movevalues)[2]; + s.cmd.buttons = PRVM_clientglobalfloat(input_buttons); + s.cmd.frametime = PRVM_clientglobalfloat(input_timelength); + s.cmd.jump = (s.cmd.buttons & 2) != 0; + s.cmd.crouch = (s.cmd.buttons & 16) != 0; + + CL_ClientMovement_PlayerMove_Frame(&s); + + if(ent == prog->edicts) + { + // deprecated use + VectorCopy(s.origin, PRVM_clientglobalvector(pmove_org)); + VectorCopy(s.velocity, PRVM_clientglobalvector(pmove_vel)); + PRVM_clientglobalfloat(pmove_jump_held) = !s.cmd.canjump; + PRVM_clientglobalfloat(pmove_waterjumptime) = s.waterjumptime; + } + else + { + // new use + VectorCopy(s.origin, PRVM_clientedictvector(ent, origin)); + VectorCopy(s.velocity, PRVM_clientedictvector(ent, velocity)); + PRVM_clientedictfloat(ent, pmove_flags) = + (s.crouched ? PMF_DUCKED : 0) | + (s.cmd.canjump ? 0 : PMF_JUMP_HELD) | + (s.onground ? PMF_ONGROUND : 0); + } +} + +//#348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) +static void VM_CL_getplayerkey (prvm_prog_t *prog) +{ + int i; + char t[128]; + const char *c; + + VM_SAFEPARMCOUNT(2, VM_CL_getplayerkey); + + i = (int)PRVM_G_FLOAT(OFS_PARM0); + c = PRVM_G_STRING(OFS_PARM1); + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + Sbar_SortFrags(); + + if (i < 0) + i = Sbar_GetSortedPlayerIndex(-1-i); + if(i < 0 || i >= cl.maxclients) + return; + + t[0] = 0; + + if(!strcasecmp(c, "name")) + strlcpy(t, cl.scores[i].name, sizeof(t)); + else + if(!strcasecmp(c, "frags")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].frags); + else + if(!strcasecmp(c, "ping")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_ping); + else + if(!strcasecmp(c, "pl")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_packetloss); + else + if(!strcasecmp(c, "movementloss")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_movementloss); + else + if(!strcasecmp(c, "entertime")) + dpsnprintf(t, sizeof(t), "%f", cl.scores[i].qw_entertime); + else + if(!strcasecmp(c, "colors")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].colors); + else + if(!strcasecmp(c, "topcolor")) + dpsnprintf(t, sizeof(t), "%i", cl.scores[i].colors & 0xf0); + else + if(!strcasecmp(c, "bottomcolor")) + dpsnprintf(t, sizeof(t), "%i", (cl.scores[i].colors &15)<<4); + else + if(!strcasecmp(c, "viewentity")) + dpsnprintf(t, sizeof(t), "%i", i+1); + else + if(gamemode == GAME_XONOTIC && !strcasecmp(c, "TEMPHACK_origin")) + { + // PLEASE REMOVE THIS once deltalisten() of EXT_CSQC_1 + // is implemented, or Xonotic uses CSQC-networked + // players, whichever comes first + entity_t *e = cl.entities + (i+1); + if(e->state_current.active) + { + vec3_t origin; + Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); + dpsnprintf(t, sizeof(t), VECTOR_LOSSLESS_FORMAT, origin[0], origin[1], origin[2]); + } + } + if(!t[0]) + return; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, t); +} + +//#351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) +static void VM_CL_setlistener (prvm_prog_t *prog) +{ + vec3_t origin, forward, left, up; + VM_SAFEPARMCOUNT(4, VM_CL_setlistener); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), origin); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), forward); + VectorNegate(PRVM_G_VECTOR(OFS_PARM2), left); + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), up); + Matrix4x4_FromVectors(&cl.csqc_listenermatrix, forward, left, up, origin); + cl.csqc_usecsqclistener = true; //use csqc listener at this frame +} + +//#352 void(string cmdname) registercommand (EXT_CSQC) +static void VM_CL_registercmd (prvm_prog_t *prog) +{ + char *t; + VM_SAFEPARMCOUNT(1, VM_CL_registercmd); + if(!Cmd_Exists(PRVM_G_STRING(OFS_PARM0))) + { + size_t alloclen; + + alloclen = strlen(PRVM_G_STRING(OFS_PARM0)) + 1; + t = (char *)Z_Malloc(alloclen); + memcpy(t, PRVM_G_STRING(OFS_PARM0), alloclen); + Cmd_AddCommand(t, NULL, "console command created by QuakeC"); + } + else + Cmd_AddCommand(PRVM_G_STRING(OFS_PARM0), NULL, "console command created by QuakeC"); + +} + +//#360 float() readbyte (EXT_CSQC) +static void VM_CL_ReadByte (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadByte); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadByte(&cl_message); +} + +//#361 float() readchar (EXT_CSQC) +static void VM_CL_ReadChar (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadChar); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadChar(&cl_message); +} + +//#362 float() readshort (EXT_CSQC) +static void VM_CL_ReadShort (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadShort); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadShort(&cl_message); +} + +//#363 float() readlong (EXT_CSQC) +static void VM_CL_ReadLong (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadLong); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadLong(&cl_message); +} + +//#364 float() readcoord (EXT_CSQC) +static void VM_CL_ReadCoord (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadCoord); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadCoord(&cl_message, cls.protocol); +} + +//#365 float() readangle (EXT_CSQC) +static void VM_CL_ReadAngle (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadAngle); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadAngle(&cl_message, cls.protocol); +} + +//#366 string() readstring (EXT_CSQC) +static void VM_CL_ReadString (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadString); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); +} + +//#367 float() readfloat (EXT_CSQC) +static void VM_CL_ReadFloat (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ReadFloat); + PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadFloat(&cl_message); +} + +//#501 string() readpicture (DP_CSQC_READWRITEPICTURE) +extern cvar_t cl_readpicture_force; +static void VM_CL_ReadPicture (prvm_prog_t *prog) +{ + const char *name; + unsigned char *data; + unsigned char *buf; + unsigned short size; + int i; + cachepic_t *pic; + + VM_SAFEPARMCOUNT(0, VM_CL_ReadPicture); + + name = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + size = (unsigned short) MSG_ReadShort(&cl_message); + + // check if a texture of that name exists + // if yes, it is used and the data is discarded + // if not, the (low quality) data is used to build a new texture, whose name will get returned + + pic = Draw_CachePic_Flags (name, CACHEPICFLAG_NOTPERSISTENT); + + if(size) + { + if(pic->tex == r_texture_notexture) + pic->tex = NULL; // don't overwrite the notexture by Draw_NewPic + if(pic->tex && !cl_readpicture_force.integer) + { + // texture found and loaded + // skip over the jpeg as we don't need it + for(i = 0; i < size; ++i) + (void) MSG_ReadByte(&cl_message); + } + else + { + // texture not found + // use the attached jpeg as texture + buf = (unsigned char *) Mem_Alloc(tempmempool, size); + MSG_ReadBytes(&cl_message, size, buf); + data = JPEG_LoadImage_BGRA(buf, size, NULL); + Mem_Free(buf); + Draw_NewPic(name, image_width, image_height, false, data); + Mem_Free(data); + } + } + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, name); +} + +////////////////////////////////////////////////////////// + +static void VM_CL_makestatic (prvm_prog_t *prog) +{ + prvm_edict_t *ent; + + VM_SAFEPARMCOUNT(1, VM_CL_makestatic); + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning(prog, "makestatic: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "makestatic: can not modify free entity\n"); + return; + } + + if (cl.num_static_entities < cl.max_static_entities) + { + int renderflags; + entity_t *staticent = &cl.static_entities[cl.num_static_entities++]; + + // copy it to the current state + memset(staticent, 0, sizeof(*staticent)); + staticent->render.model = CL_GetModelByIndex((int)PRVM_clientedictfloat(ent, modelindex)); + staticent->render.framegroupblend[0].frame = (int)PRVM_clientedictfloat(ent, frame); + staticent->render.framegroupblend[0].lerp = 1; + // make torchs play out of sync + staticent->render.framegroupblend[0].start = lhrandom(-10, -1); + staticent->render.skinnum = (int)PRVM_clientedictfloat(ent, skin); + staticent->render.effects = (int)PRVM_clientedictfloat(ent, effects); + staticent->render.alpha = PRVM_clientedictfloat(ent, alpha); + staticent->render.scale = PRVM_clientedictfloat(ent, scale); + VectorCopy(PRVM_clientedictvector(ent, colormod), staticent->render.colormod); + VectorCopy(PRVM_clientedictvector(ent, glowmod), staticent->render.glowmod); + + // sanitize values + if (!staticent->render.alpha) + staticent->render.alpha = 1.0f; + if (!staticent->render.scale) + staticent->render.scale = 1.0f; + if (!VectorLength2(staticent->render.colormod)) + VectorSet(staticent->render.colormod, 1, 1, 1); + if (!VectorLength2(staticent->render.glowmod)) + VectorSet(staticent->render.glowmod, 1, 1, 1); + + renderflags = (int)PRVM_clientedictfloat(ent, renderflags); + if (renderflags & RF_USEAXIS) + { + vec3_t forward, left, up, origin; + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + VectorCopy(PRVM_clientedictvector(ent, origin), origin); + Matrix4x4_FromVectors(&staticent->render.matrix, forward, left, up, origin); + Matrix4x4_Scale(&staticent->render.matrix, staticent->render.scale, 1); + } + else + Matrix4x4_CreateFromQuakeEntity(&staticent->render.matrix, PRVM_clientedictvector(ent, origin)[0], PRVM_clientedictvector(ent, origin)[1], PRVM_clientedictvector(ent, origin)[2], PRVM_clientedictvector(ent, angles)[0], PRVM_clientedictvector(ent, angles)[1], PRVM_clientedictvector(ent, angles)[2], staticent->render.scale); + + // either fullbright or lit + if(!r_fullbright.integer) + { + if (!(staticent->render.effects & EF_FULLBRIGHT)) + staticent->render.flags |= RENDER_LIGHT; + else if(r_equalize_entities_fullbright.integer) + staticent->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; + } + // turn off shadows from transparent objects + if (!(staticent->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (staticent->render.alpha >= 1)) + staticent->render.flags |= RENDER_SHADOW; + if (staticent->render.effects & EF_NODEPTHTEST) + staticent->render.flags |= RENDER_NODEPTHTEST; + if (staticent->render.effects & EF_ADDITIVE) + staticent->render.flags |= RENDER_ADDITIVE; + if (staticent->render.effects & EF_DOUBLESIDED) + staticent->render.flags |= RENDER_DOUBLESIDED; + + staticent->render.allowdecals = true; + CL_UpdateRenderEntity(&staticent->render); + } + else + Con_Printf("Too many static entities"); + +// throw the entity away now + PRVM_ED_Free(prog, ent); +} + +//=================================================================// + +/* +================= +VM_CL_copyentity + +copies data from one entity to another + +copyentity(src, dst) +================= +*/ +static void VM_CL_copyentity (prvm_prog_t *prog) +{ + prvm_edict_t *in, *out; + VM_SAFEPARMCOUNT(2, VM_CL_copyentity); + in = PRVM_G_EDICT(OFS_PARM0); + if (in == prog->edicts) + { + VM_Warning(prog, "copyentity: can not read world entity\n"); + return; + } + if (in->priv.server->free) + { + VM_Warning(prog, "copyentity: can not read free entity\n"); + return; + } + out = PRVM_G_EDICT(OFS_PARM1); + if (out == prog->edicts) + { + VM_Warning(prog, "copyentity: can not modify world entity\n"); + return; + } + if (out->priv.server->free) + { + VM_Warning(prog, "copyentity: can not modify free entity\n"); + return; + } + memcpy(out->fields.fp, in->fields.fp, prog->entityfields * sizeof(prvm_vec_t)); + CL_LinkEdict(out); +} + +//=================================================================// + +// #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) +static void VM_CL_effect (prvm_prog_t *prog) +{ +#if 1 + Con_Printf("WARNING: VM_CL_effect not implemented\n"); // FIXME: this needs to take modelname not modelindex, the csqc defs has it as string and so it shall be +#else + vec3_t org; + VM_SAFEPARMCOUNT(5, VM_CL_effect); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + CL_Effect(org, (int)PRVM_G_FLOAT(OFS_PARM1), (int)PRVM_G_FLOAT(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), PRVM_G_FLOAT(OFS_PARM4)); +#endif +} + +// #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) +static void VM_CL_te_blood (prvm_prog_t *prog) +{ + vec3_t pos, vel, pos2; + VM_SAFEPARMCOUNT(3, VM_CL_te_blood); + if (PRVM_G_FLOAT(OFS_PARM2) < 1) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), vel); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_BLOOD, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, vel, vel, NULL, 0); +} + +// #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) +static void VM_CL_te_bloodshower (prvm_prog_t *prog) +{ + vec_t speed; + vec3_t mincorner, maxcorner, vel1, vel2; + VM_SAFEPARMCOUNT(4, VM_CL_te_bloodshower); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + speed = PRVM_G_FLOAT(OFS_PARM2); + vel1[0] = -speed; + vel1[1] = -speed; + vel1[2] = -speed; + vel2[0] = speed; + vel2[1] = speed; + vel2[2] = speed; + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), mincorner); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), maxcorner); + CL_ParticleEffect(EFFECT_TE_BLOOD, PRVM_G_FLOAT(OFS_PARM3), mincorner, maxcorner, vel1, vel2, NULL, 0); +} + +// #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) +static void VM_CL_te_explosionrgb (prvm_prog_t *prog) +{ + vec3_t pos; + vec3_t pos2; + matrix4x4_t tempmatrix; + VM_SAFEPARMCOUNT(2, VM_CL_te_explosionrgb); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleExplosion(pos2); + Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, PRVM_G_VECTOR(OFS_PARM1)[0], PRVM_G_VECTOR(OFS_PARM1)[1], PRVM_G_VECTOR(OFS_PARM1)[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); +} + +// #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) +static void VM_CL_te_particlecube (prvm_prog_t *prog) +{ + vec3_t mincorner, maxcorner, vel; + VM_SAFEPARMCOUNT(7, VM_CL_te_particlecube); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), mincorner); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), maxcorner); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); + CL_ParticleCube(mincorner, maxcorner, vel, (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), PRVM_G_FLOAT(OFS_PARM5), PRVM_G_FLOAT(OFS_PARM6)); +} + +// #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) +static void VM_CL_te_particlerain (prvm_prog_t *prog) +{ + vec3_t mincorner, maxcorner, vel; + VM_SAFEPARMCOUNT(5, VM_CL_te_particlerain); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), mincorner); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), maxcorner); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); + CL_ParticleRain(mincorner, maxcorner, vel, (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), 0); +} + +// #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) +static void VM_CL_te_particlesnow (prvm_prog_t *prog) +{ + vec3_t mincorner, maxcorner, vel; + VM_SAFEPARMCOUNT(5, VM_CL_te_particlesnow); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), mincorner); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), maxcorner); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); + CL_ParticleRain(mincorner, maxcorner, vel, (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), 1); +} + +// #411 void(vector org, vector vel, float howmany) te_spark +static void VM_CL_te_spark (prvm_prog_t *prog) +{ + vec3_t pos, pos2, vel; + VM_SAFEPARMCOUNT(3, VM_CL_te_spark); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), vel); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SPARK, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, vel, vel, NULL, 0); +} + +extern cvar_t cl_sound_ric_gunshot; +// #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) +static void VM_CL_te_gunshotquad (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_gunshotquad); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_GUNSHOTQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer >= 2) + { + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } + } +} + +// #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) +static void VM_CL_te_spikequad (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_spikequad); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SPIKEQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } +} + +// #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) +static void VM_CL_te_superspikequad (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_superspikequad); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKEQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } +} + +// #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) +static void VM_CL_te_explosionquad (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_explosionquad); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSIONQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); +} + +// #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) +static void VM_CL_te_smallflash (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_smallflash); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleEffect(EFFECT_TE_SMALLFLASH, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); +} + +// #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) +static void VM_CL_te_customflash (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + matrix4x4_t tempmatrix; + VM_SAFEPARMCOUNT(4, VM_CL_te_customflash); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); + CL_AllocLightFlash(NULL, &tempmatrix, PRVM_G_FLOAT(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM3)[0], PRVM_G_VECTOR(OFS_PARM3)[1], PRVM_G_VECTOR(OFS_PARM3)[2], PRVM_G_FLOAT(OFS_PARM1) / PRVM_G_FLOAT(OFS_PARM2), PRVM_G_FLOAT(OFS_PARM2), 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); +} + +// #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_gunshot (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_gunshot); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_GUNSHOT, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if(cl_sound_ric_gunshot.integer == 1 || cl_sound_ric_gunshot.integer == 3) + { + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } + } +} + +// #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_spike (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_spike); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } +} + +// #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_superspike (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + int rnd; + VM_SAFEPARMCOUNT(1, VM_CL_te_superspike); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); + else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); + else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); + } +} + +// #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_explosion (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_explosion); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); +} + +// #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_tarexplosion (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_tarexplosion); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); +} + +// #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_wizspike (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_wizspike); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_wizhit, pos2, 1, 1); +} + +// #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_knightspike (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_knightspike); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); + S_StartSound(-1, 0, cl.sfx_knighthit, pos2, 1, 1); +} + +// #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_lavasplash (prvm_prog_t *prog) +{ + vec3_t pos; + VM_SAFEPARMCOUNT(1, VM_CL_te_lavasplash); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); +} + +// #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_teleport (prvm_prog_t *prog) +{ + vec3_t pos; + VM_SAFEPARMCOUNT(1, VM_CL_te_teleport); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); +} + +// #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_explosion2 (prvm_prog_t *prog) +{ + vec3_t pos, pos2, color; + matrix4x4_t tempmatrix; + int colorStart, colorLength; + unsigned char *tempcolor; + VM_SAFEPARMCOUNT(3, VM_CL_te_explosion2); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + colorStart = (int)PRVM_G_FLOAT(OFS_PARM1); + colorLength = (int)PRVM_G_FLOAT(OFS_PARM2); + CL_FindNonSolidLocation(pos, pos2, 10); + CL_ParticleExplosion2(pos2, colorStart, colorLength); + tempcolor = palette_rgb[(rand()%colorLength) + colorStart]; + color[0] = tempcolor[0] * (2.0f / 255.0f); + color[1] = tempcolor[1] * (2.0f / 255.0f); + color[2] = tempcolor[2] * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); + CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); +} + + +// #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_lightning1 (prvm_prog_t *prog) +{ + vec3_t start, end; + VM_SAFEPARMCOUNT(3, VM_CL_te_lightning1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), start); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), end); + CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), start, end, cl.model_bolt, true); +} + +// #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_lightning2 (prvm_prog_t *prog) +{ + vec3_t start, end; + VM_SAFEPARMCOUNT(3, VM_CL_te_lightning2); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), start); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), end); + CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), start, end, cl.model_bolt2, true); +} + +// #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_lightning3 (prvm_prog_t *prog) +{ + vec3_t start, end; + VM_SAFEPARMCOUNT(3, VM_CL_te_lightning3); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), start); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), end); + CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), start, end, cl.model_bolt3, false); +} + +// #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) +static void VM_CL_te_beam (prvm_prog_t *prog) +{ + vec3_t start, end; + VM_SAFEPARMCOUNT(3, VM_CL_te_beam); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), start); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), end); + CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), start, end, cl.model_beam, false); +} + +// #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) +static void VM_CL_te_plasmaburn (prvm_prog_t *prog) +{ + vec3_t pos, pos2; + VM_SAFEPARMCOUNT(1, VM_CL_te_plasmaburn); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_PLASMABURN, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); +} + +// #457 void(vector org, vector velocity, float howmany) te_flamejet (DP_TE_FLAMEJET) +static void VM_CL_te_flamejet (prvm_prog_t *prog) +{ + vec3_t pos, pos2, vel; + VM_SAFEPARMCOUNT(3, VM_CL_te_flamejet); + if (PRVM_G_FLOAT(OFS_PARM2) < 1) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), vel); + CL_FindNonSolidLocation(pos, pos2, 4); + CL_ParticleEffect(EFFECT_TE_FLAMEJET, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, vel, vel, NULL, 0); +} + + +// #443 void(entity e, entity tagentity, string tagname) setattachment +static void VM_CL_setattachment (prvm_prog_t *prog) +{ + prvm_edict_t *e; + prvm_edict_t *tagentity; + const char *tagname; + int modelindex; + int tagindex; + dp_model_t *model; + VM_SAFEPARMCOUNT(3, VM_CL_setattachment); + + e = PRVM_G_EDICT(OFS_PARM0); + tagentity = PRVM_G_EDICT(OFS_PARM1); + tagname = PRVM_G_STRING(OFS_PARM2); + + if (e == prog->edicts) + { + VM_Warning(prog, "setattachment: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning(prog, "setattachment: can not modify free entity\n"); + return; + } + + if (tagentity == NULL) + tagentity = prog->edicts; + + tagindex = 0; + if (tagentity != NULL && tagentity != prog->edicts && tagname && tagname[0]) + { + modelindex = (int)PRVM_clientedictfloat(tagentity, modelindex); + model = CL_GetModelByIndex(modelindex); + if (model) + { + tagindex = Mod_Alias_GetTagIndexForName(model, (int)PRVM_clientedictfloat(tagentity, skin), tagname); + if (tagindex == 0) + Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i (model \"%s\") but could not find it\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity), model->name); + } + else + Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i but it has no model\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity)); + } + + PRVM_clientedictedict(e, tag_entity) = PRVM_EDICT_TO_PROG(tagentity); + PRVM_clientedictfloat(e, tag_index) = tagindex; +} + +///////////////////////////////////////// +// DP_MD3_TAGINFO extension coded by VorteX + +static int CL_GetTagIndex (prvm_prog_t *prog, prvm_edict_t *e, const char *tagname) +{ + dp_model_t *model = CL_GetModelFromEdict(e); + if (model) + return Mod_Alias_GetTagIndexForName(model, (int)PRVM_clientedictfloat(e, skin), tagname); + else + return -1; +} + +static int CL_GetExtendedTagInfo (prvm_prog_t *prog, prvm_edict_t *e, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) +{ + int r; + dp_model_t *model; + + *tagname = NULL; + *parentindex = 0; + Matrix4x4_CreateIdentity(tag_localmatrix); + + if (tagindex >= 0 + && (model = CL_GetModelFromEdict(e)) + && model->animscenes) + { + r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)PRVM_clientedictfloat(e, skin), e->priv.server->frameblend, &e->priv.server->skeleton, tagindex - 1, parentindex, tagname, tag_localmatrix); + + if(!r) // success? + *parentindex += 1; + + return r; + } + + return 1; +} + +int CL_GetPitchSign(prvm_prog_t *prog, prvm_edict_t *ent) +{ + dp_model_t *model; + if ((model = CL_GetModelFromEdict(ent)) && model->type == mod_alias) + return -1; + return 1; +} + +void CL_GetEntityMatrix (prvm_prog_t *prog, prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix) +{ + float scale; + float pitchsign = 1; + + scale = PRVM_clientedictfloat(ent, scale); + if (!scale) + scale = 1.0f; + + if(viewmatrix) + *out = r_refdef.view.matrix; + else if ((int)PRVM_clientedictfloat(ent, renderflags) & RF_USEAXIS) + { + vec3_t forward; + vec3_t left; + vec3_t up; + vec3_t origin; + VectorScale(PRVM_clientglobalvector(v_forward), scale, forward); + VectorScale(PRVM_clientglobalvector(v_right), -scale, left); + VectorScale(PRVM_clientglobalvector(v_up), scale, up); + VectorCopy(PRVM_clientedictvector(ent, origin), origin); + Matrix4x4_FromVectors(out, forward, left, up, origin); + } + else + { + pitchsign = CL_GetPitchSign(prog, ent); + Matrix4x4_CreateFromQuakeEntity(out, PRVM_clientedictvector(ent, origin)[0], PRVM_clientedictvector(ent, origin)[1], PRVM_clientedictvector(ent, origin)[2], pitchsign * PRVM_clientedictvector(ent, angles)[0], PRVM_clientedictvector(ent, angles)[1], PRVM_clientedictvector(ent, angles)[2], scale); + } +} + +static int CL_GetEntityLocalTagMatrix(prvm_prog_t *prog, prvm_edict_t *ent, int tagindex, matrix4x4_t *out) +{ + dp_model_t *model; + if (tagindex >= 0 + && (model = CL_GetModelFromEdict(ent)) + && model->animscenes) + { + VM_GenerateFrameGroupBlend(prog, ent->priv.server->framegroupblend, ent); + VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model, cl.time); + VM_UpdateEdictSkeleton(prog, ent, model, ent->priv.server->frameblend); + return Mod_Alias_GetTagMatrix(model, ent->priv.server->frameblend, &ent->priv.server->skeleton, tagindex, out); + } + *out = identitymatrix; + return 0; +} + +// Warnings/errors code: +// 0 - normal (everything all-right) +// 1 - world entity +// 2 - free entity +// 3 - null or non-precached model +// 4 - no tags with requested index +// 5 - runaway loop at attachment chain +extern cvar_t cl_bob; +extern cvar_t cl_bobcycle; +extern cvar_t cl_bobup; +int CL_GetTagMatrix (prvm_prog_t *prog, matrix4x4_t *out, prvm_edict_t *ent, int tagindex) +{ + int ret; + int attachloop; + matrix4x4_t entitymatrix, tagmatrix, attachmatrix; + dp_model_t *model; + + *out = identitymatrix; // warnings and errors return identical matrix + + if (ent == prog->edicts) + return 1; + if (ent->priv.server->free) + return 2; + + model = CL_GetModelFromEdict(ent); + if(!model) + return 3; + + tagmatrix = identitymatrix; + attachloop = 0; + for(;;) + { + if(attachloop >= 256) + return 5; + // apply transformation by child's tagindex on parent entity and then + // by parent entity itself + ret = CL_GetEntityLocalTagMatrix(prog, ent, tagindex - 1, &attachmatrix); + if(ret && attachloop == 0) + return ret; + CL_GetEntityMatrix(prog, ent, &entitymatrix, false); + Matrix4x4_Concat(&tagmatrix, &attachmatrix, out); + Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); + // next iteration we process the parent entity + if (PRVM_clientedictedict(ent, tag_entity)) + { + tagindex = (int)PRVM_clientedictfloat(ent, tag_index); + ent = PRVM_EDICT_NUM(PRVM_clientedictedict(ent, tag_entity)); + } + else + break; + attachloop++; + } + + // RENDER_VIEWMODEL magic + if ((int)PRVM_clientedictfloat(ent, renderflags) & RF_VIEWMODEL) + { + Matrix4x4_Copy(&tagmatrix, out); + + CL_GetEntityMatrix(prog, prog->edicts, &entitymatrix, true); + Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); + + /* + // Cl_bob, ported from rendering code + if (PRVM_clientedictfloat(ent, health) > 0 && cl_bob.value && cl_bobcycle.value) + { + double bob, cycle; + // LordHavoc: this code is *weird*, but not replacable (I think it + // should be done in QC on the server, but oh well, quake is quake) + // LordHavoc: figured out bobup: the time at which the sin is at 180 + // degrees (which allows lengthening or squishing the peak or valley) + cycle = cl.time/cl_bobcycle.value; + cycle -= (int)cycle; + if (cycle < cl_bobup.value) + cycle = sin(M_PI * cycle / cl_bobup.value); + else + cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); + // bob is proportional to velocity in the xy plane + // (don't count Z, or jumping messes it up) + bob = sqrt(PRVM_clientedictvector(ent, velocity)[0]*PRVM_clientedictvector(ent, velocity)[0] + PRVM_clientedictvector(ent, velocity)[1]*PRVM_clientedictvector(ent, velocity)[1])*cl_bob.value; + bob = bob*0.3 + bob*0.7*cycle; + Matrix4x4_AdjustOrigin(out, 0, 0, bound(-7, bob, 4)); + } + */ + } + return 0; +} + +// #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) +static void VM_CL_gettagindex (prvm_prog_t *prog) +{ + prvm_edict_t *ent; + const char *tag_name; + int tag_index; + + VM_SAFEPARMCOUNT(2, VM_CL_gettagindex); + + ent = PRVM_G_EDICT(OFS_PARM0); + tag_name = PRVM_G_STRING(OFS_PARM1); + if (ent == prog->edicts) + { + VM_Warning(prog, "VM_CL_gettagindex(entity #%i): can't affect world entity\n", PRVM_NUM_FOR_EDICT(ent)); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "VM_CL_gettagindex(entity #%i): can't affect free entity\n", PRVM_NUM_FOR_EDICT(ent)); + return; + } + + tag_index = 0; + if (!CL_GetModelFromEdict(ent)) + Con_DPrintf("VM_CL_gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent)); + else + { + tag_index = CL_GetTagIndex(prog, ent, tag_name); + if (tag_index == 0) + if(developer_extra.integer) + Con_DPrintf("VM_CL_gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name); + } + PRVM_G_FLOAT(OFS_RETURN) = tag_index; +} + +// #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) +static void VM_CL_gettaginfo (prvm_prog_t *prog) +{ + prvm_edict_t *e; + int tagindex; + matrix4x4_t tag_matrix; + matrix4x4_t tag_localmatrix; + int parentindex; + const char *tagname; + int returncode; + vec3_t forward, left, up, origin; + const dp_model_t *model; + + VM_SAFEPARMCOUNT(2, VM_CL_gettaginfo); + + e = PRVM_G_EDICT(OFS_PARM0); + tagindex = (int)PRVM_G_FLOAT(OFS_PARM1); + returncode = CL_GetTagMatrix(prog, &tag_matrix, e, tagindex); + Matrix4x4_ToVectors(&tag_matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorScale(left, -1, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); + model = CL_GetModelFromEdict(e); + VM_GenerateFrameGroupBlend(prog, e->priv.server->framegroupblend, e); + VM_FrameBlendFromFrameGroupBlend(e->priv.server->frameblend, e->priv.server->framegroupblend, model, cl.time); + VM_UpdateEdictSkeleton(prog, e, model, e->priv.server->frameblend); + CL_GetExtendedTagInfo(prog, e, tagindex, &parentindex, &tagname, &tag_localmatrix); + Matrix4x4_ToVectors(&tag_localmatrix, forward, left, up, origin); + + PRVM_clientglobalfloat(gettaginfo_parent) = parentindex; + PRVM_clientglobalstring(gettaginfo_name) = tagname ? PRVM_SetTempString(prog, tagname) : 0; + VectorCopy(forward, PRVM_clientglobalvector(gettaginfo_forward)); + VectorScale(left, -1, PRVM_clientglobalvector(gettaginfo_right)); + VectorCopy(up, PRVM_clientglobalvector(gettaginfo_up)); + VectorCopy(origin, PRVM_clientglobalvector(gettaginfo_offset)); + + switch(returncode) + { + case 1: + VM_Warning(prog, "gettagindex: can't affect world entity\n"); + break; + case 2: + VM_Warning(prog, "gettagindex: can't affect free entity\n"); + break; + case 3: + Con_DPrintf("CL_GetTagMatrix(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(e)); + break; + case 4: + Con_DPrintf("CL_GetTagMatrix(entity #%i): model has no tag with requested index %i\n", PRVM_NUM_FOR_EDICT(e), tagindex); + break; + case 5: + Con_DPrintf("CL_GetTagMatrix(entity #%i): runaway loop at attachment chain\n", PRVM_NUM_FOR_EDICT(e)); + break; + } +} + +//============================================================================ + +//==================== +// DP_CSQC_SPAWNPARTICLE +// a QC hook to engine's CL_NewParticle +//==================== + +// particle theme struct +typedef struct vmparticletheme_s +{ + unsigned short typeindex; + qboolean initialized; + pblend_t blendmode; + porientation_t orientation; + int color1; + int color2; + int tex; + float size; + float sizeincrease; + float alpha; + float alphafade; + float gravity; + float bounce; + float airfriction; + float liquidfriction; + float originjitter; + float velocityjitter; + qboolean qualityreduction; + float lifetime; + float stretch; + int staincolor1; + int staincolor2; + int staintex; + float stainalpha; + float stainsize; + float delayspawn; + float delaycollision; + float angle; + float spin; +}vmparticletheme_t; + +// particle spawner +typedef struct vmparticlespawner_s +{ + mempool_t *pool; + qboolean initialized; + qboolean verified; + vmparticletheme_t *themes; + int max_themes; +}vmparticlespawner_t; + +vmparticlespawner_t vmpartspawner; + +// TODO: automatic max_themes grow +static void VM_InitParticleSpawner (prvm_prog_t *prog, int maxthemes) +{ + // bound max themes to not be an insane value + if (maxthemes < 4) + maxthemes = 4; + if (maxthemes > 2048) + maxthemes = 2048; + // allocate and set up structure + if (vmpartspawner.initialized) // reallocate + { + Mem_FreePool(&vmpartspawner.pool); + memset(&vmpartspawner, 0, sizeof(vmparticlespawner_t)); + } + vmpartspawner.pool = Mem_AllocPool("VMPARTICLESPAWNER", 0, NULL); + vmpartspawner.themes = (vmparticletheme_t *)Mem_Alloc(vmpartspawner.pool, sizeof(vmparticletheme_t)*maxthemes); + vmpartspawner.max_themes = maxthemes; + vmpartspawner.initialized = true; + vmpartspawner.verified = true; +} + +// reset particle theme to default values +static void VM_ResetParticleTheme (vmparticletheme_t *theme) +{ + theme->initialized = true; + theme->typeindex = pt_static; + theme->blendmode = PBLEND_ADD; + theme->orientation = PARTICLE_BILLBOARD; + theme->color1 = 0x808080; + theme->color2 = 0xFFFFFF; + theme->tex = 63; + theme->size = 2; + theme->sizeincrease = 0; + theme->alpha = 256; + theme->alphafade = 512; + theme->gravity = 0.0f; + theme->bounce = 0.0f; + theme->airfriction = 1.0f; + theme->liquidfriction = 4.0f; + theme->originjitter = 0.0f; + theme->velocityjitter = 0.0f; + theme->qualityreduction = false; + theme->lifetime = 4; + theme->stretch = 1; + theme->staincolor1 = -1; + theme->staincolor2 = -1; + theme->staintex = -1; + theme->delayspawn = 0.0f; + theme->delaycollision = 0.0f; + theme->angle = 0.0f; + theme->spin = 0.0f; +} + +// particle theme -> QC globals +static void VM_CL_ParticleThemeToGlobals(vmparticletheme_t *theme, prvm_prog_t *prog) +{ + PRVM_clientglobalfloat(particle_type) = theme->typeindex; + PRVM_clientglobalfloat(particle_blendmode) = theme->blendmode; + PRVM_clientglobalfloat(particle_orientation) = theme->orientation; + // VorteX: int only can store 0-255, not 0-256 which means 0 - 0,99609375... + VectorSet(PRVM_clientglobalvector(particle_color1), (theme->color1 >> 16) & 0xFF, (theme->color1 >> 8) & 0xFF, (theme->color1 >> 0) & 0xFF); + VectorSet(PRVM_clientglobalvector(particle_color2), (theme->color2 >> 16) & 0xFF, (theme->color2 >> 8) & 0xFF, (theme->color2 >> 0) & 0xFF); + PRVM_clientglobalfloat(particle_tex) = (prvm_vec_t)theme->tex; + PRVM_clientglobalfloat(particle_size) = theme->size; + PRVM_clientglobalfloat(particle_sizeincrease) = theme->sizeincrease; + PRVM_clientglobalfloat(particle_alpha) = theme->alpha/256; + PRVM_clientglobalfloat(particle_alphafade) = theme->alphafade/256; + PRVM_clientglobalfloat(particle_time) = theme->lifetime; + PRVM_clientglobalfloat(particle_gravity) = theme->gravity; + PRVM_clientglobalfloat(particle_bounce) = theme->bounce; + PRVM_clientglobalfloat(particle_airfriction) = theme->airfriction; + PRVM_clientglobalfloat(particle_liquidfriction) = theme->liquidfriction; + PRVM_clientglobalfloat(particle_originjitter) = theme->originjitter; + PRVM_clientglobalfloat(particle_velocityjitter) = theme->velocityjitter; + PRVM_clientglobalfloat(particle_qualityreduction) = theme->qualityreduction; + PRVM_clientglobalfloat(particle_stretch) = theme->stretch; + VectorSet(PRVM_clientglobalvector(particle_staincolor1), ((int)theme->staincolor1 >> 16) & 0xFF, ((int)theme->staincolor1 >> 8) & 0xFF, ((int)theme->staincolor1 >> 0) & 0xFF); + VectorSet(PRVM_clientglobalvector(particle_staincolor2), ((int)theme->staincolor2 >> 16) & 0xFF, ((int)theme->staincolor2 >> 8) & 0xFF, ((int)theme->staincolor2 >> 0) & 0xFF); + PRVM_clientglobalfloat(particle_staintex) = (prvm_vec_t)theme->staintex; + PRVM_clientglobalfloat(particle_stainalpha) = (prvm_vec_t)theme->stainalpha/256; + PRVM_clientglobalfloat(particle_stainsize) = (prvm_vec_t)theme->stainsize; + PRVM_clientglobalfloat(particle_delayspawn) = theme->delayspawn; + PRVM_clientglobalfloat(particle_delaycollision) = theme->delaycollision; + PRVM_clientglobalfloat(particle_angle) = theme->angle; + PRVM_clientglobalfloat(particle_spin) = theme->spin; +} + +// QC globals -> particle theme +static void VM_CL_ParticleThemeFromGlobals(vmparticletheme_t *theme, prvm_prog_t *prog) +{ + theme->typeindex = (unsigned short)PRVM_clientglobalfloat(particle_type); + theme->blendmode = (pblend_t)(int)PRVM_clientglobalfloat(particle_blendmode); + theme->orientation = (porientation_t)(int)PRVM_clientglobalfloat(particle_orientation); + theme->color1 = ((int)PRVM_clientglobalvector(particle_color1)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color1)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color1)[2]); + theme->color2 = ((int)PRVM_clientglobalvector(particle_color2)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color2)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color2)[2]); + theme->tex = (int)PRVM_clientglobalfloat(particle_tex); + theme->size = PRVM_clientglobalfloat(particle_size); + theme->sizeincrease = PRVM_clientglobalfloat(particle_sizeincrease); + theme->alpha = PRVM_clientglobalfloat(particle_alpha)*256; + theme->alphafade = PRVM_clientglobalfloat(particle_alphafade)*256; + theme->lifetime = PRVM_clientglobalfloat(particle_time); + theme->gravity = PRVM_clientglobalfloat(particle_gravity); + theme->bounce = PRVM_clientglobalfloat(particle_bounce); + theme->airfriction = PRVM_clientglobalfloat(particle_airfriction); + theme->liquidfriction = PRVM_clientglobalfloat(particle_liquidfriction); + theme->originjitter = PRVM_clientglobalfloat(particle_originjitter); + theme->velocityjitter = PRVM_clientglobalfloat(particle_velocityjitter); + theme->qualityreduction = PRVM_clientglobalfloat(particle_qualityreduction) != 0 ? true : false; + theme->stretch = PRVM_clientglobalfloat(particle_stretch); + theme->staincolor1 = ((int)PRVM_clientglobalvector(particle_staincolor1)[0])*65536 + (int)(PRVM_clientglobalvector(particle_staincolor1)[1])*256 + (int)(PRVM_clientglobalvector(particle_staincolor1)[2]); + theme->staincolor2 = (int)(PRVM_clientglobalvector(particle_staincolor2)[0])*65536 + (int)(PRVM_clientglobalvector(particle_staincolor2)[1])*256 + (int)(PRVM_clientglobalvector(particle_staincolor2)[2]); + theme->staintex =(int)PRVM_clientglobalfloat(particle_staintex); + theme->stainalpha = PRVM_clientglobalfloat(particle_stainalpha)*256; + theme->stainsize = PRVM_clientglobalfloat(particle_stainsize); + theme->delayspawn = PRVM_clientglobalfloat(particle_delayspawn); + theme->delaycollision = PRVM_clientglobalfloat(particle_delaycollision); + theme->angle = PRVM_clientglobalfloat(particle_angle); + theme->spin = PRVM_clientglobalfloat(particle_spin); +} + +// init particle spawner interface +// # float(float max_themes) initparticlespawner +static void VM_CL_InitParticleSpawner (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_InitParticleSpawner); + VM_InitParticleSpawner(prog, (int)PRVM_G_FLOAT(OFS_PARM0)); + vmpartspawner.themes[0].initialized = true; + VM_ResetParticleTheme(&vmpartspawner.themes[0]); + PRVM_G_FLOAT(OFS_RETURN) = (vmpartspawner.verified == true) ? 1 : 0; +} + +// void() resetparticle +static void VM_CL_ResetParticle (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_ResetParticle); + if (vmpartspawner.verified == false) + { + VM_Warning(prog, "VM_CL_ResetParticle: particle spawner not initialized\n"); + return; + } + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0], prog); +} + +// void(float themenum) particletheme +static void VM_CL_ParticleTheme (prvm_prog_t *prog) +{ + int themenum; + + VM_SAFEPARMCOUNT(1, VM_CL_ParticleTheme); + if (vmpartspawner.verified == false) + { + VM_Warning(prog, "VM_CL_ParticleTheme: particle spawner not initialized\n"); + return; + } + themenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (themenum < 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning(prog, "VM_CL_ParticleTheme: bad theme number %i\n", themenum); + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0], prog); + return; + } + if (vmpartspawner.themes[themenum].initialized == false) + { + VM_Warning(prog, "VM_CL_ParticleTheme: theme #%i not exists\n", themenum); + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0], prog); + return; + } + // load particle theme into globals + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[themenum], prog); +} + +// float() saveparticletheme +// void(float themenum) updateparticletheme +static void VM_CL_ParticleThemeSave (prvm_prog_t *prog) +{ + int themenum; + + VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_ParticleThemeSave); + if (vmpartspawner.verified == false) + { + VM_Warning(prog, "VM_CL_ParticleThemeSave: particle spawner not initialized\n"); + return; + } + // allocate new theme, save it and return + if (prog->argc < 1) + { + for (themenum = 0; themenum < vmpartspawner.max_themes; themenum++) + if (vmpartspawner.themes[themenum].initialized == false) + break; + if (themenum >= vmpartspawner.max_themes) + { + if (vmpartspawner.max_themes == 2048) + VM_Warning(prog, "VM_CL_ParticleThemeSave: no free theme slots\n"); + else + VM_Warning(prog, "VM_CL_ParticleThemeSave: no free theme slots, try initparticlespawner() with highter max_themes\n"); + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; + } + vmpartspawner.themes[themenum].initialized = true; + VM_CL_ParticleThemeFromGlobals(&vmpartspawner.themes[themenum], prog); + PRVM_G_FLOAT(OFS_RETURN) = themenum; + return; + } + // update existing theme + themenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (themenum < 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning(prog, "VM_CL_ParticleThemeSave: bad theme number %i\n", themenum); + return; + } + vmpartspawner.themes[themenum].initialized = true; + VM_CL_ParticleThemeFromGlobals(&vmpartspawner.themes[themenum], prog); +} + +// void(float themenum) freeparticletheme +static void VM_CL_ParticleThemeFree (prvm_prog_t *prog) +{ + int themenum; + + VM_SAFEPARMCOUNT(1, VM_CL_ParticleThemeFree); + if (vmpartspawner.verified == false) + { + VM_Warning(prog, "VM_CL_ParticleThemeFree: particle spawner not initialized\n"); + return; + } + themenum = (int)PRVM_G_FLOAT(OFS_PARM0); + // check parms + if (themenum <= 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning(prog, "VM_CL_ParticleThemeFree: bad theme number %i\n", themenum); + return; + } + if (vmpartspawner.themes[themenum].initialized == false) + { + VM_Warning(prog, "VM_CL_ParticleThemeFree: theme #%i already freed\n", themenum); + VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0], prog); + return; + } + // free theme + VM_ResetParticleTheme(&vmpartspawner.themes[themenum]); + vmpartspawner.themes[themenum].initialized = false; +} + +// float(vector org, vector dir, [float theme]) particle +// returns 0 if failed, 1 if succesful +static void VM_CL_SpawnParticle (prvm_prog_t *prog) +{ + vec3_t org, dir; + vmparticletheme_t *theme; + particle_t *part; + int themenum; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_SpawnParticle2); + if (vmpartspawner.verified == false) + { + VM_Warning(prog, "VM_CL_SpawnParticle: particle spawner not initialized\n"); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), dir); + + if (prog->argc < 3) // global-set particle + { + part = CL_NewParticle(org, + (unsigned short)PRVM_clientglobalfloat(particle_type), + ((int)PRVM_clientglobalvector(particle_color1)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color1)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color1)[2]), + ((int)PRVM_clientglobalvector(particle_color2)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color2)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color2)[2]), + (int)PRVM_clientglobalfloat(particle_tex), + PRVM_clientglobalfloat(particle_size), + PRVM_clientglobalfloat(particle_sizeincrease), + PRVM_clientglobalfloat(particle_alpha)*256, + PRVM_clientglobalfloat(particle_alphafade)*256, + PRVM_clientglobalfloat(particle_gravity), + PRVM_clientglobalfloat(particle_bounce), + org[0], + org[1], + org[2], + dir[0], + dir[1], + dir[2], + PRVM_clientglobalfloat(particle_airfriction), + PRVM_clientglobalfloat(particle_liquidfriction), + PRVM_clientglobalfloat(particle_originjitter), + PRVM_clientglobalfloat(particle_velocityjitter), + (PRVM_clientglobalfloat(particle_qualityreduction)) ? true : false, + PRVM_clientglobalfloat(particle_time), + PRVM_clientglobalfloat(particle_stretch), + (pblend_t)(int)PRVM_clientglobalfloat(particle_blendmode), + (porientation_t)(int)PRVM_clientglobalfloat(particle_orientation), + (int)(PRVM_clientglobalvector(particle_staincolor1)[0])*65536 + (int)(PRVM_clientglobalvector(particle_staincolor1)[1])*256 + (int)(PRVM_clientglobalvector(particle_staincolor1)[2]), + (int)(PRVM_clientglobalvector(particle_staincolor2)[0])*65536 + (int)(PRVM_clientglobalvector(particle_staincolor2)[1])*256 + (int)(PRVM_clientglobalvector(particle_staincolor2)[2]), + (int)PRVM_clientglobalfloat(particle_staintex), + PRVM_clientglobalfloat(particle_stainalpha)*256, + PRVM_clientglobalfloat(particle_stainsize), + PRVM_clientglobalfloat(particle_angle), + PRVM_clientglobalfloat(particle_spin), + NULL); + if (!part) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + if (PRVM_clientglobalfloat(particle_delayspawn)) + part->delayedspawn = cl.time + PRVM_clientglobalfloat(particle_delayspawn); + //if (PRVM_clientglobalfloat(particle_delaycollision)) + // part->delayedcollisions = cl.time + PRVM_clientglobalfloat(particle_delaycollision); + } + else // quick themed particle + { + themenum = (int)PRVM_G_FLOAT(OFS_PARM2); + if (themenum <= 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning(prog, "VM_CL_SpawnParticle: bad theme number %i\n", themenum); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + theme = &vmpartspawner.themes[themenum]; + part = CL_NewParticle(org, + theme->typeindex, + theme->color1, + theme->color2, + theme->tex, + theme->size, + theme->sizeincrease, + theme->alpha, + theme->alphafade, + theme->gravity, + theme->bounce, + org[0], + org[1], + org[2], + dir[0], + dir[1], + dir[2], + theme->airfriction, + theme->liquidfriction, + theme->originjitter, + theme->velocityjitter, + theme->qualityreduction, + theme->lifetime, + theme->stretch, + theme->blendmode, + theme->orientation, + theme->staincolor1, + theme->staincolor2, + theme->staintex, + theme->stainalpha, + theme->stainsize, + theme->angle, + theme->spin, + NULL); + if (!part) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + if (theme->delayspawn) + part->delayedspawn = cl.time + theme->delayspawn; + //if (theme->delaycollision) + // part->delayedcollisions = cl.time + theme->delaycollision; + } + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +// float(vector org, vector dir, float spawndelay, float collisiondelay, [float theme]) delayedparticle +// returns 0 if failed, 1 if success +static void VM_CL_SpawnParticleDelayed (prvm_prog_t *prog) +{ + vec3_t org, dir; + vmparticletheme_t *theme; + particle_t *part; + int themenum; + + VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_SpawnParticle2); + if (vmpartspawner.verified == false) + { + VM_Warning(prog, "VM_CL_SpawnParticle: particle spawner not initialized\n"); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), dir); + if (prog->argc < 5) // global-set particle + part = CL_NewParticle(org, + (unsigned short)PRVM_clientglobalfloat(particle_type), + ((int)PRVM_clientglobalvector(particle_color1)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color1)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color1)[2]), + ((int)PRVM_clientglobalvector(particle_color2)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color2)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color2)[2]), + (int)PRVM_clientglobalfloat(particle_tex), + PRVM_clientglobalfloat(particle_size), + PRVM_clientglobalfloat(particle_sizeincrease), + PRVM_clientglobalfloat(particle_alpha)*256, + PRVM_clientglobalfloat(particle_alphafade)*256, + PRVM_clientglobalfloat(particle_gravity), + PRVM_clientglobalfloat(particle_bounce), + org[0], + org[1], + org[2], + dir[0], + dir[1], + dir[2], + PRVM_clientglobalfloat(particle_airfriction), + PRVM_clientglobalfloat(particle_liquidfriction), + PRVM_clientglobalfloat(particle_originjitter), + PRVM_clientglobalfloat(particle_velocityjitter), + (PRVM_clientglobalfloat(particle_qualityreduction)) ? true : false, + PRVM_clientglobalfloat(particle_time), + PRVM_clientglobalfloat(particle_stretch), + (pblend_t)(int)PRVM_clientglobalfloat(particle_blendmode), + (porientation_t)(int)PRVM_clientglobalfloat(particle_orientation), + ((int)PRVM_clientglobalvector(particle_staincolor1)[0] << 16) + ((int)PRVM_clientglobalvector(particle_staincolor1)[1] << 8) + ((int)PRVM_clientglobalvector(particle_staincolor1)[2]), + ((int)PRVM_clientglobalvector(particle_staincolor2)[0] << 16) + ((int)PRVM_clientglobalvector(particle_staincolor2)[1] << 8) + ((int)PRVM_clientglobalvector(particle_staincolor2)[2]), + (int)PRVM_clientglobalfloat(particle_staintex), + PRVM_clientglobalfloat(particle_stainalpha)*256, + PRVM_clientglobalfloat(particle_stainsize), + PRVM_clientglobalfloat(particle_angle), + PRVM_clientglobalfloat(particle_spin), + NULL); + else // themed particle + { + themenum = (int)PRVM_G_FLOAT(OFS_PARM4); + if (themenum <= 0 || themenum >= vmpartspawner.max_themes) + { + VM_Warning(prog, "VM_CL_SpawnParticle: bad theme number %i\n", themenum); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + theme = &vmpartspawner.themes[themenum]; + part = CL_NewParticle(org, + theme->typeindex, + theme->color1, + theme->color2, + theme->tex, + theme->size, + theme->sizeincrease, + theme->alpha, + theme->alphafade, + theme->gravity, + theme->bounce, + org[0], + org[1], + org[2], + dir[0], + dir[1], + dir[2], + theme->airfriction, + theme->liquidfriction, + theme->originjitter, + theme->velocityjitter, + theme->qualityreduction, + theme->lifetime, + theme->stretch, + theme->blendmode, + theme->orientation, + theme->staincolor1, + theme->staincolor2, + theme->staintex, + theme->stainalpha, + theme->stainsize, + theme->angle, + theme->spin, + NULL); + } + if (!part) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + part->delayedspawn = cl.time + PRVM_G_FLOAT(OFS_PARM2); + //part->delayedcollisions = cl.time + PRVM_G_FLOAT(OFS_PARM3); + PRVM_G_FLOAT(OFS_RETURN) = 0; +} + +//==================== +//CSQC engine entities query +//==================== + +// float(float entitynum, float whatfld) getentity; +// vector(float entitynum, float whatfld) getentityvec; +// querying engine-drawn entity +// VorteX: currently it's only tested with whatfld = 1..7 +static void VM_CL_GetEntity (prvm_prog_t *prog) +{ + int entnum, fieldnum; + vec3_t forward, left, up, org; + VM_SAFEPARMCOUNT(2, VM_CL_GetEntityVec); + + entnum = PRVM_G_FLOAT(OFS_PARM0); + if (entnum < 0 || entnum >= cl.num_entities) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + fieldnum = PRVM_G_FLOAT(OFS_PARM1); + switch(fieldnum) + { + case 0: // active state + PRVM_G_FLOAT(OFS_RETURN) = cl.entities_active[entnum]; + break; + case 1: // origin + Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, org); + VectorCopy(org, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 2: // forward + Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, forward, left, up, org); + VectorCopy(forward, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 3: // right + Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, forward, left, up, org); + VectorNegate(left, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 4: // up + Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, forward, left, up, org); + VectorCopy(up, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 5: // scale + PRVM_G_FLOAT(OFS_RETURN) = Matrix4x4_ScaleFromMatrix(&cl.entities[entnum].render.matrix); + break; + case 6: // origin + v_forward, v_right, v_up + Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, forward, left, up, org); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(org, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 7: // alpha + PRVM_G_FLOAT(OFS_RETURN) = cl.entities[entnum].render.alpha; + break; + case 8: // colormor + VectorCopy(cl.entities[entnum].render.colormod, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 9: // pants colormod + VectorCopy(cl.entities[entnum].render.colormap_pantscolor, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 10: // shirt colormod + VectorCopy(cl.entities[entnum].render.colormap_shirtcolor, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 11: // skinnum + PRVM_G_FLOAT(OFS_RETURN) = cl.entities[entnum].render.skinnum; + break; + case 12: // mins + VectorCopy(cl.entities[entnum].render.mins, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 13: // maxs + VectorCopy(cl.entities[entnum].render.maxs, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 14: // absmin + Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, org); + VectorAdd(cl.entities[entnum].render.mins, org, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 15: // absmax + Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, org); + VectorAdd(cl.entities[entnum].render.maxs, org, PRVM_G_VECTOR(OFS_RETURN)); + break; + case 16: // light + VectorMA(cl.entities[entnum].render.modellight_ambient, 0.5, cl.entities[entnum].render.modellight_diffuse, PRVM_G_VECTOR(OFS_RETURN)); + break; + default: + PRVM_G_FLOAT(OFS_RETURN) = 0; + break; + } +} + +//==================== +//QC POLYGON functions +//==================== + +//#304 void() renderscene (EXT_CSQC) +// moved that here to reset the polygons, +// resetting them earlier causes R_Mesh_Draw to be called with numvertices = 0 +// --blub +static void VM_CL_R_RenderScene (prvm_prog_t *prog) +{ + double t = Sys_DirtyTime(); + vmpolygons_t *polys = &prog->vmpolygons; + VM_SAFEPARMCOUNT(0, VM_CL_R_RenderScene); + + // update the views + if(r_refdef.view.ismain) + { + // set the main view + csqc_main_r_refdef_view = r_refdef.view; + + // clear the flags so no other view becomes "main" unless CSQC sets VF_MAINVIEW + r_refdef.view.ismain = false; + csqc_original_r_refdef_view.ismain = false; + } + + // we need to update any RENDER_VIEWMODEL entities at this point because + // csqc supplies its own view matrix + CL_UpdateViewEntities(); + + // now draw stuff! + R_RenderView(); + + polys->num_vertices = polys->num_triangles = 0; + + // callprofile fixing hack: do not include this time in what is counted for CSQC_UpdateView + t = Sys_DirtyTime() - t;if (t < 0 || t >= 1800) t = 0; + prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= t; +} + +static void VM_ResizePolygons(vmpolygons_t *polys) +{ + float *oldvertex3f = polys->data_vertex3f; + float *oldcolor4f = polys->data_color4f; + float *oldtexcoord2f = polys->data_texcoord2f; + vmpolygons_triangle_t *oldtriangles = polys->data_triangles; + unsigned short *oldsortedelement3s = polys->data_sortedelement3s; + polys->max_vertices = min(polys->max_triangles*3, 65536); + polys->data_vertex3f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[3])); + polys->data_color4f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[4])); + polys->data_texcoord2f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[2])); + polys->data_triangles = (vmpolygons_triangle_t *)Mem_Alloc(polys->pool, polys->max_triangles*sizeof(vmpolygons_triangle_t)); + polys->data_sortedelement3s = (unsigned short *)Mem_Alloc(polys->pool, polys->max_triangles*sizeof(unsigned short[3])); + if (polys->num_vertices) + { + memcpy(polys->data_vertex3f, oldvertex3f, polys->num_vertices*sizeof(float[3])); + memcpy(polys->data_color4f, oldcolor4f, polys->num_vertices*sizeof(float[4])); + memcpy(polys->data_texcoord2f, oldtexcoord2f, polys->num_vertices*sizeof(float[2])); + } + if (polys->num_triangles) + { + memcpy(polys->data_triangles, oldtriangles, polys->num_triangles*sizeof(vmpolygons_triangle_t)); + memcpy(polys->data_sortedelement3s, oldsortedelement3s, polys->num_triangles*sizeof(unsigned short[3])); + } + if (oldvertex3f) + Mem_Free(oldvertex3f); + if (oldcolor4f) + Mem_Free(oldcolor4f); + if (oldtexcoord2f) + Mem_Free(oldtexcoord2f); + if (oldtriangles) + Mem_Free(oldtriangles); + if (oldsortedelement3s) + Mem_Free(oldsortedelement3s); +} + +static void VM_InitPolygons (vmpolygons_t* polys) +{ + memset(polys, 0, sizeof(*polys)); + polys->pool = Mem_AllocPool("VMPOLY", 0, NULL); + polys->max_triangles = 1024; + VM_ResizePolygons(polys); + polys->initialized = true; +} + +static void VM_DrawPolygonCallback (const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex; + vmpolygons_t *polys = (vmpolygons_t *)ent; +// R_Mesh_ResetTextureState(); + R_EntityMatrix(&identitymatrix); + GL_CullFace(GL_NONE); + GL_DepthTest(true); // polys in 3D space shall always have depth test + GL_DepthRange(0, 1); + R_Mesh_PrepareVertices_Generic_Arrays(polys->num_vertices, polys->data_vertex3f, polys->data_color4f, polys->data_texcoord2f); + + for (surfacelistindex = 0;surfacelistindex < numsurfaces;) + { + int numtriangles = 0; + rtexture_t *tex = polys->data_triangles[surfacelist[surfacelistindex]].texture; + int drawflag = polys->data_triangles[surfacelist[surfacelistindex]].drawflag; + DrawQ_ProcessDrawFlag(drawflag, polys->data_triangles[surfacelist[surfacelistindex]].hasalpha); + R_SetupShader_Generic(tex, NULL, GL_MODULATE, 1, false, false, false); + numtriangles = 0; + for (;surfacelistindex < numsurfaces;surfacelistindex++) + { + if (polys->data_triangles[surfacelist[surfacelistindex]].texture != tex || polys->data_triangles[surfacelist[surfacelistindex]].drawflag != drawflag) + break; + VectorCopy(polys->data_triangles[surfacelist[surfacelistindex]].elements, polys->data_sortedelement3s + 3*numtriangles); + numtriangles++; + } + R_Mesh_Draw(0, polys->num_vertices, 0, numtriangles, NULL, NULL, 0, polys->data_sortedelement3s, NULL, 0); + } +} + +static void VMPolygons_Store(vmpolygons_t *polys) +{ + qboolean hasalpha; + int i; + + // detect if we have alpha + hasalpha = polys->begin_texture_hasalpha; + for(i = 0; !hasalpha && (i < polys->begin_vertices); ++i) + if(polys->begin_color[i][3] < 1) + hasalpha = true; + + if (polys->begin_draw2d) + { + // draw the polygon as 2D immediately + drawqueuemesh_t mesh; + mesh.texture = polys->begin_texture; + mesh.num_vertices = polys->begin_vertices; + mesh.num_triangles = polys->begin_vertices-2; + mesh.data_element3i = polygonelement3i; + mesh.data_element3s = polygonelement3s; + mesh.data_vertex3f = polys->begin_vertex[0]; + mesh.data_color4f = polys->begin_color[0]; + mesh.data_texcoord2f = polys->begin_texcoord[0]; + DrawQ_Mesh(&mesh, polys->begin_drawflag, hasalpha); + } + else + { + // queue the polygon as 3D for sorted transparent rendering later + int i; + if (polys->max_triangles < polys->num_triangles + polys->begin_vertices-2) + { + while (polys->max_triangles < polys->num_triangles + polys->begin_vertices-2) + polys->max_triangles *= 2; + VM_ResizePolygons(polys); + } + if (polys->num_vertices + polys->begin_vertices <= polys->max_vertices) + { + // needle in a haystack! + // polys->num_vertices was used for copying where we actually want to copy begin_vertices + // that also caused it to not render the first polygon that is added + // --blub + memcpy(polys->data_vertex3f + polys->num_vertices * 3, polys->begin_vertex[0], polys->begin_vertices * sizeof(float[3])); + memcpy(polys->data_color4f + polys->num_vertices * 4, polys->begin_color[0], polys->begin_vertices * sizeof(float[4])); + memcpy(polys->data_texcoord2f + polys->num_vertices * 2, polys->begin_texcoord[0], polys->begin_vertices * sizeof(float[2])); + for (i = 0;i < polys->begin_vertices-2;i++) + { + polys->data_triangles[polys->num_triangles].texture = polys->begin_texture; + polys->data_triangles[polys->num_triangles].drawflag = polys->begin_drawflag; + polys->data_triangles[polys->num_triangles].elements[0] = polys->num_vertices; + polys->data_triangles[polys->num_triangles].elements[1] = polys->num_vertices + i+1; + polys->data_triangles[polys->num_triangles].elements[2] = polys->num_vertices + i+2; + polys->data_triangles[polys->num_triangles].hasalpha = hasalpha; + polys->num_triangles++; + } + polys->num_vertices += polys->begin_vertices; + } + } + polys->begin_active = false; +} + +// TODO: move this into the client code and clean-up everything else, too! [1/6/2008 Black] +// LordHavoc: agreed, this is a mess +void VM_CL_AddPolygonsToMeshQueue (prvm_prog_t *prog) +{ + int i; + vmpolygons_t *polys = &prog->vmpolygons; + vec3_t center; + + // only add polygons of the currently active prog to the queue - if there is none, we're done + if( !prog ) + return; + + if (!polys->num_triangles) + return; + + for (i = 0;i < polys->num_triangles;i++) + { + VectorMAMAM(1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[0], 1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[1], 1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[2], center); + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, VM_DrawPolygonCallback, (entity_render_t *)polys, i, NULL); + } + + /*polys->num_triangles = 0; // now done after rendering the scene, + polys->num_vertices = 0; // otherwise it's not rendered at all and prints an error message --blub */ +} + +//void(string texturename, float flag[, float is2d]) R_BeginPolygon +static void VM_CL_R_PolygonBegin (prvm_prog_t *prog) +{ + const char *picname; + skinframe_t *sf; + vmpolygons_t *polys = &prog->vmpolygons; + int tf; + + // TODO instead of using skinframes here (which provides the benefit of + // better management of flags, and is more suited for 3D rendering), what + // about supporting Q3 shaders? + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_R_PolygonBegin); + + if (!polys->initialized) + VM_InitPolygons(polys); + if (polys->begin_active) + { + VM_Warning(prog, "VM_CL_R_PolygonBegin: called twice without VM_CL_R_PolygonBegin after first\n"); + return; + } + picname = PRVM_G_STRING(OFS_PARM0); + + sf = NULL; + if(*picname) + { + tf = TEXF_ALPHA; + if((int)PRVM_G_FLOAT(OFS_PARM1) & DRAWFLAG_MIPMAP) + tf |= TEXF_MIPMAP; + + do + { + sf = R_SkinFrame_FindNextByName(sf, picname); + } + while(sf && sf->textureflags != tf); + + if(!sf || !sf->base) + sf = R_SkinFrame_LoadExternal(picname, tf, true); + + if(sf) + R_SkinFrame_MarkUsed(sf); + } + + polys->begin_texture = (sf && sf->base) ? sf->base : r_texture_white; + polys->begin_texture_hasalpha = (sf && sf->base) ? sf->hasalpha : false; + polys->begin_drawflag = (int)PRVM_G_FLOAT(OFS_PARM1) & DRAWFLAG_MASK; + polys->begin_vertices = 0; + polys->begin_active = true; + polys->begin_draw2d = (prog->argc >= 3 ? (int)PRVM_G_FLOAT(OFS_PARM2) : r_refdef.draw2dstage); +} + +//void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex +static void VM_CL_R_PolygonVertex (prvm_prog_t *prog) +{ + vmpolygons_t *polys = &prog->vmpolygons; + + VM_SAFEPARMCOUNT(4, VM_CL_R_PolygonVertex); + + if (!polys->begin_active) + { + VM_Warning(prog, "VM_CL_R_PolygonVertex: VM_CL_R_PolygonBegin wasn't called\n"); + return; + } + + if (polys->begin_vertices >= VMPOLYGONS_MAXPOINTS) + { + VM_Warning(prog, "VM_CL_R_PolygonVertex: may have %i vertices max\n", VMPOLYGONS_MAXPOINTS); + return; + } + + polys->begin_vertex[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM0)[0]; + polys->begin_vertex[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM0)[1]; + polys->begin_vertex[polys->begin_vertices][2] = PRVM_G_VECTOR(OFS_PARM0)[2]; + polys->begin_texcoord[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM1)[0]; + polys->begin_texcoord[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM1)[1]; + polys->begin_color[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM2)[0]; + polys->begin_color[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM2)[1]; + polys->begin_color[polys->begin_vertices][2] = PRVM_G_VECTOR(OFS_PARM2)[2]; + polys->begin_color[polys->begin_vertices][3] = PRVM_G_FLOAT(OFS_PARM3); + polys->begin_vertices++; +} + +//void() R_EndPolygon +static void VM_CL_R_PolygonEnd (prvm_prog_t *prog) +{ + vmpolygons_t *polys = &prog->vmpolygons; + + VM_SAFEPARMCOUNT(0, VM_CL_R_PolygonEnd); + if (!polys->begin_active) + { + VM_Warning(prog, "VM_CL_R_PolygonEnd: VM_CL_R_PolygonBegin wasn't called\n"); + return; + } + polys->begin_active = false; + if (polys->begin_vertices >= 3) + VMPolygons_Store(polys); + else + VM_Warning(prog, "VM_CL_R_PolygonEnd: %i vertices isn't a good choice\n", polys->begin_vertices); +} + +static vmpolygons_t debugPolys; + +void Debug_PolygonBegin(const char *picname, int drawflag) +{ + if(!debugPolys.initialized) + VM_InitPolygons(&debugPolys); + if(debugPolys.begin_active) + { + Con_Printf("Debug_PolygonBegin: called twice without Debug_PolygonEnd after first\n"); + return; + } + debugPolys.begin_texture = picname[0] ? Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT)->tex : r_texture_white; + debugPolys.begin_drawflag = drawflag; + debugPolys.begin_vertices = 0; + debugPolys.begin_active = true; +} + +void Debug_PolygonVertex(float x, float y, float z, float s, float t, float r, float g, float b, float a) +{ + if(!debugPolys.begin_active) + { + Con_Printf("Debug_PolygonVertex: Debug_PolygonBegin wasn't called\n"); + return; + } + + if(debugPolys.begin_vertices > VMPOLYGONS_MAXPOINTS) + { + Con_Printf("Debug_PolygonVertex: may have %i vertices max\n", VMPOLYGONS_MAXPOINTS); + return; + } + + debugPolys.begin_vertex[debugPolys.begin_vertices][0] = x; + debugPolys.begin_vertex[debugPolys.begin_vertices][1] = y; + debugPolys.begin_vertex[debugPolys.begin_vertices][2] = z; + debugPolys.begin_texcoord[debugPolys.begin_vertices][0] = s; + debugPolys.begin_texcoord[debugPolys.begin_vertices][1] = t; + debugPolys.begin_color[debugPolys.begin_vertices][0] = r; + debugPolys.begin_color[debugPolys.begin_vertices][1] = g; + debugPolys.begin_color[debugPolys.begin_vertices][2] = b; + debugPolys.begin_color[debugPolys.begin_vertices][3] = a; + debugPolys.begin_vertices++; +} + +void Debug_PolygonEnd(void) +{ + if (!debugPolys.begin_active) + { + Con_Printf("Debug_PolygonEnd: Debug_PolygonBegin wasn't called\n"); + return; + } + debugPolys.begin_active = false; + if (debugPolys.begin_vertices >= 3) + VMPolygons_Store(&debugPolys); + else + Con_Printf("Debug_PolygonEnd: %i vertices isn't a good choice\n", debugPolys.begin_vertices); +} + +/* +============= +CL_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +static qboolean CL_CheckBottom (prvm_edict_t *ent) +{ + prvm_prog_t *prog = CLVM_prog; + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); + VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (!(CL_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) + goto realcheck; + } + + return true; // we got out easy + +realcheck: +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*sv_stepheight.value; + trace = CL_TraceLine(start, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), true, false, NULL, true, false); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = CL_TraceLine(start, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), true, false, NULL, true, false); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) + return false; + } + + return true; +} + +/* +============= +CL_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done and false is returned +============= +*/ +static qboolean CL_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace) +{ + prvm_prog_t *prog = CLVM_prog; + float dz; + vec3_t oldorg, neworg, end, traceendpos; + vec3_t mins, maxs, start; + trace_t trace; + int i, svent; + prvm_edict_t *enemy; + +// try the move + VectorCopy(PRVM_clientedictvector(ent, mins), mins); + VectorCopy(PRVM_clientedictvector(ent, maxs), maxs); + VectorCopy (PRVM_clientedictvector(ent, origin), oldorg); + VectorAdd (PRVM_clientedictvector(ent, origin), move, neworg); + +// flying monsters don't step up + if ( (int)PRVM_clientedictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (PRVM_clientedictvector(ent, origin), move, neworg); + enemy = PRVM_PROG_TO_EDICT(PRVM_clientedictedict(ent, enemy)); + if (i == 0 && enemy != prog->edicts) + { + dz = PRVM_clientedictvector(ent, origin)[2] - PRVM_clientedictvector(PRVM_PROG_TO_EDICT(PRVM_clientedictedict(ent, enemy)), origin)[2]; + if (dz > 40) + neworg[2] -= 8; + if (dz < 30) + neworg[2] += 8; + } + VectorCopy(PRVM_clientedictvector(ent, origin), start); + trace = CL_TraceBox(start, mins, maxs, neworg, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true); + if (settrace) + CL_VM_SetTraceGlobals(prog, &trace, svent); + + if (trace.fraction == 1) + { + VectorCopy(trace.endpos, traceendpos); + if (((int)PRVM_clientedictfloat(ent, flags) & FL_SWIM) && !(CL_PointSuperContents(traceendpos) & SUPERCONTENTS_LIQUIDSMASK)) + return false; // swim monster left water + + VectorCopy (traceendpos, PRVM_clientedictvector(ent, origin)); + if (relink) + CL_LinkEdict(ent); + return true; + } + + if (enemy == prog->edicts) + break; + } + + return false; + } + +// push down from a step height above the wished position + neworg[2] += sv_stepheight.value; + VectorCopy (neworg, end); + end[2] -= sv_stepheight.value*2; + + trace = CL_TraceBox(neworg, mins, maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true); + if (settrace) + CL_VM_SetTraceGlobals(prog, &trace, svent); + + if (trace.startsolid) + { + neworg[2] -= sv_stepheight.value; + trace = CL_TraceBox(neworg, mins, maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true); + if (settrace) + CL_VM_SetTraceGlobals(prog, &trace, svent); + if (trace.startsolid) + return false; + } + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) + { + VectorAdd (PRVM_clientedictvector(ent, origin), move, PRVM_clientedictvector(ent, origin)); + if (relink) + CL_LinkEdict(ent); + PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) & ~FL_ONGROUND; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, PRVM_clientedictvector(ent, origin)); + + if (!CL_CheckBottom (ent)) + { + if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + CL_LinkEdict(ent); + return true; + } + VectorCopy (oldorg, PRVM_clientedictvector(ent, origin)); + return false; + } + + if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) + PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) & ~FL_PARTIALGROUND; + + PRVM_clientedictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + +// the move is ok + if (relink) + CL_LinkEdict(ent); + return true; +} + +/* +=============== +VM_CL_walkmove + +float(float yaw, float dist[, settrace]) walkmove +=============== +*/ +static void VM_CL_walkmove (prvm_prog_t *prog) +{ + prvm_edict_t *ent; + float yaw, dist; + vec3_t move; + mfunction_t *oldf; + int oldself; + qboolean settrace; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_walkmove); + + // assume failure if it returns early + PRVM_G_FLOAT(OFS_RETURN) = 0; + + ent = PRVM_PROG_TO_EDICT(PRVM_clientglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning(prog, "walkmove: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "walkmove: can not modify free entity\n"); + return; + } + yaw = PRVM_G_FLOAT(OFS_PARM0); + dist = PRVM_G_FLOAT(OFS_PARM1); + settrace = prog->argc >= 3 && PRVM_G_FLOAT(OFS_PARM2); + + if ( !( (int)PRVM_clientedictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + return; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + +// save program state, because CL_movestep may call other progs + oldf = prog->xfunction; + oldself = PRVM_clientglobaledict(self); + + PRVM_G_FLOAT(OFS_RETURN) = CL_movestep(ent, move, true, false, settrace); + + +// restore program state + prog->xfunction = oldf; + PRVM_clientglobaledict(self) = oldself; +} + +/* +=============== +VM_CL_serverkey + +string(string key) serverkey +=============== +*/ +static void VM_CL_serverkey(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNT(1, VM_CL_serverkey); + InfoString_GetValue(cl.qw_serverinfo, PRVM_G_STRING(OFS_PARM0), string, sizeof(string)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); +} + +/* +================= +VM_CL_checkpvs + +Checks if an entity is in a point's PVS. +Should be fast but can be inexact. + +float checkpvs(vector viewpos, entity viewee) = #240; +================= +*/ +static void VM_CL_checkpvs (prvm_prog_t *prog) +{ + vec3_t viewpos; + prvm_edict_t *viewee; + vec3_t mi, ma; +#if 1 + unsigned char *pvs; +#else + int fatpvsbytes; + unsigned char fatpvs[MAX_MAP_LEAFS/8]; +#endif + + VM_SAFEPARMCOUNT(2, VM_SV_checkpvs); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), viewpos); + viewee = PRVM_G_EDICT(OFS_PARM1); + + if(viewee->priv.required->free) + { + VM_Warning(prog, "checkpvs: can not check free entity\n"); + PRVM_G_FLOAT(OFS_RETURN) = 4; + return; + } + + VectorAdd(PRVM_serveredictvector(viewee, origin), PRVM_serveredictvector(viewee, mins), mi); + VectorAdd(PRVM_serveredictvector(viewee, origin), PRVM_serveredictvector(viewee, maxs), ma); + +#if 1 + if(!cl.worldmodel || !cl.worldmodel->brush.GetPVS || !cl.worldmodel->brush.BoxTouchingPVS) + { + // no PVS support on this worldmodel... darn + PRVM_G_FLOAT(OFS_RETURN) = 3; + return; + } + pvs = cl.worldmodel->brush.GetPVS(cl.worldmodel, viewpos); + if(!pvs) + { + // viewpos isn't in any PVS... darn + PRVM_G_FLOAT(OFS_RETURN) = 2; + return; + } + PRVM_G_FLOAT(OFS_RETURN) = cl.worldmodel->brush.BoxTouchingPVS(cl.worldmodel, pvs, mi, ma); +#else + // using fat PVS like FTEQW does (slow) + if(!cl.worldmodel || !cl.worldmodel->brush.FatPVS || !cl.worldmodel->brush.BoxTouchingPVS) + { + // no PVS support on this worldmodel... darn + PRVM_G_FLOAT(OFS_RETURN) = 3; + return; + } + fatpvsbytes = cl.worldmodel->brush.FatPVS(cl.worldmodel, viewpos, 8, fatpvs, sizeof(fatpvs), false); + if(!fatpvsbytes) + { + // viewpos isn't in any PVS... darn + PRVM_G_FLOAT(OFS_RETURN) = 2; + return; + } + PRVM_G_FLOAT(OFS_RETURN) = cl.worldmodel->brush.BoxTouchingPVS(cl.worldmodel, fatpvs, mi, ma); +#endif +} + +// #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. +static void VM_CL_skel_create(prvm_prog_t *prog) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = CL_GetModelByIndex(modelindex); + skeleton_t *skeleton; + int i; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (!model || !model->num_bones) + return; + for (i = 0;i < MAX_EDICTS;i++) + if (!prog->skeletons[i]) + break; + if (i == MAX_EDICTS) + return; + prog->skeletons[i] = skeleton = (skeleton_t *)Mem_Alloc(cls.levelmempool, sizeof(skeleton_t) + model->num_bones * sizeof(matrix4x4_t)); + PRVM_G_FLOAT(OFS_RETURN) = i + 1; + skeleton->model = model; + skeleton->relativetransforms = (matrix4x4_t *)(skeleton+1); + // initialize to identity matrices + for (i = 0;i < skeleton->model->num_bones;i++) + skeleton->relativetransforms[i] = identitymatrix; +} + +// #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +static void VM_CL_skel_build(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + prvm_edict_t *ed = PRVM_G_EDICT(OFS_PARM1); + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM2); + float retainfrac = PRVM_G_FLOAT(OFS_PARM3); + int firstbone = PRVM_G_FLOAT(OFS_PARM4) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM5) - 1; + dp_model_t *model = CL_GetModelByIndex(modelindex); + int numblends; + int bonenum; + int blendindex; + framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + matrix4x4_t bonematrix; + matrix4x4_t matrix; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + firstbone = max(0, firstbone); + lastbone = min(lastbone, model->num_bones - 1); + lastbone = min(lastbone, skeleton->model->num_bones - 1); + VM_GenerateFrameGroupBlend(prog, framegroupblend, ed); + VM_FrameBlendFromFrameGroupBlend(frameblend, framegroupblend, model, cl.time); + for (numblends = 0;numblends < MAX_FRAMEBLENDS && frameblend[numblends].lerp;numblends++) + ; + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + { + memset(&bonematrix, 0, sizeof(bonematrix)); + for (blendindex = 0;blendindex < numblends;blendindex++) + { + Matrix4x4_FromBonePose7s(&matrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + bonenum)); + Matrix4x4_Accumulate(&bonematrix, &matrix, frameblend[blendindex].lerp); + } + Matrix4x4_Normalize3(&bonematrix, &bonematrix); + Matrix4x4_Interpolate(&skeleton->relativetransforms[bonenum], &bonematrix, &skeleton->relativetransforms[bonenum], retainfrac); + } + PRVM_G_FLOAT(OFS_RETURN) = skeletonindex + 1; +} + +// #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton +static void VM_CL_skel_get_numbones(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->num_bones; +} + +// #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) +static void VM_CL_skel_get_bonename(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + PRVM_G_INT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, skeleton->model->data_bones[bonenum].name); +} + +// #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +static void VM_CL_skel_get_boneparent(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->data_bones[bonenum].parent + 1; +} + +// #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex +static void VM_CL_skel_find_bone(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + const char *tagname = PRVM_G_STRING(OFS_PARM1); + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + PRVM_G_FLOAT(OFS_RETURN) = Mod_Alias_GetTagIndexForName(skeleton->model, 0, tagname); +} + +// #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +static void VM_CL_skel_get_bonerel(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + matrix4x4_t matrix; + vec3_t forward, left, up, origin; + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + VectorClear(PRVM_clientglobalvector(v_forward)); + VectorClear(PRVM_clientglobalvector(v_right)); + VectorClear(PRVM_clientglobalvector(v_up)); + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + matrix = skeleton->relativetransforms[bonenum]; + Matrix4x4_ToVectors(&matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); +} + +// #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +static void VM_CL_skel_get_boneabs(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + vec3_t forward, left, up, origin; + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + VectorClear(PRVM_clientglobalvector(v_forward)); + VectorClear(PRVM_clientglobalvector(v_right)); + VectorClear(PRVM_clientglobalvector(v_up)); + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + matrix = skeleton->relativetransforms[bonenum]; + // convert to absolute + while ((bonenum = skeleton->model->data_bones[bonenum].parent) >= 0) + { + temp = matrix; + Matrix4x4_Concat(&matrix, &skeleton->relativetransforms[bonenum], &temp); + } + Matrix4x4_ToVectors(&matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); +} + +// #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +static void VM_CL_skel_set_bone(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + skeleton->relativetransforms[bonenum] = matrix; +} + +// #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +static void VM_CL_skel_mul_bone(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + temp = skeleton->relativetransforms[bonenum]; + Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); +} + +// #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +static void VM_CL_skel_mul_bones(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int firstbone = PRVM_G_FLOAT(OFS_PARM1) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM2) - 1; + int bonenum; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + firstbone = max(0, firstbone); + lastbone = min(lastbone, skeleton->model->num_bones - 1); + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + { + temp = skeleton->relativetransforms[bonenum]; + Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); + } +} + +// #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +static void VM_CL_skel_copybones(prvm_prog_t *prog) +{ + int skeletonindexdst = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int skeletonindexsrc = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + int firstbone = PRVM_G_FLOAT(OFS_PARM2) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM3) - 1; + int bonenum; + skeleton_t *skeletondst; + skeleton_t *skeletonsrc; + if (skeletonindexdst < 0 || skeletonindexdst >= MAX_EDICTS || !(skeletondst = prog->skeletons[skeletonindexdst])) + return; + if (skeletonindexsrc < 0 || skeletonindexsrc >= MAX_EDICTS || !(skeletonsrc = prog->skeletons[skeletonindexsrc])) + return; + firstbone = max(0, firstbone); + lastbone = min(lastbone, skeletondst->model->num_bones - 1); + lastbone = min(lastbone, skeletonsrc->model->num_bones - 1); + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + skeletondst->relativetransforms[bonenum] = skeletonsrc->relativetransforms[bonenum]; +} + +// #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +static void VM_CL_skel_delete(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + Mem_Free(skeleton); + prog->skeletons[skeletonindex] = NULL; +} + +// #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found +static void VM_CL_frameforname(prvm_prog_t *prog) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = CL_GetModelByIndex(modelindex); + const char *name = PRVM_G_STRING(OFS_PARM1); + int i; + PRVM_G_FLOAT(OFS_RETURN) = -1; + if (!model || !model->animscenes) + return; + for (i = 0;i < model->numframes;i++) + { + if (!strcasecmp(model->animscenes[i].name, name)) + { + PRVM_G_FLOAT(OFS_RETURN) = i; + break; + } + } +} + +// #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +static void VM_CL_frameduration(prvm_prog_t *prog) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = CL_GetModelByIndex(modelindex); + int framenum = (int)PRVM_G_FLOAT(OFS_PARM1); + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes) + return; + if (model->animscenes[framenum].framerate) + PRVM_G_FLOAT(OFS_RETURN) = model->animscenes[framenum].framecount / model->animscenes[framenum].framerate; +} + +static void VM_CL_RotateMoves(prvm_prog_t *prog) +{ + /* + * Obscure builtin used by GAME_XONOTIC. + * + * Edits the input history of cl_movement by rotating all move commands + * currently in the queue using the given transform. + * + * The vector passed is an "angles transform" as used by warpzonelib, i.e. + * v_angle-like (non-inverted) euler angles that perform the rotation + * of the space that is to be done. + * + * This is meant to be used as a fixangle replacement after passing + * through a warpzone/portal: the client is told about the warp transform, + * and calls this function in the same frame as the one on which the + * client's origin got changed by the serverside teleport. Then this code + * transforms the pre-warp input (which matches the empty space behind + * the warp plane) into post-warp input (which matches the target area + * of the warp). Also, at the same time, the client has to use + * R_SetView to adjust VF_CL_VIEWANGLES according to the same transform. + * + * This together allows warpzone motion to be perfectly predicted by + * the client! + * + * Furthermore, for perfect warpzone behaviour, the server side also + * has to detect input the client sent before it received the origin + * update, but after the warp occurred on the server, and has to adjust + * input appropriately. + */ + matrix4x4_t m; + vec3_t v = {0, 0, 0}; + vec3_t a, x, y, z; + VM_SAFEPARMCOUNT(1, VM_CL_RotateMoves); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), a); + AngleVectorsFLU(a, x, y, z); + Matrix4x4_FromVectors(&m, x, y, z, v); + CL_RotateMoves(&m); +} + +// #358 void(string cubemapname) loadcubemap +static void VM_CL_loadcubemap(prvm_prog_t *prog) +{ + const char *name; + + VM_SAFEPARMCOUNT(1, VM_CL_loadcubemap); + name = PRVM_G_STRING(OFS_PARM0); + R_GetCubemap(name); +} + +#define REFDEFFLAG_TELEPORTED 1 +#define REFDEFFLAG_JUMPING 2 +#define REFDEFFLAG_DEAD 4 +#define REFDEFFLAG_INTERMISSION 8 +static void VM_CL_V_CalcRefdef(prvm_prog_t *prog) +{ + matrix4x4_t entrendermatrix; + vec3_t clviewangles; + vec3_t clvelocity; + qboolean teleported; + qboolean clonground; + qboolean clcmdjump; + qboolean cldead; + qboolean clintermission; + float clstatsviewheight; + prvm_edict_t *ent; + int flags; + + VM_SAFEPARMCOUNT(2, VM_CL_V_CalcRefdef); + ent = PRVM_G_EDICT(OFS_PARM0); + flags = PRVM_G_FLOAT(OFS_PARM1); + + // use the CL_GetTagMatrix function on self to ensure consistent behavior (duplicate code would be bad) + CL_GetTagMatrix(prog, &entrendermatrix, ent, 0); + + VectorCopy(cl.csqc_viewangles, clviewangles); + teleported = (flags & REFDEFFLAG_TELEPORTED) != 0; + clonground = ((int)PRVM_clientedictfloat(ent, pmove_flags) & PMF_ONGROUND) != 0; + clcmdjump = (flags & REFDEFFLAG_JUMPING) != 0; + clstatsviewheight = PRVM_clientedictvector(ent, view_ofs)[2]; + cldead = (flags & REFDEFFLAG_DEAD) != 0; + clintermission = (flags & REFDEFFLAG_INTERMISSION) != 0; + VectorCopy(PRVM_clientedictvector(ent, velocity), clvelocity); + + V_CalcRefdefUsing(&entrendermatrix, clviewangles, teleported, clonground, clcmdjump, clstatsviewheight, cldead, clintermission, clvelocity); + + VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin); + VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles); + CSQC_R_RecalcView(); +} + +//============================================================================ + +// To create a almost working builtin file from this replace: +// "^NULL.*" with "" +// "^{.*//.*}:Wh\(.*\)" with "\1" +// "\:" with "//" +// "^.*//:Wh{\#:d*}:Wh{.*}" with "\2 = \1;" +// "\n\n+" with "\n\n" + +prvm_builtin_t vm_cl_builtins[] = { +NULL, // #0 NULL function (not callable) (QUAKE) +VM_CL_makevectors, // #1 void(vector ang) makevectors (QUAKE) +VM_CL_setorigin, // #2 void(entity e, vector o) setorigin (QUAKE) +VM_CL_setmodel, // #3 void(entity e, string m) setmodel (QUAKE) +VM_CL_setsize, // #4 void(entity e, vector min, vector max) setsize (QUAKE) +NULL, // #5 void(entity e, vector min, vector max) setabssize (QUAKE) +VM_break, // #6 void() break (QUAKE) +VM_random, // #7 float() random (QUAKE) +VM_CL_sound, // #8 void(entity e, float chan, string samp) sound (QUAKE) +VM_normalize, // #9 vector(vector v) normalize (QUAKE) +VM_error, // #10 void(string e) error (QUAKE) +VM_objerror, // #11 void(string e) objerror (QUAKE) +VM_vlen, // #12 float(vector v) vlen (QUAKE) +VM_vectoyaw, // #13 float(vector v) vectoyaw (QUAKE) +VM_CL_spawn, // #14 entity() spawn (QUAKE) +VM_remove, // #15 void(entity e) remove (QUAKE) +VM_CL_traceline, // #16 void(vector v1, vector v2, float tryents, entity ignoreentity) traceline (QUAKE) +NULL, // #17 entity() checkclient (QUAKE) +VM_find, // #18 entity(entity start, .string fld, string match) find (QUAKE) +VM_precache_sound, // #19 void(string s) precache_sound (QUAKE) +VM_CL_precache_model, // #20 void(string s) precache_model (QUAKE) +NULL, // #21 void(entity client, string s, ...) stuffcmd (QUAKE) +VM_CL_findradius, // #22 entity(vector org, float rad) findradius (QUAKE) +NULL, // #23 void(string s, ...) bprint (QUAKE) +NULL, // #24 void(entity client, string s, ...) sprint (QUAKE) +VM_dprint, // #25 void(string s, ...) dprint (QUAKE) +VM_ftos, // #26 string(float f) ftos (QUAKE) +VM_vtos, // #27 string(vector v) vtos (QUAKE) +VM_coredump, // #28 void() coredump (QUAKE) +VM_traceon, // #29 void() traceon (QUAKE) +VM_traceoff, // #30 void() traceoff (QUAKE) +VM_eprint, // #31 void(entity e) eprint (QUAKE) +VM_CL_walkmove, // #32 float(float yaw, float dist[, float settrace]) walkmove (QUAKE) +NULL, // #33 (QUAKE) +VM_CL_droptofloor, // #34 float() droptofloor (QUAKE) +VM_CL_lightstyle, // #35 void(float style, string value) lightstyle (QUAKE) +VM_rint, // #36 float(float v) rint (QUAKE) +VM_floor, // #37 float(float v) floor (QUAKE) +VM_ceil, // #38 float(float v) ceil (QUAKE) +NULL, // #39 (QUAKE) +VM_CL_checkbottom, // #40 float(entity e) checkbottom (QUAKE) +VM_CL_pointcontents, // #41 float(vector v) pointcontents (QUAKE) +NULL, // #42 (QUAKE) +VM_fabs, // #43 float(float f) fabs (QUAKE) +NULL, // #44 vector(entity e, float speed) aim (QUAKE) +VM_cvar, // #45 float(string s) cvar (QUAKE) +VM_localcmd, // #46 void(string s) localcmd (QUAKE) +VM_nextent, // #47 entity(entity e) nextent (QUAKE) +VM_CL_particle, // #48 void(vector o, vector d, float color, float count) particle (QUAKE) +VM_changeyaw, // #49 void() ChangeYaw (QUAKE) +NULL, // #50 (QUAKE) +VM_vectoangles, // #51 vector(vector v) vectoangles (QUAKE) +NULL, // #52 void(float to, float f) WriteByte (QUAKE) +NULL, // #53 void(float to, float f) WriteChar (QUAKE) +NULL, // #54 void(float to, float f) WriteShort (QUAKE) +NULL, // #55 void(float to, float f) WriteLong (QUAKE) +NULL, // #56 void(float to, float f) WriteCoord (QUAKE) +NULL, // #57 void(float to, float f) WriteAngle (QUAKE) +NULL, // #58 void(float to, string s) WriteString (QUAKE) +NULL, // #59 (QUAKE) +VM_sin, // #60 float(float f) sin (DP_QC_SINCOSSQRTPOW) +VM_cos, // #61 float(float f) cos (DP_QC_SINCOSSQRTPOW) +VM_sqrt, // #62 float(float f) sqrt (DP_QC_SINCOSSQRTPOW) +VM_changepitch, // #63 void(entity ent) changepitch (DP_QC_CHANGEPITCH) +VM_CL_tracetoss, // #64 void(entity e, entity ignore) tracetoss (DP_QC_TRACETOSS) +VM_etos, // #65 string(entity ent) etos (DP_QC_ETOS) +NULL, // #66 (QUAKE) +NULL, // #67 void(float step) movetogoal (QUAKE) +VM_precache_file, // #68 string(string s) precache_file (QUAKE) +VM_CL_makestatic, // #69 void(entity e) makestatic (QUAKE) +NULL, // #70 void(string s) changelevel (QUAKE) +NULL, // #71 (QUAKE) +VM_cvar_set, // #72 void(string var, string val) cvar_set (QUAKE) +NULL, // #73 void(entity client, strings) centerprint (QUAKE) +VM_CL_ambientsound, // #74 void(vector pos, string samp, float vol, float atten) ambientsound (QUAKE) +VM_CL_precache_model, // #75 string(string s) precache_model2 (QUAKE) +VM_precache_sound, // #76 string(string s) precache_sound2 (QUAKE) +VM_precache_file, // #77 string(string s) precache_file2 (QUAKE) +NULL, // #78 void(entity e) setspawnparms (QUAKE) +NULL, // #79 void(entity killer, entity killee) logfrag (QUAKEWORLD) +NULL, // #80 string(entity e, string keyname) infokey (QUAKEWORLD) +VM_stof, // #81 float(string s) stof (FRIK_FILE) +NULL, // #82 void(vector where, float set) multicast (QUAKEWORLD) +NULL, // #83 (QUAKE) +NULL, // #84 (QUAKE) +NULL, // #85 (QUAKE) +NULL, // #86 (QUAKE) +NULL, // #87 (QUAKE) +NULL, // #88 (QUAKE) +NULL, // #89 (QUAKE) +VM_CL_tracebox, // #90 void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox (DP_QC_TRACEBOX) +VM_randomvec, // #91 vector() randomvec (DP_QC_RANDOMVEC) +VM_CL_getlight, // #92 vector(vector org) getlight (DP_QC_GETLIGHT) +VM_registercvar, // #93 float(string name, string value) registercvar (DP_REGISTERCVAR) +VM_min, // #94 float(float a, floats) min (DP_QC_MINMAXBOUND) +VM_max, // #95 float(float a, floats) max (DP_QC_MINMAXBOUND) +VM_bound, // #96 float(float minimum, float val, float maximum) bound (DP_QC_MINMAXBOUND) +VM_pow, // #97 float(float f, float f) pow (DP_QC_SINCOSSQRTPOW) +VM_findfloat, // #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT) +VM_checkextension, // #99 float(string s) checkextension (the basis of the extension system) +// FrikaC and Telejano range #100-#199 +NULL, // #100 +NULL, // #101 +NULL, // #102 +NULL, // #103 +NULL, // #104 +NULL, // #105 +NULL, // #106 +NULL, // #107 +NULL, // #108 +NULL, // #109 +VM_fopen, // #110 float(string filename, float mode) fopen (FRIK_FILE) +VM_fclose, // #111 void(float fhandle) fclose (FRIK_FILE) +VM_fgets, // #112 string(float fhandle) fgets (FRIK_FILE) +VM_fputs, // #113 void(float fhandle, string s) fputs (FRIK_FILE) +VM_strlen, // #114 float(string s) strlen (FRIK_FILE) +VM_strcat, // #115 string(string s1, string s2, ...) strcat (FRIK_FILE) +VM_substring, // #116 string(string s, float start, float length) substring (FRIK_FILE) +VM_stov, // #117 vector(string) stov (FRIK_FILE) +VM_strzone, // #118 string(string s) strzone (FRIK_FILE) +VM_strunzone, // #119 void(string s) strunzone (FRIK_FILE) +NULL, // #120 +NULL, // #121 +NULL, // #122 +NULL, // #123 +NULL, // #124 +NULL, // #125 +NULL, // #126 +NULL, // #127 +NULL, // #128 +NULL, // #129 +NULL, // #130 +NULL, // #131 +NULL, // #132 +NULL, // #133 +NULL, // #134 +NULL, // #135 +NULL, // #136 +NULL, // #137 +NULL, // #138 +NULL, // #139 +NULL, // #140 +NULL, // #141 +NULL, // #142 +NULL, // #143 +NULL, // #144 +NULL, // #145 +NULL, // #146 +NULL, // #147 +NULL, // #148 +NULL, // #149 +NULL, // #150 +NULL, // #151 +NULL, // #152 +NULL, // #153 +NULL, // #154 +NULL, // #155 +NULL, // #156 +NULL, // #157 +NULL, // #158 +NULL, // #159 +NULL, // #160 +NULL, // #161 +NULL, // #162 +NULL, // #163 +NULL, // #164 +NULL, // #165 +NULL, // #166 +NULL, // #167 +NULL, // #168 +NULL, // #169 +NULL, // #170 +NULL, // #171 +NULL, // #172 +NULL, // #173 +NULL, // #174 +NULL, // #175 +NULL, // #176 +NULL, // #177 +NULL, // #178 +NULL, // #179 +NULL, // #180 +NULL, // #181 +NULL, // #182 +NULL, // #183 +NULL, // #184 +NULL, // #185 +NULL, // #186 +NULL, // #187 +NULL, // #188 +NULL, // #189 +NULL, // #190 +NULL, // #191 +NULL, // #192 +NULL, // #193 +NULL, // #194 +NULL, // #195 +NULL, // #196 +NULL, // #197 +NULL, // #198 +NULL, // #199 +// FTEQW range #200-#299 +NULL, // #200 +NULL, // #201 +NULL, // #202 +NULL, // #203 +NULL, // #204 +NULL, // #205 +NULL, // #206 +NULL, // #207 +NULL, // #208 +NULL, // #209 +NULL, // #210 +NULL, // #211 +NULL, // #212 +NULL, // #213 +NULL, // #214 +NULL, // #215 +NULL, // #216 +NULL, // #217 +VM_bitshift, // #218 float(float number, float quantity) bitshift (EXT_BITSHIFT) +NULL, // #219 +NULL, // #220 +VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) +VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) +VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) +VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) +VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) +VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) +VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) +VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) +VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) +VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) +NULL, // #231 +NULL, // #232 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) +NULL, // #233 +NULL, // #234 +NULL, // #235 +NULL, // #236 +NULL, // #237 +NULL, // #238 +NULL, // #239 +VM_CL_checkpvs, // #240 +NULL, // #241 +NULL, // #242 +NULL, // #243 +NULL, // #244 +NULL, // #245 +NULL, // #246 +NULL, // #247 +NULL, // #248 +NULL, // #249 +NULL, // #250 +NULL, // #251 +NULL, // #252 +NULL, // #253 +NULL, // #254 +NULL, // #255 +NULL, // #256 +NULL, // #257 +NULL, // #258 +NULL, // #259 +NULL, // #260 +NULL, // #261 +NULL, // #262 +VM_CL_skel_create, // #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. +VM_CL_skel_build, // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +VM_CL_skel_get_numbones, // #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton +VM_CL_skel_get_bonename, // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) +VM_CL_skel_get_boneparent, // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, -1 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +VM_CL_skel_find_bone, // #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex +VM_CL_skel_get_bonerel, // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +VM_CL_skel_get_boneabs, // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +VM_CL_skel_set_bone, // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +VM_CL_skel_mul_bone, // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +VM_CL_skel_mul_bones, // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +VM_CL_skel_copybones, // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +VM_CL_skel_delete, // #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +VM_CL_frameforname, // #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found +VM_CL_frameduration, // #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +NULL, // #278 +NULL, // #279 +NULL, // #280 +NULL, // #281 +NULL, // #282 +NULL, // #283 +NULL, // #284 +NULL, // #285 +NULL, // #286 +NULL, // #287 +NULL, // #288 +NULL, // #289 +NULL, // #290 +NULL, // #291 +NULL, // #292 +NULL, // #293 +NULL, // #294 +NULL, // #295 +NULL, // #296 +NULL, // #297 +NULL, // #298 +NULL, // #299 +// CSQC range #300-#399 +VM_CL_R_ClearScene, // #300 void() clearscene (EXT_CSQC) +VM_CL_R_AddEntities, // #301 void(float mask) addentities (EXT_CSQC) +VM_CL_R_AddEntity, // #302 void(entity ent) addentity (EXT_CSQC) +VM_CL_R_SetView, // #303 float(float property, ...) setproperty (EXT_CSQC) +VM_CL_R_RenderScene, // #304 void() renderscene (EXT_CSQC) +VM_CL_R_AddDynamicLight, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (EXT_CSQC) +VM_CL_R_PolygonBegin, // #306 void(string texturename, float flag, float is2d[NYI: , float lines]) R_BeginPolygon +VM_CL_R_PolygonVertex, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex +VM_CL_R_PolygonEnd, // #308 void() R_EndPolygon +VM_CL_R_SetView, // #309 float(float property) getproperty (EXT_CSQC) +VM_CL_unproject, // #310 vector (vector v) cs_unproject (EXT_CSQC) +VM_CL_project, // #311 vector (vector v) cs_project (EXT_CSQC) +NULL, // #312 +NULL, // #313 +NULL, // #314 +VM_drawline, // #315 void(float width, vector pos1, vector pos2, float flag) drawline (EXT_CSQC) +VM_iscachedpic, // #316 float(string name) iscachedpic (EXT_CSQC) +VM_precache_pic, // #317 string(string name, float trywad) precache_pic (EXT_CSQC) +VM_getimagesize, // #318 vector(string picname) draw_getimagesize (EXT_CSQC) +VM_freepic, // #319 void(string name) freepic (EXT_CSQC) +VM_drawcharacter, // #320 float(vector position, float character, vector scale, vector rgb, float alpha, float flag) drawcharacter (EXT_CSQC) +VM_drawstring, // #321 float(vector position, string text, vector scale, vector rgb, float alpha[, float flag]) drawstring (EXT_CSQC, DP_CSQC) +VM_drawpic, // #322 float(vector position, string pic, vector size, vector rgb, float alpha[, float flag]) drawpic (EXT_CSQC) +VM_drawfill, // #323 float(vector position, vector size, vector rgb, float alpha, float flag) drawfill (EXT_CSQC) +VM_drawsetcliparea, // #324 void(float x, float y, float width, float height) drawsetcliparea +VM_drawresetcliparea, // #325 void(void) drawresetcliparea +VM_drawcolorcodedstring, // #326 float drawcolorcodedstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) (EXT_CSQC) +VM_stringwidth, // #327 // FIXME is this okay? +VM_drawsubpic, // #328 // FIXME is this okay? +VM_drawrotpic, // #329 // FIXME is this okay? +VM_CL_getstatf, // #330 float(float stnum) getstatf (EXT_CSQC) +VM_CL_getstati, // #331 float(float stnum) getstati (EXT_CSQC) +VM_CL_getstats, // #332 string(float firststnum) getstats (EXT_CSQC) +VM_CL_setmodelindex, // #333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) +VM_CL_modelnameforindex, // #334 string(float mdlindex) modelnameforindex (EXT_CSQC) +VM_CL_particleeffectnum, // #335 float(string effectname) particleeffectnum (EXT_CSQC) +VM_CL_trailparticles, // #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) +VM_CL_pointparticles, // #337 void(float effectnum, vector origin [, vector dir, float count]) pointparticles (EXT_CSQC) +VM_centerprint, // #338 void(string s, ...) centerprint (EXT_CSQC) +VM_print, // #339 void(string s, ...) print (EXT_CSQC, DP_SV_PRINT) +VM_keynumtostring, // #340 string(float keynum) keynumtostring (EXT_CSQC) +VM_stringtokeynum, // #341 float(string keyname) stringtokeynum (EXT_CSQC) +VM_getkeybind, // #342 string(float keynum[, float bindmap]) getkeybind (EXT_CSQC) +VM_CL_setcursormode, // #343 void(float usecursor) setcursormode (DP_CSQC) +VM_CL_getmousepos, // #344 vector() getmousepos (DP_CSQC) +VM_CL_getinputstate, // #345 float(float framenum) getinputstate (EXT_CSQC) +VM_CL_setsensitivityscale, // #346 void(float sens) setsensitivityscale (EXT_CSQC) +VM_CL_runplayerphysics, // #347 void() runstandardplayerphysics (EXT_CSQC) +VM_CL_getplayerkey, // #348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) +VM_CL_isdemo, // #349 float() isdemo (EXT_CSQC) +VM_isserver, // #350 float() isserver (EXT_CSQC) +VM_CL_setlistener, // #351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) +VM_CL_registercmd, // #352 void(string cmdname) registercommand (EXT_CSQC) +VM_wasfreed, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too) +VM_CL_serverkey, // #354 string(string key) serverkey (EXT_CSQC) +VM_CL_videoplaying, // #355 +VM_findfont, // #356 float(string fontname) loadfont (DP_GFX_FONTS) +VM_loadfont, // #357 float(string fontname, string fontmaps, string sizes, float slot) loadfont (DP_GFX_FONTS) +VM_CL_loadcubemap, // #358 void(string cubemapname) loadcubemap (DP_GFX_) +NULL, // #359 +VM_CL_ReadByte, // #360 float() readbyte (EXT_CSQC) +VM_CL_ReadChar, // #361 float() readchar (EXT_CSQC) +VM_CL_ReadShort, // #362 float() readshort (EXT_CSQC) +VM_CL_ReadLong, // #363 float() readlong (EXT_CSQC) +VM_CL_ReadCoord, // #364 float() readcoord (EXT_CSQC) +VM_CL_ReadAngle, // #365 float() readangle (EXT_CSQC) +VM_CL_ReadString, // #366 string() readstring (EXT_CSQC) +VM_CL_ReadFloat, // #367 float() readfloat (EXT_CSQC) +NULL, // #368 +NULL, // #369 +NULL, // #370 +NULL, // #371 +NULL, // #372 +NULL, // #373 +NULL, // #374 +NULL, // #375 +NULL, // #376 +NULL, // #377 +NULL, // #378 +NULL, // #379 +NULL, // #380 +NULL, // #381 +NULL, // #382 +NULL, // #383 +NULL, // #384 +NULL, // #385 +NULL, // #386 +NULL, // #387 +NULL, // #388 +NULL, // #389 +NULL, // #390 +NULL, // #391 +NULL, // #392 +NULL, // #393 +NULL, // #394 +NULL, // #395 +NULL, // #396 +NULL, // #397 +NULL, // #398 +NULL, // #399 +// LordHavoc's range #400-#499 +VM_CL_copyentity, // #400 void(entity from, entity to) copyentity (DP_QC_COPYENTITY) +NULL, // #401 void(entity ent, float colors) setcolor (DP_QC_SETCOLOR) +VM_findchain, // #402 entity(.string fld, string match) findchain (DP_QC_FINDCHAIN) +VM_findchainfloat, // #403 entity(.float fld, float match) findchainfloat (DP_QC_FINDCHAINFLOAT) +VM_CL_effect, // #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) +VM_CL_te_blood, // #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) +VM_CL_te_bloodshower, // #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) +VM_CL_te_explosionrgb, // #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) +VM_CL_te_particlecube, // #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) +VM_CL_te_particlerain, // #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) +VM_CL_te_particlesnow, // #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) +VM_CL_te_spark, // #411 void(vector org, vector vel, float howmany) te_spark (DP_TE_SPARK) +VM_CL_te_gunshotquad, // #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) +VM_CL_te_spikequad, // #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) +VM_CL_te_superspikequad, // #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) +VM_CL_te_explosionquad, // #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) +VM_CL_te_smallflash, // #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) +VM_CL_te_customflash, // #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) +VM_CL_te_gunshot, // #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_spike, // #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_superspike, // #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_explosion, // #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_tarexplosion, // #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_wizspike, // #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_knightspike, // #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_lavasplash, // #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_teleport, // #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_explosion2, // #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_lightning1, // #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_lightning2, // #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_lightning3, // #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) +VM_CL_te_beam, // #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) +VM_vectorvectors, // #432 void(vector dir) vectorvectors (DP_QC_VECTORVECTORS) +VM_CL_te_plasmaburn, // #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) +VM_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE) +VM_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE) +VM_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE) +VM_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE) +VM_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE) +VM_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE) +NULL, // #440 void(entity e, string s) clientcommand (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_tokenize, // #441 float(string s) tokenize (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_argv, // #442 string(float n) argv (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_CL_setattachment, // #443 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) +VM_search_begin, // #444 float(string pattern, float caseinsensitive, float quiet) search_begin (DP_QC_FS_SEARCH) +VM_search_end, // #445 void(float handle) search_end (DP_QC_FS_SEARCH) +VM_search_getsize, // #446 float(float handle) search_getsize (DP_QC_FS_SEARCH) +VM_search_getfilename, // #447 string(float handle, float num) search_getfilename (DP_QC_FS_SEARCH) +VM_cvar_string, // #448 string(string s) cvar_string (DP_QC_CVAR_STRING) +VM_findflags, // #449 entity(entity start, .float fld, float match) findflags (DP_QC_FINDFLAGS) +VM_findchainflags, // #450 entity(.float fld, float match) findchainflags (DP_QC_FINDCHAINFLAGS) +VM_CL_gettagindex, // #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) +VM_CL_gettaginfo, // #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) +NULL, // #453 void(entity clent) dropclient (DP_SV_DROPCLIENT) +NULL, // #454 entity() spawnclient (DP_SV_BOTCLIENT) +NULL, // #455 float(entity clent) clienttype (DP_SV_BOTCLIENT) +NULL, // #456 void(float to, string s) WriteUnterminatedString (DP_SV_WRITEUNTERMINATEDSTRING) +VM_CL_te_flamejet, // #457 void(vector org, vector vel, float howmany) te_flamejet (DP_TE_FLAMEJET) +NULL, // #458 +VM_ftoe, // #459 entity(float num) entitybyindex (DP_QC_EDICT_NUM) +VM_buf_create, // #460 float() buf_create (DP_QC_STRINGBUFFERS) +VM_buf_del, // #461 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) +VM_buf_getsize, // #462 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) +VM_buf_copy, // #463 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) +VM_buf_sort, // #464 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) +VM_buf_implode, // #465 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) +VM_bufstr_get, // #466 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) +VM_bufstr_set, // #467 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) +VM_bufstr_add, // #468 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) +VM_bufstr_free, // #469 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) +NULL, // #470 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) +VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) +VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) +VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) +VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) +VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) +VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) +VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_QC_STRINGCOLORFUNCTIONS) +VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) +VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) +VM_strtolower, // #480 string(string s) VM_strtolower (DP_QC_STRING_CASE_FUNCTIONS) +VM_strtoupper, // #481 string(string s) VM_strtoupper (DP_QC_STRING_CASE_FUNCTIONS) +VM_cvar_defstring, // #482 string(string s) cvar_defstring (DP_QC_CVAR_DEFSTRING) +VM_CL_pointsound, // #483 void(vector origin, string sample, float volume, float attenuation) pointsound (DP_SV_POINTSOUND) +VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) +VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) +VM_getsurfacepointattribute,// #486 vector(entity e, float s, float n, float a) getsurfacepointattribute +VM_gecko_create, // #487 float gecko_create( string name ) +VM_gecko_destroy, // #488 void gecko_destroy( string name ) +VM_gecko_navigate, // #489 void gecko_navigate( string name, string URI ) +VM_gecko_keyevent, // #490 float gecko_keyevent( string name, float key, float eventtype ) +VM_gecko_movemouse, // #491 void gecko_mousemove( string name, float x, float y ) +VM_gecko_resize, // #492 void gecko_resize( string name, float w, float h ) +VM_gecko_get_texture_extent, // #493 vector gecko_get_texture_extent( string name ) +VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) +VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) +VM_numentityfields, // #496 float() numentityfields = #496; (QP_QC_ENTITYDATA) +VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (DP_QC_ENTITYDATA) +VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) +VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) +VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) +VM_CL_ReadPicture, // #501 string() ReadPicture = #501; +VM_CL_boxparticles, // #502 void(float effectnum, entity own, vector origin_from, vector origin_to, vector dir_from, vector dir_to, float count) boxparticles (DP_CSQC_BOXPARTICLES) +VM_whichpack, // #503 string(string) whichpack = #503; +VM_CL_GetEntity, // #504 float(float entitynum, float fldnum) getentity = #504; vector(float entitynum, float fldnum) getentityvec = #504; +NULL, // #505 +NULL, // #506 +NULL, // #507 +NULL, // #508 +NULL, // #509 +VM_uri_escape, // #510 string(string in) uri_escape = #510; +VM_uri_unescape, // #511 string(string in) uri_unescape = #511; +VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) +VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) +VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) +VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) +VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) +VM_gettime, // #519 float(float timer) gettime = #519; (DP_QC_GETTIME) +VM_keynumtostring, // #520 string keynumtostring(float keynum) +VM_findkeysforcommand, // #521 string findkeysforcommand(string command[, float bindmap]) +VM_CL_InitParticleSpawner, // #522 void(float max_themes) initparticlespawner (DP_CSQC_SPAWNPARTICLE) +VM_CL_ResetParticle, // #523 void() resetparticle (DP_CSQC_SPAWNPARTICLE) +VM_CL_ParticleTheme, // #524 void(float theme) particletheme (DP_CSQC_SPAWNPARTICLE) +VM_CL_ParticleThemeSave, // #525 void() particlethemesave, void(float theme) particlethemeupdate (DP_CSQC_SPAWNPARTICLE) +VM_CL_ParticleThemeFree, // #526 void() particlethemefree (DP_CSQC_SPAWNPARTICLE) +VM_CL_SpawnParticle, // #527 float(vector org, vector vel, [float theme]) particle (DP_CSQC_SPAWNPARTICLE) +VM_CL_SpawnParticleDelayed, // #528 float(vector org, vector vel, float delay, float collisiondelay, [float theme]) delayedparticle (DP_CSQC_SPAWNPARTICLE) +VM_loadfromdata, // #529 +VM_loadfromfile, // #530 +VM_CL_setpause, // #531 float(float ispaused) setpause = #531 (DP_CSQC_SETPAUSE) +VM_log, // #532 +VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) +VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) +VM_buf_loadfile, // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_buf_writefile, // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_bufstr_find, // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_matchpattern, // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP) +NULL, // #539 +VM_physics_enable, // #540 void(entity e, float physics_enabled) physics_enable = #540; (DP_PHYSICS_ODE) +VM_physics_addforce, // #541 void(entity e, vector force, vector relative_ofs) physics_addforce = #541; (DP_PHYSICS_ODE) +VM_physics_addtorque, // #542 void(entity e, vector torque) physics_addtorque = #542; (DP_PHYSICS_ODE) +NULL, // #543 +NULL, // #544 +NULL, // #545 +NULL, // #546 +NULL, // #547 +NULL, // #548 +NULL, // #549 +NULL, // #550 +NULL, // #551 +NULL, // #552 +NULL, // #553 +NULL, // #554 +NULL, // #555 +NULL, // #556 +NULL, // #557 +NULL, // #558 +NULL, // #559 +NULL, // #560 +NULL, // #561 +NULL, // #562 +NULL, // #563 +NULL, // #564 +NULL, // #565 +NULL, // #566 +NULL, // #567 +NULL, // #568 +NULL, // #569 +NULL, // #570 +NULL, // #571 +NULL, // #572 +NULL, // #573 +NULL, // #574 +NULL, // #575 +NULL, // #576 +NULL, // #577 +NULL, // #578 +NULL, // #579 +NULL, // #580 +NULL, // #581 +NULL, // #582 +NULL, // #583 +NULL, // #584 +NULL, // #585 +NULL, // #586 +NULL, // #587 +NULL, // #588 +NULL, // #589 +NULL, // #590 +NULL, // #591 +NULL, // #592 +NULL, // #593 +NULL, // #594 +NULL, // #595 +NULL, // #596 +NULL, // #597 +NULL, // #598 +NULL, // #599 +NULL, // #600 +NULL, // #601 +NULL, // #602 +NULL, // #603 +NULL, // #604 +VM_callfunction, // #605 +VM_writetofile, // #606 +VM_isfunction, // #607 +NULL, // #608 +NULL, // #609 +VM_findkeysforcommand, // #610 string findkeysforcommand(string command[, float bindmap]) +NULL, // #611 +NULL, // #612 +VM_parseentitydata, // #613 +NULL, // #614 +NULL, // #615 +NULL, // #616 +NULL, // #617 +NULL, // #618 +NULL, // #619 +NULL, // #620 +NULL, // #621 +NULL, // #622 +NULL, // #623 +VM_CL_getextresponse, // #624 string getextresponse(void) +NULL, // #625 +NULL, // #626 +VM_sprintf, // #627 string sprintf(string format, ...) +VM_getsurfacenumtriangles, // #628 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACETRIANGLE) +VM_getsurfacetriangle, // #629 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACETRIANGLE) +VM_setkeybind, // #630 float(float key, string bind[, float bindmap]) setkeybind +VM_getbindmaps, // #631 vector(void) getbindmap +VM_setbindmaps, // #632 float(vector bm) setbindmap +NULL, // #633 +NULL, // #634 +NULL, // #635 +NULL, // #636 +NULL, // #637 +VM_CL_RotateMoves, // #638 +VM_digest_hex, // #639 +VM_CL_V_CalcRefdef, // #640 void(entity e) V_CalcRefdef (DP_CSQC_V_CALCREFDEF) +NULL, // #641 +}; + +const int vm_cl_numbuiltins = sizeof(vm_cl_builtins) / sizeof(prvm_builtin_t); + +void VM_Polygons_Reset(prvm_prog_t *prog) +{ + vmpolygons_t *polys = &prog->vmpolygons; + + // TODO: replace vm_polygons stuff with a more general debugging polygon system, and make vm_polygons functions use that system + if(polys->initialized) + { + Mem_FreePool(&polys->pool); + polys->initialized = false; + } +} + +void CLVM_init_cmd(prvm_prog_t *prog) +{ + VM_Cmd_Init(prog); + VM_Polygons_Reset(prog); +} + +void CLVM_reset_cmd(prvm_prog_t *prog) +{ + World_End(&cl.world); + VM_Cmd_Reset(prog); + VM_Polygons_Reset(prog); +} diff --git a/app/jni/clvm_cmds.h b/app/jni/clvm_cmds.h new file mode 100644 index 0000000..259991f --- /dev/null +++ b/app/jni/clvm_cmds.h @@ -0,0 +1,27 @@ +#ifndef __CLVM_CMDS_H__ +#define __CLVM_CMDS_H__ + +/* These are VM built-ins that originate in the client-side programs support + but are reused by the other programs (usually the menu). */ + +void VM_CL_setmodel (void); +void VM_CL_precache_model (void); +void VM_CL_setorigin (void); + +void VM_CL_R_AddDynamicLight (void); +void VM_CL_R_ClearScene (void); +void VM_CL_R_AddEntities (void); +void VM_CL_R_AddEntity (void); +void VM_CL_R_SetView (void); +void VM_CL_R_RenderScene (void); +void VM_CL_R_LoadWorldModel (void); + +void VM_CL_R_PolygonBegin (void); +void VM_CL_R_PolygonVertex (void); +void VM_CL_R_PolygonEnd (void); + +void VM_CL_setattachment(void); +void VM_CL_gettagindex(void); +void VM_CL_gettaginfo(void); + +#endif /* __CLVM_CMDS_H__ */ diff --git a/app/jni/cmd.c b/app/jni/cmd.c new file mode 100644 index 0000000..ea48614 --- /dev/null +++ b/app/jni/cmd.c @@ -0,0 +1,2233 @@ +/* +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. + +*/ +// cmd.c -- Quake script command processing module + +#include "quakedef.h" +#include "thread.h" + +typedef struct cmdalias_s +{ + struct cmdalias_s *next; + char name[MAX_ALIAS_NAME]; + char *value; + qboolean initstate; // indicates this command existed at init + char *initialvalue; // backup copy of value at init +} cmdalias_t; + +static cmdalias_t *cmd_alias; + +static qboolean cmd_wait; + +static mempool_t *cmd_mempool; + +static char cmd_tokenizebuffer[CMD_TOKENIZELENGTH]; +static int cmd_tokenizebufferpos = 0; + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" +============ +*/ +static void Cmd_Wait_f (void) +{ + cmd_wait = true; +} + +typedef struct cmddeferred_s +{ + struct cmddeferred_s *next; + char *value; + double delay; +} cmddeferred_t; + +static cmddeferred_t *cmd_deferred_list = NULL; + +/* +============ +Cmd_Defer_f + +Cause a command to be executed after a delay. +============ +*/ +static void Cmd_Defer_f (void) +{ + if(Cmd_Argc() == 1) + { + cmddeferred_t *next = cmd_deferred_list; + if(!next) + Con_Printf("No commands are pending.\n"); + while(next) + { + Con_Printf("-> In %9.2f: %s\n", next->delay, next->value); + next = next->next; + } + } else if(Cmd_Argc() == 2 && !strcasecmp("clear", Cmd_Argv(1))) + { + while(cmd_deferred_list) + { + cmddeferred_t *cmd = cmd_deferred_list; + cmd_deferred_list = cmd->next; + Mem_Free(cmd->value); + Mem_Free(cmd); + } + } else if(Cmd_Argc() == 3) + { + const char *value = Cmd_Argv(2); + cmddeferred_t *defcmd = (cmddeferred_t*)Mem_Alloc(tempmempool, sizeof(*defcmd)); + size_t len = strlen(value); + + defcmd->delay = atof(Cmd_Argv(1)); + defcmd->value = (char*)Mem_Alloc(tempmempool, len+1); + memcpy(defcmd->value, value, len+1); + defcmd->next = NULL; + + if(cmd_deferred_list) + { + cmddeferred_t *next = cmd_deferred_list; + while(next->next) + next = next->next; + next->next = defcmd; + } else + cmd_deferred_list = defcmd; + /* Stupid me... this changes the order... so commands with the same delay go blub :S + defcmd->next = cmd_deferred_list; + cmd_deferred_list = defcmd;*/ + } else { + Con_Printf("usage: defer \n" + " defer clear\n"); + return; + } +} + +/* +============ +Cmd_Centerprint_f + +Print something to the center of the screen using SCR_Centerprint +============ +*/ +static void Cmd_Centerprint_f (void) +{ + char msg[MAX_INPUTLINE]; + unsigned int i, c, p; + c = Cmd_Argc(); + if(c >= 2) + { + strlcpy(msg, Cmd_Argv(1), sizeof(msg)); + for(i = 2; i < c; ++i) + { + strlcat(msg, " ", sizeof(msg)); + strlcat(msg, Cmd_Argv(i), sizeof(msg)); + } + c = strlen(msg); + for(p = 0, i = 0; i < c; ++i) + { + if(msg[i] == '\\') + { + if(msg[i+1] == 'n') + msg[p++] = '\n'; + else if(msg[i+1] == '\\') + msg[p++] = '\\'; + else { + msg[p++] = '\\'; + msg[p++] = msg[i+1]; + } + ++i; + } else { + msg[p++] = msg[i]; + } + } + msg[p] = '\0'; + SCR_CenterPrint(msg); + } +} + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +static sizebuf_t cmd_text; +static unsigned char cmd_text_buf[CMDBUFSIZE]; +void *cmd_text_mutex = NULL; + +#define Cbuf_LockThreadMutex() (void)(cmd_text_mutex ? Thread_LockMutex(cmd_text_mutex) : 0) +#define Cbuf_UnlockThreadMutex() (void)(cmd_text_mutex ? Thread_UnlockMutex(cmd_text_mutex) : 0) + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer +============ +*/ +void Cbuf_AddText (const char *text) +{ + int l; + + l = (int)strlen(text); + + Cbuf_LockThreadMutex(); + if (cmd_text.cursize + l >= cmd_text.maxsize) + Con_Print("Cbuf_AddText: overflow\n"); + else + SZ_Write(&cmd_text, (const unsigned char *)text, l); + Cbuf_UnlockThreadMutex(); +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +FIXME: actually change the command buffer to do less copying +============ +*/ +void Cbuf_InsertText (const char *text) +{ + size_t l = strlen(text); + Cbuf_LockThreadMutex(); + // we need to memmove the existing text and stuff this in before it... + if (cmd_text.cursize + l >= (size_t)cmd_text.maxsize) + Con_Print("Cbuf_InsertText: overflow\n"); + else + { + // we don't have a SZ_Prepend, so... + memmove(cmd_text.data + l, cmd_text.data, cmd_text.cursize); + cmd_text.cursize += l; + memcpy(cmd_text.data, text, l); + } + Cbuf_UnlockThreadMutex(); +} + +/* +============ +Cbuf_Execute_Deferred --blub +============ +*/ +static void Cbuf_Execute_Deferred (void) +{ + static double oldrealtime = 0; + cmddeferred_t *cmd, *prev; + double eat; + if (realtime - oldrealtime < 0 || realtime - oldrealtime > 1800) oldrealtime = realtime; + eat = realtime - oldrealtime; + if (eat < (1.0 / 120.0)) + return; + oldrealtime = realtime; + prev = NULL; + cmd = cmd_deferred_list; + while(cmd) + { + cmd->delay -= eat; + if(cmd->delay <= 0) + { + Cbuf_AddText(cmd->value); + Cbuf_AddText(";\n"); + Mem_Free(cmd->value); + + if(prev) { + prev->next = cmd->next; + Mem_Free(cmd); + cmd = prev->next; + } else { + cmd_deferred_list = cmd->next; + Mem_Free(cmd); + cmd = cmd_deferred_list; + } + continue; + } + prev = cmd; + cmd = cmd->next; + } +} + +/* +============ +Cbuf_Execute +============ +*/ +static qboolean Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ); +void Cbuf_Execute (void) +{ + int i; + char *text; + char line[MAX_INPUTLINE]; + char preprocessed[MAX_INPUTLINE]; + char *firstchar; + qboolean quotes; + char *comment; + + // LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes + cmd_tokenizebufferpos = 0; + + while (cmd_text.cursize) + { +// find a \n or ; line break + text = (char *)cmd_text.data; + + quotes = false; + comment = NULL; + for (i=0 ; i < cmd_text.cursize ; i++) + { + if(!comment) + { + if (text[i] == '"') + quotes = !quotes; + + if(quotes) + { + // make sure i doesn't get > cursize which causes a negative + // size in memmove, which is fatal --blub + if (i < (cmd_text.cursize-1) && (text[i] == '\\' && (text[i+1] == '"' || text[i+1] == '\\'))) + i++; + } + else + { + if(text[i] == '/' && text[i + 1] == '/' && (i == 0 || ISWHITESPACE(text[i-1]))) + comment = &text[i]; + if(text[i] == ';') + break; // don't break if inside a quoted string or comment + } + } + + if (text[i] == '\r' || text[i] == '\n') + break; + } + + // better than CRASHING on overlong input lines that may SOMEHOW enter the buffer + if(i >= MAX_INPUTLINE) + { + Con_Printf("Warning: console input buffer had an overlong line. Ignored.\n"); + line[0] = 0; + } + else + { + memcpy (line, text, comment ? (comment - text) : i); + line[comment ? (comment - text) : i] = 0; + } + +// delete the text from the command buffer and move remaining commands down +// this is necessary because commands (exec, alias) can insert data at the +// beginning of the text buffer + + if (i == cmd_text.cursize) + cmd_text.cursize = 0; + else + { + i++; + cmd_text.cursize -= i; + memmove (cmd_text.data, text+i, cmd_text.cursize); + } + +// execute the command line + firstchar = line; + while(*firstchar && ISWHITESPACE(*firstchar)) + ++firstchar; + if( + (strncmp(firstchar, "alias", 5) || !ISWHITESPACE(firstchar[5])) + && + (strncmp(firstchar, "bind", 4) || !ISWHITESPACE(firstchar[4])) + && + (strncmp(firstchar, "in_bind", 7) || !ISWHITESPACE(firstchar[7])) + ) + { + if(Cmd_PreprocessString( line, preprocessed, sizeof(preprocessed), NULL )) + Cmd_ExecuteString (preprocessed, src_command, false); + } + else + { + Cmd_ExecuteString (line, src_command, false); + } + + if (cmd_wait) + { // skip out while text still remains in buffer, leaving it + // for next frame + cmd_wait = false; + break; + } + } +} + +void Cbuf_Frame(void) +{ + Cbuf_Execute_Deferred(); + if (cmd_text.cursize) + { + SV_LockThreadMutex(); + Cbuf_Execute(); + SV_UnlockThreadMutex(); + } +} + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + +/* +=============== +Cmd_StuffCmds_f + +Adds command line parameters as script statements +Commands lead with a +, and continue until a - or another + +quake +prog jctest.qp +cmd amlev1 +quake -nosound +cmd amlev1 +=============== +*/ +qboolean host_stuffcmdsrun = false; +static void Cmd_StuffCmds_f (void) +{ + int i, j, l; + // this is for all commandline options combined (and is bounds checked) + char build[MAX_INPUTLINE]; + + if (Cmd_Argc () != 1) + { + Con_Print("stuffcmds : execute command line parameters\n"); + return; + } + + // no reason to run the commandline arguments twice + if (host_stuffcmdsrun) + return; + + host_stuffcmdsrun = true; + build[0] = 0; + l = 0; + for (i = 0;i < com_argc;i++) + { + if (com_argv[i] && com_argv[i][0] == '+' && (com_argv[i][1] < '0' || com_argv[i][1] > '9') && l + strlen(com_argv[i]) - 1 <= sizeof(build) - 1) + { + j = 1; + while (com_argv[i][j]) + build[l++] = com_argv[i][j++]; + i++; + for (;i < com_argc;i++) + { + if (!com_argv[i]) + continue; + if ((com_argv[i][0] == '+' || com_argv[i][0] == '-') && (com_argv[i][1] < '0' || com_argv[i][1] > '9')) + break; + if (l + strlen(com_argv[i]) + 4 > sizeof(build) - 1) + break; + build[l++] = ' '; + if (strchr(com_argv[i], ' ')) + build[l++] = '\"'; + for (j = 0;com_argv[i][j];j++) + build[l++] = com_argv[i][j]; + if (strchr(com_argv[i], ' ')) + build[l++] = '\"'; + } + build[l++] = '\n'; + i--; + } + } + // now terminate the combined string and prepend it to the command buffer + // we already reserved space for the terminator + build[l++] = 0; + Cbuf_InsertText (build); +} + +static void Cmd_Exec(const char *filename) +{ + char *f; + size_t filenameLen = strlen(filename); + qboolean isdefaultcfg = filenameLen >= 11 && !strcmp(filename + filenameLen - 11, "default.cfg"); + + if (!strcmp(filename, "config.cfg")) + { + filename = CONFIGFILENAME; + if (COM_CheckParm("-noconfig")) + return; // don't execute config.cfg + } + + f = (char *)FS_LoadFile (filename, tempmempool, false, NULL); + if (!f) + { + Con_Printf("couldn't exec %s\n",filename); + return; + } + Con_Printf("execing %s\n",filename); + + // if executing default.cfg for the first time, lock the cvar defaults + // it may seem backwards to insert this text BEFORE the default.cfg + // but Cbuf_InsertText inserts before, so this actually ends up after it. + if (isdefaultcfg) + Cbuf_InsertText("\ncvar_lockdefaults\n"); + + // insert newline after the text to make sure the last line is terminated (some text editors omit the trailing newline) + // (note: insertion order here is backwards from execution order, so this adds it after the text, by calling it before...) + Cbuf_InsertText ("\n"); + Cbuf_InsertText (f); + Mem_Free(f); + + if (isdefaultcfg) + { + // special defaults for specific games go here, these execute before default.cfg + // Nehahra pushable crates malfunction in some levels if this is on + // Nehahra NPC AI is confused by blowupfallenzombies + switch(gamemode) + { + case GAME_NORMAL: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.01388889\n" +"r_shadow_gloss 1\n" +"r_shadow_bumpscale_basetexture 0\n" + ); + break; + case GAME_NEHAHRA: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.01388889\n" +"r_shadow_gloss 1\n" +"r_shadow_bumpscale_basetexture 0\n" + ); + break; + // hipnotic mission pack has issues in their 'friendly monster' ai, which seem to attempt to attack themselves for some reason when findradius() returns non-solid entities. + // hipnotic mission pack has issues with bobbing water entities 'jittering' between different heights on alternate frames at the default 0.0138889 ticrate, 0.02 avoids this issue + // hipnotic mission pack has issues in their proximity mine sticking code, which causes them to bounce off. + case GAME_HIPNOTIC: + case GAME_QUOTH: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.02\n" +"r_shadow_gloss 1\n" +"r_shadow_bumpscale_basetexture 0\n" + ); + break; + // rogue mission pack has a guardian boss that does not wake up if findradius returns one of the entities around its spawn area + case GAME_ROGUE: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.01388889\n" +"r_shadow_gloss 1\n" +"r_shadow_bumpscale_basetexture 0\n" + ); + break; + case GAME_TENEBRAE: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.01388889\n" +"r_shadow_gloss 2\n" +"r_shadow_bumpscale_basetexture 4\n" + ); + break; + case GAME_NEXUIZ: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 1\n" +"sv_gameplayfix_findradiusdistancetobox 1\n" +"sv_gameplayfix_grenadebouncedownslopes 1\n" +"sv_gameplayfix_slidemoveprojectiles 1\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" +"sv_gameplayfix_setmodelrealbox 1\n" +"sv_gameplayfix_droptofloorstartsolid 1\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" +"sv_gameplayfix_noairborncorpse 1\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" +"sv_gameplayfix_easierwaterjump 1\n" +"sv_gameplayfix_delayprojectiles 1\n" +"sv_gameplayfix_multiplethinksperframe 1\n" +"sv_gameplayfix_fixedcheckwatertransition 1\n" +"sv_gameplayfix_q1bsptracelinereportstexture 1\n" +"sv_gameplayfix_swiminbmodels 1\n" +"sv_gameplayfix_downtracesupportsongroundflag 1\n" +"sys_ticrate 0.01388889\n" +"sv_gameplayfix_q2airaccelerate 1\n" +"sv_gameplayfix_stepmultipletimes 1\n" + ); + break; + // Steel Storm: Burning Retribution csqc misinterprets CSQC_InputEvent if type is a value other than 0 or 1 + case GAME_STEELSTORM: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 1\n" +"sv_gameplayfix_findradiusdistancetobox 1\n" +"sv_gameplayfix_grenadebouncedownslopes 1\n" +"sv_gameplayfix_slidemoveprojectiles 1\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" +"sv_gameplayfix_setmodelrealbox 1\n" +"sv_gameplayfix_droptofloorstartsolid 1\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" +"sv_gameplayfix_noairborncorpse 1\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" +"sv_gameplayfix_easierwaterjump 1\n" +"sv_gameplayfix_delayprojectiles 1\n" +"sv_gameplayfix_multiplethinksperframe 1\n" +"sv_gameplayfix_fixedcheckwatertransition 1\n" +"sv_gameplayfix_q1bsptracelinereportstexture 1\n" +"sv_gameplayfix_swiminbmodels 1\n" +"sv_gameplayfix_downtracesupportsongroundflag 1\n" +"sys_ticrate 0.01388889\n" +"cl_csqc_generatemousemoveevents 0\n" + ); + break; + default: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 1\n" +"sv_gameplayfix_findradiusdistancetobox 1\n" +"sv_gameplayfix_grenadebouncedownslopes 1\n" +"sv_gameplayfix_slidemoveprojectiles 1\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" +"sv_gameplayfix_setmodelrealbox 1\n" +"sv_gameplayfix_droptofloorstartsolid 1\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" +"sv_gameplayfix_noairborncorpse 1\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" +"sv_gameplayfix_easierwaterjump 1\n" +"sv_gameplayfix_delayprojectiles 1\n" +"sv_gameplayfix_multiplethinksperframe 1\n" +"sv_gameplayfix_fixedcheckwatertransition 1\n" +"sv_gameplayfix_q1bsptracelinereportstexture 1\n" +"sv_gameplayfix_swiminbmodels 1\n" +"sv_gameplayfix_downtracesupportsongroundflag 1\n" +"sys_ticrate 0.01388889\n" + ); + break; + } + } +} + +/* +=============== +Cmd_Exec_f +=============== +*/ +static void Cmd_Exec_f (void) +{ + fssearch_t *s; + int i; + + if (Cmd_Argc () != 2) + { + Con_Print("exec : execute a script file\n"); + return; + } + + s = FS_Search(Cmd_Argv(1), true, true); + if(!s || !s->numfilenames) + { + Con_Printf("couldn't exec %s\n",Cmd_Argv(1)); + return; + } + + for(i = 0; i < s->numfilenames; ++i) + Cmd_Exec(s->filenames[i]); + + FS_FreeSearch(s); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +static void Cmd_Echo_f (void) +{ + int i; + + for (i=1 ; i - toggles between 0 and 1\n toggle - toggles between 0 and \n toggle [string 1] [string 2]...[string n] - cycles through all strings\n"); + else + { // Correct Arguments Specified + // Acquire Potential CVar + cvar_t* cvCVar = Cvar_FindVar( Cmd_Argv(1) ); + + if(cvCVar != NULL) + { // Valid CVar + if(nNumArgs == 2) + { // Default Usage + if(cvCVar->integer) + Cvar_SetValueQuick(cvCVar, 0); + else + Cvar_SetValueQuick(cvCVar, 1); + } + else + if(nNumArgs == 3) + { // 0 and Specified Usage + if(cvCVar->integer == atoi(Cmd_Argv(2) ) ) + // CVar is Specified Value; // Reset to 0 + Cvar_SetValueQuick(cvCVar, 0); + else + if(cvCVar->integer == 0) + // CVar is 0; Specify Value + Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); + else + // CVar does not match; Reset to 0 + Cvar_SetValueQuick(cvCVar, 0); + } + else + { // Variable Values Specified + int nCnt; + int bFound = 0; + + for(nCnt = 2; nCnt < nNumArgs; nCnt++) + { // Cycle through Values + if( strcmp(cvCVar->string, Cmd_Argv(nCnt) ) == 0) + { // Current Value Located; Increment to Next + if( (nCnt + 1) == nNumArgs) + // Max Value Reached; Reset + Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); + else + // Next Value + Cvar_SetQuick(cvCVar, Cmd_Argv(nCnt + 1) ); + + // End Loop + nCnt = nNumArgs; + // Assign Found + bFound = 1; + } + } + if(!bFound) + // Value not Found; Reset to Original + Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); + } + + } + else + { // Invalid CVar + Con_Printf("ERROR : CVar '%s' not found\n", Cmd_Argv(1) ); + } + } +} + +/* +=============== +Cmd_Alias_f + +Creates a new command that executes a command string (possibly ; seperated) +=============== +*/ +static void Cmd_Alias_f (void) +{ + cmdalias_t *a; + char cmd[MAX_INPUTLINE]; + int i, c; + const char *s; + size_t alloclen; + + if (Cmd_Argc() == 1) + { + Con_Print("Current alias commands:\n"); + for (a = cmd_alias ; a ; a=a->next) + Con_Printf("%s : %s", a->name, a->value); + return; + } + + s = Cmd_Argv(1); + if (strlen(s) >= MAX_ALIAS_NAME) + { + Con_Print("Alias name is too long\n"); + return; + } + + // if the alias already exists, reuse it + for (a = cmd_alias ; a ; a=a->next) + { + if (!strcmp(s, a->name)) + { + Z_Free (a->value); + break; + } + } + + if (!a) + { + cmdalias_t *prev, *current; + + a = (cmdalias_t *)Z_Malloc (sizeof(cmdalias_t)); + strlcpy (a->name, s, sizeof (a->name)); + // insert it at the right alphanumeric position + for( prev = NULL, current = cmd_alias ; current && strcmp( current->name, a->name ) < 0 ; prev = current, current = current->next ) + ; + if( prev ) { + prev->next = a; + } else { + cmd_alias = a; + } + a->next = current; + } + + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + c = Cmd_Argc(); + for (i=2 ; i < c ; i++) + { + if (i != 2) + strlcat (cmd, " ", sizeof (cmd)); + strlcat (cmd, Cmd_Argv(i), sizeof (cmd)); + } + strlcat (cmd, "\n", sizeof (cmd)); + + alloclen = strlen (cmd) + 1; + if(alloclen >= 2) + cmd[alloclen - 2] = '\n'; // to make sure a newline is appended even if too long + a->value = (char *)Z_Malloc (alloclen); + memcpy (a->value, cmd, alloclen); +} + +/* +=============== +Cmd_UnAlias_f + +Remove existing aliases. +=============== +*/ +static void Cmd_UnAlias_f (void) +{ + cmdalias_t *a, *p; + int i; + const char *s; + + if(Cmd_Argc() == 1) + { + Con_Print("unalias: Usage: unalias alias1 [alias2 ...]\n"); + return; + } + + for(i = 1; i < Cmd_Argc(); ++i) + { + s = Cmd_Argv(i); + p = NULL; + for(a = cmd_alias; a; p = a, a = a->next) + { + if(!strcmp(s, a->name)) + { + if (a->initstate) // we can not remove init aliases + continue; + if(a == cmd_alias) + cmd_alias = a->next; + if(p) + p->next = a->next; + Z_Free(a->value); + Z_Free(a); + break; + } + } + if(!a) + Con_Printf("unalias: %s alias not found\n", s); + } +} + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + const char *name; + const char *description; + xcommand_t consolefunction; + xcommand_t clientfunction; + qboolean csqcfunc; + qboolean initstate; // indicates this command existed at init +} cmd_function_t; + +static int cmd_argc; +static const char *cmd_argv[MAX_ARGS]; +static const char *cmd_null_string = ""; +static const char *cmd_args; +cmd_source_t cmd_source; + + +static cmd_function_t *cmd_functions; // possible commands to execute + +static const char *Cmd_GetDirectCvarValue(const char *varname, cmdalias_t *alias, qboolean *is_multiple) +{ + cvar_t *cvar; + long argno; + char *endptr; + char vabuf[1024]; + + if(is_multiple) + *is_multiple = false; + + if(!varname || !*varname) + return NULL; + + if(alias) + { + if(!strcmp(varname, "*")) + { + if(is_multiple) + *is_multiple = true; + return Cmd_Args(); + } + else if(!strcmp(varname, "#")) + { + return va(vabuf, sizeof(vabuf), "%d", Cmd_Argc()); + } + else if(varname[strlen(varname) - 1] == '-') + { + argno = strtol(varname, &endptr, 10); + if(endptr == varname + strlen(varname) - 1) + { + // whole string is a number, apart from the - + const char *p = Cmd_Args(); + for(; argno > 1; --argno) + if(!COM_ParseToken_Console(&p)) + break; + if(p) + { + if(is_multiple) + *is_multiple = true; + + // kill pre-argument whitespace + for (;*p && ISWHITESPACE(*p);p++) + ; + + return p; + } + } + } + else + { + argno = strtol(varname, &endptr, 10); + if(*endptr == 0) + { + // whole string is a number + // NOTE: we already made sure we don't have an empty cvar name! + if(argno >= 0 && argno < Cmd_Argc()) + return Cmd_Argv(argno); + } + } + } + + if((cvar = Cvar_FindVar(varname)) && !(cvar->flags & CVAR_PRIVATE)) + return cvar->string; + + return NULL; +} + +qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qboolean putquotes) +{ + qboolean quote_quot = !!strchr(quoteset, '"'); + qboolean quote_backslash = !!strchr(quoteset, '\\'); + qboolean quote_dollar = !!strchr(quoteset, '$'); + + if(putquotes) + { + if(outlen <= 2) + { + *out++ = 0; + return false; + } + *out++ = '"'; --outlen; + --outlen; + } + + while(*in) + { + if(*in == '"' && quote_quot) + { + if(outlen <= 2) + goto fail; + *out++ = '\\'; --outlen; + *out++ = '"'; --outlen; + } + else if(*in == '\\' && quote_backslash) + { + if(outlen <= 2) + goto fail; + *out++ = '\\'; --outlen; + *out++ = '\\'; --outlen; + } + else if(*in == '$' && quote_dollar) + { + if(outlen <= 2) + goto fail; + *out++ = '$'; --outlen; + *out++ = '$'; --outlen; + } + else + { + if(outlen <= 1) + goto fail; + *out++ = *in; --outlen; + } + ++in; + } + if(putquotes) + *out++ = '"'; + *out++ = 0; + return true; +fail: + if(putquotes) + *out++ = '"'; + *out++ = 0; + return false; +} + +static const char *Cmd_GetCvarValue(const char *var, size_t varlen, cmdalias_t *alias) +{ + static char varname[MAX_INPUTLINE]; // cmd_mutex + static char varval[MAX_INPUTLINE]; // cmd_mutex + const char *varstr = NULL; + char *varfunc; + qboolean required = false; + qboolean optional = false; + static char asis[] = "asis"; // just to suppress const char warnings + + if(varlen >= MAX_INPUTLINE) + varlen = MAX_INPUTLINE - 1; + memcpy(varname, var, varlen); + varname[varlen] = 0; + varfunc = strchr(varname, ' '); + + if(varfunc) + { + *varfunc = 0; + ++varfunc; + } + + if(*var == 0) + { + // empty cvar name? + if(alias) + Con_Printf("Warning: Could not expand $ in alias %s\n", alias->name); + else + Con_Printf("Warning: Could not expand $\n"); + return "$"; + } + + if(varfunc) + { + char *p; + // ? means optional + while((p = strchr(varfunc, '?'))) + { + optional = true; + memmove(p, p+1, strlen(p)); // with final NUL + } + // ! means required + while((p = strchr(varfunc, '!'))) + { + required = true; + memmove(p, p+1, strlen(p)); // with final NUL + } + // kill spaces + while((p = strchr(varfunc, ' '))) + { + memmove(p, p+1, strlen(p)); // with final NUL + } + // if no function is left, NULL it + if(!*varfunc) + varfunc = NULL; + } + + if(varname[0] == '$') + varstr = Cmd_GetDirectCvarValue(Cmd_GetDirectCvarValue(varname + 1, alias, NULL), alias, NULL); + else + { + qboolean is_multiple = false; + // Exception: $* and $n- don't use the quoted form by default + varstr = Cmd_GetDirectCvarValue(varname, alias, &is_multiple); + if(is_multiple) + if(!varfunc) + varfunc = asis; + } + + if(!varstr) + { + if(required) + { + if(alias) + Con_Printf("Error: Could not expand $%s in alias %s\n", varname, alias->name); + else + Con_Printf("Error: Could not expand $%s\n", varname); + return NULL; + } + else if(optional) + { + return ""; + } + else + { + if(alias) + Con_Printf("Warning: Could not expand $%s in alias %s\n", varname, alias->name); + else + Con_Printf("Warning: Could not expand $%s\n", varname); + dpsnprintf(varval, sizeof(varval), "$%s", varname); + return varval; + } + } + + if(!varfunc || !strcmp(varfunc, "q")) // note: quoted form is default, use "asis" to override! + { + // quote it so it can be used inside double quotes + // we just need to replace " by \", and of course, double backslashes + Cmd_QuoteString(varval, sizeof(varval), varstr, "\"\\", false); + return varval; + } + else if(!strcmp(varfunc, "asis")) + { + return varstr; + } + else + Con_Printf("Unknown variable function %s\n", varfunc); + + return varstr; +} + +/* +Cmd_PreprocessString + +Preprocesses strings and replaces $*, $param#, $cvar accordingly. Also strips comments. +*/ +static qboolean Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ) { + const char *in; + size_t eat, varlen; + unsigned outlen; + const char *val; + + // don't crash if there's no room in the outtext buffer + if( maxoutlen == 0 ) { + return false; + } + maxoutlen--; // because of \0 + + in = intext; + outlen = 0; + + while( *in && outlen < maxoutlen ) { + if( *in == '$' ) { + // this is some kind of expansion, see what comes after the $ + in++; + + // The console does the following preprocessing: + // + // - $$ is transformed to a single dollar sign. + // - $var or ${var} are expanded to the contents of the named cvar, + // with quotation marks and backslashes quoted so it can safely + // be used inside quotation marks (and it should always be used + // that way) + // - ${var asis} inserts the cvar value as is, without doing this + // quoting + // - ${var ?} silently expands to the empty string if + // $var does not exist + // - ${var !} fails expansion and executes nothing if + // $var does not exist + // - prefix the cvar name with a dollar sign to do indirection; + // for example, if $x has the value timelimit, ${$x} will return + // the value of $timelimit + // - when expanding an alias, the special variable name $* refers + // to all alias parameters, and a number refers to that numbered + // alias parameter, where the name of the alias is $0, the first + // parameter is $1 and so on; as a special case, $* inserts all + // parameters, without extra quoting, so one can use $* to just + // pass all parameters around. All parameters starting from $n + // can be referred to as $n- (so $* is equivalent to $1-). + // - ${* q} and ${n- q} force quoting anyway + // + // Note: when expanding an alias, cvar expansion is done in the SAME step + // as alias expansion so that alias parameters or cvar values containing + // dollar signs have no unwanted bad side effects. However, this needs to + // be accounted for when writing complex aliases. For example, + // alias foo "set x NEW; echo $x" + // actually expands to + // "set x NEW; echo OLD" + // and will print OLD! To work around this, use a second alias: + // alias foo "set x NEW; foo2" + // alias foo2 "echo $x" + // + // Also note: lines starting with alias are exempt from cvar expansion. + // If you want cvar expansion, write "alias" instead: + // + // set x 1 + // alias foo "echo $x" + // "alias" bar "echo $x" + // set x 2 + // + // foo will print 2, because the variable $x will be expanded when the alias + // gets expanded. bar will print 1, because the variable $x was expanded + // at definition time. foo can be equivalently defined as + // + // "alias" foo "echo $$x" + // + // because at definition time, $$ will get replaced to a single $. + + if( *in == '$' ) { + val = "$"; + eat = 1; + } else if(*in == '{') { + varlen = strcspn(in + 1, "}"); + if(in[varlen + 1] == '}') + { + val = Cmd_GetCvarValue(in + 1, varlen, alias); + if(!val) + return false; + eat = varlen + 2; + } + else + { + // ran out of data? + val = NULL; + eat = varlen + 1; + } + } else { + varlen = strspn(in, "#*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-"); + val = Cmd_GetCvarValue(in, varlen, alias); + if(!val) + return false; + eat = varlen; + } + if(val) + { + // insert the cvar value + while(*val && outlen < maxoutlen) + outtext[outlen++] = *val++; + in += eat; + } + else + { + // copy the unexpanded text + outtext[outlen++] = '$'; + while(eat && outlen < maxoutlen) + { + outtext[outlen++] = *in++; + --eat; + } + } + } + else + outtext[outlen++] = *in++; + } + outtext[outlen] = 0; + return true; +} + +/* +============ +Cmd_ExecuteAlias + +Called for aliases and fills in the alias into the cbuffer +============ +*/ +static void Cmd_ExecuteAlias (cmdalias_t *alias) +{ + static char buffer[ MAX_INPUTLINE ]; // cmd_mutex + static char buffer2[ MAX_INPUTLINE ]; // cmd_mutex + qboolean ret = Cmd_PreprocessString( alias->value, buffer, sizeof(buffer) - 2, alias ); + if(!ret) + return; + // insert at start of command buffer, so that aliases execute in order + // (fixes bug introduced by Black on 20050705) + + // Note: Cbuf_PreprocessString will be called on this string AGAIN! So we + // have to make sure that no second variable expansion takes place, otherwise + // alias parameters containing dollar signs can have bad effects. + Cmd_QuoteString(buffer2, sizeof(buffer2), buffer, "$", false); + Cbuf_InsertText( buffer2 ); +} + +/* +======== +Cmd_List + + CmdList Added by EvilTypeGuy eviltypeguy@qeradiant.com + Thanks to Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ + +======== +*/ +static void Cmd_List_f (void) +{ + cmd_function_t *cmd; + const char *partial; + size_t len; + int count; + qboolean ispattern; + + if (Cmd_Argc() > 1) + { + partial = Cmd_Argv (1); + len = strlen(partial); + ispattern = (strchr(partial, '*') || strchr(partial, '?')); + } + else + { + partial = NULL; + len = 0; + ispattern = false; + } + + count = 0; + for (cmd = cmd_functions; cmd; cmd = cmd->next) + { + if (partial && (ispattern ? !matchpattern_with_separator(cmd->name, partial, false, "", false) : strncmp(partial, cmd->name, len))) + continue; + Con_Printf("%s : %s\n", cmd->name, cmd->description); + count++; + } + + if (len) + { + if(ispattern) + Con_Printf("%i Command%s matching \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); + else + Con_Printf("%i Command%s beginning with \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); + } + else + Con_Printf("%i Command%s\n\n", count, (count > 1) ? "s" : ""); +} + +static void Cmd_Apropos_f(void) +{ + cmd_function_t *cmd; + cvar_t *cvar; + cmdalias_t *alias; + const char *partial; + int count; + qboolean ispattern; + char vabuf[1024]; + + if (Cmd_Argc() > 1) + partial = Cmd_Args(); + else + { + Con_Printf("usage: apropos \n"); + return; + } + + ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); + if(!ispattern) + partial = va(vabuf, sizeof(vabuf), "*%s*", partial); + + count = 0; + for (cvar = cvar_vars; cvar; cvar = cvar->next) + { + if (!matchpattern_with_separator(cvar->name, partial, true, "", false)) + if (!matchpattern_with_separator(cvar->description, partial, true, "", false)) + continue; + Con_Printf ("cvar ^3%s^7 is \"%s\" [\"%s\"] %s\n", cvar->name, cvar->string, cvar->defstring, cvar->description); + count++; + } + for (cmd = cmd_functions; cmd; cmd = cmd->next) + { + if (!matchpattern_with_separator(cmd->name, partial, true, "", false)) + if (!matchpattern_with_separator(cmd->description, partial, true, "", false)) + continue; + Con_Printf("command ^2%s^7: %s\n", cmd->name, cmd->description); + count++; + } + for (alias = cmd_alias; alias; alias = alias->next) + { + // procede here a bit differently as an alias value always got a final \n + if (!matchpattern_with_separator(alias->name, partial, true, "", false)) + if (!matchpattern_with_separator(alias->value, partial, true, "\n", false)) // when \n is as separator wildcards don't match it + continue; + Con_Printf("alias ^5%s^7: %s", alias->name, alias->value); // do not print an extra \n + count++; + } + Con_Printf("%i result%s\n\n", count, (count > 1) ? "s" : ""); +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) +{ + cmd_mempool = Mem_AllocPool("commands", 0, NULL); + // space for commands and script files + cmd_text.data = cmd_text_buf; + cmd_text.maxsize = sizeof(cmd_text_buf); + cmd_text.cursize = 0; + + if (Thread_HasThreads()) + cmd_text_mutex = Thread_CreateMutex(); +} + +void Cmd_Init_Commands (void) +{ +// +// register our commands +// + Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f, "execute commandline parameters (must be present in quake.rc script)"); + Cmd_AddCommand ("exec",Cmd_Exec_f, "execute a script file"); + Cmd_AddCommand ("echo",Cmd_Echo_f, "print a message to the console (useful in scripts)"); + Cmd_AddCommand ("alias",Cmd_Alias_f, "create a script function (parameters are passed in as $X (being X a number), $* for all parameters, $X- for all parameters starting from $X). Without arguments show the list of all alias"); + Cmd_AddCommand ("unalias",Cmd_UnAlias_f, "remove an alias"); + Cmd_AddCommand ("cmd", Cmd_ForwardToServer, "send a console commandline to the server (used by some mods)"); + Cmd_AddCommand ("wait", Cmd_Wait_f, "make script execution wait for next rendered frame"); + Cmd_AddCommand ("set", Cvar_Set_f, "create or change the value of a console variable"); + Cmd_AddCommand ("seta", Cvar_SetA_f, "create or change the value of a console variable that will be saved to config.cfg"); + Cmd_AddCommand ("unset", Cvar_Del_f, "delete a cvar (does not work for static ones like _cl_name, or read-only ones)"); +#ifdef FILLALLCVARSWITHRUBBISH + Cmd_AddCommand ("fillallcvarswithrubbish", Cvar_FillAll_f, "fill all cvars with a specified number of characters to provoke buffer overruns"); +#endif /* FILLALLCVARSWITHRUBBISH */ + + // 2000-01-09 CmdList, CvarList commands By Matthias "Maddes" Buecher + // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com + Cmd_AddCommand ("cmdlist", Cmd_List_f, "lists all console commands beginning with the specified prefix or matching the specified wildcard pattern"); + Cmd_AddCommand ("cvarlist", Cvar_List_f, "lists all console variables beginning with the specified prefix or matching the specified wildcard pattern"); + Cmd_AddCommand ("apropos", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description"); + + Cmd_AddCommand ("cvar_lockdefaults", Cvar_LockDefaults_f, "stores the current values of all cvars into their default values, only used once during startup after parsing default.cfg"); + Cmd_AddCommand ("cvar_resettodefaults_all", Cvar_ResetToDefaults_All_f, "sets all cvars to their locked default values"); + Cmd_AddCommand ("cvar_resettodefaults_nosaveonly", Cvar_ResetToDefaults_NoSaveOnly_f, "sets all non-saved cvars to their locked default values (variables that will not be saved to config.cfg)"); + Cmd_AddCommand ("cvar_resettodefaults_saveonly", Cvar_ResetToDefaults_SaveOnly_f, "sets all saved cvars to their locked default values (variables that will be saved to config.cfg)"); + + Cmd_AddCommand ("cprint", Cmd_Centerprint_f, "print something at the screen center"); + Cmd_AddCommand ("defer", Cmd_Defer_f, "execute a command in the future"); + + // DRESK - 5/14/06 + // Support Doom3-style Toggle Command + Cmd_AddCommand( "toggle", Cmd_Toggle_f, "toggles a console variable's values (use for more info)"); +} + +/* +============ +Cmd_Shutdown +============ +*/ +void Cmd_Shutdown(void) +{ + if (cmd_text_mutex) + { + // we usually have this locked when we get here from Host_Quit_f + Cbuf_UnlockThreadMutex(); + Thread_DestroyMutex(cmd_text_mutex); + } + cmd_text_mutex = NULL; + + Mem_FreePool(&cmd_mempool); +} + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc (void) +{ + return cmd_argc; +} + +/* +============ +Cmd_Argv +============ +*/ +const char *Cmd_Argv (int arg) +{ + if (arg >= cmd_argc ) + return cmd_null_string; + return cmd_argv[arg]; +} + +/* +============ +Cmd_Args +============ +*/ +const char *Cmd_Args (void) +{ + return cmd_args; +} + + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +============ +*/ +// AK: This function should only be called from ExcuteString because the current design is a bit of an hack +static void Cmd_TokenizeString (const char *text) +{ + int l; + + cmd_argc = 0; + cmd_args = NULL; + + while (1) + { + // skip whitespace up to a /n + while (*text && ISWHITESPACE(*text) && *text != '\r' && *text != '\n') + text++; + + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + if (*text == '\n' || *text == '\r') + { + // a newline separates commands in the buffer + if (*text == '\r' && text[1] == '\n') + text++; + text++; + break; + } + + if (!*text) + return; + + if (cmd_argc == 1) + cmd_args = text; + + if (!COM_ParseToken_Console(&text)) + return; + + if (cmd_argc < MAX_ARGS) + { + l = (int)strlen(com_token) + 1; + if (cmd_tokenizebufferpos + l > CMD_TOKENIZELENGTH) + { + Con_Printf("Cmd_TokenizeString: ran out of %i character buffer space for command arguements\n", CMD_TOKENIZELENGTH); + break; + } + memcpy (cmd_tokenizebuffer + cmd_tokenizebufferpos, com_token, l); + cmd_argv[cmd_argc] = cmd_tokenizebuffer + cmd_tokenizebufferpos; + cmd_tokenizebufferpos += l; + cmd_argc++; + } + } +} + + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand_WithClientCommand (const char *cmd_name, xcommand_t consolefunction, xcommand_t clientfunction, const char *description) +{ + cmd_function_t *cmd; + cmd_function_t *prev, *current; + +// fail if the command is a variable name + if (Cvar_FindVar( cmd_name )) + { + Con_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name); + return; + } + +// fail if the command already exists + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (!strcmp (cmd_name, cmd->name)) + { + if (consolefunction || clientfunction) + { + Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name); + return; + } + else //[515]: csqc + { + cmd->csqcfunc = true; + return; + } + } + } + + cmd = (cmd_function_t *)Mem_Alloc(cmd_mempool, sizeof(cmd_function_t)); + cmd->name = cmd_name; + cmd->consolefunction = consolefunction; + cmd->clientfunction = clientfunction; + cmd->description = description; + if(!consolefunction && !clientfunction) //[515]: csqc + cmd->csqcfunc = true; + cmd->next = cmd_functions; + +// insert it at the right alphanumeric position + for( prev = NULL, current = cmd_functions ; current && strcmp( current->name, cmd->name ) < 0 ; prev = current, current = current->next ) + ; + if( prev ) { + prev->next = cmd; + } else { + cmd_functions = cmd; + } + cmd->next = current; +} + +void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *description) +{ + Cmd_AddCommand_WithClientCommand (cmd_name, function, NULL, description); +} + +/* +============ +Cmd_Exists +============ +*/ +qboolean Cmd_Exists (const char *cmd_name) +{ + cmd_function_t *cmd; + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + if (!strcmp (cmd_name,cmd->name)) + return true; + + return false; +} + + +/* +============ +Cmd_CompleteCommand +============ +*/ +const char *Cmd_CompleteCommand (const char *partial) +{ + cmd_function_t *cmd; + size_t len; + + len = strlen(partial); + + if (!len) + return NULL; + +// check functions + for (cmd = cmd_functions; cmd; cmd = cmd->next) + if (!strncasecmp(partial, cmd->name, len)) + return cmd->name; + + return NULL; +} + +/* + Cmd_CompleteCountPossible + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +int Cmd_CompleteCountPossible (const char *partial) +{ + cmd_function_t *cmd; + size_t len; + int h; + + h = 0; + len = strlen(partial); + + if (!len) + return 0; + + // Loop through the command list and count all partial matches + for (cmd = cmd_functions; cmd; cmd = cmd->next) + if (!strncasecmp(partial, cmd->name, len)) + h++; + + return h; +} + +/* + Cmd_CompleteBuildList + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +const char **Cmd_CompleteBuildList (const char *partial) +{ + cmd_function_t *cmd; + size_t len = 0; + size_t bpos = 0; + size_t sizeofbuf = (Cmd_CompleteCountPossible (partial) + 1) * sizeof (const char *); + const char **buf; + + len = strlen(partial); + buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); + // Loop through the alias list and print all matches + for (cmd = cmd_functions; cmd; cmd = cmd->next) + if (!strncasecmp(partial, cmd->name, len)) + buf[bpos++] = cmd->name; + + buf[bpos] = NULL; + return buf; +} + +// written by LordHavoc +void Cmd_CompleteCommandPrint (const char *partial) +{ + cmd_function_t *cmd; + size_t len = strlen(partial); + // Loop through the command list and print all matches + for (cmd = cmd_functions; cmd; cmd = cmd->next) + if (!strncasecmp(partial, cmd->name, len)) + Con_Printf("^2%s^7: %s\n", cmd->name, cmd->description); +} + +/* + Cmd_CompleteAlias + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +const char *Cmd_CompleteAlias (const char *partial) +{ + cmdalias_t *alias; + size_t len; + + len = strlen(partial); + + if (!len) + return NULL; + + // Check functions + for (alias = cmd_alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + return alias->name; + + return NULL; +} + +// written by LordHavoc +void Cmd_CompleteAliasPrint (const char *partial) +{ + cmdalias_t *alias; + size_t len = strlen(partial); + // Loop through the alias list and print all matches + for (alias = cmd_alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + Con_Printf("^5%s^7: %s", alias->name, alias->value); +} + + +/* + Cmd_CompleteAliasCountPossible + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +int Cmd_CompleteAliasCountPossible (const char *partial) +{ + cmdalias_t *alias; + size_t len; + int h; + + h = 0; + + len = strlen(partial); + + if (!len) + return 0; + + // Loop through the command list and count all partial matches + for (alias = cmd_alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + h++; + + return h; +} + +/* + Cmd_CompleteAliasBuildList + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +const char **Cmd_CompleteAliasBuildList (const char *partial) +{ + cmdalias_t *alias; + size_t len = 0; + size_t bpos = 0; + size_t sizeofbuf = (Cmd_CompleteAliasCountPossible (partial) + 1) * sizeof (const char *); + const char **buf; + + len = strlen(partial); + buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); + // Loop through the alias list and print all matches + for (alias = cmd_alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + buf[bpos++] = alias->name; + + buf[bpos] = NULL; + return buf; +} + +void Cmd_ClearCsqcFuncs (void) +{ + cmd_function_t *cmd; + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + cmd->csqcfunc = false; +} + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +FIXME: lookupnoadd the token to speed search? +============ +*/ +void Cmd_ExecuteString (const char *text, cmd_source_t src, qboolean lockmutex) +{ + int oldpos; + int found; + cmd_function_t *cmd; + cmdalias_t *a; + + oldpos = cmd_tokenizebufferpos; + cmd_source = src; + found = false; + + Cmd_TokenizeString (text); + +// execute the command line + if (!Cmd_Argc()) + goto done; // no tokens + +// check functions + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (!strcasecmp (cmd_argv[0],cmd->name)) + { + if (cmd->csqcfunc && CL_VM_ConsoleCommand (text)) //[515]: csqc + goto done; + switch (src) + { + case src_command: + if (cmd->consolefunction) + cmd->consolefunction (); + else if (cmd->clientfunction) + { + if (cls.state == ca_connected) + { + // forward remote commands to the server for execution + Cmd_ForwardToServer(); + } + else + Con_Printf("Can not send command \"%s\", not connected.\n", Cmd_Argv(0)); + } + else + Con_Printf("Command \"%s\" can not be executed\n", Cmd_Argv(0)); + found = true; + goto command_found; + case src_client: + if (cmd->clientfunction) + { + cmd->clientfunction (); + goto done; + } + break; + } + break; + } + } +command_found: + + // if it's a client command and no command was found, say so. + if (cmd_source == src_client) + { + Con_Printf("player \"%s\" tried to %s\n", host_client->name, text); + goto done; + } + +// check alias + for (a=cmd_alias ; a ; a=a->next) + { + if (!strcasecmp (cmd_argv[0], a->name)) + { + Cmd_ExecuteAlias(a); + goto done; + } + } + + if(found) // if the command was hooked and found, all is good + goto done; + +// check cvars + if (!Cvar_Command () && host_framecount > 0) + Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0)); + +done: + cmd_tokenizebufferpos = oldpos; +} + + +/* +=================== +Cmd_ForwardStringToServer + +Sends an entire command string over to the server, unprocessed +=================== +*/ +void Cmd_ForwardStringToServer (const char *s) +{ + char temp[128]; + if (cls.state != ca_connected) + { + Con_Printf("Can't \"%s\", not connected\n", s); + return; + } + + if (!cls.netcon) + return; + + // LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my + // attention, it has been eradicated from here, its only (former) use in + // all of darkplaces. + if (cls.protocol == PROTOCOL_QUAKEWORLD) + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + else + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + if ((!strncmp(s, "say ", 4) || !strncmp(s, "say_team ", 9)) && cl_locs_enable.integer) + { + // say/say_team commands can replace % character codes with status info + while (*s) + { + if (*s == '%' && s[1]) + { + // handle proquake message macros + temp[0] = 0; + switch (s[1]) + { + case 'l': // current location + CL_Locs_FindLocationName(temp, sizeof(temp), cl.movement_origin); + break; + case 'h': // current health + dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_HEALTH]); + break; + case 'a': // current armor + dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ARMOR]); + break; + case 'x': // current rockets + dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ROCKETS]); + break; + case 'c': // current cells + dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_CELLS]); + break; + // silly proquake macros + case 'd': // loc at last death + CL_Locs_FindLocationName(temp, sizeof(temp), cl.lastdeathorigin); + break; + case 't': // current time + dpsnprintf(temp, sizeof(temp), "%.0f:%.0f", floor(cl.time / 60), cl.time - floor(cl.time / 60) * 60); + break; + case 'r': // rocket launcher status ("I have RL", "I need rockets", "I need RL") + if (!(cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER)) + dpsnprintf(temp, sizeof(temp), "I need RL"); + else if (!cl.stats[STAT_ROCKETS]) + dpsnprintf(temp, sizeof(temp), "I need rockets"); + else + dpsnprintf(temp, sizeof(temp), "I have RL"); + break; + case 'p': // powerup status (outputs "quad" "pent" and "eyes" according to status) + if (cl.stats[STAT_ITEMS] & IT_QUAD) + { + if (temp[0]) + strlcat(temp, " ", sizeof(temp)); + strlcat(temp, "quad", sizeof(temp)); + } + if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) + { + if (temp[0]) + strlcat(temp, " ", sizeof(temp)); + strlcat(temp, "pent", sizeof(temp)); + } + if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) + { + if (temp[0]) + strlcat(temp, " ", sizeof(temp)); + strlcat(temp, "eyes", sizeof(temp)); + } + break; + case 'w': // weapon status (outputs "SSG:NG:SNG:GL:RL:LG" with the text between : characters omitted if you lack the weapon) + if (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN) + strlcat(temp, "SSG", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_NAILGUN) + strlcat(temp, "NG", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN) + strlcat(temp, "SNG", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER) + strlcat(temp, "GL", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) + strlcat(temp, "RL", sizeof(temp)); + strlcat(temp, ":", sizeof(temp)); + if (cl.stats[STAT_ITEMS] & IT_LIGHTNING) + strlcat(temp, "LG", sizeof(temp)); + break; + default: + // not a recognized macro, print it as-is... + temp[0] = s[0]; + temp[1] = s[1]; + temp[2] = 0; + break; + } + // write the resulting text + SZ_Write(&cls.netcon->message, (unsigned char *)temp, strlen(temp)); + s += 2; + continue; + } + MSG_WriteByte(&cls.netcon->message, *s); + s++; + } + MSG_WriteByte(&cls.netcon->message, 0); + } + else // any other command is passed on as-is + SZ_Write(&cls.netcon->message, (const unsigned char *)s, (int)strlen(s) + 1); +} + +/* +=================== +Cmd_ForwardToServer + +Sends the entire command line over to the server +=================== +*/ +void Cmd_ForwardToServer (void) +{ + const char *s; + char vabuf[1024]; + if (!strcasecmp(Cmd_Argv(0), "cmd")) + { + // we want to strip off "cmd", so just send the args + s = Cmd_Argc() > 1 ? Cmd_Args() : ""; + } + else + { + // we need to keep the command name, so send Cmd_Argv(0), a space and then Cmd_Args() + s = va(vabuf, sizeof(vabuf), "%s %s", Cmd_Argv(0), Cmd_Argc() > 1 ? Cmd_Args() : ""); + } + // don't send an empty forward message if the user tries "cmd" by itself + if (!s || !*s) + return; + Cmd_ForwardStringToServer(s); +} + + +/* +================ +Cmd_CheckParm + +Returns the position (1 to argc-1) in the command's argument list +where the given parameter apears, or 0 if not present +================ +*/ + +int Cmd_CheckParm (const char *parm) +{ + int i; + + if (!parm) + { + Con_Printf ("Cmd_CheckParm: NULL"); + return 0; + } + + for (i = 1; i < Cmd_Argc (); i++) + if (!strcasecmp (parm, Cmd_Argv (i))) + return i; + + return 0; +} + + + +void Cmd_SaveInitState(void) +{ + cmd_function_t *f; + cmdalias_t *a; + for (f = cmd_functions;f;f = f->next) + f->initstate = true; + for (a = cmd_alias;a;a = a->next) + { + a->initstate = true; + a->initialvalue = Mem_strdup(zonemempool, a->value); + } + Cvar_SaveInitState(); +} + +void Cmd_RestoreInitState(void) +{ + cmd_function_t *f, **fp; + cmdalias_t *a, **ap; + for (fp = &cmd_functions;(f = *fp);) + { + if (f->initstate) + fp = &f->next; + else + { + // destroy this command, it didn't exist at init + Con_DPrintf("Cmd_RestoreInitState: Destroying command %s\n", f->name); + *fp = f->next; + Z_Free(f); + } + } + for (ap = &cmd_alias;(a = *ap);) + { + if (a->initstate) + { + // restore this alias, it existed at init + if (strcmp(a->value ? a->value : "", a->initialvalue ? a->initialvalue : "")) + { + Con_DPrintf("Cmd_RestoreInitState: Restoring alias %s\n", a->name); + if (a->value) + Z_Free(a->value); + a->value = Mem_strdup(zonemempool, a->initialvalue); + } + ap = &a->next; + } + else + { + // free this alias, it didn't exist at init... + Con_DPrintf("Cmd_RestoreInitState: Destroying alias %s\n", a->name); + *ap = a->next; + if (a->value) + Z_Free(a->value); + Z_Free(a); + } + } + Cvar_RestoreInitState(); +} diff --git a/app/jni/cmd.h b/app/jni/cmd.h new file mode 100644 index 0000000..c8bf80c --- /dev/null +++ b/app/jni/cmd.h @@ -0,0 +1,173 @@ +/* +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. + +*/ + +// cmd.h -- Command buffer and command execution + +//=========================================================================== + +/* + +Any number of commands can be added in a frame, from several different sources. +Most commands come from either keybindings or console line input, but remote +servers can also send across commands and entire text files can be execed. + +The + command line options are also added to the command buffer. + +The game starts with a Cbuf_AddText ("exec quake.rc\n"); Cbuf_Execute (); + +*/ + +#ifndef CMD_H +#define CMD_H + +/// allocates an initial text buffer that will grow as needed +void Cbuf_Init (void); + +void Cmd_Init_Commands (void); + +void Cbuf_Shutdown (void); + +/*! as new commands are generated from the console or keybindings, + * the text is added to the end of the command buffer. + */ +void Cbuf_AddText (const char *text); + +/*! when a command wants to issue other commands immediately, the text is + * inserted at the beginning of the buffer, before any remaining unexecuted + * commands. + */ +void Cbuf_InsertText (const char *text); + +/*! Pulls off terminated lines of text from the command buffer and sends + * them through Cmd_ExecuteString. Stops when the buffer is empty. + * Normally called once per frame, but may be explicitly invoked. + * \note Do not call inside a command function! + */ +void Cbuf_Execute (void); +/*! Performs deferred commands and runs Cbuf_Execute, called by Host_Main */ +void Cbuf_Frame (void); + +//=========================================================================== + +/* + +Command execution takes a null terminated string, breaks it into tokens, +then searches for a command or variable that matches the first token. + +Commands can come from three sources, but the handler functions may choose +to dissallow the action or forward it to a remote server if the source is +not apropriate. + +*/ + +typedef void (*xcommand_t) (void); + +typedef enum +{ + src_client, ///< came in over a net connection as a clc_stringcmd + ///< host_client will be valid during this state. + src_command ///< from the command buffer +} cmd_source_t; + +extern cmd_source_t cmd_source; + +void Cmd_Init (void); +void Cmd_Shutdown (void); + +// called by Host_Init, this marks cvars, commands and aliases with their init values +void Cmd_SaveInitState (void); +// called by FS_GameDir_f, this restores cvars, commands and aliases to init values +void Cmd_RestoreInitState (void); + +void Cmd_AddCommand_WithClientCommand (const char *cmd_name, xcommand_t consolefunction, xcommand_t clientfunction, const char *description); +void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *description); +// called by the init functions of other parts of the program to +// register commands and functions to call for them. +// The cmd_name is referenced later, so it should not be in temp memory + +/// used by the cvar code to check for cvar / command name overlap +qboolean Cmd_Exists (const char *cmd_name); + +/// attempts to match a partial command for automatic command line completion +/// returns NULL if nothing fits +const char *Cmd_CompleteCommand (const char *partial); + +int Cmd_CompleteAliasCountPossible (const char *partial); + +const char **Cmd_CompleteAliasBuildList (const char *partial); + +int Cmd_CompleteCountPossible (const char *partial); + +const char **Cmd_CompleteBuildList (const char *partial); + +void Cmd_CompleteCommandPrint (const char *partial); + +const char *Cmd_CompleteAlias (const char *partial); + +void Cmd_CompleteAliasPrint (const char *partial); + +// Enhanced console completion by Fett erich@heintz.com + +// Added by EvilTypeGuy eviltypeguy@qeradiant.com + +int Cmd_Argc (void); +const char *Cmd_Argv (int arg); +const char *Cmd_Args (void); +// The functions that execute commands get their parameters with these +// functions. Cmd_Argv () will return an empty string, not a NULL +// if arg > argc, so string operations are always safe. + +/// Returns the position (1 to argc-1) in the command's argument list +/// where the given parameter apears, or 0 if not present +int Cmd_CheckParm (const char *parm); + +//void Cmd_TokenizeString (char *text); +// Takes a null terminated string. Does not need to be /n terminated. +// breaks the string up into arg tokens. + +/// Parses a single line of text into arguments and tries to execute it. +/// The text can come from the command buffer, a remote client, or stdin. +void Cmd_ExecuteString (const char *text, cmd_source_t src, qboolean lockmutex); + +/// adds the string as a clc_stringcmd to the client message. +/// (used when there is no reason to generate a local command to do it) +void Cmd_ForwardStringToServer (const char *s); + +/// adds the current command line as a clc_stringcmd to the client message. +/// things like godmode, noclip, etc, are commands directed to the server, +/// so when they are typed in at the console, they will need to be forwarded. +void Cmd_ForwardToServer (void); + +/// used by command functions to send output to either the graphics console or +/// passed as a print message to the client +void Cmd_Print(const char *text); + +/// quotes a string so that it can be used as a command argument again; +/// quoteset is a string that contains one or more of ", \, $ and specifies +/// the characters to be quoted (you usually want to either pass "\"\\" or +/// "\"\\$"). Returns true on success, and false on overrun (in which case out +/// will contain a part of the quoted string). If putquotes is set, the +/// enclosing quote marks are also put. +qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qboolean putquotes); + +void Cmd_ClearCsqcFuncs (void); + +#endif + diff --git a/app/jni/collision.c b/app/jni/collision.c new file mode 100644 index 0000000..ef237e9 --- /dev/null +++ b/app/jni/collision.c @@ -0,0 +1,1988 @@ + +#include "quakedef.h" +#include "polygon.h" + +#define COLLISION_EDGEDIR_DOT_EPSILON (0.999f) +#define COLLISION_EDGECROSS_MINLENGTH2 (1.0f / 4194304.0f) +#define COLLISION_SNAPSCALE (32.0f) +#define COLLISION_SNAP (1.0f / COLLISION_SNAPSCALE) +#define COLLISION_SNAP2 (2.0f / COLLISION_SNAPSCALE) +#define COLLISION_PLANE_DIST_EPSILON (2.0f / COLLISION_SNAPSCALE) + +cvar_t collision_impactnudge = {0, "collision_impactnudge", "0.03125", "how much to back off from the impact"}; +cvar_t collision_startnudge = {0, "collision_startnudge", "0", "how much to bias collision trace start"}; +cvar_t collision_endnudge = {0, "collision_endnudge", "0", "how much to bias collision trace end"}; +cvar_t collision_enternudge = {0, "collision_enternudge", "0", "how much to bias collision entry fraction"}; +cvar_t collision_leavenudge = {0, "collision_leavenudge", "0", "how much to bias collision exit fraction"}; +cvar_t collision_prefernudgedfraction = {0, "collision_prefernudgedfraction", "1", "whether to sort collision events by nudged fraction (1) or real fraction (0)"}; +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +cvar_t collision_endposnudge = {0, "collision_endposnudge", "0", "workaround to fix trace_endpos sometimes being returned where it would be inside solid by making that collision hit (recommended: values like 1)"}; +#endif +cvar_t collision_debug_tracelineasbox = {0, "collision_debug_tracelineasbox", "0", "workaround for any bugs in Collision_TraceLineBrushFloat by using Collision_TraceBrushBrushFloat"}; +cvar_t collision_cache = {0, "collision_cache", "1", "store results of collision traces for next frame to reuse if possible (optimization)"}; +//cvar_t collision_triangle_neighborsides = {0, "collision_triangle_neighborsides", "1", "override automatic side generation if triangle has neighbors with face planes that form a convex edge (perfect solution, but can not work for all edges)"}; +cvar_t collision_triangle_bevelsides = {0, "collision_triangle_bevelsides", "1", "generate sloped edge planes on triangles - if 0, see axialedgeplanes"}; +cvar_t collision_triangle_axialsides = {0, "collision_triangle_axialsides", "1", "generate axially-aligned edge planes on triangles - otherwise use perpendicular edge planes"}; + +mempool_t *collision_mempool; + +void Collision_Init (void) +{ + Cvar_RegisterVariable(&collision_impactnudge); + Cvar_RegisterVariable(&collision_startnudge); + Cvar_RegisterVariable(&collision_endnudge); + Cvar_RegisterVariable(&collision_enternudge); + Cvar_RegisterVariable(&collision_leavenudge); + Cvar_RegisterVariable(&collision_prefernudgedfraction); +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND + Cvar_RegisterVariable(&collision_endposnudge); +#endif + Cvar_RegisterVariable(&collision_debug_tracelineasbox); + Cvar_RegisterVariable(&collision_cache); +// Cvar_RegisterVariable(&collision_triangle_neighborsides); + Cvar_RegisterVariable(&collision_triangle_bevelsides); + Cvar_RegisterVariable(&collision_triangle_axialsides); + collision_mempool = Mem_AllocPool("collision cache", 0, NULL); + Collision_Cache_Init(collision_mempool); +} + + + + + + + + + + + + + + +static void Collision_PrintBrushAsQHull(colbrushf_t *brush, const char *name) +{ + int i; + Con_Printf("3 %s\n%i\n", name, brush->numpoints); + for (i = 0;i < brush->numpoints;i++) + Con_Printf("%f %f %f\n", brush->points[i].v[0], brush->points[i].v[1], brush->points[i].v[2]); + // FIXME: optimize! + Con_Printf("4\n%i\n", brush->numplanes); + for (i = 0;i < brush->numplanes;i++) + Con_Printf("%f %f %f %f\n", brush->planes[i].normal[0], brush->planes[i].normal[1], brush->planes[i].normal[2], brush->planes[i].dist); +} + +static void Collision_ValidateBrush(colbrushf_t *brush) +{ + int j, k, pointsoffplanes, pointonplanes, pointswithinsufficientplanes, printbrush; + float d; + printbrush = false; + if (!brush->numpoints) + { + Con_Print("Collision_ValidateBrush: brush with no points!\n"); + printbrush = true; + } +#if 0 + // it's ok for a brush to have one point and no planes... + if (brush->numplanes == 0 && brush->numpoints != 1) + { + Con_Print("Collision_ValidateBrush: brush with no planes and more than one point!\n"); + printbrush = true; + } +#endif + if (brush->numplanes) + { + pointsoffplanes = 0; + pointswithinsufficientplanes = 0; + for (k = 0;k < brush->numplanes;k++) + if (DotProduct(brush->planes[k].normal, brush->planes[k].normal) < 0.0001f) + Con_Printf("Collision_ValidateBrush: plane #%i (%f %f %f %f) is degenerate\n", k, brush->planes[k].normal[0], brush->planes[k].normal[1], brush->planes[k].normal[2], brush->planes[k].dist); + for (j = 0;j < brush->numpoints;j++) + { + pointonplanes = 0; + for (k = 0;k < brush->numplanes;k++) + { + d = DotProduct(brush->points[j].v, brush->planes[k].normal) - brush->planes[k].dist; + if (d > COLLISION_PLANE_DIST_EPSILON) + { + Con_Printf("Collision_ValidateBrush: point #%i (%f %f %f) infront of plane #%i (%f %f %f %f)\n", j, brush->points[j].v[0], brush->points[j].v[1], brush->points[j].v[2], k, brush->planes[k].normal[0], brush->planes[k].normal[1], brush->planes[k].normal[2], brush->planes[k].dist); + printbrush = true; + } + if (fabs(d) > COLLISION_PLANE_DIST_EPSILON) + pointsoffplanes++; + else + pointonplanes++; + } + if (pointonplanes < 3) + pointswithinsufficientplanes++; + } + if (pointswithinsufficientplanes) + { + Con_Print("Collision_ValidateBrush: some points have insufficient planes, every point must be on at least 3 planes to form a corner.\n"); + printbrush = true; + } + if (pointsoffplanes == 0) // all points are on all planes + { + Con_Print("Collision_ValidateBrush: all points lie on all planes (degenerate, no brush volume!)\n"); + printbrush = true; + } + } + if (printbrush) + Collision_PrintBrushAsQHull(brush, "unnamed"); +} + +static float nearestplanedist_float(const float *normal, const colpointf_t *points, int numpoints) +{ + float dist, bestdist; + if (!numpoints) + return 0; + bestdist = DotProduct(points->v, normal); + points++; + while(--numpoints) + { + dist = DotProduct(points->v, normal); + bestdist = min(bestdist, dist); + points++; + } + return bestdist; +} + +static float furthestplanedist_float(const float *normal, const colpointf_t *points, int numpoints) +{ + float dist, bestdist; + if (!numpoints) + return 0; + bestdist = DotProduct(points->v, normal); + points++; + while(--numpoints) + { + dist = DotProduct(points->v, normal); + bestdist = max(bestdist, dist); + points++; + } + return bestdist; +} + +static void Collision_CalcEdgeDirsForPolygonBrushFloat(colbrushf_t *brush) +{ + int i, j; + for (i = 0, j = brush->numpoints - 1;i < brush->numpoints;j = i, i++) + VectorSubtract(brush->points[i].v, brush->points[j].v, brush->edgedirs[j].v); +} + +colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalplanes, const colplanef_t *originalplanes, int supercontents, int q3surfaceflags, const texture_t *texture, int hasaabbplanes) +{ + // TODO: planesbuf could be replaced by a remapping table + int j, k, l, m, w, xyzflags; + int numpointsbuf = 0, maxpointsbuf = 256, numedgedirsbuf = 0, maxedgedirsbuf = 256, numplanesbuf = 0, maxplanesbuf = 256, numelementsbuf = 0, maxelementsbuf = 256; + int isaabb = true; + double maxdist; + colbrushf_t *brush; + colpointf_t pointsbuf[256]; + colpointf_t edgedirsbuf[256]; + colplanef_t planesbuf[256]; + int elementsbuf[1024]; + int polypointbuf[256]; + int pmaxpoints = 64; + int pnumpoints; + double p[2][3*64]; +#if 0 + // enable these if debugging to avoid seeing garbage in unused data- + memset(pointsbuf, 0, sizeof(pointsbuf)); + memset(edgedirsbuf, 0, sizeof(edgedirsbuf)); + memset(planesbuf, 0, sizeof(planesbuf)); + memset(elementsbuf, 0, sizeof(elementsbuf)); + memset(polypointbuf, 0, sizeof(polypointbuf)); + memset(p, 0, sizeof(p)); +#endif + + // check if there are too many planes and skip the brush + if (numoriginalplanes >= maxplanesbuf) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many planes for buffer\n"); + return NULL; + } + + // figure out how large a bounding box we need to properly compute this brush + maxdist = 0; + for (j = 0;j < numoriginalplanes;j++) + maxdist = max(maxdist, fabs(originalplanes[j].dist)); + // now make it large enough to enclose the entire brush, and round it off to a reasonable multiple of 1024 + maxdist = floor(maxdist * (4.0 / 1024.0) + 2) * 1024.0; + // construct a collision brush (points, planes, and renderable mesh) from + // a set of planes, this also optimizes out any unnecessary planes (ones + // whose polygon is clipped away by the other planes) + for (j = 0;j < numoriginalplanes;j++) + { + // add the new plane + VectorCopy(originalplanes[j].normal, planesbuf[numplanesbuf].normal); + planesbuf[numplanesbuf].dist = originalplanes[j].dist; + planesbuf[numplanesbuf].q3surfaceflags = originalplanes[j].q3surfaceflags; + planesbuf[numplanesbuf].texture = originalplanes[j].texture; + numplanesbuf++; + + // create a large polygon from the plane + w = 0; + PolygonD_QuadForPlane(p[w], originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist, maxdist); + pnumpoints = 4; + // clip it by all other planes + for (k = 0;k < numoriginalplanes && pnumpoints >= 3 && pnumpoints <= pmaxpoints;k++) + { + // skip the plane this polygon + // (nothing happens if it is processed, this is just an optimization) + if (k != j) + { + // we want to keep the inside of the brush plane so we flip + // the cutting plane + PolygonD_Divide(pnumpoints, p[w], -originalplanes[k].normal[0], -originalplanes[k].normal[1], -originalplanes[k].normal[2], -originalplanes[k].dist, COLLISION_PLANE_DIST_EPSILON, pmaxpoints, p[!w], &pnumpoints, 0, NULL, NULL, NULL); + w = !w; + } + } + + // if nothing is left, skip it + if (pnumpoints < 3) + { + //Con_DPrintf("Collision_NewBrushFromPlanes: warning: polygon for plane %f %f %f %f clipped away\n", originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist); + continue; + } + + for (k = 0;k < pnumpoints;k++) + { + int l, m; + m = 0; + for (l = 0;l < numoriginalplanes;l++) + if (fabs(DotProduct(&p[w][k*3], originalplanes[l].normal) - originalplanes[l].dist) < COLLISION_PLANE_DIST_EPSILON) + m++; + if (m < 3) + break; + } + if (k < pnumpoints) + { + Con_DPrintf("Collision_NewBrushFromPlanes: warning: polygon point does not lie on at least 3 planes\n"); + //return NULL; + } + + // check if there are too many polygon vertices for buffer + if (pnumpoints > pmaxpoints) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n"); + return NULL; + } + + // check if there are too many triangle elements for buffer + if (numelementsbuf + (pnumpoints - 2) * 3 > maxelementsbuf) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many triangle elements for buffer\n"); + return NULL; + } + + // add the unique points for this polygon + for (k = 0;k < pnumpoints;k++) + { + float v[3]; + // downgrade to float precision before comparing + VectorCopy(&p[w][k*3], v); + + // check if there is already a matching point (no duplicates) + for (m = 0;m < numpointsbuf;m++) + if (VectorDistance2(v, pointsbuf[m].v) < COLLISION_SNAP2) + break; + + // if there is no match, add a new one + if (m == numpointsbuf) + { + // check if there are too many and skip the brush + if (numpointsbuf >= maxpointsbuf) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n"); + return NULL; + } + // add the new one + VectorCopy(&p[w][k*3], pointsbuf[numpointsbuf].v); + numpointsbuf++; + } + + // store the index into a buffer + polypointbuf[k] = m; + } + + // add the triangles for the polygon + // (this particular code makes a triangle fan) + for (k = 0;k < pnumpoints - 2;k++) + { + elementsbuf[numelementsbuf++] = polypointbuf[0]; + elementsbuf[numelementsbuf++] = polypointbuf[k + 1]; + elementsbuf[numelementsbuf++] = polypointbuf[k + 2]; + } + + // add the unique edgedirs for this polygon + for (k = 0, l = pnumpoints-1;k < pnumpoints;l = k, k++) + { + float dir[3]; + // downgrade to float precision before comparing + VectorSubtract(&p[w][k*3], &p[w][l*3], dir); + VectorNormalize(dir); + + // check if there is already a matching edgedir (no duplicates) + for (m = 0;m < numedgedirsbuf;m++) + if (DotProduct(dir, edgedirsbuf[m].v) >= COLLISION_EDGEDIR_DOT_EPSILON) + break; + // skip this if there is + if (m < numedgedirsbuf) + continue; + + // try again with negated edgedir + VectorNegate(dir, dir); + // check if there is already a matching edgedir (no duplicates) + for (m = 0;m < numedgedirsbuf;m++) + if (DotProduct(dir, edgedirsbuf[m].v) >= COLLISION_EDGEDIR_DOT_EPSILON) + break; + // if there is no match, add a new one + if (m == numedgedirsbuf) + { + // check if there are too many and skip the brush + if (numedgedirsbuf >= maxedgedirsbuf) + { + Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many edgedirs for buffer\n"); + return NULL; + } + // add the new one + VectorCopy(dir, edgedirsbuf[numedgedirsbuf].v); + numedgedirsbuf++; + } + } + + // if any normal is not purely axial, it's not an axis-aligned box + if (isaabb && (originalplanes[j].normal[0] == 0) + (originalplanes[j].normal[1] == 0) + (originalplanes[j].normal[2] == 0) < 2) + isaabb = false; + } + + // if nothing is left, there's nothing to allocate + if (numplanesbuf < 4) + { + Con_DPrintf("Collision_NewBrushFromPlanes: failed to build collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf); + return NULL; + } + + // if no triangles or points could be constructed, then this routine failed but the brush is not discarded + if (numelementsbuf < 12 || numpointsbuf < 4) + Con_DPrintf("Collision_NewBrushFromPlanes: unable to rebuild triangles/points for collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf); + + // validate plane distances + for (j = 0;j < numplanesbuf;j++) + { + float d = furthestplanedist_float(planesbuf[j].normal, pointsbuf, numpointsbuf); + if (fabs(planesbuf[j].dist - d) > COLLISION_PLANE_DIST_EPSILON) + Con_DPrintf("plane %f %f %f %f mismatches dist %f\n", planesbuf[j].normal[0], planesbuf[j].normal[1], planesbuf[j].normal[2], planesbuf[j].dist, d); + } + + // allocate the brush and copy to it + brush = (colbrushf_t *)Mem_Alloc(mempool, sizeof(colbrushf_t) + sizeof(colpointf_t) * numpointsbuf + sizeof(colpointf_t) * numedgedirsbuf + sizeof(colplanef_t) * numplanesbuf + sizeof(int) * numelementsbuf); + brush->isaabb = isaabb; + brush->hasaabbplanes = hasaabbplanes; + brush->supercontents = supercontents; + brush->numplanes = numplanesbuf; + brush->numedgedirs = numedgedirsbuf; + brush->numpoints = numpointsbuf; + brush->numtriangles = numelementsbuf / 3; + brush->planes = (colplanef_t *)(brush + 1); + brush->points = (colpointf_t *)(brush->planes + brush->numplanes); + brush->edgedirs = (colpointf_t *)(brush->points + brush->numpoints); + brush->elements = (int *)(brush->points + brush->numpoints); + brush->q3surfaceflags = q3surfaceflags; + brush->texture = texture; + for (j = 0;j < brush->numpoints;j++) + { + brush->points[j].v[0] = pointsbuf[j].v[0]; + brush->points[j].v[1] = pointsbuf[j].v[1]; + brush->points[j].v[2] = pointsbuf[j].v[2]; + } + for (j = 0;j < brush->numedgedirs;j++) + { + brush->edgedirs[j].v[0] = edgedirsbuf[j].v[0]; + brush->edgedirs[j].v[1] = edgedirsbuf[j].v[1]; + brush->edgedirs[j].v[2] = edgedirsbuf[j].v[2]; + } + for (j = 0;j < brush->numplanes;j++) + { + brush->planes[j].normal[0] = planesbuf[j].normal[0]; + brush->planes[j].normal[1] = planesbuf[j].normal[1]; + brush->planes[j].normal[2] = planesbuf[j].normal[2]; + brush->planes[j].dist = planesbuf[j].dist; + brush->planes[j].q3surfaceflags = planesbuf[j].q3surfaceflags; + brush->planes[j].texture = planesbuf[j].texture; + } + for (j = 0;j < brush->numtriangles * 3;j++) + brush->elements[j] = elementsbuf[j]; + + xyzflags = 0; + VectorClear(brush->mins); + VectorClear(brush->maxs); + for (j = 0;j < min(6, numoriginalplanes);j++) + { + if (originalplanes[j].normal[0] == 1) {xyzflags |= 1;brush->maxs[0] = originalplanes[j].dist;} + else if (originalplanes[j].normal[0] == -1) {xyzflags |= 2;brush->mins[0] = -originalplanes[j].dist;} + else if (originalplanes[j].normal[1] == 1) {xyzflags |= 4;brush->maxs[1] = originalplanes[j].dist;} + else if (originalplanes[j].normal[1] == -1) {xyzflags |= 8;brush->mins[1] = -originalplanes[j].dist;} + else if (originalplanes[j].normal[2] == 1) {xyzflags |= 16;brush->maxs[2] = originalplanes[j].dist;} + else if (originalplanes[j].normal[2] == -1) {xyzflags |= 32;brush->mins[2] = -originalplanes[j].dist;} + } + // if not all xyzflags were set, then this is not a brush from q3map/q3map2, and needs reconstruction of the bounding box + // (this case works for any brush with valid points, but sometimes brushes are not reconstructed properly and hence the points are not valid, so this is reserved as a fallback case) + if (xyzflags != 63) + { + VectorCopy(brush->points[0].v, brush->mins); + VectorCopy(brush->points[0].v, brush->maxs); + for (j = 1;j < brush->numpoints;j++) + { + brush->mins[0] = min(brush->mins[0], brush->points[j].v[0]); + brush->mins[1] = min(brush->mins[1], brush->points[j].v[1]); + brush->mins[2] = min(brush->mins[2], brush->points[j].v[2]); + brush->maxs[0] = max(brush->maxs[0], brush->points[j].v[0]); + brush->maxs[1] = max(brush->maxs[1], brush->points[j].v[1]); + brush->maxs[2] = max(brush->maxs[2], brush->points[j].v[2]); + } + } + brush->mins[0] -= 1; + brush->mins[1] -= 1; + brush->mins[2] -= 1; + brush->maxs[0] += 1; + brush->maxs[1] += 1; + brush->maxs[2] += 1; + Collision_ValidateBrush(brush); + return brush; +} + + + +void Collision_CalcPlanesForTriangleBrushFloat(colbrushf_t *brush) +{ + int i; + float edge0[3], edge1[3], edge2[3]; + colpointf_t *p; + + TriangleNormal(brush->points[0].v, brush->points[1].v, brush->points[2].v, brush->planes[0].normal); + if (DotProduct(brush->planes[0].normal, brush->planes[0].normal) < 0.0001f) + { + // there's no point in processing a degenerate triangle (GIGO - Garbage In, Garbage Out) + // note that some of these exist in q3bsp bspline patches + brush->numplanes = 0; + return; + } + + // there are 5 planes (front, back, sides) and 3 edges + brush->numplanes = 5; + brush->numedgedirs = 3; + VectorNormalize(brush->planes[0].normal); + brush->planes[0].dist = DotProduct(brush->points->v, brush->planes[0].normal); + VectorNegate(brush->planes[0].normal, brush->planes[1].normal); + brush->planes[1].dist = -brush->planes[0].dist; + // edge directions are easy to calculate + VectorSubtract(brush->points[2].v, brush->points[0].v, edge0); + VectorSubtract(brush->points[0].v, brush->points[1].v, edge1); + VectorSubtract(brush->points[1].v, brush->points[2].v, edge2); + VectorCopy(edge0, brush->edgedirs[0].v); + VectorCopy(edge1, brush->edgedirs[1].v); + VectorCopy(edge2, brush->edgedirs[2].v); + // now select an algorithm to generate the side planes + if (collision_triangle_bevelsides.integer) + { + // use 45 degree slopes at the edges of the triangle to make a sinking trace error turn into "riding up" the slope rather than getting stuck + CrossProduct(edge0, brush->planes->normal, brush->planes[2].normal); + CrossProduct(edge1, brush->planes->normal, brush->planes[3].normal); + CrossProduct(edge2, brush->planes->normal, brush->planes[4].normal); + VectorNormalize(brush->planes[2].normal); + VectorNormalize(brush->planes[3].normal); + VectorNormalize(brush->planes[4].normal); + VectorAdd(brush->planes[2].normal, brush->planes[0].normal, brush->planes[2].normal); + VectorAdd(brush->planes[3].normal, brush->planes[0].normal, brush->planes[3].normal); + VectorAdd(brush->planes[4].normal, brush->planes[0].normal, brush->planes[4].normal); + VectorNormalize(brush->planes[2].normal); + VectorNormalize(brush->planes[3].normal); + VectorNormalize(brush->planes[4].normal); + } + else if (collision_triangle_axialsides.integer) + { + float projectionnormal[3], projectionedge0[3], projectionedge1[3], projectionedge2[3]; + int i, best; + float dist, bestdist; + bestdist = fabs(brush->planes[0].normal[0]); + best = 0; + for (i = 1;i < 3;i++) + { + dist = fabs(brush->planes[0].normal[i]); + if (bestdist < dist) + { + bestdist = dist; + best = i; + } + } + VectorClear(projectionnormal); + if (brush->planes[0].normal[best] < 0) + projectionnormal[best] = -1; + else + projectionnormal[best] = 1; + VectorCopy(edge0, projectionedge0); + VectorCopy(edge1, projectionedge1); + VectorCopy(edge2, projectionedge2); + projectionedge0[best] = 0; + projectionedge1[best] = 0; + projectionedge2[best] = 0; + CrossProduct(projectionedge0, projectionnormal, brush->planes[2].normal); + CrossProduct(projectionedge1, projectionnormal, brush->planes[3].normal); + CrossProduct(projectionedge2, projectionnormal, brush->planes[4].normal); + VectorNormalize(brush->planes[2].normal); + VectorNormalize(brush->planes[3].normal); + VectorNormalize(brush->planes[4].normal); + } + else + { + CrossProduct(edge0, brush->planes->normal, brush->planes[2].normal); + CrossProduct(edge1, brush->planes->normal, brush->planes[3].normal); + CrossProduct(edge2, brush->planes->normal, brush->planes[4].normal); + VectorNormalize(brush->planes[2].normal); + VectorNormalize(brush->planes[3].normal); + VectorNormalize(brush->planes[4].normal); + } + brush->planes[2].dist = DotProduct(brush->points[2].v, brush->planes[2].normal); + brush->planes[3].dist = DotProduct(brush->points[0].v, brush->planes[3].normal); + brush->planes[4].dist = DotProduct(brush->points[1].v, brush->planes[4].normal); + + if (developer_extra.integer) + { + // validity check - will be disabled later + Collision_ValidateBrush(brush); + for (i = 0;i < brush->numplanes;i++) + { + int j; + for (j = 0, p = brush->points;j < brush->numpoints;j++, p++) + if (DotProduct(p->v, brush->planes[i].normal) > brush->planes[i].dist + COLLISION_PLANE_DIST_EPSILON) + Con_DPrintf("Error in brush plane generation, plane %i\n", i); + } + } +} + +colbrushf_t *Collision_AllocBrushFromPermanentPolygonFloat(mempool_t *mempool, int numpoints, float *points, int supercontents, int q3surfaceflags, const texture_t *texture) +{ + colbrushf_t *brush; + brush = (colbrushf_t *)Mem_Alloc(mempool, sizeof(colbrushf_t) + sizeof(colplanef_t) * (numpoints + 2) + sizeof(colpointf_t) * numpoints); + brush->isaabb = false; + brush->hasaabbplanes = false; + brush->supercontents = supercontents; + brush->numpoints = numpoints; + brush->numedgedirs = numpoints; + brush->numplanes = numpoints + 2; + brush->planes = (colplanef_t *)(brush + 1); + brush->points = (colpointf_t *)points; + brush->edgedirs = (colpointf_t *)(brush->planes + brush->numplanes); + brush->q3surfaceflags = q3surfaceflags; + brush->texture = texture; + Sys_Error("Collision_AllocBrushFromPermanentPolygonFloat: FIXME: this code needs to be updated to generate a mesh..."); + return brush; +} + +// NOTE: start and end of each brush pair must have same numplanes/numpoints +void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *trace_start, const colbrushf_t *trace_end, const colbrushf_t *other_start, const colbrushf_t *other_end) +{ + int nplane, nplane2, nedge1, nedge2, hitq3surfaceflags = 0; + int tracenumedgedirs = trace_start->numedgedirs; + //int othernumedgedirs = other_start->numedgedirs; + int tracenumpoints = trace_start->numpoints; + int othernumpoints = other_start->numpoints; + int numplanes1 = other_start->numplanes; + int numplanes2 = numplanes1 + trace_start->numplanes; + int numplanes3 = numplanes2 + trace_start->numedgedirs * other_start->numedgedirs * 2; + vec_t enterfrac = -1, leavefrac = 1, startdist, enddist, ie, f, imove, enterfrac2 = -1; + vec4_t startplane; + vec4_t endplane; + vec4_t newimpactplane; + const texture_t *hittexture = NULL; + vec_t startdepth = 1; + vec3_t startdepthnormal; + + VectorClear(startdepthnormal); + Vector4Clear(newimpactplane); + + // fast case for AABB vs compiled brushes (which begin with AABB planes and also have precomputed bevels for AABB collisions) + if (trace_start->isaabb && other_start->hasaabbplanes) + numplanes3 = numplanes2 = numplanes1; + + // Separating Axis Theorem: + // if a supporting vector (plane normal) can be found that separates two + // objects, they are not colliding. + // + // Minkowski Sum: + // reduce the size of one object to a point while enlarging the other to + // represent the space that point can not occupy. + // + // try every plane we can construct between the two brushes and measure + // the distance between them. + for (nplane = 0;nplane < numplanes3;nplane++) + { + if (nplane < numplanes1) + { + nplane2 = nplane; + VectorCopy(other_start->planes[nplane2].normal, startplane); + VectorCopy(other_end->planes[nplane2].normal, endplane); + } + else if (nplane < numplanes2) + { + nplane2 = nplane - numplanes1; + VectorCopy(trace_start->planes[nplane2].normal, startplane); + VectorCopy(trace_end->planes[nplane2].normal, endplane); + } + else + { + // pick an edgedir from each brush and cross them + nplane2 = nplane - numplanes2; + nedge1 = nplane2 >> 1; + nedge2 = nedge1 / tracenumedgedirs; + nedge1 -= nedge2 * tracenumedgedirs; + if (nplane2 & 1) + { + CrossProduct(trace_start->edgedirs[nedge1].v, other_start->edgedirs[nedge2].v, startplane); + if (VectorLength2(startplane) < COLLISION_EDGECROSS_MINLENGTH2) + continue; // degenerate crossproduct + CrossProduct(trace_end->edgedirs[nedge1].v, other_end->edgedirs[nedge2].v, endplane); + if (VectorLength2(endplane) < COLLISION_EDGECROSS_MINLENGTH2) + continue; // degenerate crossproduct + } + else + { + CrossProduct(other_start->edgedirs[nedge2].v, trace_start->edgedirs[nedge1].v, startplane); + if (VectorLength2(startplane) < COLLISION_EDGECROSS_MINLENGTH2) + continue; // degenerate crossproduct + CrossProduct(other_end->edgedirs[nedge2].v, trace_end->edgedirs[nedge1].v, endplane); + if (VectorLength2(endplane) < COLLISION_EDGECROSS_MINLENGTH2) + continue; // degenerate crossproduct + } + VectorNormalize(startplane); + VectorNormalize(endplane); + } + startplane[3] = furthestplanedist_float(startplane, other_start->points, othernumpoints); + endplane[3] = furthestplanedist_float(startplane, other_end->points, othernumpoints); + startdist = nearestplanedist_float(startplane, trace_start->points, tracenumpoints) - startplane[3] - collision_startnudge.value; + enddist = nearestplanedist_float(endplane, trace_end->points, tracenumpoints) - endplane[3] - collision_endnudge.value; + //Con_Printf("%c%i: startdist = %f, enddist = %f, startdist / (startdist - enddist) = %f\n", nplane2 != nplane ? 'b' : 'a', nplane2, startdist, enddist, startdist / (startdist - enddist)); + + // aside from collisions, this is also used for error correction + if (startdist < collision_impactnudge.value && nplane < numplanes1 && (startdepth < startdist || startdepth == 1)) + { + startdepth = startdist; + VectorCopy(startplane, startdepthnormal); + } + + if (startdist > enddist) + { + // moving into brush + if (enddist >= collision_enternudge.value) + return; + if (startdist > 0) + { + // enter + imove = 1 / (startdist - enddist); + f = (startdist - collision_enternudge.value) * imove; + if (f < 0) + f = 0; + // check if this will reduce the collision time range + if (enterfrac < f) + { + // reduced collision time range + enterfrac = f; + // if the collision time range is now empty, no collision + if (enterfrac > leavefrac) + return; + // if the collision would be further away than the trace's + // existing collision data, we don't care about this + // collision + if (enterfrac > trace->realfraction) + return; + // calculate the nudged fraction and impact normal we'll + // need if we accept this collision later + enterfrac2 = (startdist - collision_impactnudge.value) * imove; + ie = 1.0f - enterfrac; + newimpactplane[0] = startplane[0] * ie + endplane[0] * enterfrac; + newimpactplane[1] = startplane[1] * ie + endplane[1] * enterfrac; + newimpactplane[2] = startplane[2] * ie + endplane[2] * enterfrac; + newimpactplane[3] = startplane[3] * ie + endplane[3] * enterfrac; + if (nplane < numplanes1) + { + // use the plane from other + nplane2 = nplane; + hitq3surfaceflags = other_start->planes[nplane2].q3surfaceflags; + hittexture = other_start->planes[nplane2].texture; + } + else if (nplane < numplanes2) + { + // use the plane from trace + nplane2 = nplane - numplanes1; + hitq3surfaceflags = trace_start->planes[nplane2].q3surfaceflags; + hittexture = trace_start->planes[nplane2].texture; + } + else + { + hitq3surfaceflags = other_start->q3surfaceflags; + hittexture = other_start->texture; + } + } + } + } + else + { + // moving out of brush + if (startdist > 0) + return; + if (enddist > 0) + { + // leave + f = (startdist + collision_leavenudge.value) / (startdist - enddist); + if (f > 1) + f = 1; + // check if this will reduce the collision time range + if (leavefrac > f) + { + // reduced collision time range + leavefrac = f; + // if the collision time range is now empty, no collision + if (enterfrac > leavefrac) + return; + } + } + } + } + + // at this point we know the trace overlaps the brush because it was not + // rejected at any point in the loop above + + // see if the trace started outside the brush or not + if (enterfrac > -1) + { + // started outside, and overlaps, therefore there is a collision here + // store out the impact information + if (trace->hitsupercontentsmask & other_start->supercontents) + { + trace->hitsupercontents = other_start->supercontents; + trace->hitq3surfaceflags = hitq3surfaceflags; + trace->hittexture = hittexture; + trace->realfraction = bound(0, enterfrac, 1); + trace->fraction = bound(0, enterfrac2, 1); + if (collision_prefernudgedfraction.integer) + trace->realfraction = trace->fraction; + VectorCopy(newimpactplane, trace->plane.normal); + trace->plane.dist = newimpactplane[3]; + } + } + else + { + // started inside, update startsolid and friends + trace->startsupercontents |= other_start->supercontents; + if (trace->hitsupercontentsmask & other_start->supercontents) + { + trace->startsolid = true; + if (leavefrac < 1) + trace->allsolid = true; + VectorCopy(newimpactplane, trace->plane.normal); + trace->plane.dist = newimpactplane[3]; + if (trace->startdepth > startdepth) + { + trace->startdepth = startdepth; + VectorCopy(startdepthnormal, trace->startdepthnormal); + } + } + } +} + +// NOTE: start and end of each brush pair must have same numplanes/numpoints +void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const colbrushf_t *other_start, const colbrushf_t *other_end) +{ + int nplane, hitq3surfaceflags = 0; + int numplanes = other_start->numplanes; + vec_t enterfrac = -1, leavefrac = 1, startdist, enddist, ie, f, imove, enterfrac2 = -1; + vec4_t startplane; + vec4_t endplane; + vec4_t newimpactplane; + const texture_t *hittexture = NULL; + vec_t startdepth = 1; + vec3_t startdepthnormal; + + if (collision_debug_tracelineasbox.integer) + { + colboxbrushf_t thisbrush_start, thisbrush_end; + Collision_BrushForBox(&thisbrush_start, linestart, linestart, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, lineend, lineend, 0, 0, NULL); + Collision_TraceBrushBrushFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, other_start, other_end); + return; + } + + VectorClear(startdepthnormal); + Vector4Clear(newimpactplane); + + // Separating Axis Theorem: + // if a supporting vector (plane normal) can be found that separates two + // objects, they are not colliding. + // + // Minkowski Sum: + // reduce the size of one object to a point while enlarging the other to + // represent the space that point can not occupy. + // + // try every plane we can construct between the two brushes and measure + // the distance between them. + for (nplane = 0;nplane < numplanes;nplane++) + { + VectorCopy(other_start->planes[nplane].normal, startplane); + startplane[3] = other_start->planes[nplane].dist; + VectorCopy(other_end->planes[nplane].normal, endplane); + endplane[3] = other_end->planes[nplane].dist; + startdist = DotProduct(linestart, startplane) - startplane[3] - collision_startnudge.value; + enddist = DotProduct(lineend, endplane) - endplane[3] - collision_endnudge.value; + //Con_Printf("%c%i: startdist = %f, enddist = %f, startdist / (startdist - enddist) = %f\n", nplane2 != nplane ? 'b' : 'a', nplane2, startdist, enddist, startdist / (startdist - enddist)); + + // aside from collisions, this is also used for error correction + if (startdist < collision_impactnudge.value && (startdepth < startdist || startdepth == 1)) + { + startdepth = startdist; + VectorCopy(startplane, startdepthnormal); + } + + if (startdist > enddist) + { + // moving into brush + if (enddist >= collision_enternudge.value) + return; + if (startdist > 0) + { + // enter + imove = 1 / (startdist - enddist); + f = (startdist - collision_enternudge.value) * imove; + if (f < 0) + f = 0; + // check if this will reduce the collision time range + if (enterfrac < f) + { + // reduced collision time range + enterfrac = f; + // if the collision time range is now empty, no collision + if (enterfrac > leavefrac) + return; + // if the collision would be further away than the trace's + // existing collision data, we don't care about this + // collision + if (enterfrac > trace->realfraction) + return; + // calculate the nudged fraction and impact normal we'll + // need if we accept this collision later + enterfrac2 = (startdist - collision_impactnudge.value) * imove; + ie = 1.0f - enterfrac; + newimpactplane[0] = startplane[0] * ie + endplane[0] * enterfrac; + newimpactplane[1] = startplane[1] * ie + endplane[1] * enterfrac; + newimpactplane[2] = startplane[2] * ie + endplane[2] * enterfrac; + newimpactplane[3] = startplane[3] * ie + endplane[3] * enterfrac; + hitq3surfaceflags = other_start->planes[nplane].q3surfaceflags; + hittexture = other_start->planes[nplane].texture; + } + } + } + else + { + // moving out of brush + if (startdist > 0) + return; + if (enddist > 0) + { + // leave + f = (startdist + collision_leavenudge.value) / (startdist - enddist); + if (f > 1) + f = 1; + // check if this will reduce the collision time range + if (leavefrac > f) + { + // reduced collision time range + leavefrac = f; + // if the collision time range is now empty, no collision + if (enterfrac > leavefrac) + return; + } + } + } + } + + // at this point we know the trace overlaps the brush because it was not + // rejected at any point in the loop above + + // see if the trace started outside the brush or not + if (enterfrac > -1) + { + // started outside, and overlaps, therefore there is a collision here + // store out the impact information + if (trace->hitsupercontentsmask & other_start->supercontents) + { + trace->hitsupercontents = other_start->supercontents; + trace->hitq3surfaceflags = hitq3surfaceflags; + trace->hittexture = hittexture; + trace->realfraction = bound(0, enterfrac, 1); + trace->fraction = bound(0, enterfrac2, 1); + if (collision_prefernudgedfraction.integer) + trace->realfraction = trace->fraction; + VectorCopy(newimpactplane, trace->plane.normal); + trace->plane.dist = newimpactplane[3]; + } + } + else + { + // started inside, update startsolid and friends + trace->startsupercontents |= other_start->supercontents; + if (trace->hitsupercontentsmask & other_start->supercontents) + { + trace->startsolid = true; + if (leavefrac < 1) + trace->allsolid = true; + VectorCopy(newimpactplane, trace->plane.normal); + trace->plane.dist = newimpactplane[3]; + if (trace->startdepth > startdepth) + { + trace->startdepth = startdepth; + VectorCopy(startdepthnormal, trace->startdepthnormal); + } + } + } +} + +qboolean Collision_PointInsideBrushFloat(const vec3_t point, const colbrushf_t *brush) +{ + int nplane; + const colplanef_t *plane; + + if (!BoxesOverlap(point, point, brush->mins, brush->maxs)) + return false; + for (nplane = 0, plane = brush->planes;nplane < brush->numplanes;nplane++, plane++) + if (DotProduct(plane->normal, point) > plane->dist) + return false; + return true; +} + +void Collision_TracePointBrushFloat(trace_t *trace, const vec3_t point, const colbrushf_t *thatbrush) +{ + if (!Collision_PointInsideBrushFloat(point, thatbrush)) + return; + + trace->startsupercontents |= thatbrush->supercontents; + if (trace->hitsupercontentsmask & thatbrush->supercontents) + { + trace->startsolid = true; + trace->allsolid = true; + } +} + +static void Collision_SnapCopyPoints(int numpoints, const colpointf_t *in, colpointf_t *out, float fractionprecision, float invfractionprecision) +{ + int i; + for (i = 0;i < numpoints;i++) + { + out[i].v[0] = floor(in[i].v[0] * fractionprecision + 0.5f) * invfractionprecision; + out[i].v[1] = floor(in[i].v[1] * fractionprecision + 0.5f) * invfractionprecision; + out[i].v[2] = floor(in[i].v[2] * fractionprecision + 0.5f) * invfractionprecision; + } +} + +void Collision_TraceBrushTriangleMeshFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs) +{ + int i; + colpointf_t points[3]; + colpointf_t edgedirs[3]; + colplanef_t planes[5]; + colbrushf_t brush; + memset(&brush, 0, sizeof(brush)); + brush.isaabb = false; + brush.hasaabbplanes = false; + brush.numpoints = 3; + brush.numedgedirs = 3; + brush.numplanes = 5; + brush.points = points; + brush.edgedirs = edgedirs; + brush.planes = planes; + brush.supercontents = supercontents; + brush.q3surfaceflags = q3surfaceflags; + brush.texture = texture; + for (i = 0;i < brush.numplanes;i++) + { + brush.planes[i].q3surfaceflags = q3surfaceflags; + brush.planes[i].texture = texture; + } + if(stride > 0) + { + int k, cnt, tri; + cnt = (numtriangles + stride - 1) / stride; + for(i = 0; i < cnt; ++i) + { + if(BoxesOverlap(bbox6f + i * 6, bbox6f + i * 6 + 3, segmentmins, segmentmaxs)) + { + for(k = 0; k < stride; ++k) + { + tri = i * stride + k; + if(tri >= numtriangles) + break; + VectorCopy(vertex3f + element3i[tri * 3 + 0] * 3, points[0].v); + VectorCopy(vertex3f + element3i[tri * 3 + 1] * 3, points[1].v); + VectorCopy(vertex3f + element3i[tri * 3 + 2] * 3, points[2].v); + Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); + Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); + Collision_CalcPlanesForTriangleBrushFloat(&brush); + //Collision_PrintBrushAsQHull(&brush, "brush"); + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); + } + } + } + } + else if(stride == 0) + { + for (i = 0;i < numtriangles;i++, element3i += 3) + { + if (TriangleBBoxOverlapsBox(vertex3f + element3i[0]*3, vertex3f + element3i[1]*3, vertex3f + element3i[2]*3, segmentmins, segmentmaxs)) + { + VectorCopy(vertex3f + element3i[0] * 3, points[0].v); + VectorCopy(vertex3f + element3i[1] * 3, points[1].v); + VectorCopy(vertex3f + element3i[2] * 3, points[2].v); + Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); + Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); + Collision_CalcPlanesForTriangleBrushFloat(&brush); + //Collision_PrintBrushAsQHull(&brush, "brush"); + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); + } + } + } + else + { + for (i = 0;i < numtriangles;i++, element3i += 3) + { + VectorCopy(vertex3f + element3i[0] * 3, points[0].v); + VectorCopy(vertex3f + element3i[1] * 3, points[1].v); + VectorCopy(vertex3f + element3i[2] * 3, points[2].v); + Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); + Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); + Collision_CalcPlanesForTriangleBrushFloat(&brush); + //Collision_PrintBrushAsQHull(&brush, "brush"); + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); + } + } +} + +void Collision_TraceLineTriangleMeshFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs) +{ + int i; + // FIXME: snap vertices? + if(stride > 0) + { + int k, cnt, tri; + cnt = (numtriangles + stride - 1) / stride; + for(i = 0; i < cnt; ++i) + { + if(BoxesOverlap(bbox6f + i * 6, bbox6f + i * 6 + 3, segmentmins, segmentmaxs)) + { + for(k = 0; k < stride; ++k) + { + tri = i * stride + k; + if(tri >= numtriangles) + break; + Collision_TraceLineTriangleFloat(trace, linestart, lineend, vertex3f + element3i[tri * 3 + 0] * 3, vertex3f + element3i[tri * 3 + 1] * 3, vertex3f + element3i[tri * 3 + 2] * 3, supercontents, q3surfaceflags, texture); + } + } + } + } + else + { + for (i = 0;i < numtriangles;i++, element3i += 3) + Collision_TraceLineTriangleFloat(trace, linestart, lineend, vertex3f + element3i[0] * 3, vertex3f + element3i[1] * 3, vertex3f + element3i[2] * 3, supercontents, q3surfaceflags, texture); + } +} + +void Collision_TraceBrushTriangleFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const float *v0, const float *v1, const float *v2, int supercontents, int q3surfaceflags, const texture_t *texture) +{ + int i; + colpointf_t points[3]; + colpointf_t edgedirs[3]; + colplanef_t planes[5]; + colbrushf_t brush; + memset(&brush, 0, sizeof(brush)); + brush.isaabb = false; + brush.hasaabbplanes = false; + brush.numpoints = 3; + brush.numedgedirs = 3; + brush.numplanes = 5; + brush.points = points; + brush.edgedirs = edgedirs; + brush.planes = planes; + brush.supercontents = supercontents; + brush.q3surfaceflags = q3surfaceflags; + brush.texture = texture; + for (i = 0;i < brush.numplanes;i++) + { + brush.planes[i].q3surfaceflags = q3surfaceflags; + brush.planes[i].texture = texture; + } + VectorCopy(v0, points[0].v); + VectorCopy(v1, points[1].v); + VectorCopy(v2, points[2].v); + Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); + Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); + Collision_CalcPlanesForTriangleBrushFloat(&brush); + //Collision_PrintBrushAsQHull(&brush, "brush"); + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); +} + +void Collision_BrushForBox(colboxbrushf_t *boxbrush, const vec3_t mins, const vec3_t maxs, int supercontents, int q3surfaceflags, const texture_t *texture) +{ + int i; + memset(boxbrush, 0, sizeof(*boxbrush)); + boxbrush->brush.isaabb = true; + boxbrush->brush.hasaabbplanes = true; + boxbrush->brush.points = boxbrush->points; + boxbrush->brush.edgedirs = boxbrush->edgedirs; + boxbrush->brush.planes = boxbrush->planes; + boxbrush->brush.supercontents = supercontents; + boxbrush->brush.q3surfaceflags = q3surfaceflags; + boxbrush->brush.texture = texture; + if (VectorCompare(mins, maxs)) + { + // point brush + boxbrush->brush.numpoints = 1; + boxbrush->brush.numedgedirs = 0; + boxbrush->brush.numplanes = 0; + VectorCopy(mins, boxbrush->brush.points[0].v); + } + else + { + boxbrush->brush.numpoints = 8; + boxbrush->brush.numedgedirs = 3; + boxbrush->brush.numplanes = 6; + // there are 8 points on a box + // there are 3 edgedirs on a box (both signs are tested in collision) + // there are 6 planes on a box + VectorSet(boxbrush->brush.points[0].v, mins[0], mins[1], mins[2]); + VectorSet(boxbrush->brush.points[1].v, maxs[0], mins[1], mins[2]); + VectorSet(boxbrush->brush.points[2].v, mins[0], maxs[1], mins[2]); + VectorSet(boxbrush->brush.points[3].v, maxs[0], maxs[1], mins[2]); + VectorSet(boxbrush->brush.points[4].v, mins[0], mins[1], maxs[2]); + VectorSet(boxbrush->brush.points[5].v, maxs[0], mins[1], maxs[2]); + VectorSet(boxbrush->brush.points[6].v, mins[0], maxs[1], maxs[2]); + VectorSet(boxbrush->brush.points[7].v, maxs[0], maxs[1], maxs[2]); + VectorSet(boxbrush->brush.edgedirs[0].v, 1, 0, 0); + VectorSet(boxbrush->brush.edgedirs[1].v, 0, 1, 0); + VectorSet(boxbrush->brush.edgedirs[2].v, 0, 0, 1); + VectorSet(boxbrush->brush.planes[0].normal, -1, 0, 0);boxbrush->brush.planes[0].dist = -mins[0]; + VectorSet(boxbrush->brush.planes[1].normal, 1, 0, 0);boxbrush->brush.planes[1].dist = maxs[0]; + VectorSet(boxbrush->brush.planes[2].normal, 0, -1, 0);boxbrush->brush.planes[2].dist = -mins[1]; + VectorSet(boxbrush->brush.planes[3].normal, 0, 1, 0);boxbrush->brush.planes[3].dist = maxs[1]; + VectorSet(boxbrush->brush.planes[4].normal, 0, 0, -1);boxbrush->brush.planes[4].dist = -mins[2]; + VectorSet(boxbrush->brush.planes[5].normal, 0, 0, 1);boxbrush->brush.planes[5].dist = maxs[2]; + for (i = 0;i < 6;i++) + { + boxbrush->brush.planes[i].q3surfaceflags = q3surfaceflags; + boxbrush->brush.planes[i].texture = texture; + } + } + boxbrush->brush.supercontents = supercontents; + boxbrush->brush.q3surfaceflags = q3surfaceflags; + boxbrush->brush.texture = texture; + VectorSet(boxbrush->brush.mins, mins[0] - 1, mins[1] - 1, mins[2] - 1); + VectorSet(boxbrush->brush.maxs, maxs[0] + 1, maxs[1] + 1, maxs[2] + 1); + //Collision_ValidateBrush(&boxbrush->brush); +} + +//pseudocode for detecting line/sphere overlap without calculating an impact point +//linesphereorigin = sphereorigin - linestart;linediff = lineend - linestart;linespherefrac = DotProduct(linesphereorigin, linediff) / DotProduct(linediff, linediff);return VectorLength2(linesphereorigin - bound(0, linespherefrac, 1) * linediff) >= sphereradius*sphereradius; + +// LordHavoc: currently unused, but tested +// note: this can be used for tracing a moving sphere vs a stationary sphere, +// by simply adding the moving sphere's radius to the sphereradius parameter, +// all the results are correct (impactpoint, impactnormal, and fraction) +float Collision_ClipTrace_Line_Sphere(double *linestart, double *lineend, double *sphereorigin, double sphereradius, double *impactpoint, double *impactnormal) +{ + double dir[3], scale, v[3], deviationdist2, impactdist, linelength; + // make sure the impactpoint and impactnormal are valid even if there is + // no collision + VectorCopy(lineend, impactpoint); + VectorClear(impactnormal); + // calculate line direction + VectorSubtract(lineend, linestart, dir); + // normalize direction + linelength = VectorLength(dir); + if (linelength) + { + scale = 1.0 / linelength; + VectorScale(dir, scale, dir); + } + // this dotproduct calculates the distance along the line at which the + // sphere origin is (nearest point to the sphere origin on the line) + impactdist = DotProduct(sphereorigin, dir) - DotProduct(linestart, dir); + // calculate point on line at that distance, and subtract the + // sphereorigin from it, so we have a vector to measure for the distance + // of the line from the sphereorigin (deviation, how off-center it is) + VectorMA(linestart, impactdist, dir, v); + VectorSubtract(v, sphereorigin, v); + deviationdist2 = sphereradius * sphereradius - VectorLength2(v); + // if squared offset length is outside the squared sphere radius, miss + if (deviationdist2 < 0) + return 1; // miss (off to the side) + // nudge back to find the correct impact distance + impactdist -= sqrt(deviationdist2); + if (impactdist >= linelength) + return 1; // miss (not close enough) + if (impactdist < 0) + return 1; // miss (linestart is past or inside sphere) + // calculate new impactpoint + VectorMA(linestart, impactdist, dir, impactpoint); + // calculate impactnormal (surface normal at point of impact) + VectorSubtract(impactpoint, sphereorigin, impactnormal); + // normalize impactnormal + VectorNormalize(impactnormal); + // return fraction of movement distance + return impactdist / linelength; +} + +void Collision_TraceLineTriangleFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const float *point0, const float *point1, const float *point2, int supercontents, int q3surfaceflags, const texture_t *texture) +{ +#if 1 + // more optimized + float d1, d2, d, f, impact[3], edgenormal[3], faceplanenormal[3], faceplanedist, faceplanenormallength2, edge01[3], edge21[3], edge02[3]; + + // this function executes: + // 32 ops when line starts behind triangle + // 38 ops when line ends infront of triangle + // 43 ops when line fraction is already closer than this triangle + // 72 ops when line is outside edge 01 + // 92 ops when line is outside edge 21 + // 115 ops when line is outside edge 02 + // 123 ops when line impacts triangle and updates trace results + + // this code is designed for clockwise triangles, conversion to + // counterclockwise would require swapping some things around... + // it is easier to simply swap the point0 and point2 parameters to this + // function when calling it than it is to rewire the internals. + + // calculate the faceplanenormal of the triangle, this represents the front side + // 15 ops + VectorSubtract(point0, point1, edge01); + VectorSubtract(point2, point1, edge21); + CrossProduct(edge01, edge21, faceplanenormal); + // there's no point in processing a degenerate triangle (GIGO - Garbage In, Garbage Out) + // 6 ops + faceplanenormallength2 = DotProduct(faceplanenormal, faceplanenormal); + if (faceplanenormallength2 < 0.0001f) + return; + // calculate the distance + // 5 ops + faceplanedist = DotProduct(point0, faceplanenormal); + + // if start point is on the back side there is no collision + // (we don't care about traces going through the triangle the wrong way) + + // calculate the start distance + // 6 ops + d1 = DotProduct(faceplanenormal, linestart); + if (d1 <= faceplanedist) + return; + + // calculate the end distance + // 6 ops + d2 = DotProduct(faceplanenormal, lineend); + // if both are in front, there is no collision + if (d2 >= faceplanedist) + return; + + // from here on we know d1 is >= 0 and d2 is < 0 + // this means the line starts infront and ends behind, passing through it + + // calculate the recipricol of the distance delta, + // so we can use it multiple times cheaply (instead of division) + // 2 ops + d = 1.0f / (d1 - d2); + // calculate the impact fraction by taking the start distance (> 0) + // and subtracting the face plane distance (this is the distance of the + // triangle along that same normal) + // then multiply by the recipricol distance delta + // 2 ops + f = (d1 - faceplanedist) * d; + // skip out if this impact is further away than previous ones + // 1 ops + if (f > trace->realfraction) + return; + // calculate the perfect impact point for classification of insidedness + // 9 ops + impact[0] = linestart[0] + f * (lineend[0] - linestart[0]); + impact[1] = linestart[1] + f * (lineend[1] - linestart[1]); + impact[2] = linestart[2] + f * (lineend[2] - linestart[2]); + + // calculate the edge normal and reject if impact is outside triangle + // (an edge normal faces away from the triangle, to get the desired normal + // a crossproduct with the faceplanenormal is used, and because of the way + // the insidedness comparison is written it does not need to be normalized) + + // first use the two edges from the triangle plane math + // the other edge only gets calculated if the point survives that long + + // 20 ops + CrossProduct(edge01, faceplanenormal, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point1, edgenormal)) + return; + + // 20 ops + CrossProduct(faceplanenormal, edge21, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point2, edgenormal)) + return; + + // 23 ops + VectorSubtract(point0, point2, edge02); + CrossProduct(faceplanenormal, edge02, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point0, edgenormal)) + return; + + // 8 ops (rare) + + // store the new trace fraction + trace->realfraction = f; + + // calculate a nudged fraction to keep it out of the surface + // (the main fraction remains perfect) + trace->fraction = f - collision_impactnudge.value * d; + + if (collision_prefernudgedfraction.integer) + trace->realfraction = trace->fraction; + + // store the new trace plane (because collisions only happen from + // the front this is always simply the triangle normal, never flipped) + d = 1.0 / sqrt(faceplanenormallength2); + VectorScale(faceplanenormal, d, trace->plane.normal); + trace->plane.dist = faceplanedist * d; + + trace->hitsupercontents = supercontents; + trace->hitq3surfaceflags = q3surfaceflags; + trace->hittexture = texture; +#else + float d1, d2, d, f, fnudged, impact[3], edgenormal[3], faceplanenormal[3], faceplanedist, edge[3]; + + // this code is designed for clockwise triangles, conversion to + // counterclockwise would require swapping some things around... + // it is easier to simply swap the point0 and point2 parameters to this + // function when calling it than it is to rewire the internals. + + // calculate the unnormalized faceplanenormal of the triangle, + // this represents the front side + TriangleNormal(point0, point1, point2, faceplanenormal); + // there's no point in processing a degenerate triangle + // (GIGO - Garbage In, Garbage Out) + if (DotProduct(faceplanenormal, faceplanenormal) < 0.0001f) + return; + // calculate the unnormalized distance + faceplanedist = DotProduct(point0, faceplanenormal); + + // calculate the unnormalized start distance + d1 = DotProduct(faceplanenormal, linestart) - faceplanedist; + // if start point is on the back side there is no collision + // (we don't care about traces going through the triangle the wrong way) + if (d1 <= 0) + return; + + // calculate the unnormalized end distance + d2 = DotProduct(faceplanenormal, lineend) - faceplanedist; + // if both are in front, there is no collision + if (d2 >= 0) + return; + + // from here on we know d1 is >= 0 and d2 is < 0 + // this means the line starts infront and ends behind, passing through it + + // calculate the recipricol of the distance delta, + // so we can use it multiple times cheaply (instead of division) + d = 1.0f / (d1 - d2); + // calculate the impact fraction by taking the start distance (> 0) + // and subtracting the face plane distance (this is the distance of the + // triangle along that same normal) + // then multiply by the recipricol distance delta + f = d1 * d; + // skip out if this impact is further away than previous ones + if (f > trace->realfraction) + return; + // calculate the perfect impact point for classification of insidedness + impact[0] = linestart[0] + f * (lineend[0] - linestart[0]); + impact[1] = linestart[1] + f * (lineend[1] - linestart[1]); + impact[2] = linestart[2] + f * (lineend[2] - linestart[2]); + + // calculate the edge normal and reject if impact is outside triangle + // (an edge normal faces away from the triangle, to get the desired normal + // a crossproduct with the faceplanenormal is used, and because of the way + // the insidedness comparison is written it does not need to be normalized) + + VectorSubtract(point2, point0, edge); + CrossProduct(edge, faceplanenormal, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point0, edgenormal)) + return; + + VectorSubtract(point0, point1, edge); + CrossProduct(edge, faceplanenormal, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point1, edgenormal)) + return; + + VectorSubtract(point1, point2, edge); + CrossProduct(edge, faceplanenormal, edgenormal); + if (DotProduct(impact, edgenormal) > DotProduct(point2, edgenormal)) + return; + + // store the new trace fraction + trace->realfraction = bound(0, f, 1); + + // store the new trace plane (because collisions only happen from + // the front this is always simply the triangle normal, never flipped) + VectorNormalize(faceplanenormal); + VectorCopy(faceplanenormal, trace->plane.normal); + trace->plane.dist = DotProduct(point0, faceplanenormal); + + // calculate the normalized start and end distances + d1 = DotProduct(trace->plane.normal, linestart) - trace->plane.dist; + d2 = DotProduct(trace->plane.normal, lineend) - trace->plane.dist; + + // calculate a nudged fraction to keep it out of the surface + // (the main fraction remains perfect) + fnudged = (d1 - collision_impactnudge.value) / (d1 - d2); + trace->fraction = bound(0, fnudged, 1); + + // store the new trace endpos + // not needed, it's calculated later when the trace is finished + //trace->endpos[0] = linestart[0] + fnudged * (lineend[0] - linestart[0]); + //trace->endpos[1] = linestart[1] + fnudged * (lineend[1] - linestart[1]); + //trace->endpos[2] = linestart[2] + fnudged * (lineend[2] - linestart[2]); + trace->hitsupercontents = supercontents; + trace->hitq3surfaceflags = q3surfaceflags; + trace->hittexture = texture; +#endif +} + +void Collision_BoundingBoxOfBrushTraceSegment(const colbrushf_t *start, const colbrushf_t *end, vec3_t mins, vec3_t maxs, float startfrac, float endfrac) +{ + int i; + colpointf_t *ps, *pe; + float tempstart[3], tempend[3]; + VectorLerp(start->points[0].v, startfrac, end->points[0].v, mins); + VectorCopy(mins, maxs); + for (i = 0, ps = start->points, pe = end->points;i < start->numpoints;i++, ps++, pe++) + { + VectorLerp(ps->v, startfrac, pe->v, tempstart); + VectorLerp(ps->v, endfrac, pe->v, tempend); + mins[0] = min(mins[0], min(tempstart[0], tempend[0])); + mins[1] = min(mins[1], min(tempstart[1], tempend[1])); + mins[2] = min(mins[2], min(tempstart[2], tempend[2])); + maxs[0] = min(maxs[0], min(tempstart[0], tempend[0])); + maxs[1] = min(maxs[1], min(tempstart[1], tempend[1])); + maxs[2] = min(maxs[2], min(tempstart[2], tempend[2])); + } + mins[0] -= 1; + mins[1] -= 1; + mins[2] -= 1; + maxs[0] += 1; + maxs[1] += 1; + maxs[2] += 1; +} + +//=========================================== + +static void Collision_TranslateBrush(const vec3_t shift, colbrushf_t *brush) +{ + int i; + // now we can transform the data + for(i = 0; i < brush->numplanes; ++i) + { + brush->planes[i].dist += DotProduct(shift, brush->planes[i].normal); + } + for(i = 0; i < brush->numpoints; ++i) + { + VectorAdd(brush->points[i].v, shift, brush->points[i].v); + } + VectorAdd(brush->mins, shift, brush->mins); + VectorAdd(brush->maxs, shift, brush->maxs); +} + +static void Collision_TransformBrush(const matrix4x4_t *matrix, colbrushf_t *brush) +{ + int i; + vec3_t v; + // we're breaking any AABB properties here... + brush->isaabb = false; + brush->hasaabbplanes = false; + // now we can transform the data + for(i = 0; i < brush->numplanes; ++i) + { + Matrix4x4_TransformPositivePlane(matrix, brush->planes[i].normal[0], brush->planes[i].normal[1], brush->planes[i].normal[2], brush->planes[i].dist, brush->planes[i].normal); + } + for(i = 0; i < brush->numedgedirs; ++i) + { + Matrix4x4_Transform(matrix, brush->edgedirs[i].v, v); + VectorCopy(v, brush->edgedirs[i].v); + } + for(i = 0; i < brush->numpoints; ++i) + { + Matrix4x4_Transform(matrix, brush->points[i].v, v); + VectorCopy(v, brush->points[i].v); + } + VectorCopy(brush->points[0].v, brush->mins); + VectorCopy(brush->points[0].v, brush->maxs); + for(i = 1; i < brush->numpoints; ++i) + { + if(brush->points[i].v[0] < brush->mins[0]) brush->mins[0] = brush->points[i].v[0]; + if(brush->points[i].v[1] < brush->mins[1]) brush->mins[1] = brush->points[i].v[1]; + if(brush->points[i].v[2] < brush->mins[2]) brush->mins[2] = brush->points[i].v[2]; + if(brush->points[i].v[0] > brush->maxs[0]) brush->maxs[0] = brush->points[i].v[0]; + if(brush->points[i].v[1] > brush->maxs[1]) brush->maxs[1] = brush->points[i].v[1]; + if(brush->points[i].v[2] > brush->maxs[2]) brush->maxs[2] = brush->points[i].v[2]; + } +} + +typedef struct collision_cachedtrace_parameters_s +{ + dp_model_t *model; + vec3_t end; + vec3_t start; + int hitsupercontentsmask; + matrix4x4_t matrix; +} +collision_cachedtrace_parameters_t; + +typedef struct collision_cachedtrace_s +{ + qboolean valid; + collision_cachedtrace_parameters_t p; + trace_t result; +} +collision_cachedtrace_t; + +static mempool_t *collision_cachedtrace_mempool; +static collision_cachedtrace_t *collision_cachedtrace_array; +static int collision_cachedtrace_firstfree; +static int collision_cachedtrace_lastused; +static int collision_cachedtrace_max; +static int collision_cachedtrace_sequence; +static int collision_cachedtrace_hashsize; +static int *collision_cachedtrace_hash; +static unsigned int *collision_cachedtrace_arrayfullhashindex; +static unsigned int *collision_cachedtrace_arrayhashindex; +static unsigned int *collision_cachedtrace_arraynext; +static unsigned char *collision_cachedtrace_arrayused; +static qboolean collision_cachedtrace_rebuildhash; + +void Collision_Cache_Reset(qboolean resetlimits) +{ + if (collision_cachedtrace_hash) + Mem_Free(collision_cachedtrace_hash); + if (collision_cachedtrace_array) + Mem_Free(collision_cachedtrace_array); + if (collision_cachedtrace_arrayfullhashindex) + Mem_Free(collision_cachedtrace_arrayfullhashindex); + if (collision_cachedtrace_arrayhashindex) + Mem_Free(collision_cachedtrace_arrayhashindex); + if (collision_cachedtrace_arraynext) + Mem_Free(collision_cachedtrace_arraynext); + if (collision_cachedtrace_arrayused) + Mem_Free(collision_cachedtrace_arrayused); + if (resetlimits || !collision_cachedtrace_max) + collision_cachedtrace_max = collision_cache.integer ? 128 : 1; + collision_cachedtrace_firstfree = 1; + collision_cachedtrace_lastused = 0; + collision_cachedtrace_hashsize = collision_cachedtrace_max; + collision_cachedtrace_array = (collision_cachedtrace_t *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(collision_cachedtrace_t)); + collision_cachedtrace_hash = (int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_hashsize * sizeof(int)); + collision_cachedtrace_arrayfullhashindex = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); + collision_cachedtrace_arrayhashindex = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); + collision_cachedtrace_arraynext = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); + collision_cachedtrace_arrayused = (unsigned char *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned char)); + collision_cachedtrace_sequence = 1; + collision_cachedtrace_rebuildhash = false; +} + +void Collision_Cache_Init(mempool_t *mempool) +{ + collision_cachedtrace_mempool = mempool; + Collision_Cache_Reset(true); +} + +static void Collision_Cache_RebuildHash(void) +{ + int index; + int range = collision_cachedtrace_lastused + 1; + int sequence = collision_cachedtrace_sequence; + int firstfree = collision_cachedtrace_max; + int lastused = 0; + int *hash = collision_cachedtrace_hash; + unsigned int hashindex; + unsigned int *arrayhashindex = collision_cachedtrace_arrayhashindex; + unsigned int *arraynext = collision_cachedtrace_arraynext; + collision_cachedtrace_rebuildhash = false; + memset(collision_cachedtrace_hash, 0, collision_cachedtrace_hashsize * sizeof(int)); + for (index = 1;index < range;index++) + { + if (collision_cachedtrace_arrayused[index] == sequence) + { + hashindex = arrayhashindex[index]; + arraynext[index] = hash[hashindex]; + hash[hashindex] = index; + lastused = index; + } + else + { + if (firstfree > index) + firstfree = index; + collision_cachedtrace_arrayused[index] = 0; + } + } + collision_cachedtrace_firstfree = firstfree; + collision_cachedtrace_lastused = lastused; +} + +void Collision_Cache_NewFrame(void) +{ + if (collision_cache.integer) + { + if (collision_cachedtrace_max < 128) + Collision_Cache_Reset(true); + } + else + { + if (collision_cachedtrace_max > 1) + Collision_Cache_Reset(true); + } + // rebuild hash if sequence would overflow byte, otherwise increment + if (collision_cachedtrace_sequence == 255) + { + Collision_Cache_RebuildHash(); + collision_cachedtrace_sequence = 1; + } + else + { + collision_cachedtrace_rebuildhash = true; + collision_cachedtrace_sequence++; + } +} + +static unsigned int Collision_Cache_HashIndexForArray(unsigned int *array, unsigned int size) +{ + unsigned int i; + unsigned int hashindex = 0; + // this is a super-cheesy checksum, designed only for speed + for (i = 0;i < size;i++) + hashindex += array[i] * (1 + i); + return hashindex; +} + +static collision_cachedtrace_t *Collision_Cache_Lookup(dp_model_t *model, const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + int hashindex = 0; + unsigned int fullhashindex; + int index = 0; + int range; + int sequence = collision_cachedtrace_sequence; + int *hash = collision_cachedtrace_hash; + unsigned int *arrayfullhashindex = collision_cachedtrace_arrayfullhashindex; + unsigned int *arraynext = collision_cachedtrace_arraynext; + collision_cachedtrace_t *cached = collision_cachedtrace_array + index; + collision_cachedtrace_parameters_t params; + // all non-cached traces use the same index + if (!collision_cache.integer) + r_refdef.stats[r_stat_photoncache_traced]++; + else + { + // cached trace lookup + memset(¶ms, 0, sizeof(params)); + params.model = model; + VectorCopy(start, params.start); + VectorCopy(end, params.end); + params.hitsupercontentsmask = hitsupercontentsmask; + params.matrix = *matrix; + fullhashindex = Collision_Cache_HashIndexForArray((unsigned int *)¶ms, sizeof(params) / sizeof(unsigned int)); + hashindex = (int)(fullhashindex % (unsigned int)collision_cachedtrace_hashsize); + for (index = hash[hashindex];index;index = arraynext[index]) + { + if (arrayfullhashindex[index] != fullhashindex) + continue; + cached = collision_cachedtrace_array + index; + //if (memcmp(&cached->p, ¶ms, sizeof(params))) + if (cached->p.model != params.model + || cached->p.end[0] != params.end[0] + || cached->p.end[1] != params.end[1] + || cached->p.end[2] != params.end[2] + || cached->p.start[0] != params.start[0] + || cached->p.start[1] != params.start[1] + || cached->p.start[2] != params.start[2] + || cached->p.hitsupercontentsmask != params.hitsupercontentsmask + || cached->p.matrix.m[0][0] != params.matrix.m[0][0] + || cached->p.matrix.m[0][1] != params.matrix.m[0][1] + || cached->p.matrix.m[0][2] != params.matrix.m[0][2] + || cached->p.matrix.m[0][3] != params.matrix.m[0][3] + || cached->p.matrix.m[1][0] != params.matrix.m[1][0] + || cached->p.matrix.m[1][1] != params.matrix.m[1][1] + || cached->p.matrix.m[1][2] != params.matrix.m[1][2] + || cached->p.matrix.m[1][3] != params.matrix.m[1][3] + || cached->p.matrix.m[2][0] != params.matrix.m[2][0] + || cached->p.matrix.m[2][1] != params.matrix.m[2][1] + || cached->p.matrix.m[2][2] != params.matrix.m[2][2] + || cached->p.matrix.m[2][3] != params.matrix.m[2][3] + || cached->p.matrix.m[3][0] != params.matrix.m[3][0] + || cached->p.matrix.m[3][1] != params.matrix.m[3][1] + || cached->p.matrix.m[3][2] != params.matrix.m[3][2] + || cached->p.matrix.m[3][3] != params.matrix.m[3][3] + ) + continue; + // found a matching trace in the cache + r_refdef.stats[r_stat_photoncache_cached]++; + cached->valid = true; + collision_cachedtrace_arrayused[index] = collision_cachedtrace_sequence; + return cached; + } + r_refdef.stats[r_stat_photoncache_traced]++; + // find an unused cache entry + for (index = collision_cachedtrace_firstfree, range = collision_cachedtrace_max;index < range;index++) + if (collision_cachedtrace_arrayused[index] == 0) + break; + if (index == range) + { + // all claimed, but probably some are stale... + for (index = 1, range = collision_cachedtrace_max;index < range;index++) + if (collision_cachedtrace_arrayused[index] != sequence) + break; + if (index < range) + { + // found a stale one, rebuild the hash + Collision_Cache_RebuildHash(); + } + else + { + // we need to grow the cache + collision_cachedtrace_max *= 2; + Collision_Cache_Reset(false); + index = 1; + } + } + // link the new cache entry into the hash bucket + collision_cachedtrace_firstfree = index + 1; + if (collision_cachedtrace_lastused < index) + collision_cachedtrace_lastused = index; + cached = collision_cachedtrace_array + index; + collision_cachedtrace_arraynext[index] = collision_cachedtrace_hash[hashindex]; + collision_cachedtrace_hash[hashindex] = index; + collision_cachedtrace_arrayhashindex[index] = hashindex; + cached->valid = false; + cached->p = params; + collision_cachedtrace_arrayfullhashindex[index] = fullhashindex; + collision_cachedtrace_arrayused[index] = collision_cachedtrace_sequence; + } + return cached; +} + +void Collision_Cache_ClipLineToGenericEntitySurfaces(trace_t *trace, dp_model_t *model, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + collision_cachedtrace_t *cached = Collision_Cache_Lookup(model, matrix, inversematrix, start, end, hitsupercontentsmask); + if (cached->valid) + { + *trace = cached->result; + return; + } + + Collision_ClipLineToGenericEntity(trace, model, NULL, NULL, vec3_origin, vec3_origin, 0, matrix, inversematrix, start, end, hitsupercontentsmask, true); + + cached->result = *trace; +} + +void Collision_Cache_ClipLineToWorldSurfaces(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontents) +{ + collision_cachedtrace_t *cached = Collision_Cache_Lookup(model, &identitymatrix, &identitymatrix, start, end, hitsupercontents); + if (cached->valid) + { + *trace = cached->result; + return; + } + + Collision_ClipLineToWorld(trace, model, start, end, hitsupercontents, true); + + cached->result = *trace; +} + +void Collision_ClipToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask) +{ + float starttransformed[3], endtransformed[3]; + + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + + Matrix4x4_Transform(inversematrix, start, starttransformed); + Matrix4x4_Transform(inversematrix, end, endtransformed); +#if COLLISIONPARANOID >= 3 + Con_Printf("trans(%f %f %f -> %f %f %f, %f %f %f -> %f %f %f)", start[0], start[1], start[2], starttransformed[0], starttransformed[1], starttransformed[2], end[0], end[1], end[2], endtransformed[0], endtransformed[1], endtransformed[2]); +#endif + + if (model && model->TraceBox) + { + if(model->TraceBrush && (inversematrix->m[0][1] || inversematrix->m[0][2] || inversematrix->m[1][0] || inversematrix->m[1][2] || inversematrix->m[2][0] || inversematrix->m[2][1])) + { + // we get here if TraceBrush exists, AND we have a rotation component (SOLID_BSP case) + // using starttransformed, endtransformed is WRONG in this case! + // should rather build a brush and trace using it + colboxbrushf_t thisbrush_start, thisbrush_end; + Collision_BrushForBox(&thisbrush_start, mins, maxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, mins, maxs, 0, 0, NULL); + Collision_TranslateBrush(start, &thisbrush_start.brush); + Collision_TranslateBrush(end, &thisbrush_end.brush); + Collision_TransformBrush(inversematrix, &thisbrush_start.brush); + Collision_TransformBrush(inversematrix, &thisbrush_end.brush); + //Collision_TranslateBrush(starttransformed, &thisbrush_start.brush); + //Collision_TranslateBrush(endtransformed, &thisbrush_end.brush); + model->TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask); + } + else // this is only approximate if rotated, quite useless + model->TraceBox(model, frameblend, skeleton, trace, starttransformed, mins, maxs, endtransformed, hitsupercontentsmask); + } + else // and this requires that the transformation matrix doesn't have angles components, like SV_TraceBox ensures; FIXME may get called if a model is SOLID_BSP but has no TraceBox function + Collision_ClipTrace_Box(trace, bodymins, bodymaxs, starttransformed, mins, maxs, endtransformed, hitsupercontentsmask, bodysupercontents, 0, NULL); + trace->fraction = bound(0, trace->fraction, 1); + trace->realfraction = bound(0, trace->realfraction, 1); + + VectorLerp(start, trace->fraction, end, trace->endpos); + // transform plane + // NOTE: this relies on plane.dist being directly after plane.normal + Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal); +} + +void Collision_ClipToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontents) +{ + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + // ->TraceBox: TraceBrush not needed here, as worldmodel is never rotated + if (model && model->TraceBox) + model->TraceBox(model, NULL, NULL, trace, start, mins, maxs, end, hitsupercontents); + trace->fraction = bound(0, trace->fraction, 1); + trace->realfraction = bound(0, trace->realfraction, 1); + VectorLerp(start, trace->fraction, end, trace->endpos); +} + +void Collision_ClipLineToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask, qboolean hitsurfaces) +{ + float starttransformed[3], endtransformed[3]; + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + + Matrix4x4_Transform(inversematrix, start, starttransformed); + Matrix4x4_Transform(inversematrix, end, endtransformed); +#if COLLISIONPARANOID >= 3 + Con_Printf("trans(%f %f %f -> %f %f %f, %f %f %f -> %f %f %f)", start[0], start[1], start[2], starttransformed[0], starttransformed[1], starttransformed[2], end[0], end[1], end[2], endtransformed[0], endtransformed[1], endtransformed[2]); +#endif + + if (model && model->TraceLineAgainstSurfaces && hitsurfaces) + model->TraceLineAgainstSurfaces(model, frameblend, skeleton, trace, starttransformed, endtransformed, hitsupercontentsmask); + else if (model && model->TraceLine) + model->TraceLine(model, frameblend, skeleton, trace, starttransformed, endtransformed, hitsupercontentsmask); + else + Collision_ClipTrace_Box(trace, bodymins, bodymaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, hitsupercontentsmask, bodysupercontents, 0, NULL); + trace->fraction = bound(0, trace->fraction, 1); + trace->realfraction = bound(0, trace->realfraction, 1); + + VectorLerp(start, trace->fraction, end, trace->endpos); + // transform plane + // NOTE: this relies on plane.dist being directly after plane.normal + Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal); +} + +void Collision_ClipLineToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontents, qboolean hitsurfaces) +{ + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + if (model && model->TraceLineAgainstSurfaces && hitsurfaces) + model->TraceLineAgainstSurfaces(model, NULL, NULL, trace, start, end, hitsupercontents); + else if (model && model->TraceLine) + model->TraceLine(model, NULL, NULL, trace, start, end, hitsupercontents); + trace->fraction = bound(0, trace->fraction, 1); + trace->realfraction = bound(0, trace->realfraction, 1); + VectorLerp(start, trace->fraction, end, trace->endpos); +} + +void Collision_ClipPointToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, int hitsupercontentsmask) +{ + float starttransformed[3]; + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + + Matrix4x4_Transform(inversematrix, start, starttransformed); +#if COLLISIONPARANOID >= 3 + Con_Printf("trans(%f %f %f -> %f %f %f)", start[0], start[1], start[2], starttransformed[0], starttransformed[1], starttransformed[2]); +#endif + + if (model && model->TracePoint) + model->TracePoint(model, NULL, NULL, trace, starttransformed, hitsupercontentsmask); + else + Collision_ClipTrace_Point(trace, bodymins, bodymaxs, starttransformed, hitsupercontentsmask, bodysupercontents, 0, NULL); + + VectorCopy(start, trace->endpos); + // transform plane + // NOTE: this relies on plane.dist being directly after plane.normal + Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal); +} + +void Collision_ClipPointToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, int hitsupercontents) +{ + memset(trace, 0, sizeof(*trace)); + trace->fraction = trace->realfraction = 1; + if (model && model->TracePoint) + model->TracePoint(model, NULL, NULL, trace, start, hitsupercontents); + VectorCopy(start, trace->endpos); +} + +void Collision_CombineTraces(trace_t *cliptrace, const trace_t *trace, void *touch, qboolean isbmodel) +{ + // take the 'best' answers from the new trace and combine with existing data + if (trace->allsolid) + cliptrace->allsolid = true; + if (trace->startsolid) + { + if (isbmodel) + cliptrace->bmodelstartsolid = true; + cliptrace->startsolid = true; + if (cliptrace->realfraction == 1) + cliptrace->ent = touch; + if (cliptrace->startdepth > trace->startdepth) + { + cliptrace->startdepth = trace->startdepth; + VectorCopy(trace->startdepthnormal, cliptrace->startdepthnormal); + } + } + // don't set this except on the world, because it can easily confuse + // monsters underwater if there's a bmodel involved in the trace + // (inopen && inwater is how they check water visibility) + //if (trace->inopen) + // cliptrace->inopen = true; + if (trace->inwater) + cliptrace->inwater = true; + if ((trace->realfraction < cliptrace->realfraction) && (VectorLength2(trace->plane.normal) > 0)) + { + cliptrace->fraction = trace->fraction; + cliptrace->realfraction = trace->realfraction; + VectorCopy(trace->endpos, cliptrace->endpos); + cliptrace->plane = trace->plane; + cliptrace->ent = touch; + cliptrace->hitsupercontents = trace->hitsupercontents; + cliptrace->hitq3surfaceflags = trace->hitq3surfaceflags; + cliptrace->hittexture = trace->hittexture; + } + cliptrace->startsupercontents |= trace->startsupercontents; +} + +void Collision_ShortenTrace(trace_t *trace, float shorten_factor, const vec3_t end) +{ + // now undo our moving end 1 qu farther... + trace->fraction = bound(trace->fraction, trace->fraction / shorten_factor - 1e-6, 1); // we subtract 1e-6 to guard for roundoff errors + trace->realfraction = bound(trace->realfraction, trace->realfraction / shorten_factor - 1e-6, 1); // we subtract 1e-6 to guard for roundoff errors + if(trace->fraction >= 1) // trace would NOT hit if not expanded! + { + trace->fraction = 1; + trace->realfraction = 1; + VectorCopy(end, trace->endpos); + memset(&trace->plane, 0, sizeof(trace->plane)); + trace->ent = NULL; + trace->hitsupercontentsmask = 0; + trace->hitsupercontents = 0; + trace->hitq3surfaceflags = 0; + trace->hittexture = NULL; + } +} diff --git a/app/jni/collision.h b/app/jni/collision.h new file mode 100644 index 0000000..0da1247 --- /dev/null +++ b/app/jni/collision.h @@ -0,0 +1,191 @@ + +#ifndef COLLISION_H +#define COLLISION_H + +typedef struct plane_s +{ + vec3_t normal; + float dist; +} +plane_t; + +struct texture_s; +typedef struct trace_s +{ + // if true, the entire trace was in solid (see hitsupercontentsmask) + int allsolid; + // if true, the initial point was in solid (see hitsupercontentsmask) + int startsolid; + // this is set to true in world.c if startsolid was set in a trace against world + int worldstartsolid; + // this is set to true in world.c if startsolid was set in a trace against a SOLID_BSP entity, in other words this is true if the entity is stuck in a door or wall, but not if stuck in another normal entity + int bmodelstartsolid; + // if true, the trace passed through empty somewhere + // (set only by Q1BSP tracing) + int inopen; + // if true, the trace passed through water/slime/lava somewhere + // (set only by Q1BSP tracing) + int inwater; + // fraction of the total distance that was traveled before impact + // (1.0 = did not hit anything) + double fraction; + // like fraction but is not nudged away from the surface (better for + // comparisons between two trace structs, as only one nudge for the final + // result is ever needed) + double realfraction; + // final position of the trace (simply a point between start and end) + double endpos[3]; + // surface normal at impact (not really correct for edge collisions) + plane_t plane; + // entity the surface is on + // (not set by trace functions, only by physics) + void *ent; + // which SUPERCONTENTS bits to collide with, I.E. to consider solid + // (this also affects startsolid/allsolid) + int hitsupercontentsmask; + // the supercontents mask at the start point + int startsupercontents; + // the supercontents of the impacted surface + int hitsupercontents; + // the q3 surfaceflags of the impacted surface + int hitq3surfaceflags; + // the texture of the impacted surface + const struct texture_s *hittexture; + // initially false, set when the start leaf is found + // (set only by Q1BSP tracing and entity box tracing) + int startfound; + // if startsolid, contains the minimum penetration depth found in the + // trace, and the normal needed to push it out of that solid + double startdepth; + double startdepthnormal[3]; +} +trace_t; + +void Collision_Init(void); +void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture); +void Collision_ClipTrace_Point(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture); + +void Collision_Cache_Reset(qboolean resetlimits); +void Collision_Cache_Init(mempool_t *mempool); +void Collision_Cache_NewFrame(void); + +typedef struct colpointf_s +{ + vec3_t v; +} +colpointf_t; + +typedef struct colplanef_s +{ + const struct texture_s *texture; + int q3surfaceflags; + vec3_t normal; + vec_t dist; +} +colplanef_t; + +typedef struct colbrushf_s +{ + // culling box + vec3_t mins; + vec3_t maxs; + // used to avoid tracing against the same brush more than once per sweep + int markframe; + // the content flags of this brush + int supercontents; + // bounding planes (face planes) of this brush + int numplanes; + colplanef_t *planes; + // edge directions (normals) of this brush + int numedgedirs; + colpointf_t *edgedirs; + // points (corners) of this brush + int numpoints; + colpointf_t *points; + // renderable triangles representing this brush, using the points + int numtriangles; + int *elements; + // texture data for cases where an edgedir is used + const struct texture_s *texture; + int q3surfaceflags; + // optimized collisions for common cases + int isaabb; // indicates this is an axis aligned box + int hasaabbplanes; // indicates this has precomputed planes for AABB collisions +} +colbrushf_t; + +typedef struct colboxbrushf_s +{ + colpointf_t points[8]; + colpointf_t edgedirs[6]; + colplanef_t planes[6]; + colbrushf_t brush; +} +colboxbrushf_t; + +void Collision_CalcPlanesForTriangleBrushFloat(colbrushf_t *brush); +colbrushf_t *Collision_AllocBrushFromPermanentPolygonFloat(mempool_t *mempool, int numpoints, float *points, int supercontents, int q3surfaceflags, const texture_t *texture); +colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalplanes, const colplanef_t *originalplanes, int supercontents, int q3surfaceflags, const texture_t *texture, int hasaabbplanes); +void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const colbrushf_t *thatbrush_start, const colbrushf_t *thatbrush_end); +void Collision_TraceBrushTriangleMeshFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs); +void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const colbrushf_t *thatbrush_start, const colbrushf_t *thatbrush_end); +void Collision_TraceLineTriangleMeshFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs); +void Collision_TracePointBrushFloat(trace_t *trace, const vec3_t point, const colbrushf_t *thatbrush); +qboolean Collision_PointInsideBrushFloat(const vec3_t point, const colbrushf_t *brush); + +void Collision_BrushForBox(colboxbrushf_t *boxbrush, const vec3_t mins, const vec3_t maxs, int supercontents, int q3surfaceflags, const texture_t *texture); + +void Collision_BoundingBoxOfBrushTraceSegment(const colbrushf_t *start, const colbrushf_t *end, vec3_t mins, vec3_t maxs, float startfrac, float endfrac); + +float Collision_ClipTrace_Line_Sphere(double *linestart, double *lineend, double *sphereorigin, double sphereradius, double *impactpoint, double *impactnormal); +void Collision_TraceLineTriangleFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const float *point0, const float *point1, const float *point2, int supercontents, int q3surfaceflags, const texture_t *texture); +void Collision_TraceBrushTriangleFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const float *v0, const float *v1, const float *v2, int supercontents, int q3surfaceflags, const texture_t *texture); + +// traces a box move against a single entity +// mins and maxs are relative +// +// if the entire move stays in a single solid brush, trace.allsolid will be set +// +// if the starting point is in a solid, it will be allowed to move out to an +// open area, and trace.startsolid will be set +// +// type is one of the MOVE_ values such as MOVE_NOMONSTERS which skips box +// entities, only colliding with SOLID_BSP entities (doors, lifts) +// +// passedict is excluded from clipping checks +struct frameblend_s; +struct skeleton_s; +void Collision_ClipToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask); +void Collision_ClipLineToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask, qboolean hitsurfaces); +void Collision_ClipPointToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, int hitsupercontentsmask); +// like above but does not do a transform and does nothing if model is NULL +void Collision_ClipToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontents); +void Collision_ClipLineToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontents, qboolean hitsurfaces); +void Collision_ClipPointToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, int hitsupercontents); +// caching surface trace for renderer (NOT THREAD SAFE) +void Collision_Cache_ClipLineToGenericEntitySurfaces(trace_t *trace, dp_model_t *model, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask); +void Collision_Cache_ClipLineToWorldSurfaces(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontents); +// combines data from two traces: +// merges contents flags, startsolid, allsolid, inwater +// updates fraction, endpos, plane and surface info if new fraction is shorter +void Collision_CombineTraces(trace_t *cliptrace, const trace_t *trace, void *touch, qboolean isbmodel); + +// shorten a trace by the given factor +void Collision_ShortenTrace(trace_t *trace, float shorten_factor, const vec3_t end); + +// this enables rather large debugging spew! +// settings: +// 0 = no spew +// 1 = spew trace calls if something odd is happening +// 2 = spew trace calls always +// 3 = spew detailed trace flow (bsp tree recursion info) +#define COLLISIONPARANOID 0 + +// make every trace qu longer, and shorten the result, to work around a stupid bug somewhere +#define COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +extern cvar_t collision_endposnudge; +#endif + + +#endif diff --git a/app/jni/common.c b/app/jni/common.c new file mode 100644 index 0000000..a669680 --- /dev/null +++ b/app/jni/common.c @@ -0,0 +1,2276 @@ +/* +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. + +*/ +// common.c -- misc functions used in client and server + +#include +#include +#ifndef WIN32 +#include +#endif + +#include "quakedef.h" +#include "utf8lib.h" + +#include "snprintf.h" + +cvar_t registered = {0, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"}; +cvar_t cmdline = {0, "cmdline","0", "contains commandline the engine was launched with"}; + +char com_token[MAX_INPUTLINE]; +int com_argc; +const char **com_argv; +int com_selffd = -1; + +gamemode_t gamemode; +const char *gamename; +const char *gamedirname1; +const char *gamedirname2; +const char *gamescreenshotname; +const char *gameuserdirname; +char com_modname[MAX_OSPATH] = ""; + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + + +float BuffBigFloat (const unsigned char *buffer) +{ + union + { + float f; + unsigned int i; + } + u; + u.i = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; + return u.f; +} + +int BuffBigLong (const unsigned char *buffer) +{ + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; +} + +short BuffBigShort (const unsigned char *buffer) +{ + return (buffer[0] << 8) | buffer[1]; +} + +float BuffLittleFloat (const unsigned char *buffer) +{ + union + { + float f; + unsigned int i; + } + u; + u.i = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; + return u.f; +} + +int BuffLittleLong (const unsigned char *buffer) +{ + return (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; +} + +short BuffLittleShort (const unsigned char *buffer) +{ + return (buffer[1] << 8) | buffer[0]; +} + +void StoreBigLong (unsigned char *buffer, unsigned int i) +{ + buffer[0] = (i >> 24) & 0xFF; + buffer[1] = (i >> 16) & 0xFF; + buffer[2] = (i >> 8) & 0xFF; + buffer[3] = i & 0xFF; +} + +void StoreBigShort (unsigned char *buffer, unsigned short i) +{ + buffer[0] = (i >> 8) & 0xFF; + buffer[1] = i & 0xFF; +} + +void StoreLittleLong (unsigned char *buffer, unsigned int i) +{ + buffer[0] = i & 0xFF; + buffer[1] = (i >> 8) & 0xFF; + buffer[2] = (i >> 16) & 0xFF; + buffer[3] = (i >> 24) & 0xFF; +} + +void StoreLittleShort (unsigned char *buffer, unsigned short i) +{ + buffer[0] = i & 0xFF; + buffer[1] = (i >> 8) & 0xFF; +} + +/* +============================================================================ + + CRC FUNCTIONS + +============================================================================ +*/ + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +unsigned short CRC_Block(const unsigned char *data, size_t size) +{ + unsigned short crc = CRC_INIT_VALUE; + while (size--) + crc = (crc << 8) ^ crctable[(crc >> 8) ^ (*data++)]; + return crc ^ CRC_XOR_VALUE; +} + +unsigned short CRC_Block_CaseInsensitive(const unsigned char *data, size_t size) +{ + unsigned short crc = CRC_INIT_VALUE; + while (size--) + crc = (crc << 8) ^ crctable[(crc >> 8) ^ (tolower(*data++))]; + return crc ^ CRC_XOR_VALUE; +} + +// QuakeWorld +static unsigned char chktbl[1024 + 4] = +{ + 0x78,0xd2,0x94,0xe3,0x41,0xec,0xd6,0xd5,0xcb,0xfc,0xdb,0x8a,0x4b,0xcc,0x85,0x01, + 0x23,0xd2,0xe5,0xf2,0x29,0xa7,0x45,0x94,0x4a,0x62,0xe3,0xa5,0x6f,0x3f,0xe1,0x7a, + 0x64,0xed,0x5c,0x99,0x29,0x87,0xa8,0x78,0x59,0x0d,0xaa,0x0f,0x25,0x0a,0x5c,0x58, + 0xfb,0x00,0xa7,0xa8,0x8a,0x1d,0x86,0x80,0xc5,0x1f,0xd2,0x28,0x69,0x71,0x58,0xc3, + 0x51,0x90,0xe1,0xf8,0x6a,0xf3,0x8f,0xb0,0x68,0xdf,0x95,0x40,0x5c,0xe4,0x24,0x6b, + 0x29,0x19,0x71,0x3f,0x42,0x63,0x6c,0x48,0xe7,0xad,0xa8,0x4b,0x91,0x8f,0x42,0x36, + 0x34,0xe7,0x32,0x55,0x59,0x2d,0x36,0x38,0x38,0x59,0x9b,0x08,0x16,0x4d,0x8d,0xf8, + 0x0a,0xa4,0x52,0x01,0xbb,0x52,0xa9,0xfd,0x40,0x18,0x97,0x37,0xff,0xc9,0x82,0x27, + 0xb2,0x64,0x60,0xce,0x00,0xd9,0x04,0xf0,0x9e,0x99,0xbd,0xce,0x8f,0x90,0x4a,0xdd, + 0xe1,0xec,0x19,0x14,0xb1,0xfb,0xca,0x1e,0x98,0x0f,0xd4,0xcb,0x80,0xd6,0x05,0x63, + 0xfd,0xa0,0x74,0xa6,0x86,0xf6,0x19,0x98,0x76,0x27,0x68,0xf7,0xe9,0x09,0x9a,0xf2, + 0x2e,0x42,0xe1,0xbe,0x64,0x48,0x2a,0x74,0x30,0xbb,0x07,0xcc,0x1f,0xd4,0x91,0x9d, + 0xac,0x55,0x53,0x25,0xb9,0x64,0xf7,0x58,0x4c,0x34,0x16,0xbc,0xf6,0x12,0x2b,0x65, + 0x68,0x25,0x2e,0x29,0x1f,0xbb,0xb9,0xee,0x6d,0x0c,0x8e,0xbb,0xd2,0x5f,0x1d,0x8f, + 0xc1,0x39,0xf9,0x8d,0xc0,0x39,0x75,0xcf,0x25,0x17,0xbe,0x96,0xaf,0x98,0x9f,0x5f, + 0x65,0x15,0xc4,0x62,0xf8,0x55,0xfc,0xab,0x54,0xcf,0xdc,0x14,0x06,0xc8,0xfc,0x42, + 0xd3,0xf0,0xad,0x10,0x08,0xcd,0xd4,0x11,0xbb,0xca,0x67,0xc6,0x48,0x5f,0x9d,0x59, + 0xe3,0xe8,0x53,0x67,0x27,0x2d,0x34,0x9e,0x9e,0x24,0x29,0xdb,0x69,0x99,0x86,0xf9, + 0x20,0xb5,0xbb,0x5b,0xb0,0xf9,0xc3,0x67,0xad,0x1c,0x9c,0xf7,0xcc,0xef,0xce,0x69, + 0xe0,0x26,0x8f,0x79,0xbd,0xca,0x10,0x17,0xda,0xa9,0x88,0x57,0x9b,0x15,0x24,0xba, + 0x84,0xd0,0xeb,0x4d,0x14,0xf5,0xfc,0xe6,0x51,0x6c,0x6f,0x64,0x6b,0x73,0xec,0x85, + 0xf1,0x6f,0xe1,0x67,0x25,0x10,0x77,0x32,0x9e,0x85,0x6e,0x69,0xb1,0x83,0x00,0xe4, + 0x13,0xa4,0x45,0x34,0x3b,0x40,0xff,0x41,0x82,0x89,0x79,0x57,0xfd,0xd2,0x8e,0xe8, + 0xfc,0x1d,0x19,0x21,0x12,0x00,0xd7,0x66,0xe5,0xc7,0x10,0x1d,0xcb,0x75,0xe8,0xfa, + 0xb6,0xee,0x7b,0x2f,0x1a,0x25,0x24,0xb9,0x9f,0x1d,0x78,0xfb,0x84,0xd0,0x17,0x05, + 0x71,0xb3,0xc8,0x18,0xff,0x62,0xee,0xed,0x53,0xab,0x78,0xd3,0x65,0x2d,0xbb,0xc7, + 0xc1,0xe7,0x70,0xa2,0x43,0x2c,0x7c,0xc7,0x16,0x04,0xd2,0x45,0xd5,0x6b,0x6c,0x7a, + 0x5e,0xa1,0x50,0x2e,0x31,0x5b,0xcc,0xe8,0x65,0x8b,0x16,0x85,0xbf,0x82,0x83,0xfb, + 0xde,0x9f,0x36,0x48,0x32,0x79,0xd6,0x9b,0xfb,0x52,0x45,0xbf,0x43,0xf7,0x0b,0x0b, + 0x19,0x19,0x31,0xc3,0x85,0xec,0x1d,0x8c,0x20,0xf0,0x3a,0xfa,0x80,0x4d,0x2c,0x7d, + 0xac,0x60,0x09,0xc0,0x40,0xee,0xb9,0xeb,0x13,0x5b,0xe8,0x2b,0xb1,0x20,0xf0,0xce, + 0x4c,0xbd,0xc6,0x04,0x86,0x70,0xc6,0x33,0xc3,0x15,0x0f,0x65,0x19,0xfd,0xc2,0xd3, + + // map checksum goes here + 0x00,0x00,0x00,0x00 +}; + +// QuakeWorld +unsigned char COM_BlockSequenceCRCByteQW(unsigned char *base, int length, int sequence) +{ + unsigned char *p; + unsigned char chkb[60 + 4]; + + p = chktbl + (sequence % (sizeof(chktbl) - 8)); + + if (length > 60) + length = 60; + memcpy(chkb, base, length); + + chkb[length] = (sequence & 0xff) ^ p[0]; + chkb[length+1] = p[1]; + chkb[length+2] = ((sequence>>8) & 0xff) ^ p[2]; + chkb[length+3] = p[3]; + + return CRC_Block(chkb, length + 4) & 0xff; +} + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles byte ordering and avoids alignment errors +============================================================================== +*/ + +// +// writing functions +// + +void MSG_WriteChar (sizebuf_t *sb, int c) +{ + unsigned char *buf; + + buf = SZ_GetSpace (sb, 1); + buf[0] = c; +} + +void MSG_WriteByte (sizebuf_t *sb, int c) +{ + unsigned char *buf; + + buf = SZ_GetSpace (sb, 1); + buf[0] = c; +} + +void MSG_WriteShort (sizebuf_t *sb, int c) +{ + unsigned char *buf; + + buf = SZ_GetSpace (sb, 2); + buf[0] = c&0xff; + buf[1] = c>>8; +} + +void MSG_WriteLong (sizebuf_t *sb, int c) +{ + unsigned char *buf; + + buf = SZ_GetSpace (sb, 4); + buf[0] = c&0xff; + buf[1] = (c>>8)&0xff; + buf[2] = (c>>16)&0xff; + buf[3] = c>>24; +} + +void MSG_WriteFloat (sizebuf_t *sb, float f) +{ + union + { + float f; + int l; + } dat; + + + dat.f = f; + dat.l = LittleLong (dat.l); + + SZ_Write (sb, (unsigned char *)&dat.l, 4); +} + +void MSG_WriteString (sizebuf_t *sb, const char *s) +{ + if (!s || !*s) + MSG_WriteChar (sb, 0); + else + SZ_Write (sb, (unsigned char *)s, (int)strlen(s)+1); +} + +void MSG_WriteUnterminatedString (sizebuf_t *sb, const char *s) +{ + if (s && *s) + SZ_Write (sb, (unsigned char *)s, (int)strlen(s)); +} + +void MSG_WriteCoord13i (sizebuf_t *sb, float f) +{ + if (f >= 0) + MSG_WriteShort (sb, (int)(f * 8.0 + 0.5)); + else + MSG_WriteShort (sb, (int)(f * 8.0 - 0.5)); +} + +void MSG_WriteCoord16i (sizebuf_t *sb, float f) +{ + if (f >= 0) + MSG_WriteShort (sb, (int)(f + 0.5)); + else + MSG_WriteShort (sb, (int)(f - 0.5)); +} + +void MSG_WriteCoord32f (sizebuf_t *sb, float f) +{ + MSG_WriteFloat (sb, f); +} + +void MSG_WriteCoord (sizebuf_t *sb, float f, protocolversion_t protocol) +{ + if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD) + MSG_WriteCoord13i (sb, f); + else if (protocol == PROTOCOL_DARKPLACES1) + MSG_WriteCoord32f (sb, f); + else if (protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4) + MSG_WriteCoord16i (sb, f); + else + MSG_WriteCoord32f (sb, f); +} + +void MSG_WriteVector (sizebuf_t *sb, const vec3_t v, protocolversion_t protocol) +{ + MSG_WriteCoord (sb, v[0], protocol); + MSG_WriteCoord (sb, v[1], protocol); + MSG_WriteCoord (sb, v[2], protocol); +} + +// LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem +void MSG_WriteAngle8i (sizebuf_t *sb, float f) +{ + if (f >= 0) + MSG_WriteByte (sb, (int)(f*(256.0/360.0) + 0.5) & 255); + else + MSG_WriteByte (sb, (int)(f*(256.0/360.0) - 0.5) & 255); +} + +void MSG_WriteAngle16i (sizebuf_t *sb, float f) +{ + if (f >= 0) + MSG_WriteShort (sb, (int)(f*(65536.0/360.0) + 0.5) & 65535); + else + MSG_WriteShort (sb, (int)(f*(65536.0/360.0) - 0.5) & 65535); +} + +void MSG_WriteAngle32f (sizebuf_t *sb, float f) +{ + MSG_WriteFloat (sb, f); +} + +void MSG_WriteAngle (sizebuf_t *sb, float f, protocolversion_t protocol) +{ + if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD) + MSG_WriteAngle8i (sb, f); + else + MSG_WriteAngle16i (sb, f); +} + +// +// reading functions +// + +void MSG_InitReadBuffer (sizebuf_t *buf, unsigned char *data, int size) +{ + memset(buf, 0, sizeof(*buf)); + buf->data = data; + buf->maxsize = buf->cursize = size; + MSG_BeginReading(buf); +} + +void MSG_BeginReading(sizebuf_t *sb) +{ + sb->readcount = 0; + sb->badread = false; +} + +int MSG_ReadLittleShort(sizebuf_t *sb) +{ + if (sb->readcount+2 > sb->cursize) + { + sb->badread = true; + return -1; + } + sb->readcount += 2; + return (short)(sb->data[sb->readcount-2] | (sb->data[sb->readcount-1]<<8)); +} + +int MSG_ReadBigShort (sizebuf_t *sb) +{ + if (sb->readcount+2 > sb->cursize) + { + sb->badread = true; + return -1; + } + sb->readcount += 2; + return (short)((sb->data[sb->readcount-2]<<8) + sb->data[sb->readcount-1]); +} + +int MSG_ReadLittleLong (sizebuf_t *sb) +{ + if (sb->readcount+4 > sb->cursize) + { + sb->badread = true; + return -1; + } + sb->readcount += 4; + return sb->data[sb->readcount-4] | (sb->data[sb->readcount-3]<<8) | (sb->data[sb->readcount-2]<<16) | (sb->data[sb->readcount-1]<<24); +} + +int MSG_ReadBigLong (sizebuf_t *sb) +{ + if (sb->readcount+4 > sb->cursize) + { + sb->badread = true; + return -1; + } + sb->readcount += 4; + return (sb->data[sb->readcount-4]<<24) + (sb->data[sb->readcount-3]<<16) + (sb->data[sb->readcount-2]<<8) + sb->data[sb->readcount-1]; +} + +float MSG_ReadLittleFloat (sizebuf_t *sb) +{ + union + { + float f; + int l; + } dat; + if (sb->readcount+4 > sb->cursize) + { + sb->badread = true; + return -1; + } + sb->readcount += 4; + dat.l = sb->data[sb->readcount-4] | (sb->data[sb->readcount-3]<<8) | (sb->data[sb->readcount-2]<<16) | (sb->data[sb->readcount-1]<<24); + return dat.f; +} + +float MSG_ReadBigFloat (sizebuf_t *sb) +{ + union + { + float f; + int l; + } dat; + if (sb->readcount+4 > sb->cursize) + { + sb->badread = true; + return -1; + } + sb->readcount += 4; + dat.l = (sb->data[sb->readcount-4]<<24) | (sb->data[sb->readcount-3]<<16) | (sb->data[sb->readcount-2]<<8) | sb->data[sb->readcount-1]; + return dat.f; +} + +char *MSG_ReadString (sizebuf_t *sb, char *string, size_t maxstring) +{ + int c; + size_t l = 0; + // read string into sbfer, but only store as many characters as will fit + while ((c = MSG_ReadByte(sb)) > 0) + if (l < maxstring - 1) + string[l++] = c; + string[l] = 0; + return string; +} + +int MSG_ReadBytes (sizebuf_t *sb, int numbytes, unsigned char *out) +{ + int l, c; + for (l = 0;l < numbytes && (c = MSG_ReadByte(sb)) != -1;l++) + out[l] = c; + return l; +} + +float MSG_ReadCoord13i (sizebuf_t *sb) +{ + return MSG_ReadLittleShort(sb) * (1.0/8.0); +} + +float MSG_ReadCoord16i (sizebuf_t *sb) +{ + return (signed short) MSG_ReadLittleShort(sb); +} + +float MSG_ReadCoord32f (sizebuf_t *sb) +{ + return MSG_ReadLittleFloat(sb); +} + +float MSG_ReadCoord (sizebuf_t *sb, protocolversion_t protocol) +{ + if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD) + return MSG_ReadCoord13i(sb); + else if (protocol == PROTOCOL_DARKPLACES1) + return MSG_ReadCoord32f(sb); + else if (protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4) + return MSG_ReadCoord16i(sb); + else + return MSG_ReadCoord32f(sb); +} + +void MSG_ReadVector (sizebuf_t *sb, vec3_t v, protocolversion_t protocol) +{ + v[0] = MSG_ReadCoord(sb, protocol); + v[1] = MSG_ReadCoord(sb, protocol); + v[2] = MSG_ReadCoord(sb, protocol); +} + +// LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem +float MSG_ReadAngle8i (sizebuf_t *sb) +{ + return (signed char) MSG_ReadByte (sb) * (360.0/256.0); +} + +float MSG_ReadAngle16i (sizebuf_t *sb) +{ + return (signed short)MSG_ReadShort (sb) * (360.0/65536.0); +} + +float MSG_ReadAngle32f (sizebuf_t *sb) +{ + return MSG_ReadFloat (sb); +} + +float MSG_ReadAngle (sizebuf_t *sb, protocolversion_t protocol) +{ + if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD) + return MSG_ReadAngle8i (sb); + else + return MSG_ReadAngle16i (sb); +} + + +//=========================================================================== + +void SZ_Clear (sizebuf_t *buf) +{ + buf->cursize = 0; +} + +unsigned char *SZ_GetSpace (sizebuf_t *buf, int length) +{ + unsigned char *data; + + if (buf->cursize + length > buf->maxsize) + { + if (!buf->allowoverflow) + Host_Error ("SZ_GetSpace: overflow without allowoverflow set"); + + if (length > buf->maxsize) + Host_Error ("SZ_GetSpace: %i is > full buffer size", length); + + buf->overflowed = true; + Con_Print("SZ_GetSpace: overflow\n"); + SZ_Clear (buf); + } + + data = buf->data + buf->cursize; + buf->cursize += length; + + return data; +} + +void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length) +{ + memcpy (SZ_GetSpace(buf,length),data,length); +} + +// LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my +// attention, it has been eradicated from here, its only (former) use in +// all of darkplaces. + +static const char *hexchar = "0123456789ABCDEF"; +void Com_HexDumpToConsole(const unsigned char *data, int size) +{ + int i, j, n; + char text[1024]; + char *cur, *flushpointer; + const unsigned char *d; + cur = text; + flushpointer = text + 512; + for (i = 0;i < size;) + { + n = 16; + if (n > size - i) + n = size - i; + d = data + i; + // print offset + *cur++ = hexchar[(i >> 12) & 15]; + *cur++ = hexchar[(i >> 8) & 15]; + *cur++ = hexchar[(i >> 4) & 15]; + *cur++ = hexchar[(i >> 0) & 15]; + *cur++ = ':'; + // print hex + for (j = 0;j < 16;j++) + { + if (j < n) + { + *cur++ = hexchar[(d[j] >> 4) & 15]; + *cur++ = hexchar[(d[j] >> 0) & 15]; + } + else + { + *cur++ = ' '; + *cur++ = ' '; + } + if ((j & 3) == 3) + *cur++ = ' '; + } + // print text + for (j = 0;j < 16;j++) + { + if (j < n) + { + // color change prefix character has to be treated specially + if (d[j] == STRING_COLOR_TAG) + { + *cur++ = STRING_COLOR_TAG; + *cur++ = STRING_COLOR_TAG; + } + else if (d[j] >= (unsigned char) ' ') + *cur++ = d[j]; + else + *cur++ = '.'; + } + else + *cur++ = ' '; + } + *cur++ = '\n'; + i += n; + if (cur >= flushpointer || i >= size) + { + *cur++ = 0; + Con_Print(text); + cur = text; + } + } +} + +void SZ_HexDumpToConsole(const sizebuf_t *buf) +{ + Com_HexDumpToConsole(buf->data, buf->cursize); +} + + +//============================================================================ + +/* +============== +COM_Wordwrap + +Word wraps a string. The wordWidth function is guaranteed to be called exactly +once for each word in the string, so it may be stateful, no idea what that +would be good for any more. At the beginning of the string, it will be called +for the char 0 to initialize a clean state, and then once with the string " " +(a space) so the routine knows how long a space is. + +In case no single character fits into the given width, the wordWidth function +must return the width of exactly one character. + +Wrapped lines get the isContinuation flag set and are continuationWidth less wide. + +The sum of the return values of the processLine function will be returned. +============== +*/ +int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL) +{ + // Logic is as follows: + // + // For each word or whitespace: + // Newline found? Output current line, advance to next line. This is not a continuation. Continue. + // Space found? Always add it to the current line, no matter if it fits. + // Word found? Check if current line + current word fits. + // If it fits, append it. Continue. + // If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue. + + qboolean isContinuation = false; + float spaceWidth; + const char *startOfLine = string; + const char *cursor = string; + const char *end = string + length; + float spaceUsedInLine = 0; + float spaceUsedForWord; + int result = 0; + size_t wordLen; + size_t dummy; + + dummy = 0; + wordWidth(passthroughCW, NULL, &dummy, -1); + dummy = 1; + spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1); + + for(;;) + { + char ch = (cursor < end) ? *cursor : 0; + switch(ch) + { + case 0: // end of string + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + goto out; + case '\n': // end of line + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + isContinuation = false; + ++cursor; + startOfLine = cursor; + break; + case ' ': // space + ++cursor; + spaceUsedInLine += spaceWidth; + break; + default: // word + wordLen = 1; + while(cursor + wordLen < end) + { + switch(cursor[wordLen]) + { + case 0: + case '\n': + case ' ': + goto out_inner; + default: + ++wordLen; + break; + } + } + out_inner: + spaceUsedForWord = wordWidth(passthroughCW, cursor, &wordLen, maxWidth - continuationWidth); // this may have reduced wordLen when it won't fit - but this is GOOD. TODO fix words that do fit in a non-continuation line + if(wordLen < 1) // cannot happen according to current spec of wordWidth + { + wordLen = 1; + spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself + } + if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine) + { + // we can simply append it + cursor += wordLen; + spaceUsedInLine += spaceUsedForWord; + } + else + { + // output current line + result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + isContinuation = true; + startOfLine = cursor; + cursor += wordLen; + spaceUsedInLine = continuationWidth + spaceUsedForWord; + } + } + } + out: + + return result; + +/* + qboolean isContinuation = false; + float currentWordSpace = 0; + const char *currentWord = 0; + float minReserve = 0; + + float spaceUsedInLine = 0; + const char *currentLine = 0; + const char *currentLineEnd = 0; + float currentLineFinalWhitespace = 0; + const char *p; + + int result = 0; + minReserve = charWidth(passthroughCW, 0); + minReserve += charWidth(passthroughCW, ' '); + + if(maxWidth < continuationWidth + minReserve) + maxWidth = continuationWidth + minReserve; + + charWidth(passthroughCW, 0); + + for(p = string; p < string + length; ++p) + { + char c = *p; + float w = charWidth(passthroughCW, c); + + if(!currentWord) + { + currentWord = p; + currentWordSpace = 0; + } + + if(!currentLine) + { + currentLine = p; + spaceUsedInLine = isContinuation ? continuationWidth : 0; + currentLineEnd = 0; + } + + if(c == ' ') + { + // 1. I can add the word AND a space - then just append it. + if(spaceUsedInLine + currentWordSpace + w <= maxWidth) + { + currentLineEnd = p; // note: space not included here + currentLineFinalWhitespace = w; + spaceUsedInLine += currentWordSpace + w; + } + // 2. I can just add the word - then append it, output current line and go to next one. + else if(spaceUsedInLine + currentWordSpace <= maxWidth) + { + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + currentLine = 0; + isContinuation = true; + } + // 3. Otherwise, output current line and go to next one, where I can add the word. + else if(continuationWidth + currentWordSpace + w <= maxWidth) + { + if(currentLineEnd) + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + currentLine = currentWord; + spaceUsedInLine = continuationWidth + currentWordSpace + w; + currentLineEnd = p; + currentLineFinalWhitespace = w; + isContinuation = true; + } + // 4. We can't even do that? Then output both current and next word as new lines. + else + { + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + currentLine = 0; + isContinuation = true; + } + currentWord = 0; + } + else if(c == '\n') + { + // 1. I can add the word - then do it. + if(spaceUsedInLine + currentWordSpace <= maxWidth) + { + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + } + // 2. Otherwise, output current line, next one and make tabula rasa. + else + { + if(currentLineEnd) + { + processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + } + currentWord = 0; + currentLine = 0; + isContinuation = false; + } + else + { + currentWordSpace += w; + if( + spaceUsedInLine + currentWordSpace > maxWidth // can't join this line... + && + continuationWidth + currentWordSpace > maxWidth // can't join any other line... + ) + { + // this word cannot join ANY line... + // so output the current line... + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + + // then this word's beginning... + if(isContinuation) + { + // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces + float pieceWidth = maxWidth - continuationWidth; + const char *pos = currentWord; + currentWordSpace = 0; + + // reset the char width function to a state where no kerning occurs (start of word) + charWidth(passthroughCW, ' '); + while(pos <= p) + { + float w = charWidth(passthroughCW, *pos); + if(currentWordSpace + w > pieceWidth) // this piece won't fit any more + { + // print everything until it + result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true); + // go to here + currentWord = pos; + currentWordSpace = 0; + } + currentWordSpace += w; + ++pos; + } + // now we have a currentWord that fits... set up its next line + // currentWordSpace has been set + // currentWord has been set + spaceUsedInLine = continuationWidth; + currentLine = currentWord; + currentLineEnd = 0; + isContinuation = true; + } + else + { + // we have a guarantee that it will fix (see if clause) + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation); + + // and use the rest of this word as new start of a line + currentWordSpace = w; + currentWord = p; + spaceUsedInLine = continuationWidth; + currentLine = p; + currentLineEnd = 0; + isContinuation = true; + } + } + } + } + + if(!currentWord) + { + currentWord = p; + currentWordSpace = 0; + } + + if(currentLine) // Same procedure as \n + { + // Can I append the current word? + if(spaceUsedInLine + currentWordSpace <= maxWidth) + result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); + else + { + if(currentLineEnd) + { + result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); + isContinuation = true; + } + result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); + } + } + + return result; +*/ +} + +/* +============== +COM_ParseToken_Simple + +Parse a token out of a string +============== +*/ +int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash, qboolean parsecomments) +{ + int len; + int c; + const char *data = *datapointer; + + len = 0; + com_token[0] = 0; + + if (!data) + { + *datapointer = NULL; + return false; + } + +// skip whitespace +skipwhite: + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) + { + if (*data == 0) + { + // end of file + *datapointer = NULL; + return false; + } + } + + // handle Windows line ending + if (data[0] == '\r' && data[1] == '\n') + data++; + + if (parsecomments && data[0] == '/' && data[1] == '/') + { + // comment + while (*data && *data != '\n' && *data != '\r') + data++; + goto skipwhite; + } + else if (parsecomments && data[0] == '/' && data[1] == '*') + { + // comment + data++; + while (*data && (data[0] != '*' || data[1] != '/')) + data++; + if (*data) + data++; + if (*data) + data++; + goto skipwhite; + } + else if (*data == '\"') + { + // quoted string + for (data++;*data && *data != '\"';data++) + { + c = *data; + if (*data == '\\' && parsebackslash) + { + data++; + c = *data; + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + } + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = c; + } + com_token[len] = 0; + if (*data == '\"') + data++; + *datapointer = data; + return true; + } + else if (*data == '\r') + { + // translate Mac line ending to UNIX + com_token[len++] = '\n';data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else if (*data == '\n') + { + // single character + com_token[len++] = *data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else + { + // regular word + for (;!ISWHITESPACE(*data);data++) + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + com_token[len] = 0; + *datapointer = data; + return true; + } +} + +/* +============== +COM_ParseToken_QuakeC + +Parse a token out of a string +============== +*/ +int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline) +{ + int len; + int c; + const char *data = *datapointer; + + len = 0; + com_token[0] = 0; + + if (!data) + { + *datapointer = NULL; + return false; + } + +// skip whitespace +skipwhite: + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) + { + if (*data == 0) + { + // end of file + *datapointer = NULL; + return false; + } + } + + // handle Windows line ending + if (data[0] == '\r' && data[1] == '\n') + data++; + + if (data[0] == '/' && data[1] == '/') + { + // comment + while (*data && *data != '\n' && *data != '\r') + data++; + goto skipwhite; + } + else if (data[0] == '/' && data[1] == '*') + { + // comment + data++; + while (*data && (data[0] != '*' || data[1] != '/')) + data++; + if (*data) + data++; + if (*data) + data++; + goto skipwhite; + } + else if (*data == '\"' || *data == '\'') + { + // quoted string + char quote = *data; + for (data++;*data && *data != quote;data++) + { + c = *data; + if (*data == '\\') + { + data++; + c = *data; + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + } + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = c; + } + com_token[len] = 0; + if (*data == quote) + data++; + *datapointer = data; + return true; + } + else if (*data == '\r') + { + // translate Mac line ending to UNIX + com_token[len++] = '\n';data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';') + { + // single character + com_token[len++] = *data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else + { + // regular word + for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++) + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + com_token[len] = 0; + *datapointer = data; + return true; + } +} + +/* +============== +COM_ParseToken_VM_Tokenize + +Parse a token out of a string +============== +*/ +int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline) +{ + int len; + int c; + const char *data = *datapointer; + + len = 0; + com_token[0] = 0; + + if (!data) + { + *datapointer = NULL; + return false; + } + +// skip whitespace +skipwhite: + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) + { + if (*data == 0) + { + // end of file + *datapointer = NULL; + return false; + } + } + + // handle Windows line ending + if (data[0] == '\r' && data[1] == '\n') + data++; + + if (data[0] == '/' && data[1] == '/') + { + // comment + while (*data && *data != '\n' && *data != '\r') + data++; + goto skipwhite; + } + else if (data[0] == '/' && data[1] == '*') + { + // comment + data++; + while (*data && (data[0] != '*' || data[1] != '/')) + data++; + if (*data) + data++; + if (*data) + data++; + goto skipwhite; + } + else if (*data == '\"' || *data == '\'') + { + char quote = *data; + // quoted string + for (data++;*data && *data != quote;data++) + { + c = *data; + if (*data == '\\') + { + data++; + c = *data; + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + } + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = c; + } + com_token[len] = 0; + if (*data == quote) + data++; + *datapointer = data; + return true; + } + else if (*data == '\r') + { + // translate Mac line ending to UNIX + com_token[len++] = '\n';data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';') + { + // single character + com_token[len++] = *data++; + com_token[len] = 0; + *datapointer = data; + return true; + } + else + { + // regular word + for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++) + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + com_token[len] = 0; + *datapointer = data; + return true; + } +} + +/* +============== +COM_ParseToken_Console + +Parse a token out of a string, behaving like the qwcl console +============== +*/ +int COM_ParseToken_Console(const char **datapointer) +{ + int len; + const char *data = *datapointer; + + len = 0; + com_token[0] = 0; + + if (!data) + { + *datapointer = NULL; + return false; + } + +// skip whitespace +skipwhite: + for (;ISWHITESPACE(*data);data++) + { + if (*data == 0) + { + // end of file + *datapointer = NULL; + return false; + } + } + + if (*data == '/' && data[1] == '/') + { + // comment + while (*data && *data != '\n' && *data != '\r') + data++; + goto skipwhite; + } + else if (*data == '\"') + { + // quoted string + for (data++;*data && *data != '\"';data++) + { + // allow escaped " and \ case + if (*data == '\\' && (data[1] == '\"' || data[1] == '\\')) + data++; + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + } + com_token[len] = 0; + if (*data == '\"') + data++; + *datapointer = data; + } + else + { + // regular word + for (;!ISWHITESPACE(*data);data++) + if (len < (int)sizeof(com_token) - 1) + com_token[len++] = *data; + com_token[len] = 0; + *datapointer = data; + } + + return true; +} + + +/* +================ +COM_CheckParm + +Returns the position (1 to argc-1) in the program's argument list +where the given parameter apears, or 0 if not present +================ +*/ +int COM_CheckParm (const char *parm) +{ + int i; + + for (i=1 ; i= 0 && gamemode != gamemode_info[index].mode) + COM_SetGameType(index); +} + +static void COM_SetGameType(int index) +{ + int i, t; + if (index < 0 || index >= (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]))) + index = 0; + gamemode = gamemode_info[index].mode; + gamename = gamemode_info[index].gamename; + gamedirname1 = gamemode_info[index].gamedirname1; + gamedirname2 = gamemode_info[index].gamedirname2; + gamescreenshotname = gamemode_info[index].gamescreenshotname; + gameuserdirname = gamemode_info[index].gameuserdirname; + + if (gamemode == com_startupgamemode) + { + if((t = COM_CheckParm("-customgamename")) && t + 1 < com_argc) + gamename = com_argv[t+1]; + if((t = COM_CheckParm("-customgamedirname1")) && t + 1 < com_argc) + gamedirname1 = com_argv[t+1]; + if((t = COM_CheckParm("-customgamedirname2")) && t + 1 < com_argc) + gamedirname2 = *com_argv[t+1] ? com_argv[t+1] : NULL; + if((t = COM_CheckParm("-customgamescreenshotname")) && t + 1 < com_argc) + gamescreenshotname = com_argv[t+1]; + if((t = COM_CheckParm("-customgameuserdirname")) && t + 1 < com_argc) + gameuserdirname = com_argv[t+1]; + } + + if (gamedirname2 && gamedirname2[0]) + Con_Printf("Game is %s using base gamedirs %s %s", gamename, gamedirname1, gamedirname2); + else + Con_Printf("Game is %s using base gamedir %s", gamename, gamedirname1); + for (i = 0;i < fs_numgamedirs;i++) + { + if (i == 0) + Con_Printf(", with mod gamedirs"); + Con_Printf(" %s", fs_gamedirs[i]); + } + Con_Printf("\n"); +} + + +/* +================ +COM_Init +================ +*/ +void COM_Init_Commands (void) +{ + int i, j, n; + char com_cmdline[MAX_INPUTLINE]; + + Cvar_RegisterVariable (®istered); + Cvar_RegisterVariable (&cmdline); + + // reconstitute the command line for the cmdline externally visible cvar + n = 0; + for (j = 0;(j < MAX_NUM_ARGVS) && (j < com_argc);j++) + { + i = 0; + if (strstr(com_argv[j], " ")) + { + // arg contains whitespace, store quotes around it + com_cmdline[n++] = '\"'; + while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i]) + com_cmdline[n++] = com_argv[j][i++]; + com_cmdline[n++] = '\"'; + } + else + { + while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i]) + com_cmdline[n++] = com_argv[j][i++]; + } + if (n < ((int)sizeof(com_cmdline) - 1)) + com_cmdline[n++] = ' '; + else + break; + } + com_cmdline[n] = 0; + Cvar_Set ("cmdline", com_cmdline); +} + +/* +============ +va + +varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf) +============ +*/ +char *va(char *buf, size_t buflen, const char *format, ...) +{ + va_list argptr; + + va_start (argptr, format); + dpvsnprintf (buf, buflen, format,argptr); + va_end (argptr); + + return buf; +} + +char *portable_va(char *buf, size_t buflen, const char *format, ...) +{ + va_list argptr; + + va_start (argptr, format); + portable_snprintf (buf, buflen, format,argptr); + va_end (argptr); + + return buf; +} + +//====================================== + +// snprintf and vsnprintf are NOT portable. Use their DP counterparts instead + +#undef snprintf +#undef vsnprintf + +#ifdef WIN32 +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#endif + + +int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...) +{ + va_list args; + int result; + + va_start (args, format); + result = dpvsnprintf (buffer, buffersize, format, args); + va_end (args); + + return result; +} + + +int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args) +{ + int result; + + result = vsnprintf (buffer, buffersize, format, args); + + if (result < 0 || (size_t)result >= buffersize) + { + buffer[buffersize - 1] = '\0'; + return -1; + } + + return result; +} + + +//====================================== + +void COM_ToLowerString (const char *in, char *out, size_t size_out) +{ + if (size_out == 0) + return; + + if(utf8_enable.integer) + { + *out = 0; + while(*in && size_out > 1) + { + int n; + Uchar ch = u8_getchar_utf8_enabled(in, &in); + ch = u8_tolower(ch); + n = u8_fromchar(ch, out, size_out); + if(n <= 0) + break; + out += n; + size_out -= n; + } + return; + } + + while (*in && size_out > 1) + { + if (*in >= 'A' && *in <= 'Z') + *out++ = *in++ + 'a' - 'A'; + else + *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +void COM_ToUpperString (const char *in, char *out, size_t size_out) +{ + if (size_out == 0) + return; + + if(utf8_enable.integer) + { + *out = 0; + while(*in && size_out > 1) + { + int n; + Uchar ch = u8_getchar_utf8_enabled(in, &in); + ch = u8_toupper(ch); + n = u8_fromchar(ch, out, size_out); + if(n <= 0) + break; + out += n; + size_out -= n; + } + return; + } + + while (*in && size_out > 1) + { + if (*in >= 'a' && *in <= 'z') + *out++ = *in++ + 'A' - 'a'; + else + *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +int COM_StringBeginsWith(const char *s, const char *match) +{ + for (;*s && *match;s++, match++) + if (*s != *match) + return false; + return true; +} + +int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix) +{ + int argc, commentprefixlength; + char *tokenbufend; + const char *l; + argc = 0; + tokenbufend = tokenbuf + tokenbufsize; + l = *text; + commentprefixlength = 0; + if (commentprefix) + commentprefixlength = (int)strlen(commentprefix); + while (*l && *l != '\n' && *l != '\r') + { + if (!ISWHITESPACE(*l)) + { + if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength)) + { + while (*l && *l != '\n' && *l != '\r') + l++; + break; + } + if (argc >= maxargc) + return -1; + argv[argc++] = tokenbuf; + if (*l == '"') + { + l++; + while (*l && *l != '"') + { + if (tokenbuf >= tokenbufend) + return -1; + *tokenbuf++ = *l++; + } + if (*l == '"') + l++; + } + else + { + while (!ISWHITESPACE(*l)) + { + if (tokenbuf >= tokenbufend) + return -1; + *tokenbuf++ = *l++; + } + } + if (tokenbuf >= tokenbufend) + return -1; + *tokenbuf++ = 0; + } + else + l++; + } + // line endings: + // UNIX: \n + // Mac: \r + // Windows: \r\n + if (*l == '\r') + l++; + if (*l == '\n') + l++; + *text = l; + return argc; +} + +/* +============ +COM_StringLengthNoColors + +calculates the visible width of a color coded string. + +*valid is filled with TRUE if the string is a valid colored string (that is, if +it does not end with an unfinished color code). If it gets filled with FALSE, a +fix would be adding a STRING_COLOR_TAG at the end of the string. + +valid can be set to NULL if the caller doesn't care. + +For size_s, specify the maximum number of characters from s to use, or 0 to use +all characters until the zero terminator. +============ +*/ +size_t +COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid) +{ + const char *end = size_s ? (s + size_s) : NULL; + size_t len = 0; + for(;;) + { + switch((s == end) ? 0 : *s) + { + case 0: + if(valid) + *valid = TRUE; + return len; + case STRING_COLOR_TAG: + ++s; + switch((s == end) ? 0 : *s) + { + case STRING_COLOR_RGB_TAG_CHAR: + if (s+1 != end && isxdigit(s[1]) && + s+2 != end && isxdigit(s[2]) && + s+3 != end && isxdigit(s[3]) ) + { + s+=3; + break; + } + ++len; // STRING_COLOR_TAG + ++len; // STRING_COLOR_RGB_TAG_CHAR + break; + case 0: // ends with unfinished color code! + ++len; + if(valid) + *valid = FALSE; + return len; + case STRING_COLOR_TAG: // escaped ^ + ++len; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': // color code + break; + default: // not a color code + ++len; // STRING_COLOR_TAG + ++len; // the character + break; + } + break; + default: + ++len; + break; + } + ++s; + } + // never get here +} + +/* +============ +COM_StringDecolorize + +removes color codes from a string. + +If escape_carets is true, the resulting string will be safe for printing. If +escape_carets is false, the function will just strip color codes (for logging +for example). + +If the output buffer size did not suffice for converting, the function returns +FALSE. Generally, if escape_carets is false, the output buffer needs +strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2 +bytes. In any case, the function makes sure that the resulting string is +zero terminated. + +For size_in, specify the maximum number of characters from in to use, or 0 to use +all characters until the zero terminator. +============ +*/ +qboolean +COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets) +{ +#define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return FALSE; } } while(0) + const char *end = size_in ? (in + size_in) : NULL; + if(size_out < 1) + return FALSE; + for(;;) + { + switch((in == end) ? 0 : *in) + { + case 0: + *out++ = 0; + return TRUE; + case STRING_COLOR_TAG: + ++in; + switch((in == end) ? 0 : *in) + { + case STRING_COLOR_RGB_TAG_CHAR: + if (in+1 != end && isxdigit(in[1]) && + in+2 != end && isxdigit(in[2]) && + in+3 != end && isxdigit(in[3]) ) + { + in+=3; + break; + } + APPEND(STRING_COLOR_TAG); + if(escape_carets) + APPEND(STRING_COLOR_TAG); + APPEND(STRING_COLOR_RGB_TAG_CHAR); + break; + case 0: // ends with unfinished color code! + APPEND(STRING_COLOR_TAG); + // finish the code by appending another caret when escaping + if(escape_carets) + APPEND(STRING_COLOR_TAG); + *out++ = 0; + return TRUE; + case STRING_COLOR_TAG: // escaped ^ + APPEND(STRING_COLOR_TAG); + // append a ^ twice when escaping + if(escape_carets) + APPEND(STRING_COLOR_TAG); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': // color code + break; + default: // not a color code + APPEND(STRING_COLOR_TAG); + APPEND(*in); + break; + } + break; + default: + APPEND(*in); + break; + } + ++in; + } + // never get here +#undef APPEND +} + +char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength) +{ + int pos = 0, j; + size_t keylength; + if (!key) + key = ""; + keylength = strlen(key); + if (valuelength < 1 || !value) + { + Con_Printf("InfoString_GetValue: no room in value\n"); + return NULL; + } + value[0] = 0; + if (strchr(key, '\\')) + { + Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key); + return NULL; + } + if (strchr(key, '\"')) + { + Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key); + return NULL; + } + if (!key[0]) + { + Con_Printf("InfoString_GetValue: can not look up a key with no name\n"); + return NULL; + } + while (buffer[pos] == '\\') + { + if (!memcmp(buffer + pos+1, key, keylength)) + { + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + pos++; + for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++) + value[j] = buffer[pos+j]; + value[j] = 0; + return value; + } + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + } + // if we reach this point the key was not found + return NULL; +} + +void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value) +{ + int pos = 0, pos2; + size_t keylength; + if (!key) + key = ""; + if (!value) + value = ""; + keylength = strlen(key); + if (strchr(key, '\\') || strchr(value, '\\')) + { + Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \\ which is not possible to store in an infostring\n", key, value); + return; + } + if (strchr(key, '\"') || strchr(value, '\"')) + { + Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \" which is not allowed in an infostring\n", key, value); + return; + } + if (!key[0]) + { + Con_Printf("InfoString_SetValue: can not set a key with no name\n"); + return; + } + while (buffer[pos] == '\\') + { + if (!memcmp(buffer + pos+1, key, keylength)) + break; + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); + } + // if we found the key, find the end of it because we will be replacing it + pos2 = pos; + if (buffer[pos] == '\\') + { + for (pos2++;buffer[pos2] && buffer[pos2] != '\\';pos2++); + for (pos2++;buffer[pos2] && buffer[pos2] != '\\';pos2++); + } + if (bufferlength <= pos + 1 + strlen(key) + 1 + strlen(value) + strlen(buffer + pos2)) + { + Con_Printf("InfoString_SetValue: no room for \"%s\" \"%s\" in infostring\n", key, value); + return; + } + if (value && value[0]) + { + // set the key/value and append the remaining text + char tempbuffer[MAX_INPUTLINE]; + strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer)); + dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer); + } + else + { + // just remove the key from the text + strlcpy(buffer + pos, buffer + pos2, bufferlength - pos); + } +} + +void InfoString_Print(char *buffer) +{ + int i; + char key[MAX_INPUTLINE]; + char value[MAX_INPUTLINE]; + while (*buffer) + { + if (*buffer != '\\') + { + Con_Printf("InfoString_Print: corrupt string\n"); + return; + } + for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++) + if (i < (int)sizeof(key)-1) + key[i++] = *buffer; + key[i] = 0; + if (*buffer != '\\') + { + Con_Printf("InfoString_Print: corrupt string\n"); + return; + } + for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++) + if (i < (int)sizeof(value)-1) + value[i++] = *buffer; + value[i] = 0; + // empty value is an error case + Con_Printf("%20s %s\n", key, value[0] ? value : "NO VALUE"); + } +} + +//======================================================== +// strlcat and strlcpy, from OpenBSD + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $ */ +/* $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $ */ + + +#ifndef HAVE_STRLCAT +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} +#endif // #ifndef HAVE_STRLCAT + + +#ifndef HAVE_STRLCPY +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +#endif // #ifndef HAVE_STRLCPY + +void FindFraction(double val, int *num, int *denom, int denomMax) +{ + int i; + double bestdiff; + // initialize + bestdiff = fabs(val); + *num = 0; + *denom = 1; + + for(i = 1; i <= denomMax; ++i) + { + int inum = (int) floor(0.5 + val * i); + double diff = fabs(val - inum / (double)i); + if(diff < bestdiff) + { + bestdiff = diff; + *num = inum; + *denom = i; + } + } +} + +// decodes an XPM from C syntax +char **XPM_DecodeString(const char *in) +{ + static char *tokens[257]; + static char lines[257][512]; + size_t line = 0; + + // skip until "{" token + while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{")); + + // now, read in succession: string, comma-or-} + while(COM_ParseToken_QuakeC(&in, false)) + { + tokens[line] = lines[line]; + strlcpy(lines[line++], com_token, sizeof(lines[0])); + if(!COM_ParseToken_QuakeC(&in, false)) + return NULL; + if(!strcmp(com_token, "}")) + break; + if(strcmp(com_token, ",")) + return NULL; + if(line >= sizeof(tokens) / sizeof(tokens[0])) + return NULL; + } + + return tokens; +} + +static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes) +{ + unsigned char i0 = (bytes > 0) ? in[0] : 0; + unsigned char i1 = (bytes > 1) ? in[1] : 0; + unsigned char i2 = (bytes > 2) ? in[2] : 0; + unsigned char o0 = base64[i0 >> 2]; + unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077]; + unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077]; + unsigned char o3 = base64[i2 & 077]; + out[0] = (bytes > 0) ? o0 : '?'; + out[1] = (bytes > 0) ? o1 : '?'; + out[2] = (bytes > 1) ? o2 : '='; + out[3] = (bytes > 2) ? o3 : '='; +} + +size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen) +{ + size_t blocks, i; + // expand the out-buffer + blocks = (buflen + 2) / 3; + if(blocks*4 > outbuflen) + return 0; + for(i = blocks; i > 0; ) + { + --i; + base64_3to4(buf + 3*i, buf + 4*i, buflen - 3*i); + } + return blocks * 4; +} diff --git a/app/jni/common.h b/app/jni/common.h new file mode 100644 index 0000000..a48a425 --- /dev/null +++ b/app/jni/common.h @@ -0,0 +1,380 @@ +/* +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. + +*/ + +#ifndef COMMON_H +#define COMMON_H + + +/// MSVC has a different name for several standard functions +#ifdef WIN32 +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +#endif + +// Create our own define for Mac OS X +#if defined(__APPLE__) && defined(__MACH__) +# define MACOSX +#endif + +#ifdef SUNOS +#include ///< Needed for FNDELAY +#endif + +//============================================================================ + +typedef struct sizebuf_s +{ + qboolean allowoverflow; ///< if false, do a Sys_Error + qboolean overflowed; ///< set to true if the buffer size failed + unsigned char *data; + int maxsize; + int cursize; + int readcount; + qboolean badread; // set if a read goes beyond end of message +} sizebuf_t; + +void SZ_Clear (sizebuf_t *buf); +unsigned char *SZ_GetSpace (sizebuf_t *buf, int length); +void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length); +void SZ_HexDumpToConsole(const sizebuf_t *buf); + +void Com_HexDumpToConsole(const unsigned char *data, int size); + +unsigned short CRC_Block(const unsigned char *data, size_t size); +unsigned short CRC_Block_CaseInsensitive(const unsigned char *data, size_t size); // for hash lookup functions that use strcasecmp for comparison + +unsigned char COM_BlockSequenceCRCByteQW(unsigned char *base, int length, int sequence); + +// these are actually md4sum (mdfour.c) +unsigned Com_BlockChecksum (void *buffer, int length); +void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf); + +void COM_Init_Commands(void); + + +//============================================================================ +// Endianess handling +//============================================================================ + +// check mem_bigendian if you need to know the system byte order + +/*! \name Byte order functions. + * @{ + */ + +// unaligned memory access crashes on some platform, so always read bytes... +#define BigShort(l) BuffBigShort((unsigned char *)&(l)) +#define LittleShort(l) BuffLittleShort((unsigned char *)&(l)) +#define BigLong(l) BuffBigLong((unsigned char *)&(l)) +#define LittleLong(l) BuffLittleLong((unsigned char *)&(l)) +#define BigFloat(l) BuffBigFloat((unsigned char *)&(l)) +#define LittleFloat(l) BuffLittleFloat((unsigned char *)&(l)) + +/// Extract a big endian 32bit float from the given \p buffer. +float BuffBigFloat (const unsigned char *buffer); + +/// Extract a big endian 32bit int from the given \p buffer. +int BuffBigLong (const unsigned char *buffer); + +/// Extract a big endian 16bit short from the given \p buffer. +short BuffBigShort (const unsigned char *buffer); + +/// Extract a little endian 32bit float from the given \p buffer. +float BuffLittleFloat (const unsigned char *buffer); + +/// Extract a little endian 32bit int from the given \p buffer. +int BuffLittleLong (const unsigned char *buffer); + +/// Extract a little endian 16bit short from the given \p buffer. +short BuffLittleShort (const unsigned char *buffer); + +/// Encode a big endian 32bit int to the given \p buffer +void StoreBigLong (unsigned char *buffer, unsigned int i); + +/// Encode a big endian 16bit int to the given \p buffer +void StoreBigShort (unsigned char *buffer, unsigned short i); + +/// Encode a little endian 32bit int to the given \p buffer +void StoreLittleLong (unsigned char *buffer, unsigned int i); + +/// Encode a little endian 16bit int to the given \p buffer +void StoreLittleShort (unsigned char *buffer, unsigned short i); +//@} + +//============================================================================ + +// these versions are purely for internal use, never sent in network protocol +// (use Protocol_EnumForNumber and Protocol_NumberToEnum to convert) +typedef enum protocolversion_e +{ + PROTOCOL_UNKNOWN, + PROTOCOL_DARKPLACES7, ///< added QuakeWorld-style movement protocol to allow more consistent prediction + PROTOCOL_DARKPLACES6, ///< various changes + PROTOCOL_DARKPLACES5, ///< uses EntityFrame5 entity snapshot encoder/decoder which is based on a Tribes networking article at http://www.garagegames.com/articles/networking1/ + PROTOCOL_DARKPLACES4, ///< various changes + PROTOCOL_DARKPLACES3, ///< uses EntityFrame4 entity snapshot encoder/decoder which is broken, this attempted to do partial snapshot updates on a QuakeWorld-like protocol, but it is broken and impossible to fix + PROTOCOL_DARKPLACES2, ///< various changes + PROTOCOL_DARKPLACES1, ///< uses EntityFrame entity snapshot encoder/decoder which is a QuakeWorld-like entity snapshot delta compression method + PROTOCOL_QUAKEDP, ///< darkplaces extended quake protocol (used by TomazQuake and others), backwards compatible as long as no extended features are used + PROTOCOL_NEHAHRAMOVIE, ///< Nehahra movie protocol, a big nasty hack dating back to early days of the Quake Standards Group (but only ever used by neh_gl.exe), this is potentially backwards compatible with quake protocol as long as no extended features are used (but in actuality the neh_gl.exe which wrote this protocol ALWAYS wrote the extended information) + PROTOCOL_QUAKE, ///< quake (aka netquake/normalquake/nq) protocol + PROTOCOL_QUAKEWORLD, ///< quakeworld protocol + PROTOCOL_NEHAHRABJP, ///< same as QUAKEDP but with 16bit modelindex + PROTOCOL_NEHAHRABJP2, ///< same as NEHAHRABJP but with 16bit soundindex + PROTOCOL_NEHAHRABJP3 ///< same as NEHAHRABJP2 but with some changes +} +protocolversion_t; + +/*! \name Message IO functions. + * Handles byte ordering and avoids alignment errors + * @{ + */ + +void MSG_InitReadBuffer (sizebuf_t *buf, unsigned char *data, int size); +void MSG_WriteChar (sizebuf_t *sb, int c); +void MSG_WriteByte (sizebuf_t *sb, int c); +void MSG_WriteShort (sizebuf_t *sb, int c); +void MSG_WriteLong (sizebuf_t *sb, int c); +void MSG_WriteFloat (sizebuf_t *sb, vec_t f); +void MSG_WriteString (sizebuf_t *sb, const char *s); +void MSG_WriteUnterminatedString (sizebuf_t *sb, const char *s); +void MSG_WriteAngle8i (sizebuf_t *sb, vec_t f); +void MSG_WriteAngle16i (sizebuf_t *sb, vec_t f); +void MSG_WriteAngle32f (sizebuf_t *sb, vec_t f); +void MSG_WriteCoord13i (sizebuf_t *sb, vec_t f); +void MSG_WriteCoord16i (sizebuf_t *sb, vec_t f); +void MSG_WriteCoord32f (sizebuf_t *sb, vec_t f); +void MSG_WriteCoord (sizebuf_t *sb, vec_t f, protocolversion_t protocol); +void MSG_WriteVector (sizebuf_t *sb, const vec3_t v, protocolversion_t protocol); +void MSG_WriteAngle (sizebuf_t *sb, vec_t f, protocolversion_t protocol); + +void MSG_BeginReading (sizebuf_t *sb); +int MSG_ReadLittleShort (sizebuf_t *sb); +int MSG_ReadBigShort (sizebuf_t *sb); +int MSG_ReadLittleLong (sizebuf_t *sb); +int MSG_ReadBigLong (sizebuf_t *sb); +float MSG_ReadLittleFloat (sizebuf_t *sb); +float MSG_ReadBigFloat (sizebuf_t *sb); +char *MSG_ReadString (sizebuf_t *sb, char *string, size_t maxstring); +int MSG_ReadBytes (sizebuf_t *sb, int numbytes, unsigned char *out); + +#define MSG_ReadChar(sb) ((sb)->readcount >= (sb)->cursize ? ((sb)->badread = true, -1) : (signed char)(sb)->data[(sb)->readcount++]) +#define MSG_ReadByte(sb) ((sb)->readcount >= (sb)->cursize ? ((sb)->badread = true, -1) : (unsigned char)(sb)->data[(sb)->readcount++]) +#define MSG_ReadShort MSG_ReadLittleShort +#define MSG_ReadLong MSG_ReadLittleLong +#define MSG_ReadFloat MSG_ReadLittleFloat + +float MSG_ReadAngle8i (sizebuf_t *sb); +float MSG_ReadAngle16i (sizebuf_t *sb); +float MSG_ReadAngle32f (sizebuf_t *sb); +float MSG_ReadCoord13i (sizebuf_t *sb); +float MSG_ReadCoord16i (sizebuf_t *sb); +float MSG_ReadCoord32f (sizebuf_t *sb); +float MSG_ReadCoord (sizebuf_t *sb, protocolversion_t protocol); +void MSG_ReadVector (sizebuf_t *sb, vec3_t v, protocolversion_t protocol); +float MSG_ReadAngle (sizebuf_t *sb, protocolversion_t protocol); +//@} +//============================================================================ + +typedef float (*COM_WordWidthFunc_t) (void *passthrough, const char *w, size_t *length, float maxWidth); // length is updated to the longest fitting string into maxWidth; if maxWidth < 0, all characters are used and length is used as is +typedef int (*COM_LineProcessorFunc) (void *passthrough, const char *line, size_t length, float width, qboolean isContination); +int COM_Wordwrap(const char *string, size_t length, float continuationSize, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL); + +extern char com_token[MAX_INPUTLINE]; + +int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash, qboolean parsecomments); +int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline); +int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline); +int COM_ParseToken_Console(const char **datapointer); + +extern int com_argc; +extern const char **com_argv; +extern int com_selffd; + +int COM_CheckParm (const char *parm); +void COM_Init (void); +void COM_Shutdown (void); +void COM_InitGameType (void); + +char *va(char *buf, size_t buflen, const char *format, ...) DP_FUNC_PRINTF(3); +char *portable_va(char *buf, size_t buflen, const char *format, ...) DP_FUNC_PRINTF(3); +// does a varargs printf into provided buffer, returns buffer (so it can be called in-line unlike dpsnprintf) + + +// snprintf and vsnprintf are NOT portable. Use their DP counterparts instead +#ifdef snprintf +# undef snprintf +#endif +#define snprintf DO_NOT_USE_SNPRINTF__USE_DPSNPRINTF +#ifdef vsnprintf +# undef vsnprintf +#endif +//#define vsnprintf DO_NOT_USE_VSNPRINTF__USE_DPVSNPRINTF + +// dpsnprintf and dpvsnprintf +// return the number of printed characters, excluding the final '\0' +// or return -1 if the buffer isn't big enough to contain the entire string. +// buffer is ALWAYS null-terminated +extern int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...) DP_FUNC_PRINTF(3); +extern int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args); + +// A bunch of functions are forbidden for security reasons (and also to please MSVS 2005, for some of them) +// LordHavoc: added #undef lines here to avoid warnings in Linux +#undef strcat +#define strcat DO_NOT_USE_STRCAT__USE_STRLCAT_OR_MEMCPY +#undef strncat +#define strncat DO_NOT_USE_STRNCAT__USE_STRLCAT_OR_MEMCPY +#undef strcpy +#define strcpy DO_NOT_USE_STRCPY__USE_STRLCPY_OR_MEMCPY +#undef strncpy +#define strncpy DO_NOT_USE_STRNCPY__USE_STRLCPY_OR_MEMCPY +//#undef sprintf +//#define sprintf DO_NOT_USE_SPRINTF__USE_DPSNPRINTF + + +//============================================================================ + +extern struct cvar_s registered; +extern struct cvar_s cmdline; + +typedef enum userdirmode_e +{ + USERDIRMODE_NOHOME, // basedir only + USERDIRMODE_HOME, // Windows basedir, general POSIX (~/.) + USERDIRMODE_MYGAMES, // pre-Vista (My Documents/My Games/), general POSIX (~/.) + USERDIRMODE_SAVEDGAMES, // Vista (%USERPROFILE%/Saved Games/), OSX (~/Library/Application Support/), Linux (~/.config) + USERDIRMODE_COUNT +} +userdirmode_t; + +typedef enum gamemode_e +{ + GAME_NORMAL, + GAME_HIPNOTIC, + GAME_ROGUE, + GAME_QUOTH, + GAME_NEHAHRA, + GAME_NEXUIZ, + GAME_XONOTIC, + GAME_TRANSFUSION, + GAME_GOODVSBAD2, + GAME_TEU, + GAME_BATTLEMECH, + GAME_ZYMOTIC, + GAME_SETHERAL, + GAME_TENEBRAE, // full of evil hackery + GAME_NEOTERIC, + GAME_OPENQUARTZ, //this game sucks + GAME_PRYDON, + GAME_DELUXEQUAKE, + GAME_THEHUNTED, + GAME_DEFEATINDETAIL2, + GAME_DARSANA, + GAME_CONTAGIONTHEORY, + GAME_EDU2P, + GAME_PROPHECY, + GAME_BLOODOMNICIDE, + GAME_STEELSTORM, // added by motorsep + GAME_STEELSTORM2, // added by motorsep + GAME_TOMESOFMEPHISTOPHELES, // added by motorsep + GAME_STRAPBOMB, // added by motorsep for Urre + GAME_MOONHELM, + GAME_COUNT +} +gamemode_t; + +extern gamemode_t gamemode; +extern const char *gamename; +extern const char *gamedirname1; +extern const char *gamedirname2; +extern const char *gamescreenshotname; +extern const char *gameuserdirname; +extern char com_modname[MAX_OSPATH]; + +void COM_ChangeGameTypeForGameDirs(void); + +void COM_ToLowerString (const char *in, char *out, size_t size_out); +void COM_ToUpperString (const char *in, char *out, size_t size_out); +int COM_StringBeginsWith(const char *s, const char *match); + +int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix); + +size_t COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); +qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets); +void COM_ToLowerString (const char *in, char *out, size_t size_out); +void COM_ToUpperString (const char *in, char *out, size_t size_out); + +typedef struct stringlist_s +{ + /// maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +int matchpattern(const char *in, const char *pattern, int caseinsensitive); +int matchpattern_with_separator(const char *in, const char *pattern, int caseinsensitive, const char *separators, qboolean wildcard_least_one); +void stringlistinit(stringlist_t *list); +void stringlistfreecontents(stringlist_t *list); +void stringlistappend(stringlist_t *list, const char *text); +void stringlistsort(stringlist_t *list, qboolean uniq); +void listdirectory(stringlist_t *list, const char *basepath, const char *path); + +char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength); +void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value); +void InfoString_Print(char *buffer); + +// strlcat and strlcpy, from OpenBSD +// Most (all?) BSDs already have them +#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(MACOSX) +# define HAVE_STRLCAT 1 +# define HAVE_STRLCPY 1 +#endif + +#ifndef HAVE_STRLCAT +/*! + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t strlcat(char *dst, const char *src, size_t siz); +#endif // #ifndef HAVE_STRLCAT + +#ifndef HAVE_STRLCPY +/*! + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t strlcpy(char *dst, const char *src, size_t siz); + +#endif // #ifndef HAVE_STRLCPY + +void FindFraction(double val, int *num, int *denom, int denomMax); + +// decodes XPM file to XPM array (as if #include'd) +char **XPM_DecodeString(const char *in); + +size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen); + +#endif + diff --git a/app/jni/conproc.c b/app/jni/conproc.c new file mode 100644 index 0000000..ccbb849 --- /dev/null +++ b/app/jni/conproc.c @@ -0,0 +1,365 @@ +/* +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. + +*/ +// conproc.c + +#include "quakedef.h" + +#include +#include +#include "conproc.h" + +HANDLE heventDone; +HANDLE hfileBuffer; +HANDLE heventChildSend; +HANDLE heventParentSend; +HANDLE hStdout; +HANDLE hStdin; + +DWORD RequestProc (DWORD dwNichts); +LPVOID GetMappedBuffer (HANDLE hfileBuffer); +void ReleaseMappedBuffer (LPVOID pBuffer); +BOOL GetScreenBufferLines (int *piLines); +BOOL SetScreenBufferLines (int iLines); +BOOL ReadText (LPTSTR pszText, int iBeginLine, int iEndLine); +BOOL WriteText (LPCTSTR szText); +int CharToCode (int c); +BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy); + + +void InitConProc (HANDLE hFile, HANDLE heventParent, HANDLE heventChild) +{ + DWORD dwID; + +// ignore if we don't have all the events. + if (!hFile || !heventParent || !heventChild) + return; + + hfileBuffer = hFile; + heventParentSend = heventParent; + heventChildSend = heventChild; + +// so we'll know when to go away. + heventDone = CreateEvent (NULL, false, false, NULL); + + if (!heventDone) + { + Con_Print("Couldn't create heventDone\n"); + return; + } + + if (!CreateThread (NULL, + 0, + (LPTHREAD_START_ROUTINE) RequestProc, + 0, + 0, + &dwID)) + { + CloseHandle (heventDone); + Con_Print("Couldn't create QHOST thread\n"); + return; + } + +// save off the input/output handles. + hStdout = GetStdHandle (STD_OUTPUT_HANDLE); + hStdin = GetStdHandle (STD_INPUT_HANDLE); + +// force 80 character width, at least 25 character height + SetConsoleCXCY (hStdout, 80, 25); +} + + +void DeinitConProc (void) +{ + if (heventDone) + SetEvent (heventDone); +} + + +DWORD RequestProc (DWORD dwNichts) +{ + int *pBuffer; + DWORD dwRet; + HANDLE heventWait[2]; + int iBeginLine, iEndLine; + + heventWait[0] = heventParentSend; + heventWait[1] = heventDone; + + while (1) + { + dwRet = WaitForMultipleObjects (2, heventWait, false, INFINITE); + + // heventDone fired, so we're exiting. + if (dwRet == WAIT_OBJECT_0 + 1) + break; + + pBuffer = (int *) GetMappedBuffer (hfileBuffer); + + // hfileBuffer is invalid. Just leave. + if (!pBuffer) + { + Con_Print("Invalid hfileBuffer\n"); + break; + } + + switch (pBuffer[0]) + { + case CCOM_WRITE_TEXT: + // Param1 : Text + pBuffer[0] = WriteText ((LPCTSTR) (pBuffer + 1)); + break; + + case CCOM_GET_TEXT: + // Param1 : Begin line + // Param2 : End line + iBeginLine = pBuffer[1]; + iEndLine = pBuffer[2]; + pBuffer[0] = ReadText ((LPTSTR) (pBuffer + 1), iBeginLine, + iEndLine); + break; + + case CCOM_GET_SCR_LINES: + // No params + pBuffer[0] = GetScreenBufferLines (&pBuffer[1]); + break; + + case CCOM_SET_SCR_LINES: + // Param1 : Number of lines + pBuffer[0] = SetScreenBufferLines (pBuffer[1]); + break; + } + + ReleaseMappedBuffer (pBuffer); + SetEvent (heventChildSend); + } + + return 0; +} + + +LPVOID GetMappedBuffer (HANDLE hfileBuffer) +{ + LPVOID pBuffer; + + pBuffer = MapViewOfFile (hfileBuffer, + FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); + + return pBuffer; +} + + +void ReleaseMappedBuffer (LPVOID pBuffer) +{ + UnmapViewOfFile (pBuffer); +} + + +BOOL GetScreenBufferLines (int *piLines) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + BOOL bRet; + + bRet = GetConsoleScreenBufferInfo (hStdout, &info); + + if (bRet) + *piLines = info.dwSize.Y; + + return bRet; +} + + +BOOL SetScreenBufferLines (int iLines) +{ + + return SetConsoleCXCY (hStdout, 80, iLines); +} + + +BOOL ReadText (LPTSTR pszText, int iBeginLine, int iEndLine) +{ + COORD coord; + DWORD dwRead; + BOOL bRet; + + coord.X = 0; + coord.Y = iBeginLine; + + bRet = ReadConsoleOutputCharacter( + hStdout, + pszText, + 80 * (iEndLine - iBeginLine + 1), + coord, + &dwRead); + + // Make sure it's null terminated. + if (bRet) + pszText[dwRead] = '\0'; + + return bRet; +} + + +BOOL WriteText (LPCTSTR szText) +{ + DWORD dwWritten; + INPUT_RECORD rec; + char upper, *sz; + + sz = (LPTSTR) szText; + + while (*sz) + { + // 13 is the code for a carriage return (\n) instead of 10. + if (*sz == 10) + *sz = 13; + + upper = toupper(*sz); + + rec.EventType = KEY_EVENT; + rec.Event.KeyEvent.bKeyDown = true; + rec.Event.KeyEvent.wRepeatCount = 1; + rec.Event.KeyEvent.wVirtualKeyCode = upper; + rec.Event.KeyEvent.wVirtualScanCode = CharToCode (*sz); + rec.Event.KeyEvent.uChar.AsciiChar = *sz; + rec.Event.KeyEvent.uChar.UnicodeChar = *sz; + rec.Event.KeyEvent.dwControlKeyState = isupper(*sz) ? 0x80 : 0x0; + + WriteConsoleInput( + hStdin, + &rec, + 1, + &dwWritten); + + rec.Event.KeyEvent.bKeyDown = false; + + WriteConsoleInput( + hStdin, + &rec, + 1, + &dwWritten); + + sz++; + } + + return true; +} + + +int CharToCode (int c) +{ + char upper; + + upper = toupper(c); + + switch (c) + { + case 13: + return 28; + + default: + break; + } + + if (isalpha(c)) + return (30 + upper - 65); + + if (isdigit(c)) + return (1 + upper - 47); + + return c; +} + + +BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + COORD coordMax; + + coordMax = GetLargestConsoleWindowSize(hStdout); + + if (cy > coordMax.Y) + cy = coordMax.Y; + + if (cx > coordMax.X) + cx = coordMax.X; + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return false; + +// height + info.srWindow.Left = 0; + info.srWindow.Right = info.dwSize.X - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = cy - 1; + + if (cy < info.dwSize.Y) + { + if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) + return false; + + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return false; + } + else if (cy > info.dwSize.Y) + { + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return false; + + if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) + return false; + } + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return false; + +// width + info.srWindow.Left = 0; + info.srWindow.Right = cx - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = info.dwSize.Y - 1; + + if (cx < info.dwSize.X) + { + if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) + return false; + + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return false; + } + else if (cx > info.dwSize.X) + { + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return false; + + if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) + return false; + } + + return true; +} + diff --git a/app/jni/conproc.h b/app/jni/conproc.h new file mode 100644 index 0000000..8fe112a --- /dev/null +++ b/app/jni/conproc.h @@ -0,0 +1,42 @@ +/* +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. + +*/ +// conproc.h + +#ifndef CONPROC_H +#define CONPROC_H + +#define CCOM_WRITE_TEXT 0x2 +// Param1 : Text + +#define CCOM_GET_TEXT 0x3 +// Param1 : Begin line +// Param2 : End line + +#define CCOM_GET_SCR_LINES 0x4 +// No params + +#define CCOM_SET_SCR_LINES 0x5 +// Param1 : Number of lines + +void InitConProc (HANDLE hFile, HANDLE heventParent, HANDLE heventChild); +void DeinitConProc (void); + +#endif + diff --git a/app/jni/console.c b/app/jni/console.c new file mode 100644 index 0000000..c3ddecb --- /dev/null +++ b/app/jni/console.c @@ -0,0 +1,3014 @@ +/* +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. + +*/ +// console.c + +#if !defined(WIN32) || defined(__MINGW32__) +# include +#endif +#include + +#include "quakedef.h" +#include "thread.h" + +// for u8_encodech +#include "ft2.h" + +float con_cursorspeed = 4; + +// lines up from bottom to display +int con_backscroll; + +conbuffer_t con; +void *con_mutex = NULL; + +#define CON_LINES(i) CONBUFFER_LINES(&con, i) +#define CON_LINES_LAST CONBUFFER_LINES_LAST(&con) +#define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con) + +cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"}; +cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"}; +cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"}; + +cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"}; +cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"}; +cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"}; +cvar_t con_chatrect = {CVAR_SAVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"}; +cvar_t con_chatrect_x = {CVAR_SAVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"}; +cvar_t con_chatrect_y = {CVAR_SAVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"}; +cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"}; +cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"}; +cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"}; +cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"}; +cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"}; + + +cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"}; +#ifdef WIN32 +cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"}; +#else +cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"}; +#endif + + +cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"}; +cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: " + "0: add nothing after completion. " + "1: add the last color after completion. " + "2: add a quote when starting a quote instead of the color. " + "4: will replace 1, will force color, even after a quote. " + "8: ignore non-alphanumerics. " + "16: ignore spaces. "}; +#define NICKS_ADD_COLOR 1 +#define NICKS_ADD_QUOTE 2 +#define NICKS_FORCE_COLOR 4 +#define NICKS_ALPHANUMERICS_ONLY 8 +#define NICKS_NO_SPACES 16 + +cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"}; +cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"}; +cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"}; + +int con_linewidth; +int con_vislines; + +qboolean con_initialized; + +extern void jni_BigScreenMode(int mode); + +// used for server replies to rcon command +lhnetsocket_t *rcon_redirect_sock = NULL; +lhnetaddress_t *rcon_redirect_dest = NULL; +int rcon_redirect_bufferpos = 0; +char rcon_redirect_buffer[1400]; +qboolean rcon_redirect_proquakeprotocol = false; + +// generic functions for console buffers + +void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool) +{ + buf->active = true; + buf->textsize = textsize; + buf->text = (char *) Mem_Alloc(mempool, textsize); + buf->maxlines = maxlines; + buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines)); + buf->lines_first = 0; + buf->lines_count = 0; +} + +/* +================ +ConBuffer_Clear +================ +*/ +void ConBuffer_Clear (conbuffer_t *buf) +{ + buf->lines_count = 0; +} + +/* +================ +ConBuffer_Shutdown +================ +*/ +void ConBuffer_Shutdown(conbuffer_t *buf) +{ + buf->active = false; + if (buf->text) + Mem_Free(buf->text); + if (buf->lines) + Mem_Free(buf->lines); + buf->text = NULL; + buf->lines = NULL; +} + +/* +================ +ConBuffer_FixTimes + +Notifies the console code about the current time +(and shifts back times of other entries when the time +went backwards) +================ +*/ +void ConBuffer_FixTimes(conbuffer_t *buf) +{ + int i; + if(buf->lines_count >= 1) + { + double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime; + if(diff < 0) + { + for(i = 0; i < buf->lines_count; ++i) + CONBUFFER_LINES(buf, i).addtime += diff; + } + } +} + +/* +================ +ConBuffer_DeleteLine + +Deletes the first line from the console history. +================ +*/ +void ConBuffer_DeleteLine(conbuffer_t *buf) +{ + if(buf->lines_count == 0) + return; + --buf->lines_count; + buf->lines_first = (buf->lines_first + 1) % buf->maxlines; +} + +/* +================ +ConBuffer_DeleteLastLine + +Deletes the last line from the console history. +================ +*/ +void ConBuffer_DeleteLastLine(conbuffer_t *buf) +{ + if(buf->lines_count == 0) + return; + --buf->lines_count; +} + +/* +================ +ConBuffer_BytesLeft + +Checks if there is space for a line of the given length, and if yes, returns a +pointer to the start of such a space, and NULL otherwise. +================ +*/ +static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len) +{ + if(len > buf->textsize) + return NULL; + if(buf->lines_count == 0) + return buf->text; + else + { + char *firstline_start = buf->lines[buf->lines_first].start; + char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len; + // the buffer is cyclic, so we first have two cases... + if(firstline_start < lastline_onepastend) // buffer is contiguous + { + // put at end? + if(len <= buf->text + buf->textsize - lastline_onepastend) + return lastline_onepastend; + // put at beginning? + else if(len <= firstline_start - buf->text) + return buf->text; + else + return NULL; + } + else // buffer has a contiguous hole + { + if(len <= firstline_start - lastline_onepastend) + return lastline_onepastend; + else + return NULL; + } + } +} + +/* +================ +ConBuffer_AddLine + +Appends a given string as a new line to the console. +================ +*/ +void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask) +{ + char *putpos; + con_lineinfo_t *p; + + // developer_memory 1 during shutdown prints while conbuffer_t is being freed + if (!buf->active) + return; + + ConBuffer_FixTimes(buf); + + if(len >= buf->textsize) + { + // line too large? + // only display end of line. + line += len - buf->textsize + 1; + len = buf->textsize - 1; + } + while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines) + ConBuffer_DeleteLine(buf); + memcpy(putpos, line, len); + putpos[len] = 0; + ++buf->lines_count; + + //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST); + + p = &CONBUFFER_LINES_LAST(buf); + p->start = putpos; + p->len = len; + p->addtime = cl.time; + p->mask = mask; + p->height = -1; // calculate when needed +} + +int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start) +{ + int i; + if(start == -1) + start = buf->lines_count; + for(i = start - 1; i >= 0; --i) + { + con_lineinfo_t *l = &CONBUFFER_LINES(buf, i); + + if((l->mask & mask_must) != mask_must) + continue; + if(l->mask & mask_mustnot) + continue; + + return i; + } + + return -1; +} + +const char *ConBuffer_GetLine(conbuffer_t *buf, int i) +{ + static char copybuf[MAX_INPUTLINE]; // client only + con_lineinfo_t *l = &CONBUFFER_LINES(buf, i); + size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1; + strlcpy(copybuf, l->start, sz); + return copybuf; +} + +/* +============================================================================== + +LOGGING + +============================================================================== +*/ + +/// \name Logging +//@{ +cvar_t log_file = {0, "log_file","", "filename to log messages to"}; +cvar_t log_dest_udp = {0, "log_dest_udp","", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"}; +char log_dest_buffer[1400]; // UDP packet +size_t log_dest_buffer_pos; +unsigned int log_dest_buffer_appending; +char crt_log_file [MAX_OSPATH] = ""; +qfile_t* logfile = NULL; + +unsigned char* logqueue = NULL; +size_t logq_ind = 0; +size_t logq_size = 0; + +void Log_ConPrint (const char *msg); +//@} +static void Log_DestBuffer_Init(void) +{ + memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print + log_dest_buffer_pos = 5; +} + +static void Log_DestBuffer_Flush_NoLock(void) +{ + lhnetaddress_t log_dest_addr; + lhnetsocket_t *log_dest_socket; + const char *s = log_dest_udp.string; + qboolean have_opened_temp_sockets = false; + if(s) if(log_dest_buffer_pos > 5) + { + ++log_dest_buffer_appending; + log_dest_buffer[log_dest_buffer_pos++] = 0; + + if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one + { + have_opened_temp_sockets = true; + NetConn_OpenServerPorts(true); + } + + while(COM_ParseToken_Console(&s)) + if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000)) + { + log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr); + if(!log_dest_socket) + log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr); + if(log_dest_socket) + NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr); + } + + if(have_opened_temp_sockets) + NetConn_CloseServerPorts(); + --log_dest_buffer_appending; + } + log_dest_buffer_pos = 0; +} + +/* +==================== +Log_DestBuffer_Flush +==================== +*/ +void Log_DestBuffer_Flush(void) +{ + if (con_mutex) + Thread_LockMutex(con_mutex); + Log_DestBuffer_Flush_NoLock(); + if (con_mutex) + Thread_UnlockMutex(con_mutex); +} + +static const char* Log_Timestamp (const char *desc) +{ + static char timestamp [128]; // init/shutdown only + time_t crt_time; +#if _MSC_VER >= 1400 + struct tm crt_tm; +#else + struct tm *crt_tm; +#endif + char timestring [64]; + + // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993"); + time (&crt_time); +#if _MSC_VER >= 1400 + localtime_s (&crt_tm, &crt_time); + strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm); +#else + crt_tm = localtime (&crt_time); + strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm); +#endif + + if (desc != NULL) + dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring); + else + dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring); + + return timestamp; +} + +static void Log_Open (void) +{ + if (logfile != NULL || log_file.string[0] == '\0') + return; + + logfile = FS_OpenRealFile(log_file.string, "a", false); + if (logfile != NULL) + { + strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file)); + FS_Print (logfile, Log_Timestamp ("Log started")); + } +} + +/* +==================== +Log_Close +==================== +*/ +void Log_Close (void) +{ + if (logfile == NULL) + return; + + FS_Print (logfile, Log_Timestamp ("Log stopped")); + FS_Print (logfile, "\n"); + FS_Close (logfile); + + logfile = NULL; + crt_log_file[0] = '\0'; +} + + +/* +==================== +Log_Start +==================== +*/ +void Log_Start (void) +{ + size_t pos; + size_t n; + Log_Open (); + + // Dump the contents of the log queue into the log file and free it + if (logqueue != NULL) + { + unsigned char *temp = logqueue; + logqueue = NULL; + if(logq_ind != 0) + { + if (logfile != NULL) + FS_Write (logfile, temp, logq_ind); + if(*log_dest_udp.string) + { + for(pos = 0; pos < logq_ind; ) + { + if(log_dest_buffer_pos == 0) + Log_DestBuffer_Init(); + n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos); + memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n); + log_dest_buffer_pos += n; + Log_DestBuffer_Flush_NoLock(); + pos += n; + } + } + } + Mem_Free (temp); + logq_ind = 0; + logq_size = 0; + } +} + + +/* +================ +Log_ConPrint +================ +*/ +void Log_ConPrint (const char *msg) +{ + static qboolean inprogress = false; + + // don't allow feedback loops with memory error reports + if (inprogress) + return; + inprogress = true; + + // Until the host is completely initialized, we maintain a log queue + // to store the messages, since the log can't be started before + if (logqueue != NULL) + { + size_t remain = logq_size - logq_ind; + size_t len = strlen (msg); + + // If we need to enlarge the log queue + if (len > remain) + { + size_t factor = ((logq_ind + len) / logq_size) + 1; + unsigned char* newqueue; + + logq_size *= factor; + newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size); + memcpy (newqueue, logqueue, logq_ind); + Mem_Free (logqueue); + logqueue = newqueue; + remain = logq_size - logq_ind; + } + memcpy (&logqueue[logq_ind], msg, len); + logq_ind += len; + + inprogress = false; + return; + } + + // Check if log_file has changed + if (strcmp (crt_log_file, log_file.string) != 0) + { + Log_Close (); + Log_Open (); + } + + // If a log file is available + if (logfile != NULL) + FS_Print (logfile, msg); + + inprogress = false; +} + + +/* +================ +Log_Printf +================ +*/ +void Log_Printf (const char *logfilename, const char *fmt, ...) +{ + qfile_t *file; + + file = FS_OpenRealFile(logfilename, "a", true); + if (file != NULL) + { + va_list argptr; + + va_start (argptr, fmt); + FS_VPrintf (file, fmt, argptr); + va_end (argptr); + + FS_Close (file); + } +} + + +/* +============================================================================== + +CONSOLE + +============================================================================== +*/ + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f (void) +{ + if (COM_CheckParm ("-noconsole")) + if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER)) + return; // only allow the key bind to turn off console + + // toggle the 'user wants console' bit + key_consoleactive ^= KEY_CONSOLEACTIVE_USER; + + jni_BigScreenMode(key_consoleactive > 0 ? 1 : 0); + + Con_ClearNotify(); +} + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify (void) +{ + int i; + for(i = 0; i < CON_LINES_COUNT; ++i) + if(!(CON_LINES(i).mask & CON_MASK_CHAT)) + CON_LINES(i).mask |= CON_MASK_HIDENOTIFY; +} + + +/* +================ +Con_MessageMode_f +================ +*/ +static void Con_MessageMode_f (void) +{ + key_dest = key_message; + chat_mode = 0; // "say" + if(Cmd_Argc() > 1) + { + dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args()); + chat_bufferlen = strlen(chat_buffer); + } +} + + +/* +================ +Con_MessageMode2_f +================ +*/ +static void Con_MessageMode2_f (void) +{ + key_dest = key_message; + chat_mode = 1; // "say_team" + if(Cmd_Argc() > 1) + { + dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args()); + chat_bufferlen = strlen(chat_buffer); + } +} + +/* +================ +Con_CommandMode_f +================ +*/ +static void Con_CommandMode_f (void) +{ + key_dest = key_message; + if(Cmd_Argc() > 1) + { + dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args()); + chat_bufferlen = strlen(chat_buffer); + } + chat_mode = -1; // command +} + +/* +================ +Con_CheckResize +================ +*/ +void Con_CheckResize (void) +{ + int i, width; + float f; + + f = bound(1, con_textsize.value, 128); + if(f != con_textsize.value) + Cvar_SetValueQuick(&con_textsize, f); + width = (int)floor(vid_conwidth.value / con_textsize.value); + width = bound(1, width, con.textsize/4); + // FIXME uses con in a non abstracted way + + if (width == con_linewidth) + return; + + con_linewidth = width; + + for(i = 0; i < CON_LINES_COUNT; ++i) + CON_LINES(i).height = -1; // recalculate when next needed + + Con_ClearNotify(); + con_backscroll = 0; +} + +//[515]: the simplest command ever +//LordHavoc: not so simple after I made it print usage... +static void Con_Maps_f (void) +{ + if (Cmd_Argc() > 2) + { + Con_Printf("usage: maps [mapnameprefix]\n"); + return; + } + else if (Cmd_Argc() == 2) + GetMapList(Cmd_Argv(1), NULL, 0); + else + GetMapList("", NULL, 0); +} + +static void Con_ConDump_f (void) +{ + int i; + qfile_t *file; + if (Cmd_Argc() != 2) + { + Con_Printf("usage: condump \n"); + return; + } + file = FS_OpenRealFile(Cmd_Argv(1), "w", false); + if (!file) + { + Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1)); + return; + } + if (con_mutex) Thread_LockMutex(con_mutex); + for(i = 0; i < CON_LINES_COUNT; ++i) + { + FS_Write(file, CON_LINES(i).start, CON_LINES(i).len); + FS_Write(file, "\n", 1); + } + if (con_mutex) Thread_UnlockMutex(con_mutex); + FS_Close(file); +} + +void Con_Clear_f (void) +{ + if (con_mutex) Thread_LockMutex(con_mutex); + ConBuffer_Clear(&con); + if (con_mutex) Thread_UnlockMutex(con_mutex); +} + +/* +================ +Con_Init +================ +*/ +void Con_Init (void) +{ + con_linewidth = 80; + ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool); + if (Thread_HasThreads()) + con_mutex = Thread_CreateMutex(); + + // Allocate a log queue, this will be freed after configs are parsed + logq_size = MAX_INPUTLINE; + logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size); + logq_ind = 0; + + Cvar_RegisterVariable (&sys_colortranslation); + Cvar_RegisterVariable (&sys_specialcharactertranslation); + + Cvar_RegisterVariable (&log_file); + Cvar_RegisterVariable (&log_dest_udp); + + // support for the classic Quake option +// COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file + if (COM_CheckParm ("-condebug") != 0) + Cvar_SetQuick (&log_file, "qconsole.log"); + + // register our cvars + Cvar_RegisterVariable (&con_chat); + Cvar_RegisterVariable (&con_chatpos); + Cvar_RegisterVariable (&con_chatrect_x); + Cvar_RegisterVariable (&con_chatrect_y); + Cvar_RegisterVariable (&con_chatrect); + Cvar_RegisterVariable (&con_chatsize); + Cvar_RegisterVariable (&con_chattime); + Cvar_RegisterVariable (&con_chatwidth); + Cvar_RegisterVariable (&con_notify); + Cvar_RegisterVariable (&con_notifyalign); + Cvar_RegisterVariable (&con_notifysize); + Cvar_RegisterVariable (&con_notifytime); + Cvar_RegisterVariable (&con_textsize); + Cvar_RegisterVariable (&con_chatsound); + + // --blub + Cvar_RegisterVariable (&con_nickcompletion); + Cvar_RegisterVariable (&con_nickcompletion_flags); + + Cvar_RegisterVariable (&con_completion_playdemo); // *.dem + Cvar_RegisterVariable (&con_completion_timedemo); // *.dem + Cvar_RegisterVariable (&con_completion_exec); // *.cfg + + // register our commands + Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console"); + Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone"); + Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team"); + Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command"); + Cmd_AddCommand ("clear", Con_Clear_f, "clear console history"); + Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps"); + Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)"); + + con_initialized = true; + Con_DPrint("Console initialized.\n"); +} + +void Con_Shutdown (void) +{ + if (con_mutex) Thread_LockMutex(con_mutex); + ConBuffer_Shutdown(&con); + if (con_mutex) Thread_UnlockMutex(con_mutex); + if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL; +} + +/* +================ +Con_PrintToHistory + +Handles cursor positioning, line wrapping, etc +All console printing must go through this in order to be displayed +If no console is visible, the notify window will pop up. +================ +*/ +static void Con_PrintToHistory(const char *txt, int mask) +{ + // process: + // \n goes to next line + // \r deletes current line and makes a new one + + static int cr_pending = 0; + static char buf[CON_TEXTSIZE]; // con_mutex + static int bufpos = 0; + + if(!con.text) // FIXME uses a non-abstracted property of con + return; + + for(; *txt; ++txt) + { + if(cr_pending) + { + ConBuffer_DeleteLastLine(&con); + cr_pending = 0; + } + switch(*txt) + { + case 0: + break; + case '\r': + ConBuffer_AddLine(&con, buf, bufpos, mask); + bufpos = 0; + cr_pending = 1; + break; + case '\n': + ConBuffer_AddLine(&con, buf, bufpos, mask); + bufpos = 0; + break; + default: + buf[bufpos++] = *txt; + if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con + { + ConBuffer_AddLine(&con, buf, bufpos, mask); + bufpos = 0; + } + break; + } + } +} + +/*! The translation table between the graphical font and plain ASCII --KB */ +static char qfont_table[256] = { + '\0', '#', '#', '#', '#', '.', '#', '#', + '#', 9, 10, '#', ' ', 13, '.', '.', + '[', ']', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '.', '<', '=', '>', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', '<', + + '<', '=', '>', '#', '#', '.', '#', '#', + '#', '#', ' ', '#', ' ', '>', '.', '.', + '[', ']', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '.', '<', '=', '>', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', '<' +}; + +void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol) +{ + rcon_redirect_sock = sock; + rcon_redirect_dest = dest; + rcon_redirect_proquakeprotocol = proquakeprotocol; + if (rcon_redirect_proquakeprotocol) + { + // reserve space for the packet header + rcon_redirect_buffer[0] = 0; + rcon_redirect_buffer[1] = 0; + rcon_redirect_buffer[2] = 0; + rcon_redirect_buffer[3] = 0; + // this is a reply to a CCREQ_RCON + rcon_redirect_buffer[4] = (char)CCREP_RCON; + } + else + memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print + rcon_redirect_bufferpos = 5; +} + +static void Con_Rcon_Redirect_Flush(void) +{ + if(rcon_redirect_sock) + { + rcon_redirect_buffer[rcon_redirect_bufferpos] = 0; + if (rcon_redirect_proquakeprotocol) + { + // update the length in the packet header + StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK)); + } + NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest); + } + memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print + rcon_redirect_bufferpos = 5; + rcon_redirect_proquakeprotocol = false; +} + +void Con_Rcon_Redirect_End(void) +{ + Con_Rcon_Redirect_Flush(); + rcon_redirect_dest = NULL; + rcon_redirect_sock = NULL; +} + +void Con_Rcon_Redirect_Abort(void) +{ + rcon_redirect_dest = NULL; + rcon_redirect_sock = NULL; +} + +/* +================ +Con_Rcon_AddChar +================ +*/ +/// Adds a character to the rcon buffer. +static void Con_Rcon_AddChar(int c) +{ + if(log_dest_buffer_appending) + return; + ++log_dest_buffer_appending; + + // if this print is in response to an rcon command, add the character + // to the rcon redirect buffer + + if (rcon_redirect_dest) + { + rcon_redirect_buffer[rcon_redirect_bufferpos++] = c; + if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1) + Con_Rcon_Redirect_Flush(); + } + else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way + { + if(log_dest_buffer_pos == 0) + Log_DestBuffer_Init(); + log_dest_buffer[log_dest_buffer_pos++] = c; + if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero + Log_DestBuffer_Flush_NoLock(); + } + else + log_dest_buffer_pos = 0; + + --log_dest_buffer_appending; +} + +/** + * Convert an RGB color to its nearest quake color. + * I'll cheat on this a bit by translating the colors to HSV first, + * S and V decide if it's black or white, otherwise, H will decide the + * actual color. + * @param _r Red (0-255) + * @param _g Green (0-255) + * @param _b Blue (0-255) + * @return A quake color character. + */ +static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b) +{ + float r = ((float)_r)/255.0; + float g = ((float)_g)/255.0; + float b = ((float)_b)/255.0; + float min = min(r, min(g, b)); + float max = max(r, max(g, b)); + + int h; ///< Hue angle [0,360] + float s; ///< Saturation [0,1] + float v = max; ///< In HSV v == max [0,1] + + if(max == min) + s = 0; + else + s = 1.0 - (min/max); + + // Saturation threshold. We now say 0.2 is the minimum value for a color! + if(s < 0.2) + { + // If the value is less than half, return a black color code. + // Otherwise return a white one. + if(v < 0.5) + return '0'; + return '7'; + } + + // Let's get the hue angle to define some colors: + if(max == min) + h = 0; + else if(max == r) + h = (int)(60.0 * (g-b)/(max-min))%360; + else if(max == g) + h = (int)(60.0 * (b-r)/(max-min) + 120); + else // if(max == b) redundant check + h = (int)(60.0 * (r-g)/(max-min) + 240); + + if(h < 36) // *red* to orange + return '1'; + else if(h < 80) // orange over *yellow* to evilish-bright-green + return '3'; + else if(h < 150) // evilish-bright-green over *green* to ugly bright blue + return '2'; + else if(h < 200) // ugly bright blue over *bright blue* to darkish blue + return '5'; + else if(h < 270) // darkish blue over *dark blue* to cool purple + return '4'; + else if(h < 330) // cool purple over *purple* to ugly swiny red + return '6'; + else // ugly red to red closes the circly + return '1'; +} + +/* +================ +Con_MaskPrint +================ +*/ +extern cvar_t timestamps; +extern cvar_t timeformat; +extern qboolean sys_nostdout; +void Con_MaskPrint(int additionalmask, const char *msg) +{ + static int mask = 0; + static int index = 0; + static char line[MAX_INPUTLINE]; + + if (con_mutex) + Thread_LockMutex(con_mutex); + + for (;*msg;msg++) + { + Con_Rcon_AddChar(*msg); + // if this is the beginning of a new line, print timestamp + if (index == 0) + { + const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : ""; + // reset the color + // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7! + line[index++] = STRING_COLOR_TAG; + // assert( STRING_COLOR_DEFAULT < 10 ) + line[index++] = STRING_COLOR_DEFAULT + '0'; + // special color codes for chat messages must always come first + // for Con_PrintToHistory to work properly + if (*msg == 1 || *msg == 2 || *msg == 3) + { + // play talk wav + if (*msg == 1) + { + if (con_chatsound.value) + { + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + { + if(msg[1] == '\r' && cl.foundtalk2wav) + S_LocalSound ("sound/misc/talk2.wav"); + else + S_LocalSound ("sound/misc/talk.wav"); + } + else + { + if (msg[1] == '(' && cl.foundtalk2wav) + S_LocalSound ("sound/misc/talk2.wav"); + else + S_LocalSound ("sound/misc/talk.wav"); + } + } + } + + // Send to chatbox for say/tell (1) and messages (3) + // 3 is just so that a message can be sent to the chatbox without a sound. + if (*msg == 1 || *msg == 3) + mask = CON_MASK_CHAT; + + line[index++] = STRING_COLOR_TAG; + line[index++] = '3'; + msg++; + Con_Rcon_AddChar(*msg); + } + // store timestamp + for (;*timestamp;index++, timestamp++) + if (index < (int)sizeof(line) - 2) + line[index] = *timestamp; + // add the mask + mask |= additionalmask; + } + // append the character + line[index++] = *msg; + // if this is a newline character, we have a complete line to print + if (*msg == '\n' || index >= (int)sizeof(line) / 2) + { + // terminate the line + line[index] = 0; + // send to log file + Log_ConPrint(line); + // send to scrollable buffer + if (con_initialized && cls.state != ca_dedicated) + { + Con_PrintToHistory(line, mask); + } + // send to terminal or dedicated server window + if (!sys_nostdout) + if (developer.integer || !(mask & CON_MASK_DEVELOPER)) + { + if(sys_specialcharactertranslation.integer) + { + char *p; + const char *q; + p = line; + while(*p) + { + int ch = u8_getchar(p, &q); + if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20) + { + *p = qfont_table[ch - 0xE000]; + if(q > p+1) + memmove(p+1, q, strlen(q)+1); + p = p + 1; + } + else + p = p + (q - p); + } + } + + if(sys_colortranslation.integer == 1) // ANSI + { + static char printline[MAX_INPUTLINE * 4 + 3]; + // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end + // a newline can transform into four bytes, but then prevents the three extra bytes from appearing + int lastcolor = 0; + const char *in; + char *out; + int color; + for(in = line, out = printline; *in; ++in) + { + switch(*in) + { + case STRING_COLOR_TAG: + if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) ) + { + char r = tolower(in[2]); + char g = tolower(in[3]); + char b = tolower(in[4]); + // it's a hex digit already, so the else part needs no check --blub + if(isdigit(r)) r -= '0'; + else r -= 87; + if(isdigit(g)) g -= '0'; + else g -= 87; + if(isdigit(b)) b -= '0'; + else b -= 87; + + color = Sys_Con_NearestColor(r * 17, g * 17, b * 17); + in += 3; // 3 only, the switch down there does the fourth + } + else + color = in[1]; + + switch(color) + { + case STRING_COLOR_TAG: + ++in; + *out++ = STRING_COLOR_TAG; + break; + case '0': + case '7': + // normal color + ++in; + if(lastcolor == 0) break; else lastcolor = 0; + *out++ = 0x1B; *out++ = '['; *out++ = 'm'; + break; + case '1': + // light red + ++in; + if(lastcolor == 1) break; else lastcolor = 1; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm'; + break; + case '2': + // light green + ++in; + if(lastcolor == 2) break; else lastcolor = 2; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm'; + break; + case '3': + // yellow + ++in; + if(lastcolor == 3) break; else lastcolor = 3; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm'; + break; + case '4': + // light blue + ++in; + if(lastcolor == 4) break; else lastcolor = 4; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm'; + break; + case '5': + // light cyan + ++in; + if(lastcolor == 5) break; else lastcolor = 5; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm'; + break; + case '6': + // light magenta + ++in; + if(lastcolor == 6) break; else lastcolor = 6; + *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm'; + break; + // 7 handled above + case '8': + case '9': + // bold normal color + ++in; + if(lastcolor == 8) break; else lastcolor = 8; + *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm'; + break; + default: + *out++ = STRING_COLOR_TAG; + break; + } + break; + case '\n': + if(lastcolor != 0) + { + *out++ = 0x1B; *out++ = '['; *out++ = 'm'; + lastcolor = 0; + } + *out++ = *in; + break; + default: + *out++ = *in; + break; + } + } + if(lastcolor != 0) + { + *out++ = 0x1B; + *out++ = '['; + *out++ = 'm'; + } + *out++ = 0; + Sys_PrintToTerminal(printline); + } + else if(sys_colortranslation.integer == 2) // Quake + { + Sys_PrintToTerminal(line); + } + else // strip + { + static char printline[MAX_INPUTLINE]; // it can only get shorter here + const char *in; + char *out; + for(in = line, out = printline; *in; ++in) + { + switch(*in) + { + case STRING_COLOR_TAG: + switch(in[1]) + { + case STRING_COLOR_RGB_TAG_CHAR: + if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) ) + { + in+=4; + break; + } + *out++ = STRING_COLOR_TAG; + *out++ = STRING_COLOR_RGB_TAG_CHAR; + ++in; + break; + case STRING_COLOR_TAG: + ++in; + *out++ = STRING_COLOR_TAG; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ++in; + break; + default: + *out++ = STRING_COLOR_TAG; + break; + } + break; + default: + *out++ = *in; + break; + } + } + *out++ = 0; + Sys_PrintToTerminal(printline); + } + } + // empty the line buffer + index = 0; + mask = 0; + } + } + + if (con_mutex) + Thread_UnlockMutex(con_mutex); +} + +/* +================ +Con_MaskPrintf +================ +*/ +void Con_MaskPrintf(int mask, const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Con_MaskPrint(mask, msg); +} + +/* +================ +Con_Print +================ +*/ +void Con_Print(const char *msg) +{ + Con_MaskPrint(CON_MASK_PRINT, msg); +} + +/* +================ +Con_Printf +================ +*/ +void Con_Printf(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Con_MaskPrint(CON_MASK_PRINT, msg); +} + +/* +================ +Con_DPrint +================ +*/ +void Con_DPrint(const char *msg) +{ + if(developer.integer < 0) // at 0, we still add to the buffer but hide + return; + + Con_MaskPrint(CON_MASK_DEVELOPER, msg); +} + +/* +================ +Con_DPrintf +================ +*/ +void Con_DPrintf(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + if(developer.integer < 0) // at 0, we still add to the buffer but hide + return; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Con_MaskPrint(CON_MASK_DEVELOPER, msg); +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + +/* +================ +Con_DrawInput + +The input line scrolls horizontally if typing goes beyond the right edge + +Modified by EvilTypeGuy eviltypeguy@qeradiant.com +================ +*/ +extern cvar_t r_font_disable_freetype; +static void Con_DrawInput (void) +{ + int y; + int i; + char editlinecopy[MAX_INPUTLINE+1], *text; + float x, xo; + size_t len_out; + int col_out; + + if (!key_consoleactive) + return; // don't draw anything + + strlcpy(editlinecopy, key_line, sizeof(editlinecopy)); + text = editlinecopy; + + // Advanced Console Editing by Radix radix@planetquake.com + // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com + // use strlen of edit_line instead of key_linepos to allow editing + // of early characters w/o erasing + + y = (int)strlen(text); + + // append enoug nul-bytes to cover the utf8-versions of the cursor too + for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i) + text[i] = 0; + + // add the cursor frame + if (r_font_disable_freetype.integer) + { + // this code is freetype incompatible! + if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible + { + if (!utf8_enable.integer) + text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right + else if (y + 3 < (int)sizeof(editlinecopy)-1) + { + int ofs = u8_bytelen(text + key_linepos, 1); + size_t len; + const char *curbuf; + char charbuf16[16]; + curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16); + + if (curbuf) + { + memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len); + memcpy(text + key_linepos, curbuf, len); + } + } else + text[key_linepos] = '-' + ('+' - '-') * key_insert; + } + } + +// text[key_linepos + 1] = 0; + + len_out = key_linepos; + col_out = -1; + xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000); + x = vid_conwidth.value * 0.95 - xo; // scroll + if(x >= 0) + x = 0; + + // draw it + DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE ); + + // add a cursor on top of this (when using freetype) + if (!r_font_disable_freetype.integer) + { + if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible + { + if (!utf8_enable.integer) + { + text[0] = 11 + 130 * key_insert; // either solid or triangle facing right + text[1] = 0; + } + else + { + size_t len; + const char *curbuf; + char charbuf16[16]; + curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16); + memcpy(text, curbuf, len); + text[len] = 0; + } + DrawQ_String(x + xo, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, FONT_CONSOLE); + } + } + + // remove cursor +// key_line[key_linepos] = 0; +} + +typedef struct +{ + dp_font_t *font; + float alignment; // 0 = left, 0.5 = center, 1 = right + float fontsize; + float x; + float y; + float width; + float ymin, ymax; + const char *continuationString; + + // PRIVATE: + int colorindex; // init to -1 +} +con_text_info_t; + +static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth) +{ + con_text_info_t *ti = (con_text_info_t *) passthrough; + if(w == NULL) + { + ti->colorindex = -1; + return ti->fontsize * ti->font->maxwidth; + } + if(maxWidth >= 0) + return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char + else if(maxWidth == -1) + return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font); + else + { + Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth); + // Note: this is NOT a Con_Printf, as it could print recursively + return 0; + } +} + +static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) +{ + (void) passthrough; + (void) line; + (void) length; + (void) width; + (void) isContinuation; + return 1; +} + +static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) +{ + con_text_info_t *ti = (con_text_info_t *) passthrough; + + if(ti->y < ti->ymin - 0.001) + (void) 0; + else if(ti->y > ti->ymax - ti->fontsize + 0.001) + (void) 0; + else + { + int x = (int) (ti->x + (ti->width - width) * ti->alignment); + if(isContinuation && *ti->continuationString) + x = (int) DrawQ_String(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font); + if(length > 0) + DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font); + } + + ti->y += ti->fontsize; + return 1; +} + +static int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString) +{ + int i; + int lines = 0; + int maxlines = (int) floor(height / fontsize + 0.01f); + int startidx; + int nskip = 0; + int continuationWidth = 0; + size_t l; + double t = cl.time; // saved so it won't change + con_text_info_t ti; + + ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY; + ti.fontsize = fontsize; + ti.alignment = alignment_x; + ti.width = width; + ti.ymin = y; + ti.ymax = y + height; + ti.continuationString = continuationString; + + l = 0; + Con_WordWidthFunc(&ti, NULL, &l, -1); + l = strlen(continuationString); + continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1); + + // first find the first line to draw by backwards iterating and word wrapping to find their length... + startidx = CON_LINES_COUNT; + for(i = CON_LINES_COUNT - 1; i >= 0; --i) + { + con_lineinfo_t *l = &CON_LINES(i); + int mylines; + + if((l->mask & mask_must) != mask_must) + continue; + if(l->mask & mask_mustnot) + continue; + if(maxage && (l->addtime < t - maxage)) + continue; + + // WE FOUND ONE! + // Calculate its actual height... + mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti); + if(lines + mylines >= maxlines) + { + nskip = lines + mylines - maxlines; + lines = maxlines; + startidx = i; + break; + } + lines += mylines; + startidx = i; + } + + // then center according to the calculated amount of lines... + ti.x = x; + ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize; + + // then actually draw + for(i = startidx; i < CON_LINES_COUNT; ++i) + { + con_lineinfo_t *l = &CON_LINES(i); + + if((l->mask & mask_must) != mask_must) + continue; + if(l->mask & mask_mustnot) + continue; + if(maxage && (l->addtime < t - maxage)) + continue; + + COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti); + } + + return lines; +} + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify (void) +{ + float x, v, xr; + float chatstart, notifystart, inputsize, height; + float align; + char temptext[MAX_INPUTLINE]; + int numChatlines; + int chatpos; + + if (con_mutex) Thread_LockMutex(con_mutex); + ConBuffer_FixTimes(&con); + + numChatlines = con_chat.integer; + + chatpos = con_chatpos.integer; + + if (con_notify.integer < 0) + Cvar_SetValueQuick(&con_notify, 0); + if (gamemode == GAME_TRANSFUSION) + v = 8; // vertical offset + else + v = 0; + + // GAME_NEXUIZ: center, otherwise left justify + align = con_notifyalign.value; + if(!*con_notifyalign.string) // empty string, evaluated to 0 above + { + if(gamemode == GAME_NEXUIZ) + align = 0.5; + } + + if(numChatlines || !con_chatrect.integer) + { + if(chatpos == 0) + { + // first chat, input line, then notify + chatstart = v; + notifystart = v + (numChatlines + 1) * con_chatsize.value; + } + else if(chatpos > 0) + { + // first notify, then (chatpos-1) empty lines, then chat, then input + notifystart = v; + chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value; + } + else // if(chatpos < 0) + { + // first notify, then much space, then chat, then input, then -chatpos-1 empty lines + notifystart = v; + chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value; + } + } + else + { + // just notify and input + notifystart = v; + chatstart = 0; // shut off gcc warning + } + + v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, ""); + + if(con_chatrect.integer) + { + x = con_chatrect_x.value * vid_conwidth.value; + v = con_chatrect_y.value * vid_conheight.value; + } + else + { + x = 0; + if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong + v = chatstart; + } + height = numChatlines * con_chatsize.value; + + if(numChatlines) + { + Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, con_chatsize.value, 0.0, 1.0, (utf8_enable.integer ? "^3\xee\x80\x8c\xee\x80\x8c\xee\x80\x8c " : "^3\014\014\014 ")); // 015 is ·> character in conchars.tga + v += height; + } + if (key_dest == key_message) + { + //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on } + int colorindex = -1; + const char *cursor; + char charbuf16[16]; + cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16); + + // LordHavoc: speedup, and other improvements + if (chat_mode < 0) + dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor); + else if(chat_mode) + dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor); + else + dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor); + + // FIXME word wrap + inputsize = (numChatlines ? con_chatsize : con_notifysize).value; + xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT); + x = min(xr, x); + DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT); + } + if (con_mutex) Thread_UnlockMutex(con_mutex); +} + +/* +================ +Con_LineHeight + +Returns the height of a given console line; calculates it if necessary. +================ +*/ +static int Con_LineHeight(int lineno) +{ + con_lineinfo_t *li = &CON_LINES(lineno); + if(li->height == -1) + { + float width = vid_conwidth.value; + con_text_info_t ti; + con_lineinfo_t *li = &CON_LINES(lineno); + ti.fontsize = con_textsize.value; + ti.font = FONT_CONSOLE; + li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL); + } + return li->height; +} + +/* +================ +Con_DrawConsoleLine + +Draws a line of the console; returns its height in lines. +If alpha is 0, the line is not drawn, but still wrapped and its height +returned. +================ +*/ +static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax) +{ + float width = vid_conwidth.value; + con_text_info_t ti; + con_lineinfo_t *li = &CON_LINES(lineno); + + if((li->mask & mask_must) != mask_must) + return 0; + if((li->mask & mask_mustnot) != 0) + return 0; + + ti.continuationString = ""; + ti.alignment = 0; + ti.fontsize = con_textsize.value; + ti.font = FONT_CONSOLE; + ti.x = 0; + ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize; + ti.ymin = ymin; + ti.ymax = ymax; + ti.width = width; + + return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti); +} + +/* +================ +Con_LastVisibleLine + +Calculates the last visible line index and how much to show of it based on +con_backscroll. +================ +*/ +static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast) +{ + int lines_seen = 0; + int i; + + if(con_backscroll < 0) + con_backscroll = 0; + + *last = 0; + + // now count until we saw con_backscroll actual lines + for(i = CON_LINES_COUNT - 1; i >= 0; --i) + if((CON_LINES(i).mask & mask_must) == mask_must) + if((CON_LINES(i).mask & mask_mustnot) == 0) + { + int h = Con_LineHeight(i); + + // line is the last visible line? + *last = i; + if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll) + { + *limitlast = lines_seen + h - con_backscroll; + return; + } + + lines_seen += h; + } + + // if we get here, no line was on screen - scroll so that one line is + // visible then. + con_backscroll = lines_seen - 1; + *limitlast = 1; +} + +/* +================ +Con_DrawConsole + +Draws the console with the solid background +The typing input line at the bottom should only be drawn if typing is allowed +================ +*/ +void Con_DrawConsole (int lines) +{ + float alpha, alpha0; + double sx, sy; + int mask_must = 0; + int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER; + cachepic_t *conbackpic; + + if (lines <= 0) + return; + + if (con_mutex) Thread_LockMutex(con_mutex); + + if (con_backscroll < 0) + con_backscroll = 0; + + con_vislines = lines; + + r_draw2d_force = true; + +// draw the background + alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game + if((alpha = alpha0 * scr_conalphafactor.value) > 0) + { + sx = scr_conscroll_x.value; + sy = scr_conscroll_y.value; + conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL; + sx *= realtime; sy *= realtime; + sx -= floor(sx); sy -= floor(sy); + if (conbackpic && conbackpic->tex != r_texture_notexture) + DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, + 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0); + else + DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0); + } + if((alpha = alpha0 * scr_conalpha2factor.value) > 0) + { + sx = scr_conscroll2_x.value; + sy = scr_conscroll2_y.value; + conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0); + sx *= realtime; sy *= realtime; + sx -= floor(sx); sy -= floor(sy); + if(conbackpic && conbackpic->tex != r_texture_notexture) + DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, + 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0); + } + if((alpha = alpha0 * scr_conalpha3factor.value) > 0) + { + sx = scr_conscroll3_x.value; + sy = scr_conscroll3_y.value; + conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0); + sx *= realtime; sy *= realtime; + sx -= floor(sx); sy -= floor(sy); + if(conbackpic && conbackpic->tex != r_texture_notexture) + DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, + 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, + 0); + } + DrawQ_String(vid_conwidth.integer - DrawQ_TextWidth(engineversion, 0, con_textsize.value, con_textsize.value, false, FONT_CONSOLE), lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE); + +// draw the text +#if 0 + { + int i; + int count = CON_LINES_COUNT; + float ymax = con_vislines - 2 * con_textsize.value; + float y = ymax + con_textsize.value * con_backscroll; + for (i = 0;i < count && y >= 0;i++) + y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value; + // fix any excessive scrollback for the next frame + if (i >= count && y >= 0) + { + con_backscroll -= (int)(y / con_textsize.value); + if (con_backscroll < 0) + con_backscroll = 0; + } + } +#else + if(CON_LINES_COUNT > 0) + { + int i, last, limitlast; + float y; + float ymax = con_vislines - 2 * con_textsize.value; + Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast); + //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast); + y = ymax - con_textsize.value; + + if(limitlast) + y += (CON_LINES(last).height - limitlast) * con_textsize.value; + i = last; + + for(;;) + { + y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value; + if(i == 0) + break; // top of console buffer + if(y < 0) + break; // top of console window + limitlast = 0; + --i; + } + } +#endif + +// draw the input prompt, user text, and cursor if desired + Con_DrawInput (); + + r_draw2d_force = false; + if (con_mutex) Thread_UnlockMutex(con_mutex); +} + +/* +GetMapList + +Made by [515] +Prints not only map filename, but also +its format (q1/q2/q3/hl) and even its message +*/ +//[515]: here is an ugly hack.. two gotos... oh my... *but it works* +//LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing +//LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto +//LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups... +qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength) +{ + fssearch_t *t; + char message[1024]; + int i, k, max, p, o, min; + unsigned char *len; + qfile_t *f; + unsigned char buf[1024]; + + dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s); + t = FS_Search(message, 1, true); + if(!t) + return false; + if (t->numfilenames > 1) + Con_Printf("^1 %i maps found :\n", t->numfilenames); + len = (unsigned char *)Z_Malloc(t->numfilenames); + min = 666; + for(max=i=0;inumfilenames;i++) + { + k = (int)strlen(t->filenames[i]); + k -= 9; + if(max < k) + max = k; + else + if(min > k) + min = k; + len[i] = k; + } + o = (int)strlen(s); + for(i=0;inumfilenames;i++) + { + int lumpofs = 0, lumplen = 0; + char *entities = NULL; + const char *data = NULL; + char keyname[64]; + char entfilename[MAX_QPATH]; + char desc[64]; + desc[0] = 0; + strlcpy(message, "^1ERROR: open failed^7", sizeof(message)); + p = 0; + f = FS_OpenVirtualFile(t->filenames[i], true); + if(f) + { + strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message)); + memset(buf, 0, 1024); + FS_Read(f, buf, 1024); + if (!memcmp(buf, "IBSP", 4)) + { + p = LittleLong(((int *)buf)[1]); + if (p == Q3BSPVERSION) + { + q3dheader_t *header = (q3dheader_t *)buf; + lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs); + lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen); + dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p); + } + else if (p == Q2BSPVERSION) + { + q2dheader_t *header = (q2dheader_t *)buf; + lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs); + lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen); + dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p); + } + else + dpsnprintf(desc, sizeof(desc), "IBSP%i", p); + } + else if (BuffLittleLong(buf) == BSPVERSION) + { + lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES); + lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4); + dpsnprintf(desc, sizeof(desc), "BSP29"); + } + else if (BuffLittleLong(buf) == 30) + { + lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES); + lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4); + dpsnprintf(desc, sizeof(desc), "BSPHL"); + } + else if (!memcmp(buf, "BSP2", 4)) + { + lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES); + lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4); + dpsnprintf(desc, sizeof(desc), "BSP2"); + } + else if (!memcmp(buf, "2PSB", 4)) + { + lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES); + lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4); + dpsnprintf(desc, sizeof(desc), "BSP2RMQe"); + } + else + { + dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf)); + } + strlcpy(entfilename, t->filenames[i], sizeof(entfilename)); + memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5); + entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL); + if (!entities && lumplen >= 10) + { + FS_Seek(f, lumpofs, SEEK_SET); + entities = (char *)Z_Malloc(lumplen + 1); + FS_Read(f, entities, lumplen); + } + if (entities) + { + // if there are entities to parse, a missing message key just + // means there is no title, so clear the message string now + message[0] = 0; + data = entities; + for (;;) + { + int l; + if (!COM_ParseToken_Simple(&data, false, false, true)) + break; + if (com_token[0] == '{') + continue; + if (com_token[0] == '}') + break; + // skip leading whitespace + for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++); + for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++) + keyname[l] = com_token[k+l]; + keyname[l] = 0; + if (!COM_ParseToken_Simple(&data, false, false, true)) + break; + if (developer_extra.integer) + Con_DPrintf("key: %s %s\n", keyname, com_token); + if (!strcmp(keyname, "message")) + { + // get the message contents + strlcpy(message, com_token, sizeof(message)); + break; + } + } + } + } + if (entities) + Z_Free(entities); + if(f) + FS_Close(f); + *(t->filenames[i]+len[i]+5) = 0; + Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message); + } + Con_Print("\n"); + for(p=o;pfilenames[0]+5+p); + if(k == 0) + goto endcomplete; + for(i=1;inumfilenames;i++) + if(*(t->filenames[i]+5+p) != k) + goto endcomplete; + } +endcomplete: + if(p > o && completedname && completednamebufferlength > 0) + { + memset(completedname, 0, completednamebufferlength); + memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1)); + } + Z_Free(len); + FS_FreeSearch(t); + return p > o; +} + +/* + Con_DisplayList + + New function for tab-completion system + Added by EvilTypeGuy + MEGA Thanks to Taniwha + +*/ +void Con_DisplayList(const char **list) +{ + int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4); + const char **walk = list; + + while (*walk) { + len = (int)strlen(*walk); + if (len > maxlen) + maxlen = len; + walk++; + } + maxlen += 1; + + while (*list) { + len = (int)strlen(*list); + if (pos + maxlen >= width) { + Con_Print("\n"); + pos = 0; + } + + Con_Print(*list); + for (i = 0; i < (maxlen - len); i++) + Con_Print(" "); + + pos += maxlen; + list++; + } + + if (pos) + Con_Print("\n\n"); +} + +/* + SanitizeString strips color tags from the string in + and writes the result on string out +*/ +static void SanitizeString(char *in, char *out) +{ + while(*in) + { + if(*in == STRING_COLOR_TAG) + { + ++in; + if(!*in) + { + out[0] = STRING_COLOR_TAG; + out[1] = 0; + return; + } + else if (*in >= '0' && *in <= '9') // ^[0-9] found + { + ++in; + if(!*in) + { + *out = 0; + return; + } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9] + continue; + } + else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found + { + if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) ) + { + in+=4; + if (!*in) + { + *out = 0; + return; + } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb + continue; + } + else in--; + } + else if (*in != STRING_COLOR_TAG) + --in; + } + *out = qfont_table[*(unsigned char*)in]; + ++in; + ++out; + } + *out = 0; +} + +// Now it becomes TRICKY :D --blub +static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that +static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches. +// means: when somebody uses a cvar's name as his name, we won't ever get his colors in there... +static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys +static int Nicks_matchpos; + +// co against <<:BLASTER:>> is true!? +static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len) +{ + while(a_len) + { + if(tolower(*a) == tolower(*b)) + { + if(*a == 0) + return 0; + --a_len; + ++a; + ++b; + continue; + } + if(!*a) + return -1; + if(!*b) + return 1; + if(*a == ' ') + return (*a < *b) ? -1 : 1; + if(*b == ' ') + ++b; + else + return (*a < *b) ? -1 : 1; + } + return 0; +} +static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len) +{ + char space_char; + if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)) + { + if(con_nickcompletion_flags.integer & NICKS_NO_SPACES) + return Nicks_strncasecmp_nospaces(a, b, a_len); + return strncasecmp(a, b, a_len); + } + + space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; + + // ignore non alphanumerics of B + // if A contains a non-alphanumeric, B must contain it as well though! + while(a_len) + { + qboolean alnum_a, alnum_b; + + if(tolower(*a) == tolower(*b)) + { + if(*a == 0) // end of both strings, they're equal + return 0; + --a_len; + ++a; + ++b; + continue; + } + // not equal, end of one string? + if(!*a) + return -1; + if(!*b) + return 1; + // ignore non alphanumerics + alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char); + alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char); + if(!alnum_a) // b must contain this + return (*a < *b) ? -1 : 1; + if(!alnum_b) + ++b; + // otherwise, both are alnum, they're just not equal, return the appropriate number + else + return (*a < *b) ? -1 : 1; + } + return 0; +} + + +/* Nicks_CompleteCountPossible + + Count the number of possible nicks to complete + */ +static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon) +{ + char name[128]; + int i, p; + int match; + int spos; + int count = 0; + + if(!con_nickcompletion.integer) + return 0; + + // changed that to 1 + if(!line[0])// || !line[1]) // we want at least... 2 written characters + return 0; + + for(i = 0; i < cl.maxclients; ++i) + { + p = i; + if(!cl.scores[p].name[0]) + continue; + + SanitizeString(cl.scores[p].name, name); + //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name); + + if(!name[0]) + continue; + + match = -1; + spos = pos - 1; // no need for a minimum of characters :) + + while(spos >= 0) + { + if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'') + { + if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start + !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start + { + --spos; + continue; + } + } + if(isCon && spos == 0) + break; + if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0) + match = spos; + --spos; + } + if(match < 0) + continue; + //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name); + strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count])); + + // the sanitized list + strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count])); + if(!count) + { + Nicks_matchpos = match; + } + + Nicks_offset[count] = s - (&line[match]); + //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]); + + ++count; + } + return count; +} + +static void Cmd_CompleteNicksPrint(int count) +{ + int i; + for(i = 0; i < count; ++i) + Con_Printf("%s\n", Nicks_list[i]); +} + +static void Nicks_CutMatchesNormal(int count) +{ + // cut match 0 down to the longest possible completion + int i; + unsigned int c, l; + c = strlen(Nicks_sanlist[0]) - 1; + for(i = 1; i < count; ++i) + { + l = strlen(Nicks_sanlist[i]) - 1; + if(l < c) + c = l; + + for(l = 0; l <= c; ++l) + if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l])) + { + c = l-1; + break; + } + } + Nicks_sanlist[0][c+1] = 0; + //Con_Printf("List0: %s\n", Nicks_sanlist[0]); +} + +static unsigned int Nicks_strcleanlen(const char *s) +{ + unsigned int l = 0; + while(*s) + { + if( (*s >= 'a' && *s <= 'z') || + (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9') || + *s == ' ') + ++l; + ++s; + } + return l; +} + +static void Nicks_CutMatchesAlphaNumeric(int count) +{ + // cut match 0 down to the longest possible completion + int i; + unsigned int c, l; + char tempstr[sizeof(Nicks_sanlist[0])]; + char *a, *b; + char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces + + c = strlen(Nicks_sanlist[0]); + for(i = 0, l = 0; i < (int)c; ++i) + { + if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') || + (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') || + (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED + { + tempstr[l++] = Nicks_sanlist[0][i]; + } + } + tempstr[l] = 0; + + for(i = 1; i < count; ++i) + { + a = tempstr; + b = Nicks_sanlist[i]; + while(1) + { + if(!*a) + break; + if(!*b) + { + *a = 0; + break; + } + if(tolower(*a) == tolower(*b)) + { + ++a; + ++b; + continue; + } + if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char) + { + // b is alnum, so cut + *a = 0; + break; + } + ++b; + } + } + // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit + Nicks_CutMatchesNormal(count); + //if(!Nicks_sanlist[0][0]) + if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr)) + { + // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there + strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0])); + } +} + +static void Nicks_CutMatchesNoSpaces(int count) +{ + // cut match 0 down to the longest possible completion + int i; + unsigned int c, l; + char tempstr[sizeof(Nicks_sanlist[0])]; + char *a, *b; + + c = strlen(Nicks_sanlist[0]); + for(i = 0, l = 0; i < (int)c; ++i) + { + if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied + { + tempstr[l++] = Nicks_sanlist[0][i]; + } + } + tempstr[l] = 0; + + for(i = 1; i < count; ++i) + { + a = tempstr; + b = Nicks_sanlist[i]; + while(1) + { + if(!*a) + break; + if(!*b) + { + *a = 0; + break; + } + if(tolower(*a) == tolower(*b)) + { + ++a; + ++b; + continue; + } + if(*b != ' ') + { + *a = 0; + break; + } + ++b; + } + } + // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit + Nicks_CutMatchesNormal(count); + //if(!Nicks_sanlist[0][0]) + //Con_Printf("TS: %s\n", tempstr); + if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr)) + { + // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there + strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0])); + } +} + +static void Nicks_CutMatches(int count) +{ + if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY) + Nicks_CutMatchesAlphaNumeric(count); + else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES) + Nicks_CutMatchesNoSpaces(count); + else + Nicks_CutMatchesNormal(count); +} + +static const char **Nicks_CompleteBuildList(int count) +{ + const char **buf; + int bpos = 0; + // the list is freed by Con_CompleteCommandLine, so create a char** + buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *)); + + for(; bpos < count; ++bpos) + buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos]; + + Nicks_CutMatches(count); + + buf[bpos] = NULL; + return buf; +} + +/* + Nicks_AddLastColor + Restores the previous used color, after the autocompleted name. +*/ +static int Nicks_AddLastColor(char *buffer, int pos) +{ + qboolean quote_added = false; + int match; + int color = STRING_COLOR_DEFAULT + '0'; + char r = 0, g = 0, b = 0; + + if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"') + { + // we'll have to add a quote :) + buffer[pos++] = '\"'; + quote_added = true; + } + + if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR) + { + // add color when no quote was added, or when flags &4? + // find last color + for(match = Nicks_matchpos-1; match >= 0; --match) + { + if(buffer[match] == STRING_COLOR_TAG) + { + if( isdigit(buffer[match+1]) ) + { + color = buffer[match+1]; + break; + } + else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR) + { + if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) ) + { + r = buffer[match+2]; + g = buffer[match+3]; + b = buffer[match+4]; + color = -1; + break; + } + } + } + } + if(!quote_added) + { + if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4 + pos -= 2; + else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR + && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) ) + pos -= 5; + } + buffer[pos++] = STRING_COLOR_TAG; + if (color == -1) + { + buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR; + buffer[pos++] = r; + buffer[pos++] = g; + buffer[pos++] = b; + } + else + buffer[pos++] = color; + } + return pos; +} + +int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos) +{ + int n; + /*if(!con_nickcompletion.integer) + return; is tested in Nicks_CompletionCountPossible */ + n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false); + if(n == 1) + { + size_t len; + char *msg; + + msg = Nicks_list[0]; + len = min(size - Nicks_matchpos - 3, strlen(msg)); + memcpy(&buffer[Nicks_matchpos], msg, len); + if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0 + len = Nicks_AddLastColor(buffer, Nicks_matchpos+len); + buffer[len++] = ' '; + buffer[len] = 0; + return len; + } else if(n > 1) + { + int len; + char *msg; + Con_Printf("\n%i possible nicks:\n", n); + Cmd_CompleteNicksPrint(n); + + Nicks_CutMatches(n); + + msg = Nicks_sanlist[0]; + len = min(size - Nicks_matchpos, strlen(msg)); + memcpy(&buffer[Nicks_matchpos], msg, len); + buffer[Nicks_matchpos + len] = 0; + //pos += len; + return Nicks_matchpos + len; + } + return pos; +} + + +/* + Con_CompleteCommandLine + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + Enhanced to tab-complete map names by [515] + +*/ +void Con_CompleteCommandLine (void) +{ + const char *cmd = ""; + char *s; + const char **list[4] = {0, 0, 0, 0}; + char s2[512]; + char command[512]; + int c, v, a, i, cmd_len, pos, k; + int n; // nicks --blub + const char *space, *patterns; + char vabuf[1024]; + + //find what we want to complete + pos = key_linepos; + while(--pos) + { + k = key_line[pos]; + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + } + pos++; + + s = key_line + pos; + strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor + key_line[key_linepos] = 0; //hide them + + space = strchr(key_line + 1, ' '); + if(space && pos == (space - key_line) + 1) + { + strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line))); + + patterns = Cvar_VariableString(va(vabuf, sizeof(vabuf), "con_completion_%s", command)); // TODO maybe use a better place for this? + if(patterns && !*patterns) + patterns = NULL; // get rid of the empty string + + if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map"))) + { + //maps search + char t[MAX_QPATH]; + if (GetMapList(s, t, sizeof(t))) + { + // first move the cursor + key_linepos += (int)strlen(t) - (int)strlen(s); + + // and now do the actual work + *s = 0; + strlcat(key_line, t, MAX_INPUTLINE); + strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor + + // and fix the cursor + if(key_linepos > (int) strlen(key_line)) + key_linepos = (int) strlen(key_line); + } + return; + } + else + { + if(patterns) + { + char t[MAX_QPATH]; + stringlist_t resultbuf, dirbuf; + + // Usage: + // // store completion patterns (space separated) for command foo in con_completion_foo + // set con_completion_foo "foodata/*.foodefault *.foo" + // foo + // + // Note: patterns with slash are always treated as absolute + // patterns; patterns without slash search in the innermost + // directory the user specified. There is no way to "complete into" + // a directory as of now, as directories seem to be unknown to the + // FS subsystem. + // + // Examples: + // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm" + // set con_completion_playdemo "*.dem" + // set con_completion_play "*.wav *.ogg" + // + // TODO somehow add support for directories; these shall complete + // to their name + an appended slash. + + stringlistinit(&resultbuf); + stringlistinit(&dirbuf); + while(COM_ParseToken_Simple(&patterns, false, false, true)) + { + fssearch_t *search; + if(strchr(com_token, '/')) + { + search = FS_Search(com_token, true, true); + } + else + { + const char *slash = strrchr(s, '/'); + if(slash) + { + strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash + strlcat(t, com_token, sizeof(t)); + search = FS_Search(t, true, true); + } + else + search = FS_Search(com_token, true, true); + } + if(search) + { + for(i = 0; i < search->numfilenames; ++i) + if(!strncmp(search->filenames[i], s, strlen(s))) + if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE) + stringlistappend(&resultbuf, search->filenames[i]); + FS_FreeSearch(search); + } + } + + // In any case, add directory names + { + fssearch_t *search; + const char *slash = strrchr(s, '/'); + if(slash) + { + strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash + strlcat(t, "*", sizeof(t)); + search = FS_Search(t, true, true); + } + else + search = FS_Search("*", true, true); + if(search) + { + for(i = 0; i < search->numfilenames; ++i) + if(!strncmp(search->filenames[i], s, strlen(s))) + if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY) + stringlistappend(&dirbuf, search->filenames[i]); + FS_FreeSearch(search); + } + } + + if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0) + { + const char *p, *q; + unsigned int matchchars; + if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1) + { + dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]); + } + else + if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0) + { + dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]); + } + else + { + stringlistsort(&resultbuf, true); // dirbuf is already sorted + Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings); + for(i = 0; i < dirbuf.numstrings; ++i) + { + Con_Printf("^4%s^7/\n", dirbuf.strings[i]); + } + for(i = 0; i < resultbuf.numstrings; ++i) + { + Con_Printf("%s\n", resultbuf.strings[i]); + } + matchchars = sizeof(t) - 1; + if(resultbuf.numstrings > 0) + { + p = resultbuf.strings[0]; + q = resultbuf.strings[resultbuf.numstrings - 1]; + for(; *p && *p == *q; ++p, ++q); + matchchars = (unsigned int)(p - resultbuf.strings[0]); + } + if(dirbuf.numstrings > 0) + { + p = dirbuf.strings[0]; + q = dirbuf.strings[dirbuf.numstrings - 1]; + for(; *p && *p == *q; ++p, ++q); + matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0])); + } + // now p points to the first non-equal character, or to the end + // of resultbuf.strings[0]. We want to append the characters + // from resultbuf.strings[0] to (not including) p as these are + // the unique prefix + strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t))); + } + + // first move the cursor + key_linepos += (int)strlen(t) - (int)strlen(s); + + // and now do the actual work + *s = 0; + strlcat(key_line, t, MAX_INPUTLINE); + strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor + + // and fix the cursor + if(key_linepos > (int) strlen(key_line)) + key_linepos = (int) strlen(key_line); + } + stringlistfreecontents(&resultbuf); + stringlistfreecontents(&dirbuf); + + return; // bail out, when we complete for a command that wants a file name + } + } + } + + // Count number of possible matches and print them + c = Cmd_CompleteCountPossible(s); + if (c) + { + Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":"); + Cmd_CompleteCommandPrint(s); + } + v = Cvar_CompleteCountPossible(s); + if (v) + { + Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":"); + Cvar_CompleteCvarPrint(s); + } + a = Cmd_CompleteAliasCountPossible(s); + if (a) + { + Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":"); + Cmd_CompleteAliasPrint(s); + } + n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true); + if (n) + { + Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":"); + Cmd_CompleteNicksPrint(n); + } + + if (!(c + v + a + n)) // No possible matches + { + if(s2[0]) + strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos); + return; + } + + if (c) + cmd = *(list[0] = Cmd_CompleteBuildList(s)); + if (v) + cmd = *(list[1] = Cvar_CompleteBuildList(s)); + if (a) + cmd = *(list[2] = Cmd_CompleteAliasBuildList(s)); + if (n) + cmd = *(list[3] = Nicks_CompleteBuildList(n)); + + for (cmd_len = (int)strlen(s);;cmd_len++) + { + const char **l; + for (i = 0; i < 3; i++) + if (list[i]) + for (l = list[i];*l;l++) + if ((*l)[cmd_len] != cmd[cmd_len]) + goto done; + // all possible matches share this character, so we continue... + if (!cmd[cmd_len]) + { + // if all matches ended at the same position, stop + // (this means there is only one match) + break; + } + } +done: + + // prevent a buffer overrun by limiting cmd_len according to remaining space + cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos); + if (cmd) + { + key_linepos = pos; + memcpy(&key_line[key_linepos], cmd, cmd_len); + key_linepos += cmd_len; + // if there is only one match, add a space after it + if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1) + { + if(n) + { // was a nick, might have an offset, and needs colors ;) --blub + key_linepos = pos - Nicks_offset[0]; + cmd_len = strlen(Nicks_list[0]); + cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos); + + memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len); + key_linepos += cmd_len; + if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0 + key_linepos = Nicks_AddLastColor(key_line, key_linepos); + } + key_line[key_linepos++] = ' '; + } + } + + // use strlcat to avoid a buffer overrun + key_line[key_linepos] = 0; + strlcat(key_line, s2, sizeof(key_line)); + + // free the command, cvar, and alias lists + for (i = 0; i < 4; i++) + if (list[i]) + Mem_Free((void *)list[i]); +} + diff --git a/app/jni/console.h b/app/jni/console.h new file mode 100644 index 0000000..860c501 --- /dev/null +++ b/app/jni/console.h @@ -0,0 +1,151 @@ +/* +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. + +*/ + +#ifndef CONSOLE_H +#define CONSOLE_H + +// +// console +// +extern int con_totallines; +extern int con_backscroll; +extern qboolean con_initialized; + +void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol); +void Con_Rcon_Redirect_End(void); +void Con_Rcon_Redirect_Abort(void); + +/// If the line width has changed, reformat the buffer. +void Con_CheckResize (void); +void Con_Init (void); +void Con_Init_Commands (void); +void Con_Shutdown (void); +void Con_DrawConsole (int lines); + +/// Prints to a chosen console target +void Con_MaskPrint(int mask, const char *msg); + +// Prints to a chosen console target +void Con_MaskPrintf(int mask, const char *fmt, ...) DP_FUNC_PRINTF(2); + +/// Prints to all appropriate console targets, and adds timestamps +void Con_Print(const char *txt); + +/// Prints to all appropriate console targets. +void Con_Printf(const char *fmt, ...) DP_FUNC_PRINTF(1); + +/// A Con_Print that only shows up if the "developer" cvar is set. +void Con_DPrint(const char *msg); + +/// A Con_Printf that only shows up if the "developer" cvar is set +void Con_DPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); +void Con_Clear_f (void); +void Con_DrawNotify (void); + +/// Clear all notify lines. +void Con_ClearNotify (void); +void Con_ToggleConsole_f (void); + +int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos); + +qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength); + +/// wrapper function to attempt to either complete the command line +/// or to list possible matches grouped by type +/// (i.e. will display possible variables, aliases, commands +/// that match what they've typed so far) +void Con_CompleteCommandLine(void); + +/// Generic libs/util/console.c function to display a list +/// formatted in columns on the console +void Con_DisplayList(const char **list); + + +/*! \name log + * @{ + */ +void Log_Init (void); +void Log_Close (void); +void Log_Start (void); +void Log_DestBuffer_Flush (void); ///< call this once per frame to send out replies to rcon streaming clients + +void Log_Printf(const char *logfilename, const char *fmt, ...) DP_FUNC_PRINTF(2); +//@} + +// CON_MASK_PRINT is the default (Con_Print/Con_Printf) +// CON_MASK_DEVELOPER is used by Con_DPrint/Con_DPrintf +#define CON_MASK_HIDENOTIFY 128 +#define CON_MASK_CHAT 1 +#define CON_MASK_INPUT 2 +#define CON_MASK_DEVELOPER 4 +#define CON_MASK_PRINT 8 + +typedef struct con_lineinfo_s +{ + char *start; + size_t len; + int mask; + + /// used only by console.c + double addtime; + int height; ///< recalculated line height when needed (-1 to unset) +} +con_lineinfo_t; + +typedef struct conbuffer_s +{ + qboolean active; + int textsize; + char *text; + int maxlines; + con_lineinfo_t *lines; + int lines_first; + int lines_count; ///< cyclic buffer +} +conbuffer_t; + +#define CONBUFFER_LINES(buf, i) (buf)->lines[((buf)->lines_first + (i)) % (buf)->maxlines] +#define CONBUFFER_LINES_COUNT(buf) ((buf)->lines_count) +#define CONBUFFER_LINES_LAST(buf) CONBUFFER_LINES(buf, CONBUFFER_LINES_COUNT(buf) - 1) + +void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool); +void ConBuffer_Clear (conbuffer_t *buf); +void ConBuffer_Shutdown(conbuffer_t *buf); + +/*! Notifies the console code about the current time + * (and shifts back times of other entries when the time + * went backwards) + */ +void ConBuffer_FixTimes(conbuffer_t *buf); + +/// Deletes the first line from the console history. +void ConBuffer_DeleteLine(conbuffer_t *buf); + +/// Deletes the last line from the console history. +void ConBuffer_DeleteLastLine(conbuffer_t *buf); + +/// Appends a given string as a new line to the console. +void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask); +int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start); +int ConBuffer_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start); +const char *ConBuffer_GetLine(conbuffer_t *buf, int i); + +#endif + diff --git a/app/jni/crypto.c b/app/jni/crypto.c new file mode 100644 index 0000000..a681379 --- /dev/null +++ b/app/jni/crypto.c @@ -0,0 +1,2588 @@ +// TODO key loading, generating, saving +#include "quakedef.h" +#include "crypto.h" +#include "common.h" +#include "thread.h" + +#include "hmac.h" +#include "libcurl.h" + +cvar_t crypto_developer = {CVAR_SAVE, "crypto_developer", "0", "print extra info about crypto handshake"}; +cvar_t crypto_servercpupercent = {CVAR_SAVE, "crypto_servercpupercent", "10", "allowed crypto CPU load in percent for server operation (0 = no limit, faster)"}; +cvar_t crypto_servercpumaxtime = {CVAR_SAVE, "crypto_servercpumaxtime", "0.01", "maximum allowed crypto CPU time per frame (0 = no limit)"}; +cvar_t crypto_servercpudebug = {CVAR_SAVE, "crypto_servercpudebug", "0", "print statistics about time usage by crypto"}; +static double crypto_servercpu_accumulator = 0; +static double crypto_servercpu_lastrealtime = 0; +cvar_t crypto_aeslevel = {CVAR_SAVE, "crypto_aeslevel", "1", "whether to support AES encryption in authenticated connections (0 = no, 1 = supported, 2 = requested, 3 = required)"}; +int crypto_keyfp_recommended_length; +static const char *crypto_idstring = NULL; +static char crypto_idstring_buf[512]; + +#define PROTOCOL_D0_BLIND_ID FOURCC_D0PK +#define PROTOCOL_VLEN (('v' << 0) | ('l' << 8) | ('e' << 16) | ('n' << 24)) + +// BEGIN stuff shared with crypto-keygen-standalone +#define FOURCC_D0PK (('d' << 0) | ('0' << 8) | ('p' << 16) | ('k' << 24)) +#define FOURCC_D0SK (('d' << 0) | ('0' << 8) | ('s' << 16) | ('k' << 24)) +#define FOURCC_D0PI (('d' << 0) | ('0' << 8) | ('p' << 16) | ('i' << 24)) +#define FOURCC_D0SI (('d' << 0) | ('0' << 8) | ('s' << 16) | ('i' << 24)) +#define FOURCC_D0IQ (('d' << 0) | ('0' << 8) | ('i' << 16) | ('q' << 24)) +#define FOURCC_D0IR (('d' << 0) | ('0' << 8) | ('i' << 16) | ('r' << 24)) +#define FOURCC_D0ER (('d' << 0) | ('0' << 8) | ('e' << 16) | ('r' << 24)) +#define FOURCC_D0IC (('d' << 0) | ('0' << 8) | ('i' << 16) | ('c' << 24)) + +static unsigned long Crypto_LittleLong(const char *data) +{ + return + ((unsigned char) data[0]) | + (((unsigned char) data[1]) << 8) | + (((unsigned char) data[2]) << 16) | + (((unsigned char) data[3]) << 24); +} + +static void Crypto_UnLittleLong(char *data, unsigned long l) +{ + data[0] = l & 0xFF; + data[1] = (l >> 8) & 0xFF; + data[2] = (l >> 16) & 0xFF; + data[3] = (l >> 24) & 0xFF; +} + +static size_t Crypto_ParsePack(const char *buf, size_t len, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + if(Crypto_LittleLong(buf) != header) + return 0; + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 > len) + return 0; + lumpsize[i] = Crypto_LittleLong(&buf[pos]); + pos += 4; + if(pos + lumpsize[i] > len) + return 0; + lumps[i] = &buf[pos]; + pos += lumpsize[i]; + } + return pos; +} + +static size_t Crypto_UnParsePack(char *buf, size_t len, unsigned long header, const char *const *lumps, const size_t *lumpsize, size_t nlumps) +{ + size_t i; + size_t pos; + pos = 0; + if(header) + { + if(len < 4) + return 0; + Crypto_UnLittleLong(buf, header); + pos += 4; + } + for(i = 0; i < nlumps; ++i) + { + if(pos + 4 + lumpsize[i] > len) + return 0; + Crypto_UnLittleLong(&buf[pos], lumpsize[i]); + pos += 4; + memcpy(&buf[pos], lumps[i], lumpsize[i]); + pos += lumpsize[i]; + } + return pos; +} +// END stuff shared with xonotic-keygen + +#define USE_AES + +#ifdef CRYPTO_STATIC + +#include + +#define d0_blind_id_dll 1 +#define Crypto_OpenLibrary() true +#define Crypto_CloseLibrary() + +#define qd0_blind_id_new d0_blind_id_new +#define qd0_blind_id_free d0_blind_id_free +//#define qd0_blind_id_clear d0_blind_id_clear +#define qd0_blind_id_copy d0_blind_id_copy +//#define qd0_blind_id_generate_private_key d0_blind_id_generate_private_key +//#define qd0_blind_id_generate_private_key_fastreject d0_blind_id_generate_private_key_fastreject +//#define qd0_blind_id_read_private_key d0_blind_id_read_private_key +#define qd0_blind_id_read_public_key d0_blind_id_read_public_key +//#define qd0_blind_id_write_private_key d0_blind_id_write_private_key +//#define qd0_blind_id_write_public_key d0_blind_id_write_public_key +#define qd0_blind_id_fingerprint64_public_key d0_blind_id_fingerprint64_public_key +//#define qd0_blind_id_generate_private_id_modulus d0_blind_id_generate_private_id_modulus +#define qd0_blind_id_read_private_id_modulus d0_blind_id_read_private_id_modulus +//#define qd0_blind_id_write_private_id_modulus d0_blind_id_write_private_id_modulus +#define qd0_blind_id_generate_private_id_start d0_blind_id_generate_private_id_start +#define qd0_blind_id_generate_private_id_request d0_blind_id_generate_private_id_request +//#define qd0_blind_id_answer_private_id_request d0_blind_id_answer_private_id_request +#define qd0_blind_id_finish_private_id_request d0_blind_id_finish_private_id_request +//#define qd0_blind_id_read_private_id_request_camouflage d0_blind_id_read_private_id_request_camouflage +//#define qd0_blind_id_write_private_id_request_camouflage d0_blind_id_write_private_id_request_camouflage +#define qd0_blind_id_read_private_id d0_blind_id_read_private_id +//#define qd0_blind_id_read_public_id d0_blind_id_read_public_id +#define qd0_blind_id_write_private_id d0_blind_id_write_private_id +//#define qd0_blind_id_write_public_id d0_blind_id_write_public_id +#define qd0_blind_id_authenticate_with_private_id_start d0_blind_id_authenticate_with_private_id_start +#define qd0_blind_id_authenticate_with_private_id_challenge d0_blind_id_authenticate_with_private_id_challenge +#define qd0_blind_id_authenticate_with_private_id_response d0_blind_id_authenticate_with_private_id_response +#define qd0_blind_id_authenticate_with_private_id_verify d0_blind_id_authenticate_with_private_id_verify +#define qd0_blind_id_fingerprint64_public_id d0_blind_id_fingerprint64_public_id +#define qd0_blind_id_sessionkey_public_id d0_blind_id_sessionkey_public_id +#define qd0_blind_id_INITIALIZE d0_blind_id_INITIALIZE +#define qd0_blind_id_SHUTDOWN d0_blind_id_SHUTDOWN +#define qd0_blind_id_util_sha256 d0_blind_id_util_sha256 +#define qd0_blind_id_sign_with_private_id_sign d0_blind_id_sign_with_private_id_sign +#define qd0_blind_id_sign_with_private_id_sign_detached d0_blind_id_sign_with_private_id_sign_detached +#define qd0_blind_id_setmallocfuncs d0_blind_id_setmallocfuncs +#define qd0_blind_id_setmutexfuncs d0_blind_id_setmutexfuncs +#define qd0_blind_id_verify_public_id d0_blind_id_verify_public_id +#define qd0_blind_id_verify_private_id d0_blind_id_verify_private_id + +#else + +// d0_blind_id interface +#define D0_EXPORT +#ifdef __GNUC__ +#define D0_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define D0_WARN_UNUSED_RESULT +#endif +#define D0_BOOL int + +typedef void *(d0_malloc_t)(size_t len); +typedef void (d0_free_t)(void *p); +typedef void *(d0_createmutex_t)(void); +typedef void (d0_destroymutex_t)(void *); +typedef int (d0_lockmutex_t)(void *); // zero on success +typedef int (d0_unlockmutex_t)(void *); // zero on success + +typedef struct d0_blind_id_s d0_blind_id_t; +typedef D0_BOOL (*d0_fastreject_function) (const d0_blind_id_t *ctx, void *pass); +static D0_EXPORT D0_WARN_UNUSED_RESULT d0_blind_id_t *(*qd0_blind_id_new) (void); +static D0_EXPORT void (*qd0_blind_id_free) (d0_blind_id_t *a); +//static D0_EXPORT void (*qd0_blind_id_clear) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_copy) (d0_blind_id_t *ctx, const d0_blind_id_t *src); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_key) (d0_blind_id_t *ctx, int k); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_key_fastreject) (d0_blind_id_t *ctx, int k, d0_fastreject_function reject, void *pass); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_key) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_public_key) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_public_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_fingerprint64_public_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_modulus) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id_modulus) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id_modulus) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_start) (d0_blind_id_t *ctx); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_request) (d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_answer_private_id_request) (const d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_finish_private_id_request) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id_request_camouflage) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id_request_camouflage) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_public_id) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +//static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_start) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_challenge) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL recv_modulus, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen, D0_BOOL *status); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_response) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_verify) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *msg, size_t *msglen, D0_BOOL *status); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_fingerprint64_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sessionkey_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); // can only be done after successful key exchange, this performs a modpow; key length is limited by SHA_DIGESTSIZE for now; also ONLY valid after successful d0_blind_id_authenticate_with_private_id_verify/d0_blind_id_fingerprint64_public_id +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_INITIALIZE) (void); +static D0_EXPORT void (*qd0_blind_id_SHUTDOWN) (void); +static D0_EXPORT void (*qd0_blind_id_util_sha256) (char *out, const char *in, size_t n); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sign_with_private_id_sign) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sign_with_private_id_sign_detached) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); +static D0_EXPORT void (*qd0_blind_id_setmallocfuncs)(d0_malloc_t *m, d0_free_t *f); +static D0_EXPORT void (*qd0_blind_id_setmutexfuncs)(d0_createmutex_t *c, d0_destroymutex_t *d, d0_lockmutex_t *l, d0_unlockmutex_t *u); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_verify_public_id)(const d0_blind_id_t *ctx, D0_BOOL *status); +static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_verify_private_id)(const d0_blind_id_t *ctx); +static dllfunction_t d0_blind_id_funcs[] = +{ + {"d0_blind_id_new", (void **) &qd0_blind_id_new}, + {"d0_blind_id_free", (void **) &qd0_blind_id_free}, + //{"d0_blind_id_clear", (void **) &qd0_blind_id_clear}, + {"d0_blind_id_copy", (void **) &qd0_blind_id_copy}, + //{"d0_blind_id_generate_private_key", (void **) &qd0_blind_id_generate_private_key}, + //{"d0_blind_id_generate_private_key_fastreject", (void **) &qd0_blind_id_generate_private_key_fastreject}, + //{"d0_blind_id_read_private_key", (void **) &qd0_blind_id_read_private_key}, + {"d0_blind_id_read_public_key", (void **) &qd0_blind_id_read_public_key}, + //{"d0_blind_id_write_private_key", (void **) &qd0_blind_id_write_private_key}, + //{"d0_blind_id_write_public_key", (void **) &qd0_blind_id_write_public_key}, + {"d0_blind_id_fingerprint64_public_key", (void **) &qd0_blind_id_fingerprint64_public_key}, + //{"d0_blind_id_generate_private_id_modulus", (void **) &qd0_blind_id_generate_private_id_modulus}, + {"d0_blind_id_read_private_id_modulus", (void **) &qd0_blind_id_read_private_id_modulus}, + //{"d0_blind_id_write_private_id_modulus", (void **) &qd0_blind_id_write_private_id_modulus}, + {"d0_blind_id_generate_private_id_start", (void **) &qd0_blind_id_generate_private_id_start}, + {"d0_blind_id_generate_private_id_request", (void **) &qd0_blind_id_generate_private_id_request}, + //{"d0_blind_id_answer_private_id_request", (void **) &qd0_blind_id_answer_private_id_request}, + {"d0_blind_id_finish_private_id_request", (void **) &qd0_blind_id_finish_private_id_request}, + //{"d0_blind_id_read_private_id_request_camouflage", (void **) &qd0_blind_id_read_private_id_request_camouflage}, + //{"d0_blind_id_write_private_id_request_camouflage", (void **) &qd0_blind_id_write_private_id_request_camouflage}, + {"d0_blind_id_read_private_id", (void **) &qd0_blind_id_read_private_id}, + //{"d0_blind_id_read_public_id", (void **) &qd0_blind_id_read_public_id}, + {"d0_blind_id_write_private_id", (void **) &qd0_blind_id_write_private_id}, + //{"d0_blind_id_write_public_id", (void **) &qd0_blind_id_write_public_id}, + {"d0_blind_id_authenticate_with_private_id_start", (void **) &qd0_blind_id_authenticate_with_private_id_start}, + {"d0_blind_id_authenticate_with_private_id_challenge", (void **) &qd0_blind_id_authenticate_with_private_id_challenge}, + {"d0_blind_id_authenticate_with_private_id_response", (void **) &qd0_blind_id_authenticate_with_private_id_response}, + {"d0_blind_id_authenticate_with_private_id_verify", (void **) &qd0_blind_id_authenticate_with_private_id_verify}, + {"d0_blind_id_fingerprint64_public_id", (void **) &qd0_blind_id_fingerprint64_public_id}, + {"d0_blind_id_sessionkey_public_id", (void **) &qd0_blind_id_sessionkey_public_id}, + {"d0_blind_id_INITIALIZE", (void **) &qd0_blind_id_INITIALIZE}, + {"d0_blind_id_SHUTDOWN", (void **) &qd0_blind_id_SHUTDOWN}, + {"d0_blind_id_util_sha256", (void **) &qd0_blind_id_util_sha256}, + {"d0_blind_id_sign_with_private_id_sign", (void **) &qd0_blind_id_sign_with_private_id_sign}, + {"d0_blind_id_sign_with_private_id_sign_detached", (void **) &qd0_blind_id_sign_with_private_id_sign_detached}, + {"d0_blind_id_setmallocfuncs", (void **) &qd0_blind_id_setmallocfuncs}, + {"d0_blind_id_setmutexfuncs", (void **) &qd0_blind_id_setmutexfuncs}, + {"d0_blind_id_verify_public_id", (void **) &qd0_blind_id_verify_public_id}, + {"d0_blind_id_verify_private_id", (void **) &qd0_blind_id_verify_private_id}, + {NULL, NULL} +}; +// end of d0_blind_id interface + +static dllhandle_t d0_blind_id_dll = NULL; +static qboolean Crypto_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libd0_blind_id-0.dll", +#elif defined(MACOSX) + "libd0_blind_id.0.dylib", +#else + "libd0_blind_id.so.0", + "libd0_blind_id.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (d0_blind_id_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &d0_blind_id_dll, d0_blind_id_funcs); +} + +static void Crypto_CloseLibrary (void) +{ + Sys_UnloadLibrary (&d0_blind_id_dll); +} + +#endif + +#ifdef CRYPTO_RIJNDAEL_STATIC + +#include + +#define d0_rijndael_dll 1 +#define Crypto_Rijndael_OpenLibrary() true +#define Crypto_Rijndael_CloseLibrary() + +#define qd0_rijndael_setup_encrypt d0_rijndael_setup_encrypt +#define qd0_rijndael_setup_decrypt d0_rijndael_setup_decrypt +#define qd0_rijndael_encrypt d0_rijndael_encrypt +#define qd0_rijndael_decrypt d0_rijndael_decrypt + +#else + +// no need to do the #define dance here, as the upper part declares out macros either way + +D0_EXPORT int (*qd0_rijndael_setup_encrypt) (unsigned long *rk, const unsigned char *key, + int keybits); +D0_EXPORT int (*qd0_rijndael_setup_decrypt) (unsigned long *rk, const unsigned char *key, + int keybits); +D0_EXPORT void (*qd0_rijndael_encrypt) (const unsigned long *rk, int nrounds, + const unsigned char plaintext[16], unsigned char ciphertext[16]); +D0_EXPORT void (*qd0_rijndael_decrypt) (const unsigned long *rk, int nrounds, + const unsigned char ciphertext[16], unsigned char plaintext[16]); +#define D0_RIJNDAEL_KEYLENGTH(keybits) ((keybits)/8) +#define D0_RIJNDAEL_RKLENGTH(keybits) ((keybits)/8+28) +#define D0_RIJNDAEL_NROUNDS(keybits) ((keybits)/32+6) +static dllfunction_t d0_rijndael_funcs[] = +{ + {"d0_rijndael_setup_decrypt", (void **) &qd0_rijndael_setup_decrypt}, + {"d0_rijndael_setup_encrypt", (void **) &qd0_rijndael_setup_encrypt}, + {"d0_rijndael_decrypt", (void **) &qd0_rijndael_decrypt}, + {"d0_rijndael_encrypt", (void **) &qd0_rijndael_encrypt}, + {NULL, NULL} +}; +// end of d0_blind_id interface + +static dllhandle_t d0_rijndael_dll = NULL; +static qboolean Crypto_Rijndael_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libd0_rijndael-0.dll", +#elif defined(MACOSX) + "libd0_rijndael.0.dylib", +#else + "libd0_rijndael.so.0", + "libd0_rijndael.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (d0_rijndael_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &d0_rijndael_dll, d0_rijndael_funcs); +} + +static void Crypto_Rijndael_CloseLibrary (void) +{ + Sys_UnloadLibrary (&d0_rijndael_dll); +} + +#endif + +// various helpers +void sha256(unsigned char *out, const unsigned char *in, int n) +{ + qd0_blind_id_util_sha256((char *) out, (const char *) in, n); +} + +static size_t Crypto_LoadFile(const char *path, char *buf, size_t nmax, qboolean inuserdir) +{ + char vabuf[1024]; + qfile_t *f = NULL; + fs_offset_t n; + if(inuserdir) + f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%s%s", *fs_userdir ? fs_userdir : fs_basedir, path), "rb", false); + else + f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%s%s", fs_basedir, path), "rb", false); + if(!f) + return 0; + n = FS_Read(f, buf, nmax); + if(n < 0) + n = 0; + FS_Close(f); + return (size_t) n; +} + +static qboolean PutWithNul(char **data, size_t *len, const char *str) +{ + // invariant: data points to insertion point + size_t l = strlen(str); + if(l >= *len) + return false; + memcpy(*data, str, l+1); + *data += l+1; + *len -= l+1; + return true; +} + +static const char *GetUntilNul(const char **data, size_t *len) +{ + // invariant: data points to next character to take + const char *data_save = *data; + size_t n; + const char *p; + + if(!*data) + return NULL; + + if(!*len) + { + *data = NULL; + return NULL; + } + + p = (const char *) memchr(*data, 0, *len); + if(!p) // no terminating NUL + { + *data = NULL; + *len = 0; + return NULL; + } + else + { + n = (p - *data) + 1; + *len -= n; + *data += n; + if(*len == 0) + *data = NULL; + return (const char *) data_save; + } + *data = NULL; + return NULL; +} + +// d0pk reading +static d0_blind_id_t *Crypto_ReadPublicKey(char *buf, size_t len) +{ + d0_blind_id_t *pk = NULL; + const char *p[2]; + size_t l[2]; + if(Crypto_ParsePack(buf, len, FOURCC_D0PK, p, l, 2)) + { + pk = qd0_blind_id_new(); + if(pk) + if(qd0_blind_id_read_public_key(pk, p[0], l[0])) + if(qd0_blind_id_read_private_id_modulus(pk, p[1], l[1])) + return pk; + } + if(pk) + qd0_blind_id_free(pk); + return NULL; +} + +// d0si reading +static qboolean Crypto_AddPrivateKey(d0_blind_id_t *pk, char *buf, size_t len) +{ + const char *p[1]; + size_t l[1]; + if(Crypto_ParsePack(buf, len, FOURCC_D0SI, p, l, 1)) + { + if(qd0_blind_id_read_private_id(pk, p[0], l[0])) + return true; + } + return false; +} + +#define MAX_PUBKEYS 16 +static d0_blind_id_t *pubkeys[MAX_PUBKEYS]; +static char pubkeys_fp64[MAX_PUBKEYS][FP64_SIZE+1]; +static qboolean pubkeys_havepriv[MAX_PUBKEYS]; +static qboolean pubkeys_havesig[MAX_PUBKEYS]; +static char pubkeys_priv_fp64[MAX_PUBKEYS][FP64_SIZE+1]; +static char challenge_append[1400]; +static size_t challenge_append_length; + +static int keygen_i = -1; +static char keygen_buf[8192]; + +#define MAX_CRYPTOCONNECTS 16 +#define CRYPTOCONNECT_NONE 0 +#define CRYPTOCONNECT_PRECONNECT 1 +#define CRYPTOCONNECT_CONNECT 2 +#define CRYPTOCONNECT_RECONNECT 3 +#define CRYPTOCONNECT_DUPLICATE 4 +typedef struct server_cryptoconnect_s +{ + double lasttime; + lhnetaddress_t address; + crypto_t crypto; + int next_step; +} +server_cryptoconnect_t; +static server_cryptoconnect_t cryptoconnects[MAX_CRYPTOCONNECTS]; + +static int cdata_id = 0; +typedef struct +{ + d0_blind_id_t *id; + int s, c; + int next_step; + char challenge[2048]; + char wantserver_idfp[FP64_SIZE+1]; + qboolean wantserver_aes; + int cdata_id; +} +crypto_data_t; + +// crypto specific helpers +#define CDATA ((crypto_data_t *) crypto->data) +#define MAKE_CDATA if(!crypto->data) crypto->data = Z_Malloc(sizeof(crypto_data_t)) +#define CLEAR_CDATA if(crypto->data) { if(CDATA->id) qd0_blind_id_free(CDATA->id); Z_Free(crypto->data); } crypto->data = NULL + +static crypto_t *Crypto_ServerFindInstance(lhnetaddress_t *peeraddress, qboolean allow_create) +{ + crypto_t *crypto; + int i, best; + + if(!d0_blind_id_dll) + return NULL; // no support + + for(i = 0; i < MAX_CRYPTOCONNECTS; ++i) + if(LHNETADDRESS_Compare(peeraddress, &cryptoconnects[i].address)) + break; + if(i < MAX_CRYPTOCONNECTS && (allow_create || cryptoconnects[i].crypto.data)) + { + crypto = &cryptoconnects[i].crypto; + cryptoconnects[i].lasttime = realtime; + return crypto; + } + if(!allow_create) + return NULL; + best = 0; + for(i = 1; i < MAX_CRYPTOCONNECTS; ++i) + if(cryptoconnects[i].lasttime < cryptoconnects[best].lasttime) + best = i; + crypto = &cryptoconnects[best].crypto; + cryptoconnects[best].lasttime = realtime; + memcpy(&cryptoconnects[best].address, peeraddress, sizeof(cryptoconnects[best].address)); + CLEAR_CDATA; + return crypto; +} + +qboolean Crypto_ServerFinishInstance(crypto_t *out, crypto_t *crypto) +{ + // no check needed here (returned pointers are only used in prefilled fields) + if(!crypto || !crypto->authenticated) + { + Con_Printf("Passed an invalid crypto connect instance\n"); + memset(out, 0, sizeof(*out)); + return false; + } + CLEAR_CDATA; + memcpy(out, crypto, sizeof(*out)); + memset(crypto, 0, sizeof(*crypto)); + return true; +} + +crypto_t *Crypto_ServerGetInstance(lhnetaddress_t *peeraddress) +{ + // no check needed here (returned pointers are only used in prefilled fields) + return Crypto_ServerFindInstance(peeraddress, false); +} + +typedef struct crypto_storedhostkey_s +{ + struct crypto_storedhostkey_s *next; + lhnetaddress_t addr; + int keyid; + char idfp[FP64_SIZE+1]; + int aeslevel; +} +crypto_storedhostkey_t; +static crypto_storedhostkey_t *crypto_storedhostkey_hashtable[CRYPTO_HOSTKEY_HASHSIZE]; + +static void Crypto_InitHostKeys(void) +{ + int i; + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + crypto_storedhostkey_hashtable[i] = NULL; +} + +static void Crypto_ClearHostKeys(void) +{ + int i; + crypto_storedhostkey_t *hk, *hkn; + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + { + for(hk = crypto_storedhostkey_hashtable[i]; hk; hk = hkn) + { + hkn = hk->next; + Z_Free(hk); + } + crypto_storedhostkey_hashtable[i] = NULL; + } +} + +static qboolean Crypto_ClearHostKey(lhnetaddress_t *peeraddress) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t **hkp; + qboolean found = false; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hkp = &crypto_storedhostkey_hashtable[hashindex]; *hkp && LHNETADDRESS_Compare(&((*hkp)->addr), peeraddress); hkp = &((*hkp)->next)); + + if(*hkp) + { + crypto_storedhostkey_t *hk = *hkp; + *hkp = hk->next; + Z_Free(hk); + found = true; + } + + return found; +} + +static void Crypto_StoreHostKey(lhnetaddress_t *peeraddress, const char *keystring, qboolean complain) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t *hk; + int keyid; + char idfp[FP64_SIZE+1]; + int aeslevel; + + if(!d0_blind_id_dll) + return; + + // syntax of keystring: + // aeslevel id@key id@key ... + + if(!*keystring) + return; + aeslevel = bound(0, *keystring - '0', 3); + while(*keystring && *keystring != ' ') + ++keystring; + + keyid = -1; + while(*keystring && keyid < 0) + { + // id@key + const char *idstart, *idend, *keystart, *keyend; + ++keystring; // skip the space + idstart = keystring; + while(*keystring && *keystring != ' ' && *keystring != '@') + ++keystring; + idend = keystring; + if(!*keystring) + break; + ++keystring; + keystart = keystring; + while(*keystring && *keystring != ' ') + ++keystring; + keyend = keystring; + + if(idend - idstart == FP64_SIZE && keyend - keystart == FP64_SIZE) + { + for(keyid = 0; keyid < MAX_PUBKEYS; ++keyid) + if(pubkeys[keyid]) + if(!memcmp(pubkeys_fp64[keyid], keystart, FP64_SIZE)) + { + memcpy(idfp, idstart, FP64_SIZE); + idfp[FP64_SIZE] = 0; + break; + } + if(keyid >= MAX_PUBKEYS) + keyid = -1; + } + } + + if(keyid < 0) + return; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hk = crypto_storedhostkey_hashtable[hashindex]; hk && LHNETADDRESS_Compare(&hk->addr, peeraddress); hk = hk->next); + + if(hk) + { + if(complain) + { + if(hk->keyid != keyid || memcmp(hk->idfp, idfp, FP64_SIZE+1)) + Con_Printf("Server %s tried to change the host key to a value not in the host cache. Connecting to it will fail. To accept the new host key, do crypto_hostkey_clear %s\n", buf, buf); + if(hk->aeslevel > aeslevel) + Con_Printf("Server %s tried to reduce encryption status, not accepted. Connecting to it will fail. To accept, do crypto_hostkey_clear %s\n", buf, buf); + } + hk->aeslevel = max(aeslevel, hk->aeslevel); + return; + } + + // great, we did NOT have it yet + hk = (crypto_storedhostkey_t *) Z_Malloc(sizeof(*hk)); + memcpy(&hk->addr, peeraddress, sizeof(hk->addr)); + hk->keyid = keyid; + memcpy(hk->idfp, idfp, FP64_SIZE+1); + hk->next = crypto_storedhostkey_hashtable[hashindex]; + hk->aeslevel = aeslevel; + crypto_storedhostkey_hashtable[hashindex] = hk; +} + +qboolean Crypto_RetrieveHostKey(lhnetaddress_t *peeraddress, int *keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, int *aeslevel) +{ + char buf[128]; + int hashindex; + crypto_storedhostkey_t *hk; + + if(!d0_blind_id_dll) + return false; + + LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); + hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; + for(hk = crypto_storedhostkey_hashtable[hashindex]; hk && LHNETADDRESS_Compare(&hk->addr, peeraddress); hk = hk->next); + + if(!hk) + return false; + + if(keyid) + *keyid = hk->keyid; + if(keyfp) + strlcpy(keyfp, pubkeys_fp64[hk->keyid], keyfplen); + if(idfp) + strlcpy(idfp, hk->idfp, idfplen); + if(aeslevel) + *aeslevel = hk->aeslevel; + + return true; +} +int Crypto_RetrieveLocalKey(int keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, qboolean *issigned) // return value: -1 if more to come, +1 if valid, 0 if end of list +{ + if(keyid < 0 || keyid >= MAX_PUBKEYS) + return 0; + if(keyfp) + *keyfp = 0; + if(idfp) + *idfp = 0; + if(!pubkeys[keyid]) + return -1; + if(keyfp) + strlcpy(keyfp, pubkeys_fp64[keyid], keyfplen); + if(idfp) + if(pubkeys_havepriv[keyid]) + strlcpy(idfp, pubkeys_priv_fp64[keyid], keyfplen); + if(issigned) + *issigned = pubkeys_havesig[keyid]; + return 1; +} +// end + +// init/shutdown code +static void Crypto_BuildChallengeAppend(void) +{ + char *p, *lengthptr, *startptr; + size_t n; + int i; + p = challenge_append; + n = sizeof(challenge_append); + Crypto_UnLittleLong(p, PROTOCOL_VLEN); + p += 4; + n -= 4; + lengthptr = p; + Crypto_UnLittleLong(p, 0); + p += 4; + n -= 4; + Crypto_UnLittleLong(p, PROTOCOL_D0_BLIND_ID); + p += 4; + n -= 4; + startptr = p; + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys_havepriv[i]) + PutWithNul(&p, &n, pubkeys_fp64[i]); + PutWithNul(&p, &n, ""); + for(i = 0; i < MAX_PUBKEYS; ++i) + if(!pubkeys_havepriv[i] && pubkeys[i]) + PutWithNul(&p, &n, pubkeys_fp64[i]); + Crypto_UnLittleLong(lengthptr, p - startptr); + challenge_append_length = p - challenge_append; +} + +static qboolean Crypto_SavePubKeyTextFile(int i) +{ + qfile_t *f; + char vabuf[1024]; + + if(!pubkeys_havepriv[i]) + return false; + f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%skey_%d-public-fp%s.txt", *fs_userdir ? fs_userdir : fs_basedir, i, sessionid.string), "w", false); + if(!f) + return false; + + // we ignore errors for this file, as it's not necessary to have + FS_Printf(f, "ID-Fingerprint: %s\n", pubkeys_priv_fp64[i]); + FS_Printf(f, "ID-Is-Signed: %s\n", pubkeys_havesig[i] ? "yes" : "no"); + FS_Printf(f, "ID-Is-For-Key: %s\n", pubkeys_fp64[i]); + FS_Printf(f, "\n"); + FS_Printf(f, "This is a PUBLIC ID file for DarkPlaces.\n"); + FS_Printf(f, "You are free to share this file or its contents.\n"); + FS_Printf(f, "\n"); + FS_Printf(f, "This file will be automatically generated again if deleted.\n"); + FS_Printf(f, "\n"); + FS_Printf(f, "However, NEVER share the accompanying SECRET ID file called\n"); + FS_Printf(f, "key_%d.d0si%s, as doing so would compromise security!\n", i, sessionid.string); + FS_Close(f); + + return true; +} + +void Crypto_LoadKeys(void) +{ + char buf[8192]; + size_t len, len2; + int i; + char vabuf[1024]; + + if(!d0_blind_id_dll) // don't if we can't + return; + + if(crypto_idstring) // already loaded? then not + return; + + Host_LockSession(); // we use the session ID here + + // load keys + // note: we are just a CLIENT + // so we load: + // PUBLIC KEYS to accept (including modulus) + // PRIVATE KEY of user + + crypto_idstring = NULL; + dpsnprintf(crypto_idstring_buf, sizeof(crypto_idstring_buf), "%d", d0_rijndael_dll ? crypto_aeslevel.integer : 0); + for(i = 0; i < MAX_PUBKEYS; ++i) + { + memset(pubkeys_fp64[i], 0, sizeof(pubkeys_fp64[i])); + memset(pubkeys_priv_fp64[i], 0, sizeof(pubkeys_fp64[i])); + pubkeys_havepriv[i] = false; + pubkeys_havesig[i] = false; + len = Crypto_LoadFile(va(vabuf, sizeof(vabuf), "key_%d.d0pk", i), buf, sizeof(buf), false); + if((pubkeys[i] = Crypto_ReadPublicKey(buf, len))) + { + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_key(pubkeys[i], pubkeys_fp64[i], &len2)) // keeps final NUL + { + Con_Printf("Loaded public key key_%d.d0pk (fingerprint: %s)\n", i, pubkeys_fp64[i]); + len = Crypto_LoadFile(va(vabuf, sizeof(vabuf), "key_%d.d0si%s", i, sessionid.string), buf, sizeof(buf), true); + if(len) + { + if(Crypto_AddPrivateKey(pubkeys[i], buf, len)) + { + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_id(pubkeys[i], pubkeys_priv_fp64[i], &len2)) // keeps final NUL + { + D0_BOOL status = 0; + + Con_Printf("Loaded private ID key_%d.d0si%s for key_%d.d0pk (public key fingerprint: %s)\n", i, sessionid.string, i, pubkeys_priv_fp64[i]); + + // verify the key we just loaded (just in case) + if(qd0_blind_id_verify_private_id(pubkeys[i]) && qd0_blind_id_verify_public_id(pubkeys[i], &status)) + { + pubkeys_havepriv[i] = true; + strlcat(crypto_idstring_buf, va(vabuf, sizeof(vabuf), " %s@%s", pubkeys_priv_fp64[i], pubkeys_fp64[i]), sizeof(crypto_idstring_buf)); + + // verify the key we just got (just in case) + if(status) + pubkeys_havesig[i] = true; + else + Con_Printf("NOTE: this ID has not yet been signed!\n"); + + Crypto_SavePubKeyTextFile(i); + } + else + { + Con_Printf("d0_blind_id_verify_private_id failed, this is not a valid key!\n"); + qd0_blind_id_free(pubkeys[i]); + pubkeys[i] = NULL; + } + } + else + { + Con_Printf("d0_blind_id_fingerprint64_public_id failed\n"); + qd0_blind_id_free(pubkeys[i]); + pubkeys[i] = NULL; + } + } + } + } + else + { + // can't really happen + qd0_blind_id_free(pubkeys[i]); + pubkeys[i] = NULL; + } + } + } + crypto_idstring = crypto_idstring_buf; + + keygen_i = -1; + Crypto_BuildChallengeAppend(); + + // find a good prefix length for all the keys we know (yes, algorithm is not perfect yet, may yield too long prefix length) + crypto_keyfp_recommended_length = 0; + memset(buf+256, 0, MAX_PUBKEYS + MAX_PUBKEYS); + while(crypto_keyfp_recommended_length < FP64_SIZE) + { + memset(buf, 0, 256); + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + ++buf[(unsigned char) pubkeys_fp64[i][crypto_keyfp_recommended_length]]; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + ++buf[(unsigned char) pubkeys_priv_fp64[i][crypto_keyfp_recommended_length]]; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + if(buf[(unsigned char) pubkeys_fp64[i][crypto_keyfp_recommended_length]] < 2) + buf[256 + i] = 1; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + if(buf[(unsigned char) pubkeys_priv_fp64[i][crypto_keyfp_recommended_length]] < 2) + buf[256 + MAX_PUBKEYS + i] = 1; + } + ++crypto_keyfp_recommended_length; + for(i = 0; i < MAX_PUBKEYS; ++i) + if(pubkeys[i]) + { + if(!buf[256 + i]) + break; + if(pubkeys_havepriv[i]) + if(!buf[256 + MAX_PUBKEYS + i]) + break; + } + if(i >= MAX_PUBKEYS) + break; + } + if(crypto_keyfp_recommended_length < 7) + crypto_keyfp_recommended_length = 7; +} + +static void Crypto_UnloadKeys(void) +{ + int i; + + keygen_i = -1; + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + qd0_blind_id_free(pubkeys[i]); + pubkeys[i] = NULL; + pubkeys_havepriv[i] = false; + pubkeys_havesig[i] = false; + memset(pubkeys_fp64[i], 0, sizeof(pubkeys_fp64[i])); + memset(pubkeys_priv_fp64[i], 0, sizeof(pubkeys_fp64[i])); + challenge_append_length = 0; + } + crypto_idstring = NULL; +} + +static mempool_t *cryptomempool; + +#ifdef __cplusplus +extern "C" +{ +#endif +static void *Crypto_d0_malloc(size_t len) +{ + return Mem_Alloc(cryptomempool, len); +} + +static void Crypto_d0_free(void *p) +{ + Mem_Free(p); +} + +static void *Crypto_d0_createmutex(void) +{ + return Thread_CreateMutex(); +} + +static void Crypto_d0_destroymutex(void *m) +{ + Thread_DestroyMutex(m); +} + +static int Crypto_d0_lockmutex(void *m) +{ + return Thread_LockMutex(m); +} + +static int Crypto_d0_unlockmutex(void *m) +{ + return Thread_UnlockMutex(m); +} +#ifdef __cplusplus +} +#endif + +void Crypto_Shutdown(void) +{ + crypto_t *crypto; + int i; + + Crypto_Rijndael_CloseLibrary(); + + if(d0_blind_id_dll) + { + // free memory + for(i = 0; i < MAX_CRYPTOCONNECTS; ++i) + { + crypto = &cryptoconnects[i].crypto; + CLEAR_CDATA; + } + memset(cryptoconnects, 0, sizeof(cryptoconnects)); + crypto = &cls.crypto; + CLEAR_CDATA; + + Crypto_UnloadKeys(); + + qd0_blind_id_SHUTDOWN(); + + Crypto_CloseLibrary(); + } + + Mem_FreePool(&cryptomempool); +} + +void Crypto_Init(void) +{ + cryptomempool = Mem_AllocPool("crypto", 0, NULL); + + if(!Crypto_OpenLibrary()) + return; + + qd0_blind_id_setmallocfuncs(Crypto_d0_malloc, Crypto_d0_free); + if (Thread_HasThreads()) + qd0_blind_id_setmutexfuncs(Crypto_d0_createmutex, Crypto_d0_destroymutex, Crypto_d0_lockmutex, Crypto_d0_unlockmutex); + + if(!qd0_blind_id_INITIALIZE()) + { + Crypto_Rijndael_CloseLibrary(); + Crypto_CloseLibrary(); + Con_Printf("libd0_blind_id initialization FAILED, cryptography support has been disabled\n"); + return; + } + + (void) Crypto_Rijndael_OpenLibrary(); // if this fails, it's uncritical + + Crypto_InitHostKeys(); +} +// end + +qboolean Crypto_Available(void) +{ + if(!d0_blind_id_dll) + return false; + return true; +} + +// keygen code +static void Crypto_KeyGen_Finished(int code, size_t length_received, unsigned char *buffer, void *cbdata) +{ + const char *p[1]; + size_t l[1]; + static char buf[8192]; + static char buf2[8192]; + size_t buf2size; + qfile_t *f = NULL; + D0_BOOL status; + char vabuf[1024]; + + SV_LockThreadMutex(); + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + + if(keygen_i >= MAX_PUBKEYS || !pubkeys[keygen_i]) + { + Con_Printf("overflow of keygen_i\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + if(keygen_i < 0) + { + Con_Printf("Unexpected response from keygen server:\n"); + Com_HexDumpToConsole(buffer, length_received); + SV_UnlockThreadMutex(); + return; + } + if(!Crypto_ParsePack((const char *) buffer, length_received, FOURCC_D0IR, p, l, 1)) + { + if(length_received >= 5 && Crypto_LittleLong((const char *) buffer) == FOURCC_D0ER) + { + Con_Printf("Error response from keygen server: %.*s\n", (int)(length_received - 5), buffer + 5); + } + else + { + Con_Printf("Invalid response from keygen server:\n"); + Com_HexDumpToConsole(buffer, length_received); + } + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + if(!qd0_blind_id_finish_private_id_request(pubkeys[keygen_i], p[0], l[0])) + { + Con_Printf("d0_blind_id_finish_private_id_request failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + + // verify the key we just got (just in case) + if(!qd0_blind_id_verify_public_id(pubkeys[keygen_i], &status) || !status) + { + Con_Printf("d0_blind_id_verify_public_id failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + + // we have a valid key now! + // make the rest of crypto.c know that + Con_Printf("Received signature for private ID key_%d.d0pk (public key fingerprint: %s)\n", keygen_i, pubkeys_priv_fp64[keygen_i]); + pubkeys_havesig[keygen_i] = true; + + // write the key to disk + p[0] = buf; + l[0] = sizeof(buf); + if(!qd0_blind_id_write_private_id(pubkeys[keygen_i], buf, &l[0])) + { + Con_Printf("d0_blind_id_write_private_id failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + if(!(buf2size = Crypto_UnParsePack(buf2, sizeof(buf2), FOURCC_D0SI, p, l, 1))) + { + Con_Printf("Crypto_UnParsePack failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + + FS_CreatePath(va(vabuf, sizeof(vabuf), "%skey_%d.d0si%s", *fs_userdir ? fs_userdir : fs_basedir, keygen_i, sessionid.string)); + f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%skey_%d.d0si%s", *fs_userdir ? fs_userdir : fs_basedir, keygen_i, sessionid.string), "wb", false); + if(!f) + { + Con_Printf("Cannot open key_%d.d0si%s\n", keygen_i, sessionid.string); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + FS_Write(f, buf2, buf2size); + FS_Close(f); + + Crypto_SavePubKeyTextFile(keygen_i); + + Con_Printf("Saved to key_%d.d0si%s\n", keygen_i, sessionid.string); + + keygen_i = -1; + SV_UnlockThreadMutex(); +} + +static void Crypto_KeyGen_f(void) +{ + int i; + const char *p[1]; + size_t l[1]; + static char buf[8192]; + static char buf2[8192]; + size_t buf2size; + size_t buf2l, buf2pos; + char vabuf[1024]; + size_t len2; + qfile_t *f = NULL; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + if(Cmd_Argc() != 3) + { + Con_Printf("usage:\n%s id url\n", Cmd_Argv(0)); + return; + } + SV_LockThreadMutex(); + Crypto_LoadKeys(); + i = atoi(Cmd_Argv(1)); + if(!pubkeys[i]) + { + Con_Printf("there is no public key %d\n", i); + SV_UnlockThreadMutex(); + return; + } + if(keygen_i >= 0) + { + Con_Printf("there is already a keygen run on the way\n"); + SV_UnlockThreadMutex(); + return; + } + keygen_i = i; + + // how to START the keygenning... + if(pubkeys_havepriv[keygen_i]) + { + if(pubkeys_havesig[keygen_i]) + { + Con_Printf("there is already a signed private key for %d\n", i); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + // if we get here, we only need a signature, no new keygen run needed + Con_Printf("Only need a signature for an existing key...\n"); + } + else + { + // we also need a new ID itself + if(!qd0_blind_id_generate_private_id_start(pubkeys[keygen_i])) + { + Con_Printf("d0_blind_id_start failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + // verify the key we just got (just in case) + if(!qd0_blind_id_verify_private_id(pubkeys[keygen_i])) + { + Con_Printf("d0_blind_id_verify_private_id failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + // we have a valid key now! + // make the rest of crypto.c know that + len2 = FP64_SIZE; + if(qd0_blind_id_fingerprint64_public_id(pubkeys[keygen_i], pubkeys_priv_fp64[keygen_i], &len2)) // keeps final NUL + { + Con_Printf("Generated private ID key_%d.d0pk (public key fingerprint: %s)\n", keygen_i, pubkeys_priv_fp64[keygen_i]); + pubkeys_havepriv[keygen_i] = true; + strlcat(crypto_idstring_buf, va(vabuf, sizeof(vabuf), " %s@%s", pubkeys_priv_fp64[keygen_i], pubkeys_fp64[keygen_i]), sizeof(crypto_idstring_buf)); + crypto_idstring = crypto_idstring_buf; + Crypto_BuildChallengeAppend(); + } + // write the key to disk + p[0] = buf; + l[0] = sizeof(buf); + if(!qd0_blind_id_write_private_id(pubkeys[keygen_i], buf, &l[0])) + { + Con_Printf("d0_blind_id_write_private_id failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + if(!(buf2size = Crypto_UnParsePack(buf2, sizeof(buf2), FOURCC_D0SI, p, l, 1))) + { + Con_Printf("Crypto_UnParsePack failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + + FS_CreatePath(va(vabuf, sizeof(vabuf), "%skey_%d.d0si%s", *fs_userdir ? fs_userdir : fs_basedir, keygen_i, sessionid.string)); + f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%skey_%d.d0si%s", *fs_userdir ? fs_userdir : fs_basedir, keygen_i, sessionid.string), "wb", false); + if(!f) + { + Con_Printf("Cannot open key_%d.d0si%s\n", keygen_i, sessionid.string); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + FS_Write(f, buf2, buf2size); + FS_Close(f); + + Crypto_SavePubKeyTextFile(keygen_i); + + Con_Printf("Saved unsigned key to key_%d.d0si%s\n", keygen_i, sessionid.string); + } + p[0] = buf; + l[0] = sizeof(buf); + if(!qd0_blind_id_generate_private_id_request(pubkeys[keygen_i], buf, &l[0])) + { + Con_Printf("d0_blind_id_generate_private_id_request failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + buf2pos = strlen(Cmd_Argv(2)); + memcpy(buf2, Cmd_Argv(2), buf2pos); + if(!(buf2l = Crypto_UnParsePack(buf2 + buf2pos, sizeof(buf2) - buf2pos - 1, FOURCC_D0IQ, p, l, 1))) + { + Con_Printf("Crypto_UnParsePack failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + if(!(buf2l = base64_encode((unsigned char *) (buf2 + buf2pos), buf2l, sizeof(buf2) - buf2pos - 1))) + { + Con_Printf("base64_encode failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + buf2l += buf2pos; + buf2[buf2l] = 0; + if(!Curl_Begin_ToMemory(buf2, 0, (unsigned char *) keygen_buf, sizeof(keygen_buf), Crypto_KeyGen_Finished, NULL)) + { + Con_Printf("curl failed\n"); + keygen_i = -1; + SV_UnlockThreadMutex(); + return; + } + Con_Printf("Signature generation in progress...\n"); + SV_UnlockThreadMutex(); +} +// end + +// console commands +static void Crypto_Reload_f(void) +{ + Crypto_ClearHostKeys(); + Crypto_UnloadKeys(); + Crypto_LoadKeys(); +} + +static void Crypto_Keys_f(void) +{ + int i; + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + { + Con_Printf("%2d: public key key_%d.d0pk (fingerprint: %s)\n", i, i, pubkeys_fp64[i]); + if(pubkeys_havepriv[i]) + { + Con_Printf(" private ID key_%d.d0si%s (public key fingerprint: %s)\n", i, sessionid.string, pubkeys_priv_fp64[i]); + if(!pubkeys_havesig[i]) + Con_Printf(" NOTE: this ID has not yet been signed!\n"); + } + } + } +} + +static void Crypto_HostKeys_f(void) +{ + int i; + crypto_storedhostkey_t *hk; + char buf[128]; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) + { + for(hk = crypto_storedhostkey_hashtable[i]; hk; hk = hk->next) + { + LHNETADDRESS_ToString(&hk->addr, buf, sizeof(buf), 1); + Con_Printf("%d %s@%.*s %s\n", + hk->aeslevel, + hk->idfp, + crypto_keyfp_recommended_length, pubkeys_fp64[hk->keyid], + buf); + } + } +} + +static void Crypto_HostKey_Clear_f(void) +{ + lhnetaddress_t addr; + int i; + + if(!d0_blind_id_dll) + { + Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); + return; + } + + for(i = 1; i < Cmd_Argc(); ++i) + { + LHNETADDRESS_FromString(&addr, Cmd_Argv(i), 26000); + if(Crypto_ClearHostKey(&addr)) + { + Con_Printf("cleared host key for %s\n", Cmd_Argv(i)); + } + } +} + +void Crypto_Init_Commands(void) +{ + if(d0_blind_id_dll) + { + Cmd_AddCommand("crypto_reload", Crypto_Reload_f, "reloads cryptographic keys"); + Cmd_AddCommand("crypto_keygen", Crypto_KeyGen_f, "generates and saves a cryptographic key"); + Cmd_AddCommand("crypto_keys", Crypto_Keys_f, "lists the loaded keys"); + Cmd_AddCommand("crypto_hostkeys", Crypto_HostKeys_f, "lists the cached host keys"); + Cmd_AddCommand("crypto_hostkey_clear", Crypto_HostKey_Clear_f, "clears a cached host key"); + Cvar_RegisterVariable(&crypto_developer); + if(d0_rijndael_dll) + Cvar_RegisterVariable(&crypto_aeslevel); + else + crypto_aeslevel.integer = 0; // make sure + Cvar_RegisterVariable(&crypto_servercpupercent); + Cvar_RegisterVariable(&crypto_servercpumaxtime); + Cvar_RegisterVariable(&crypto_servercpudebug); + } +} +// end + +// AES encryption +static void aescpy(unsigned char *key, const unsigned char *iv, unsigned char *dst, const unsigned char *src, size_t len) +{ + const unsigned char *xorpos = iv; + unsigned char xorbuf[16]; + unsigned long rk[D0_RIJNDAEL_RKLENGTH(DHKEY_SIZE * 8)]; + size_t i; + qd0_rijndael_setup_encrypt(rk, key, DHKEY_SIZE * 8); + while(len > 16) + { + for(i = 0; i < 16; ++i) + xorbuf[i] = src[i] ^ xorpos[i]; + qd0_rijndael_encrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), xorbuf, dst); + xorpos = dst; + len -= 16; + src += 16; + dst += 16; + } + if(len > 0) + { + for(i = 0; i < len; ++i) + xorbuf[i] = src[i] ^ xorpos[i]; + for(; i < 16; ++i) + xorbuf[i] = xorpos[i]; + qd0_rijndael_encrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), xorbuf, dst); + } +} +static void seacpy(unsigned char *key, const unsigned char *iv, unsigned char *dst, const unsigned char *src, size_t len) +{ + const unsigned char *xorpos = iv; + unsigned char xorbuf[16]; + unsigned long rk[D0_RIJNDAEL_RKLENGTH(DHKEY_SIZE * 8)]; + size_t i; + qd0_rijndael_setup_decrypt(rk, key, DHKEY_SIZE * 8); + while(len > 16) + { + qd0_rijndael_decrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), src, xorbuf); + for(i = 0; i < 16; ++i) + dst[i] = xorbuf[i] ^ xorpos[i]; + xorpos = src; + len -= 16; + src += 16; + dst += 16; + } + if(len > 0) + { + qd0_rijndael_decrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), src, xorbuf); + for(i = 0; i < len; ++i) + dst[i] = xorbuf[i] ^ xorpos[i]; + } +} + +// NOTE: we MUST avoid the following begins of the packet: +// 1. 0xFF, 0xFF, 0xFF, 0xFF +// 2. 0x80, 0x00, length/256, length%256 +// this luckily does NOT affect AES mode, where the first byte always is in the range from 0x00 to 0x0F +const void *Crypto_EncryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len) +{ + unsigned char h[32]; + int i; + if(crypto->authenticated) + { + if(crypto->use_aes) + { + // AES packet = 1 byte length overhead, 15 bytes from HMAC-SHA-256, data, 0..15 bytes padding + // 15 bytes HMAC-SHA-256 (112bit) suffice as the attacker can't do more than forge a random-looking packet + // HMAC is needed to not leak information about packet content + if(developer_networking.integer) + { + Con_Print("To be encrypted:\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + } + if(len_src + 32 > len || !HMAC_SHA256_32BYTES(h, (const unsigned char *) data_src, len_src, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("Crypto_EncryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = ((len_src + 15) / 16) * 16 + 16; // add 16 for HMAC, then round to 16-size for AES + ((unsigned char *) data_dst)[0] = *len_dst - len_src; + memcpy(((unsigned char *) data_dst)+1, h, 15); + aescpy(crypto->dhkey, (const unsigned char *) data_dst, ((unsigned char *) data_dst) + 16, (const unsigned char *) data_src, len_src); + // IV dst src len + } + else + { + // HMAC packet = 16 bytes HMAC-SHA-256 (truncated to 128 bits), data + if(len_src + 16 > len || !HMAC_SHA256_32BYTES(h, (const unsigned char *) data_src, len_src, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("Crypto_EncryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src + 16; + memcpy(data_dst, h, 16); + memcpy(((unsigned char *) data_dst) + 16, (unsigned char *) data_src, len_src); + + // handle the "avoid" conditions: + i = BuffBigLong((unsigned char *) data_dst); + if( + (i == (int)0xFFFFFFFF) // avoid QW control packet + || + (i == (int)0x80000000 + (int)*len_dst) // avoid NQ control packet + ) + *(unsigned char *)data_dst ^= 0x80; // this will ALWAYS fix it + } + return data_dst; + } + else + { + *len_dst = len_src; + return data_src; + } +} + +const void *Crypto_DecryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len) +{ + unsigned char h[32]; + int i; + + // silently handle non-crypto packets + i = BuffBigLong((unsigned char *) data_src); + if( + (i == (int)0xFFFFFFFF) // avoid QW control packet + || + (i == (int)0x80000000 + (int)len_src) // avoid NQ control packet + ) + return NULL; + + if(crypto->authenticated) + { + if(crypto->use_aes) + { + if(len_src < 16 || ((len_src - 16) % 16)) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src - ((unsigned char *) data_src)[0]; + if(len < *len_dst || *len_dst > len_src - 16) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d->%d bytes out)\n", (int) len_src, (int) *len_dst, (int) len); + return NULL; + } + seacpy(crypto->dhkey, (unsigned char *) data_src, (unsigned char *) data_dst, ((const unsigned char *) data_src) + 16, *len_dst); + // IV dst src len + if(!HMAC_SHA256_32BYTES(h, (const unsigned char *) data_dst, *len_dst, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("HMAC fail\n"); + return NULL; + } + if(memcmp(((const unsigned char *) data_src)+1, h, 15)) // ignore first byte, used for length + { + Con_Printf("HMAC mismatch\n"); + return NULL; + } + if(developer_networking.integer) + { + Con_Print("Decrypted:\n"); + Com_HexDumpToConsole((const unsigned char *) data_dst, *len_dst); + } + return data_dst; // no need to copy + } + else + { + if(len_src < 16) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); + return NULL; + } + *len_dst = len_src - 16; + if(len < *len_dst) + { + Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d->%d bytes out)\n", (int) len_src, (int) *len_dst, (int) len); + return NULL; + } + //memcpy(data_dst, data_src + 16, *len_dst); + if(!HMAC_SHA256_32BYTES(h, ((const unsigned char *) data_src) + 16, *len_dst, crypto->dhkey, DHKEY_SIZE)) + { + Con_Printf("HMAC fail\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + return NULL; + } + + if(memcmp((const unsigned char *) data_src, h, 16)) // ignore first byte, used for length + { + // undo the "avoid conditions" + if( + (i == (int)0x7FFFFFFF) // avoided QW control packet + || + (i == (int)0x00000000 + (int)len_src) // avoided NQ control packet + ) + { + // do the avoidance on the hash too + h[0] ^= 0x80; + if(memcmp((const unsigned char *) data_src, h, 16)) // ignore first byte, used for length + { + Con_Printf("HMAC mismatch\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + return NULL; + } + } + else + { + Con_Printf("HMAC mismatch\n"); + Com_HexDumpToConsole((const unsigned char *) data_src, len_src); + return NULL; + } + } + return ((const unsigned char *) data_src) + 16; // no need to copy, so data_dst is not used + } + } + else + { + *len_dst = len_src; + return data_src; + } +} +// end + +const char *Crypto_GetInfoResponseDataString(void) +{ + crypto_idstring_buf[0] = '0' + crypto_aeslevel.integer; + return crypto_idstring; +} + +// network protocol +qboolean Crypto_ServerAppendToChallenge(const char *data_in, size_t len_in, char *data_out, size_t *len_out, size_t maxlen_out) +{ + // cheap op, all is precomputed + if(!d0_blind_id_dll) + return false; // no support + // append challenge + if(maxlen_out <= *len_out + challenge_append_length) + return false; + memcpy(data_out + *len_out, challenge_append, challenge_append_length); + *len_out += challenge_append_length; + return false; +} + +static int Crypto_ServerError(char *data_out, size_t *len_out, const char *msg, const char *msg_client) +{ + if(!msg_client) + msg_client = msg; + Con_DPrintf("rejecting client: %s\n", msg); + if(*msg_client) + dpsnprintf(data_out, *len_out, "reject %s", msg_client); + *len_out = strlen(data_out); + return CRYPTO_DISCARD; +} + +static int Crypto_SoftServerError(char *data_out, size_t *len_out, const char *msg) +{ + *len_out = 0; + Con_DPrintf("%s\n", msg); + return CRYPTO_DISCARD; +} + +static int Crypto_ServerParsePacket_Internal(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + // if "connect": reject if in the middle of crypto handshake + crypto_t *crypto = NULL; + char *data_out_p = data_out; + const char *string = data_in; + int aeslevel; + D0_BOOL aes; + D0_BOOL status; + char infostringvalue[MAX_INPUTLINE]; + char vabuf[1024]; + + if(!d0_blind_id_dll) + return CRYPTO_NOMATCH; // no support + + if (len_in > 8 && !memcmp(string, "connect\\", 8) && d0_rijndael_dll && crypto_aeslevel.integer >= 3) + { + const char *s; + int i; + // sorry, we have to verify the challenge here to not reflect network spam + + if (!(s = InfoString_GetValue(string + 4, "challenge", infostringvalue, sizeof(infostringvalue)))) + return CRYPTO_NOMATCH; // will be later accepted if encryption was set up + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) // challenge mismatch is silent + return CRYPTO_DISCARD; // pre-challenge: rather be silent + + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto || !crypto->authenticated) + return Crypto_ServerError(data_out, len_out, "This server requires authentication and encryption to be supported by your client", NULL); + } + else if(len_in > 5 && !memcmp(string, "d0pk\\", 5) && ((LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP) || sv_public.integer > -3)) + { + const char *cnt, *s, *p; + int id; + int clientid = -1, serverid = -1; + cnt = InfoString_GetValue(string + 4, "id", infostringvalue, sizeof(infostringvalue)); + id = (cnt ? atoi(cnt) : -1); + cnt = InfoString_GetValue(string + 4, "cnt", infostringvalue, sizeof(infostringvalue)); + if(!cnt) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + GetUntilNul(&data_in, &len_in); + if(!data_in) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + if(!strcmp(cnt, "0")) + { + int i; + if (!(s = InfoString_GetValue(string + 4, "challenge", infostringvalue, sizeof(infostringvalue)))) + return CRYPTO_DISCARD; // pre-challenge: rather be silent + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) // challenge mismatch is silent + return CRYPTO_DISCARD; // pre-challenge: rather be silent + + if (!(s = InfoString_GetValue(string + 4, "aeslevel", infostringvalue, sizeof(infostringvalue)))) + aeslevel = 0; // not supported + else + aeslevel = bound(0, atoi(s), 3); + switch(bound(0, d0_rijndael_dll ? crypto_aeslevel.integer : 0, 3)) + { + default: // dummy, never happens, but to make gcc happy... + case 0: + if(aeslevel >= 3) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL); + aes = false; + break; + case 1: + aes = (aeslevel >= 2); + break; + case 2: + aes = (aeslevel >= 1); + break; + case 3: + if(aeslevel <= 0) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL); + aes = true; + break; + } + + p = GetUntilNul(&data_in, &len_in); + if(p && *p) + { + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + if(pubkeys_havepriv[i]) + if(serverid < 0) + serverid = i; + } + if(serverid < 0) + return Crypto_ServerError(data_out, len_out, "Invalid server key", NULL); + } + p = GetUntilNul(&data_in, &len_in); + if(p && *p) + { + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + if(clientid < 0) + clientid = i; + } + if(clientid < 0) + return Crypto_ServerError(data_out, len_out, "Invalid client key", NULL); + } + + crypto = Crypto_ServerFindInstance(peeraddress, true); + if(!crypto) + return Crypto_ServerError(data_out, len_out, "Could not create a crypto connect instance", NULL); + MAKE_CDATA; + CDATA->cdata_id = id; + CDATA->s = serverid; + CDATA->c = clientid; + memset(crypto->dhkey, 0, sizeof(crypto->dhkey)); + CDATA->challenge[0] = 0; + crypto->client_keyfp[0] = 0; + crypto->client_idfp[0] = 0; + crypto->server_keyfp[0] = 0; + crypto->server_idfp[0] = 0; + crypto->use_aes = aes != 0; + + if(CDATA->s >= 0) + { + // I am the server, and my key is ok... so let's set server_keyfp and server_idfp + strlcpy(crypto->server_keyfp, pubkeys_fp64[CDATA->s], sizeof(crypto->server_keyfp)); + strlcpy(crypto->server_idfp, pubkeys_priv_fp64[CDATA->s], sizeof(crypto->server_idfp)); + + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_new failed", "Internal error"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->s])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\1\\id\\%d\\aes\\%d", CDATA->cdata_id, crypto->use_aes)); + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed", "Internal error"); + } + CDATA->next_step = 2; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(CDATA->c >= 0) + { + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_new failed", "Internal error"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\5\\id\\%d\\aes\\%d", CDATA->cdata_id, crypto->use_aes)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed", "Internal error"); + } + CDATA->next_step = 6; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "Missing client and server key", NULL); + } + } + else if(!strcmp(cnt, "2")) + { + size_t fpbuflen; + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 2) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\3\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_response(CDATA->id, data_in, len_in, data_out_p, len_out)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_response failed", "Internal error"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) crypto->dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed", "Internal error"); + } + if(CDATA->c >= 0) + { + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); + } + CDATA->next_step = 4; + } + else + { + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + } + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "4")) + { + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 4) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\5\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed", "Internal error"); + } + CDATA->next_step = 6; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "6")) + { + static char msgbuf[32]; + size_t msgbuflen = sizeof(msgbuf); + size_t fpbuflen; + int i; + unsigned char dhkey[DHKEY_SIZE]; + crypto = Crypto_ServerFindInstance(peeraddress, false); + if(!crypto) + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 6) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + if(!qd0_blind_id_authenticate_with_private_id_verify(CDATA->id, data_in, len_in, msgbuf, &msgbuflen, &status)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_verify failed (authentication error)", "Authentication error"); + } + if(status) + strlcpy(crypto->client_keyfp, pubkeys_fp64[CDATA->c], sizeof(crypto->client_keyfp)); + else + crypto->client_keyfp[0] = 0; + memset(crypto->client_idfp, 0, sizeof(crypto->client_idfp)); + fpbuflen = FP64_SIZE; + if(!qd0_blind_id_fingerprint64_public_id(CDATA->id, crypto->client_idfp, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_fingerprint64_public_id failed", "Internal error"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ServerError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed", "Internal error"); + } + // XOR the two DH keys together to make one + for(i = 0; i < DHKEY_SIZE; ++i) + crypto->dhkey[i] ^= dhkey[i]; + + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + // send a challenge-less challenge + PutWithNul(&data_out_p, len_out, "challenge "); + *len_out = data_out_p - data_out; + --*len_out; // remove NUL terminator + return CRYPTO_MATCH; + } + return CRYPTO_NOMATCH; // pre-challenge, rather be silent + } + return CRYPTO_NOMATCH; +} + +int Crypto_ServerParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + int ret; + double t = 0; + static double complain_time = 0; + const char *cnt; + qboolean do_time = false; + qboolean do_reject = false; + char infostringvalue[MAX_INPUTLINE]; + if(crypto_servercpupercent.value > 0 || crypto_servercpumaxtime.value > 0) + if(len_in > 5 && !memcmp(data_in, "d0pk\\", 5)) + { + do_time = true; + cnt = InfoString_GetValue(data_in + 4, "cnt", infostringvalue, sizeof(infostringvalue)); + if(cnt) + if(!strcmp(cnt, "0")) + do_reject = true; + } + if(do_time) + { + // check if we may perform crypto... + if(crypto_servercpupercent.value > 0) + { + crypto_servercpu_accumulator += (realtime - crypto_servercpu_lastrealtime) * crypto_servercpupercent.value * 0.01; + if(crypto_servercpumaxtime.value) + if(crypto_servercpu_accumulator > crypto_servercpumaxtime.value) + crypto_servercpu_accumulator = crypto_servercpumaxtime.value; + } + else + { + if(crypto_servercpumaxtime.value > 0) + if(realtime != crypto_servercpu_lastrealtime) + crypto_servercpu_accumulator = crypto_servercpumaxtime.value; + } + crypto_servercpu_lastrealtime = realtime; + if(do_reject && crypto_servercpu_accumulator < 0) + { + if(realtime > complain_time + 5) + Con_Printf("crypto: cannot perform requested crypto operations; denial service attack or crypto_servercpupercent/crypto_servercpumaxtime are too low\n"); + *len_out = 0; + return CRYPTO_DISCARD; + } + t = Sys_DirtyTime(); + } + ret = Crypto_ServerParsePacket_Internal(data_in, len_in, data_out, len_out, peeraddress); + if(do_time) + { + t = Sys_DirtyTime() - t;if (t < 0.0) t = 0.0; // dirtytime can step backwards + if(crypto_servercpudebug.integer) + Con_Printf("crypto: accumulator was %.1f ms, used %.1f ms for crypto, ", crypto_servercpu_accumulator * 1000, t * 1000); + crypto_servercpu_accumulator -= t; + if(crypto_servercpudebug.integer) + Con_Printf("is %.1f ms\n", crypto_servercpu_accumulator * 1000); + } + return ret; +} + +static int Crypto_ClientError(char *data_out, size_t *len_out, const char *msg) +{ + dpsnprintf(data_out, *len_out, "reject %s", msg); + *len_out = strlen(data_out); + return CRYPTO_REPLACE; +} + +static int Crypto_SoftClientError(char *data_out, size_t *len_out, const char *msg) +{ + *len_out = 0; + Con_Printf("%s\n", msg); + return CRYPTO_DISCARD; +} + +int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) +{ + crypto_t *crypto = &cls.crypto; + const char *string = data_in; + const char *s; + D0_BOOL aes; + char *data_out_p = data_out; + D0_BOOL status; + char infostringvalue[MAX_INPUTLINE]; + char vabuf[1024]; + + if(!d0_blind_id_dll) + return CRYPTO_NOMATCH; // no support + + // if "challenge": verify challenge, and discard message, send next crypto protocol message instead + // otherwise, just handle actual protocol messages + + if (len_in == 6 && !memcmp(string, "accept", 6) && cls.connect_trying && d0_rijndael_dll) + { + int wantserverid = -1; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL); + if(!crypto || !crypto->authenticated) // we ALSO get here if we are using an encrypted connection, so let's rule this out + { + if(wantserverid >= 0) + return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); + if(crypto_aeslevel.integer >= 3) + return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); + } + return CRYPTO_NOMATCH; + } + else if (len_in >= 1 && string[0] == 'j' && cls.connect_trying && d0_rijndael_dll) + { + int wantserverid = -1; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL); + //if(!crypto || !crypto->authenticated) + { + if(wantserverid >= 0) + return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); + if(crypto_aeslevel.integer >= 3) + return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); + } + return CRYPTO_NOMATCH; + } + else if (len_in >= 5 && BuffLittleLong((unsigned char *) string) == ((int)NETFLAG_CTL | (int)len_in)) + { + int wantserverid = -1; + + // these three are harmless + if(string[4] == CCREP_SERVER_INFO) + return CRYPTO_NOMATCH; + if(string[4] == CCREP_PLAYER_INFO) + return CRYPTO_NOMATCH; + if(string[4] == CCREP_RULE_INFO) + return CRYPTO_NOMATCH; + + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL); + //if(!crypto || !crypto->authenticated) + { + if(wantserverid >= 0) + return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); + if(crypto_aeslevel.integer >= 3) + return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); + } + return CRYPTO_NOMATCH; + } + else if (len_in >= 13 && !memcmp(string, "infoResponse\x0A", 13)) + { + s = InfoString_GetValue(string + 13, "d0_blind_id", infostringvalue, sizeof(infostringvalue)); + if(s) + Crypto_StoreHostKey(peeraddress, s, true); + return CRYPTO_NOMATCH; + } + else if (len_in >= 15 && !memcmp(string, "statusResponse\x0A", 15)) + { + char save = 0; + const char *p; + p = strchr(string + 15, '\n'); + if(p) + { + save = *p; + * (char *) p = 0; // cut off the string there + } + s = InfoString_GetValue(string + 15, "d0_blind_id", infostringvalue, sizeof(infostringvalue)); + if(s) + Crypto_StoreHostKey(peeraddress, s, true); + if(p) + { + * (char *) p = save; + // invoking those nasal demons again (do not run this on the DS9k) + } + return CRYPTO_NOMATCH; + } + else if(len_in > 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) + { + const char *vlen_blind_id_ptr = NULL; + size_t len_blind_id_ptr = 0; + unsigned long k, v; + const char *challenge = data_in + 10; + const char *p; + int i; + int clientid = -1, serverid = -1, wantserverid = -1; + qboolean server_can_auth = true; + char wantserver_idfp[FP64_SIZE+1]; + int wantserver_aeslevel = 0; + + // if we have a stored host key for the server, assume serverid to already be selected! + // (the loop will refuse to overwrite this one then) + wantserver_idfp[0] = 0; + Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, wantserver_idfp, sizeof(wantserver_idfp), &wantserver_aeslevel); + // requirement: wantserver_idfp is a full ID if wantserverid set + + // if we leave, we have to consider the connection + // unauthenticated; NOTE: this may be faked by a clever + // attacker to force an unauthenticated connection; so we have + // a safeguard check in place when encryption is required too + // in place, or when authentication is required by the server + crypto->authenticated = false; + + GetUntilNul(&data_in, &len_in); + if(!data_in) + return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present") : + (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + + // FTEQW extension protocol + while(len_in >= 8) + { + k = Crypto_LittleLong(data_in); + v = Crypto_LittleLong(data_in + 4); + data_in += 8; + len_in -= 8; + switch(k) + { + case PROTOCOL_VLEN: + if(len_in >= 4 + v) + { + k = Crypto_LittleLong(data_in); + data_in += 4; + len_in -= 4; + switch(k) + { + case PROTOCOL_D0_BLIND_ID: + vlen_blind_id_ptr = data_in; + len_blind_id_ptr = v; + break; + } + data_in += v; + len_in -= v; + } + break; + default: + break; + } + } + + if(!vlen_blind_id_ptr) + return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though authentication is required") : + (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + + data_in = vlen_blind_id_ptr; + len_in = len_blind_id_ptr; + + // parse fingerprints + // once we found a fingerprint we can auth to (ANY), select it as clientfp + // once we found a fingerprint in the first list that we know, select it as serverfp + + for(;;) + { + p = GetUntilNul(&data_in, &len_in); + if(!p) + break; + if(!*p) + { + if(!server_can_auth) + break; // other protocol message may follow + server_can_auth = false; + if(clientid >= 0) + break; + continue; + } + for(i = 0; i < MAX_PUBKEYS; ++i) + { + if(pubkeys[i]) + if(!strcmp(p, pubkeys_fp64[i])) + { + if(pubkeys_havepriv[i]) + if(clientid < 0) + clientid = i; + if(server_can_auth) + if(serverid < 0) + if(wantserverid < 0 || i == wantserverid) + serverid = i; + } + } + if(clientid >= 0 && serverid >= 0) + break; + } + + // if stored host key is not found: + if(wantserverid >= 0 && serverid < 0) + return Crypto_ClientError(data_out, len_out, "Server CA does not match stored host key, refusing to connect"); + + if(serverid >= 0 || clientid >= 0) + { + // TODO at this point, fill clientside crypto struct! + MAKE_CDATA; + CDATA->cdata_id = ++cdata_id; + CDATA->s = serverid; + CDATA->c = clientid; + memset(crypto->dhkey, 0, sizeof(crypto->dhkey)); + strlcpy(CDATA->challenge, challenge, sizeof(CDATA->challenge)); + crypto->client_keyfp[0] = 0; + crypto->client_idfp[0] = 0; + crypto->server_keyfp[0] = 0; + crypto->server_idfp[0] = 0; + memcpy(CDATA->wantserver_idfp, wantserver_idfp, sizeof(crypto->server_idfp)); + + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + switch(bound(0, d0_rijndael_dll ? crypto_aeslevel.integer : 0, 3)) + { + default: // dummy, never happens, but to make gcc happy... + case 0: + if(wantserver_aeslevel >= 3) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL); + CDATA->wantserver_aes = false; + break; + case 1: + CDATA->wantserver_aes = (wantserver_aeslevel >= 2); + break; + case 2: + CDATA->wantserver_aes = (wantserver_aeslevel >= 1); + break; + case 3: + if(wantserver_aeslevel <= 0) + return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL); + CDATA->wantserver_aes = true; + break; + } + + // build outgoing message + // append regular stuff + PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\0\\id\\%d\\aeslevel\\%d\\challenge\\%s", CDATA->cdata_id, d0_rijndael_dll ? crypto_aeslevel.integer : 0, challenge)); + PutWithNul(&data_out_p, len_out, serverid >= 0 ? pubkeys_fp64[serverid] : ""); + PutWithNul(&data_out_p, len_out, clientid >= 0 ? pubkeys_fp64[clientid] : ""); + + if(clientid >= 0) + { + // I am the client, and my key is ok... so let's set client_keyfp and client_idfp + strlcpy(crypto->client_keyfp, pubkeys_fp64[CDATA->c], sizeof(crypto->client_keyfp)); + strlcpy(crypto->client_idfp, pubkeys_priv_fp64[CDATA->c], sizeof(crypto->client_idfp)); + } + + if(serverid >= 0) + { + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_new failed"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->s])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + CDATA->next_step = 1; + *len_out = data_out_p - data_out; + } + else if(clientid >= 0) + { + // skip over server auth, perform client auth only + if(!CDATA->id) + CDATA->id = qd0_blind_id_new(); + if(!CDATA->id) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_new failed"); + } + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed"); + } + CDATA->next_step = 5; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + } + else + *len_out = data_out_p - data_out; + + return CRYPTO_DISCARD; + } + else + { + if(wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(wantserver_aeslevel >= 3) + return Crypto_ClientError(data_out, len_out, "Server insists on encryption, but neither can authenticate to the other"); + return (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL) : + CRYPTO_NOMATCH; + } + } + else if(len_in > 5 && !memcmp(string, "d0pk\\", 5) && cls.connect_trying) + { + const char *cnt; + int id; + cnt = InfoString_GetValue(string + 4, "id", infostringvalue, sizeof(infostringvalue)); + id = (cnt ? atoi(cnt) : -1); + cnt = InfoString_GetValue(string + 4, "cnt", infostringvalue, sizeof(infostringvalue)); + if(!cnt) + return Crypto_ClientError(data_out, len_out, "d0pk\\ message without cnt"); + GetUntilNul(&data_in, &len_in); + if(!data_in) + return Crypto_ClientError(data_out, len_out, "d0pk\\ message without attachment"); + + if(!strcmp(cnt, "1")) + { + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 1) + return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if((s = InfoString_GetValue(string + 4, "aes", infostringvalue, sizeof(infostringvalue)))) + aes = atoi(s); + else + aes = false; + // we CANNOT toggle the AES status any more! + // as the server already decided + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(!aes && CDATA->wantserver_aes) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Stored host key requires encryption, but server did not enable encryption"); + } + if(aes && (!d0_rijndael_dll || crypto_aeslevel.integer <= 0)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on encryption too hard"); + } + if(!aes && (d0_rijndael_dll && crypto_aeslevel.integer >= 3)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on plaintext too hard"); + } + crypto->use_aes = aes != 0; + + PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\2\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed"); + } + CDATA->next_step = 3; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else if(!strcmp(cnt, "3")) + { + static char msgbuf[32]; + size_t msgbuflen = sizeof(msgbuf); + size_t fpbuflen; + + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 3) + return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if(!qd0_blind_id_authenticate_with_private_id_verify(CDATA->id, data_in, len_in, msgbuf, &msgbuflen, &status)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_verify failed (server authentication error)"); + } + if(status) + strlcpy(crypto->server_keyfp, pubkeys_fp64[CDATA->s], sizeof(crypto->server_keyfp)); + else + crypto->server_keyfp[0] = 0; + memset(crypto->server_idfp, 0, sizeof(crypto->server_idfp)); + fpbuflen = FP64_SIZE; + if(!qd0_blind_id_fingerprint64_public_id(CDATA->id, crypto->server_idfp, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_fingerprint64_public_id failed"); + } + if(CDATA->wantserver_idfp[0]) + if(memcmp(CDATA->wantserver_idfp, crypto->server_idfp, sizeof(crypto->server_idfp))) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server ID does not match stored host key, refusing to connect"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) crypto->dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed"); + } + + // cache the server key + Crypto_StoreHostKey(&cls.connect_address, va(vabuf, sizeof(vabuf), "%d %s@%s", crypto->use_aes ? 1 : 0, crypto->server_idfp, pubkeys_fp64[CDATA->s]), false); + + if(CDATA->c >= 0) + { + // client will auth next + PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\4\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); + } + if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed"); + } + CDATA->next_step = 5; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + else + { + // session key is FINISHED (no server part is to be expected)! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + // assume we got the empty challenge to finish the protocol + PutWithNul(&data_out_p, len_out, "challenge "); + *len_out = data_out_p - data_out; + --*len_out; // remove NUL terminator + return CRYPTO_REPLACE; + } + } + else if(!strcmp(cnt, "5")) + { + size_t fpbuflen; + unsigned char dhkey[DHKEY_SIZE]; + int i; + + if(id >= 0) + if(CDATA->cdata_id != id) + return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); + if(CDATA->next_step != 5) + return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); + + cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" + + if(CDATA->s < 0) // only if server didn't auth + { + if((s = InfoString_GetValue(string + 4, "aes", infostringvalue, sizeof(infostringvalue)))) + aes = atoi(s); + else + aes = false; + if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting + if(!aes && CDATA->wantserver_aes) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Stored host key requires encryption, but server did not enable encryption"); + } + if(aes && (!d0_rijndael_dll || crypto_aeslevel.integer <= 0)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on encryption too hard"); + } + if(!aes && (d0_rijndael_dll && crypto_aeslevel.integer >= 3)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "Server insists on plaintext too hard"); + } + crypto->use_aes = aes != 0; + } + + PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\6\\id\\%d", CDATA->cdata_id)); + if(!qd0_blind_id_authenticate_with_private_id_response(CDATA->id, data_in, len_in, data_out_p, len_out)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_response failed"); + } + fpbuflen = DHKEY_SIZE; + if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) dhkey, &fpbuflen)) + { + CLEAR_CDATA; + return Crypto_ClientError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed"); + } + // XOR the two DH keys together to make one + for(i = 0; i < DHKEY_SIZE; ++i) + crypto->dhkey[i] ^= dhkey[i]; + // session key is FINISHED! By this, all keys are set up + crypto->authenticated = true; + CDATA->next_step = 0; + data_out_p += *len_out; + *len_out = data_out_p - data_out; + return CRYPTO_DISCARD; + } + return Crypto_SoftClientError(data_out, len_out, "Got unknown d0_blind_id message from server"); + } + + return CRYPTO_NOMATCH; +} + +size_t Crypto_SignData(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size) +{ + if(keyid < 0 || keyid >= MAX_PUBKEYS) + return 0; + if(!pubkeys_havepriv[keyid]) + return 0; + if(qd0_blind_id_sign_with_private_id_sign(pubkeys[keyid], true, false, (const char *)data, datasize, (char *)signed_data, &signed_size)) + return signed_size; + return 0; +} + +size_t Crypto_SignDataDetached(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size) +{ + if(keyid < 0 || keyid >= MAX_PUBKEYS) + return 0; + if(!pubkeys_havepriv[keyid]) + return 0; + if(qd0_blind_id_sign_with_private_id_sign_detached(pubkeys[keyid], true, false, (const char *)data, datasize, (char *)signed_data, &signed_size)) + return signed_size; + return 0; +} diff --git a/app/jni/crypto.h b/app/jni/crypto.h new file mode 100644 index 0000000..ddc00a9 --- /dev/null +++ b/app/jni/crypto.h @@ -0,0 +1,158 @@ +#ifndef CRYPTO_H +#define CRYPTO_H + +extern cvar_t crypto_developer; +extern cvar_t crypto_aeslevel; +#define ENCRYPTION_REQUIRED (crypto_aeslevel.integer >= 3) + +extern int crypto_keyfp_recommended_length; // applies to LOCAL IDs, and to ALL keys + +#define CRYPTO_HEADERSIZE 31 +// AES case causes 16 to 31 bytes overhead +// SHA256 case causes 16 bytes overhead as we truncate to 128bit + +#include "lhnet.h" + +#define FP64_SIZE 44 +#define DHKEY_SIZE 16 + +typedef struct +{ + unsigned char dhkey[DHKEY_SIZE]; // shared key, not NUL terminated + char client_idfp[FP64_SIZE+1]; + char client_keyfp[FP64_SIZE+1]; // NULL if signature fail + char server_idfp[FP64_SIZE+1]; + char server_keyfp[FP64_SIZE+1]; // NULL if signature fail + qboolean authenticated; + qboolean use_aes; + void *data; +} +crypto_t; + +void Crypto_Init(void); +void Crypto_Init_Commands(void); +void Crypto_LoadKeys(void); // NOTE: when this is called, the SV_LockThreadMutex MUST be active +void Crypto_Shutdown(void); +qboolean Crypto_Available(void); +void sha256(unsigned char *out, const unsigned char *in, int n); // may ONLY be called if Crypto_Available() +const void *Crypto_EncryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len); +const void *Crypto_DecryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len); +#define CRYPTO_NOMATCH 0 // process as usual (packet was not used) +#define CRYPTO_MATCH 1 // process as usual (packet was used) +#define CRYPTO_DISCARD 2 // discard this packet +#define CRYPTO_REPLACE 3 // make the buffer the current packet +int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress); +int Crypto_ServerParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress); + +// if len_out is nonzero, the packet is to be sent to the client + +qboolean Crypto_ServerAppendToChallenge(const char *data_in, size_t len_in, char *data_out, size_t *len_out, size_t maxlen); +crypto_t *Crypto_ServerGetInstance(lhnetaddress_t *peeraddress); +qboolean Crypto_ServerFinishInstance(crypto_t *out, crypto_t *in); // also clears allocated memory +const char *Crypto_GetInfoResponseDataString(void); + +// retrieves a host key for an address (can be exposed to menuqc, or used by the engine to look up stored keys e.g. for server bookmarking) +// pointers may be NULL +qboolean Crypto_RetrieveHostKey(lhnetaddress_t *peeraddress, int *keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, int *aeslevel); +int Crypto_RetrieveLocalKey(int keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, qboolean *issigned); // return value: -1 if more to come, +1 if valid, 0 if end of list + +size_t Crypto_SignData(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size); +size_t Crypto_SignDataDetached(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size); + +// netconn protocol: +// non-crypto: +// getchallenge > +// < challenge +// connect > +// < accept (or: reject) +// crypto: +// getchallenge > +// < challenge SP NUL vlen d0pk NUL NUL +// +// IF serverfp: +// d0pk\cnt\0\challenge\\aeslevel\ NUL NUL +// > +// check if client would get accepted; if not, do "reject" now +// require non-control packets to be encrypted require non-control packets to be encrypted +// do not send anything yet do not send anything yet +// RESET to serverfp RESET to serverfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// < d0pk\cnt\1\aes\ NUL *startdata* +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// d0pk\cnt\2 NUL *challengedata* > +// d0_blind_id_authenticate_with_private_id_response() = 0 +// < d0pk\cnt\3 NUL *responsedata* +// d0_blind_id_authenticate_with_private_id_verify() = 1 +// store server's fingerprint NOW +// d0_blind_id_sessionkey_public_id() = 1 d0_blind_id_sessionkey_public_id() = 1 +// +// IF clientfp AND NOT serverfp: +// RESET to clientfp RESET to clientfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// d0pk\cnt\0\challenge\\aeslevel\ NUL NUL NUL *startdata* +// > +// check if client would get accepted; if not, do "reject" now +// require non-control packets to be encrypted require non-control packets to be encrypted +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// < d0pk\cnt\5\aes\ NUL *challengedata* +// +// IF clientfp AND serverfp: +// RESET to clientfp RESET to clientfp +// d0_blind_id_authenticate_with_private_id_start() = 1 +// d0pk\cnt\4 NUL *startdata* > +// d0_blind_id_authenticate_with_private_id_challenge() = 1 +// < d0pk\cnt\5 NUL *challengedata* +// +// IF clientfp: +// d0_blind_id_authenticate_with_private_id_response() = 0 +// d0pk\cnt\6 NUL *responsedata* > +// d0_blind_id_authenticate_with_private_id_verify() = 1 +// store client's fingerprint NOW +// d0_blind_id_sessionkey_public_id() = 1 d0_blind_id_sessionkey_public_id() = 1 +// note: the ... is the "connect" message, except without the challenge. Reinterpret as regular connect message on server side +// +// enforce encrypted transmission (key is XOR of the two DH keys) +// +// IF clientfp: +// < challenge (mere sync message) +// +// connect\... > +// < accept (ALWAYS accept if connection is encrypted, ignore challenge as it had been checked before) +// +// commence with ingame protocol + +// in short: +// server: +// getchallenge NUL d0_blind_id: reply with challenge with added fingerprints +// cnt=0: IF server will auth, cnt=1, ELSE cnt=5 +// cnt=2: cnt=3 +// cnt=4: cnt=5 +// cnt=6: send "challenge" +// client: +// challenge with added fingerprints: cnt=0; if client will auth but not server, append client auth start +// cnt=1: cnt=2 +// cnt=3: IF client will auth, cnt=4, ELSE rewrite as "challenge" +// cnt=5: cnt=6, server will continue by sending "challenge" (let's avoid sending two packets as response to one) +// other change: +// accept empty "challenge", and challenge-less connect in case crypto protocol has executed and finished +// statusResponse and infoResponse get an added d0_blind_id key that lists +// the keys the server can auth with and to in key@ca SPACE key@ca notation +// any d0pk\ message has an appended "id" parameter; messages with an unexpected "id" are ignored to prevent errors from multiple concurrent auth runs + + +// comparison to OTR: +// - encryption: yes +// - authentication: yes +// - deniability: no (attacker requires the temporary session key to prove you +// have sent a specific message, the private key itself does not suffice), no +// measures are taken to provide forgeability to even provide deniability +// against an attacker who knows the temporary session key, as using CTR mode +// for the encryption - which, together with deriving the MAC key from the +// encryption key, and MACing the ciphertexts instead of the plaintexts, +// would provide forgeability and thus deniability - requires longer +// encrypted packets and deniability was not a goal of this, as we may e.g. +// reserve the right to capture packet dumps + extra state info to prove a +// client/server has sent specific packets to prove cheating) +// - perfect forward secrecy: yes (session key is derived via DH key exchange) + +#endif diff --git a/app/jni/csprogs.c b/app/jni/csprogs.c new file mode 100644 index 0000000..36cecf8 --- /dev/null +++ b/app/jni/csprogs.c @@ -0,0 +1,1252 @@ +#include "quakedef.h" +#include "progsvm.h" +#include "clprogdefs.h" +#include "csprogs.h" +#include "cl_collision.h" +#include "snd_main.h" +#include "clvm_cmds.h" +#include "prvm_cmds.h" + +//============================================================================ +// Client prog handling +//[515]: omg !!! optimize it ! a lot of hacks here and there also :P + +#define CSQC_RETURNVAL prog->globals.fp[OFS_RETURN] +#define CSQC_BEGIN +#define CSQC_END + +void CL_VM_PreventInformationLeaks(void) +{ + prvm_prog_t *prog = CLVM_prog; + if(!cl.csqc_loaded) + return; + CSQC_BEGIN + VM_ClearTraceGlobals(prog); + PRVM_clientglobalfloat(trace_networkentity) = 0; + CSQC_END +} + +//[515]: these are required funcs +static const char *cl_required_func[] = +{ + "CSQC_Init", + "CSQC_InputEvent", + "CSQC_UpdateView", + "CSQC_ConsoleCommand", +}; + +static int cl_numrequiredfunc = sizeof(cl_required_func) / sizeof(char*); + +#define CL_REQFIELDS (sizeof(cl_reqfields) / sizeof(prvm_required_field_t)) + +prvm_required_field_t cl_reqfields[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) {ev_float, #x }, +#define PRVM_DECLARE_clientfieldvector(x) {ev_vector, #x }, +#define PRVM_DECLARE_clientfieldstring(x) {ev_string, #x }, +#define PRVM_DECLARE_clientfieldedict(x) {ev_entity, #x }, +#define PRVM_DECLARE_clientfieldfunction(x) {ev_function, #x }, +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +#define CL_REQGLOBALS (sizeof(cl_reqglobals) / sizeof(prvm_required_field_t)) + +prvm_required_field_t cl_reqglobals[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_clientglobalvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_clientglobalstring(x) {ev_string, #x}, +#define PRVM_DECLARE_clientglobaledict(x) {ev_entity, #x}, +#define PRVM_DECLARE_clientglobalfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin) +{ + prvm_prog_t *prog = CLVM_prog; + if(cl.csqc_loaded) + { + CSQC_BEGIN + PRVM_clientglobalfloat(dmg_take) = dmg_take; + PRVM_clientglobalfloat(dmg_save) = dmg_save; + VectorCopy(dmg_origin, PRVM_clientglobalvector(dmg_origin)); + CSQC_END + } +} + +void CSQC_UpdateNetworkTimes(double newtime, double oldtime) +{ + prvm_prog_t *prog = CLVM_prog; + if(!cl.csqc_loaded) + return; + CSQC_BEGIN + PRVM_clientglobalfloat(servertime) = newtime; + PRVM_clientglobalfloat(serverprevtime) = oldtime; + PRVM_clientglobalfloat(serverdeltatime) = newtime - oldtime; + CSQC_END +} + +//[515]: set globals before calling R_UpdateView, WEIRD CRAP +static void CSQC_SetGlobals (double frametime) +{ + vec3_t pmove_org; + prvm_prog_t *prog = CLVM_prog; + CSQC_BEGIN + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobalfloat(frametime) = frametime; + PRVM_clientglobalfloat(servercommandframe) = cls.servermovesequence; + PRVM_clientglobalfloat(clientcommandframe) = cl.movecmd[0].sequence; + VectorCopy(cl.viewangles, PRVM_clientglobalvector(input_angles)); + // // FIXME: this actually belongs into getinputstate().. [12/17/2007 Black] + PRVM_clientglobalfloat(input_buttons) = cl.movecmd[0].buttons; + VectorSet(PRVM_clientglobalvector(input_movevalues), cl.movecmd[0].forwardmove, cl.movecmd[0].sidemove, cl.movecmd[0].upmove); + VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin); + VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles); + + // LordHavoc: Spike says not to do this, but without pmove_org the + // CSQC is useless as it can't alter the view origin without + // completely replacing it + Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, pmove_org); + VectorCopy(pmove_org, PRVM_clientglobalvector(pmove_org)); + VectorCopy(cl.movement_velocity, PRVM_clientglobalvector(pmove_vel)); + PRVM_clientglobalfloat(pmove_onground) = cl.onground; + PRVM_clientglobalfloat(pmove_inwater) = cl.inwater; + + VectorCopy(cl.viewangles, PRVM_clientglobalvector(view_angles)); + VectorCopy(cl.punchangle, PRVM_clientglobalvector(view_punchangle)); + VectorCopy(cl.punchvector, PRVM_clientglobalvector(view_punchvector)); + PRVM_clientglobalfloat(maxclients) = cl.maxclients; + + PRVM_clientglobalfloat(player_localentnum) = cl.viewentity; + + CSQC_R_RecalcView(); + CSQC_END +} + +void CSQC_Predraw (prvm_edict_t *ed) +{ + prvm_prog_t *prog = CLVM_prog; + int b; + if(!PRVM_clientedictfunction(ed, predraw)) + return; + b = PRVM_clientglobaledict(self); + PRVM_clientglobaledict(self) = PRVM_EDICT_TO_PROG(ed); + prog->ExecuteProgram(prog, PRVM_clientedictfunction(ed, predraw), "CSQC_Predraw: NULL function\n"); + PRVM_clientglobaledict(self) = b; +} + +void CSQC_Think (prvm_edict_t *ed) +{ + prvm_prog_t *prog = CLVM_prog; + int b; + if(PRVM_clientedictfunction(ed, think)) + if(PRVM_clientedictfloat(ed, nextthink) && PRVM_clientedictfloat(ed, nextthink) <= PRVM_clientglobalfloat(time)) + { + PRVM_clientedictfloat(ed, nextthink) = 0; + b = PRVM_clientglobaledict(self); + PRVM_clientglobaledict(self) = PRVM_EDICT_TO_PROG(ed); + prog->ExecuteProgram(prog, PRVM_clientedictfunction(ed, think), "CSQC_Think: NULL function\n"); + PRVM_clientglobaledict(self) = b; + } +} + +extern cvar_t cl_noplayershadow; +extern cvar_t r_equalize_entities_fullbright; +qboolean CSQC_AddRenderEdict(prvm_edict_t *ed, int edictnum) +{ + prvm_prog_t *prog = CLVM_prog; + int renderflags; + int c; + float scale; + entity_render_t *entrender; + dp_model_t *model; + + model = CL_GetModelFromEdict(ed); + if (!model) + return false; + + if (edictnum) + { + if (r_refdef.scene.numentities >= r_refdef.scene.maxentities) + return false; + entrender = cl.csqcrenderentities + edictnum; + r_refdef.scene.entities[r_refdef.scene.numentities++] = entrender; + entrender->entitynumber = edictnum + MAX_EDICTS; + //entrender->shadertime = 0; // shadertime was set by spawn() + entrender->flags = 0; + entrender->effects = 0; + entrender->alpha = 1; + entrender->scale = 1; + VectorSet(entrender->colormod, 1, 1, 1); + VectorSet(entrender->glowmod, 1, 1, 1); + entrender->allowdecals = true; + } + else + { + entrender = CL_NewTempEntity(0); + if (!entrender) + return false; + } + + entrender->userwavefunc_param[0] = PRVM_clientedictfloat(ed, userwavefunc_param0); + entrender->userwavefunc_param[1] = PRVM_clientedictfloat(ed, userwavefunc_param1); + entrender->userwavefunc_param[2] = PRVM_clientedictfloat(ed, userwavefunc_param2); + entrender->userwavefunc_param[3] = PRVM_clientedictfloat(ed, userwavefunc_param3); + + entrender->model = model; + entrender->skinnum = (int)PRVM_clientedictfloat(ed, skin); + entrender->effects |= entrender->model->effects; + renderflags = (int)PRVM_clientedictfloat(ed, renderflags); + entrender->alpha = PRVM_clientedictfloat(ed, alpha); + entrender->scale = scale = PRVM_clientedictfloat(ed, scale); + VectorCopy(PRVM_clientedictvector(ed, colormod), entrender->colormod); + VectorCopy(PRVM_clientedictvector(ed, glowmod), entrender->glowmod); + if(PRVM_clientedictfloat(ed, effects)) entrender->effects |= (int)PRVM_clientedictfloat(ed, effects); + if (!entrender->alpha) + entrender->alpha = 1.0f; + if (!entrender->scale) + entrender->scale = scale = 1.0f; + if (!VectorLength2(entrender->colormod)) + VectorSet(entrender->colormod, 1, 1, 1); + if (!VectorLength2(entrender->glowmod)) + VectorSet(entrender->glowmod, 1, 1, 1); + + // LordHavoc: use the CL_GetTagMatrix function on self to ensure consistent behavior (duplicate code would be bad) + CL_GetTagMatrix(prog, &entrender->matrix, ed, 0); + + // set up the animation data + VM_GenerateFrameGroupBlend(prog, ed->priv.server->framegroupblend, ed); + VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model, cl.time); + VM_UpdateEdictSkeleton(prog, ed, model, ed->priv.server->frameblend); + if (PRVM_clientedictfloat(ed, shadertime)) // hack for csprogs.dat files that do not set shadertime, leaves the value at entity spawn time + entrender->shadertime = PRVM_clientedictfloat(ed, shadertime); + + // transparent offset + if (renderflags & RF_USETRANSPARENTOFFSET) + entrender->transparent_offset = PRVM_clientglobalfloat(transparent_offset); + + // model light + if (renderflags & RF_MODELLIGHT) + { + if (PRVM_clientedictvector(ed, modellight_ambient)) VectorCopy(PRVM_clientedictvector(ed, modellight_ambient), entrender->modellight_ambient); else VectorClear(entrender->modellight_ambient); + if (PRVM_clientedictvector(ed, modellight_diffuse)) VectorCopy(PRVM_clientedictvector(ed, modellight_diffuse), entrender->modellight_diffuse); else VectorClear(entrender->modellight_diffuse); + if (PRVM_clientedictvector(ed, modellight_dir)) VectorCopy(PRVM_clientedictvector(ed, modellight_dir), entrender->modellight_lightdir); else VectorClear(entrender->modellight_lightdir); + entrender->flags |= RENDER_CUSTOMIZEDMODELLIGHT; + } + + if(renderflags) + { + if(renderflags & RF_VIEWMODEL) entrender->flags |= RENDER_VIEWMODEL | RENDER_NODEPTHTEST; + if(renderflags & RF_EXTERNALMODEL) entrender->flags |= RENDER_EXTERIORMODEL; + if(renderflags & RF_WORLDOBJECT) entrender->flags |= RENDER_WORLDOBJECT; + if(renderflags & RF_DEPTHHACK) entrender->flags |= RENDER_NODEPTHTEST; + if(renderflags & RF_ADDITIVE) entrender->flags |= RENDER_ADDITIVE; + if(renderflags & RF_DYNAMICMODELLIGHT) entrender->flags |= RENDER_DYNAMICMODELLIGHT; + } + + c = (int)PRVM_clientedictfloat(ed, colormap); + if (c <= 0) + CL_SetEntityColormapColors(entrender, -1); + else if (c <= cl.maxclients && cl.scores != NULL) + CL_SetEntityColormapColors(entrender, cl.scores[c-1].colors); + else + CL_SetEntityColormapColors(entrender, c); + + entrender->flags &= ~(RENDER_SHADOW | RENDER_LIGHT | RENDER_NOSELFSHADOW); + // either fullbright or lit + if(!r_fullbright.integer) + { + if (!(entrender->effects & EF_FULLBRIGHT) && !(renderflags & RF_FULLBRIGHT)) + entrender->flags |= RENDER_LIGHT; + else if(r_equalize_entities_fullbright.integer) + entrender->flags |= RENDER_LIGHT | RENDER_EQUALIZE; + } + // hide player shadow during intermission or nehahra movie + if (!(entrender->effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) + && (entrender->alpha >= 1) + && !(renderflags & RF_NOSHADOW) + && !(entrender->flags & RENDER_VIEWMODEL) + && (!(entrender->flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer))) + entrender->flags |= RENDER_SHADOW; + if (entrender->flags & RENDER_VIEWMODEL) + entrender->flags |= RENDER_NOSELFSHADOW; + if (entrender->effects & EF_NOSELFSHADOW) + entrender->flags |= RENDER_NOSELFSHADOW; + if (entrender->effects & EF_NODEPTHTEST) + entrender->flags |= RENDER_NODEPTHTEST; + if (entrender->effects & EF_ADDITIVE) + entrender->flags |= RENDER_ADDITIVE; + if (entrender->effects & EF_DOUBLESIDED) + entrender->flags |= RENDER_DOUBLESIDED; + if (entrender->effects & EF_DYNAMICMODELLIGHT) + entrender->flags |= RENDER_DYNAMICMODELLIGHT; + + // make the other useful stuff + memcpy(entrender->framegroupblend, ed->priv.server->framegroupblend, sizeof(ed->priv.server->framegroupblend)); + CL_UpdateRenderEntity(entrender); + + // override animation data with full control + memcpy(entrender->frameblend, ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend)); + if (ed->priv.server->skeleton.relativetransforms) + entrender->skeleton = &ed->priv.server->skeleton; + else + entrender->skeleton = NULL; + + return true; +} + +// 0 = keydown, key, character (EXT_CSQC) +// 1 = keyup, key, character (EXT_CSQC) +// 2 = mousemove relative, x, y (EXT_CSQC) +// 3 = mousemove absolute, x, y (DP_CSQC) +qboolean CL_VM_InputEvent (int eventtype, int x, int y) +{ + prvm_prog_t *prog = CLVM_prog; + qboolean r; + + if(!cl.csqc_loaded) + return false; + + CSQC_BEGIN + if (!PRVM_clientfunction(CSQC_InputEvent)) + r = false; + else + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + PRVM_G_FLOAT(OFS_PARM0) = eventtype; + PRVM_G_FLOAT(OFS_PARM1) = x; // key or x + PRVM_G_FLOAT(OFS_PARM2) = y; // ascii or y + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_InputEvent), "QC function CSQC_InputEvent is missing"); + r = CSQC_RETURNVAL != 0; + } + CSQC_END + return r; +} + +extern r_refdef_view_t csqc_original_r_refdef_view; +extern r_refdef_view_t csqc_main_r_refdef_view; +qboolean CL_VM_UpdateView (double frametime) +{ + prvm_prog_t *prog = CLVM_prog; + vec3_t emptyvector; + emptyvector[0] = 0; + emptyvector[1] = 0; + emptyvector[2] = 0; +// vec3_t oldangles; + if(!cl.csqc_loaded) + return false; + R_TimeReport("pre-UpdateView"); + CSQC_BEGIN + r_refdef.view.ismain = true; + csqc_original_r_refdef_view = r_refdef.view; + csqc_main_r_refdef_view = r_refdef.view; + //VectorCopy(cl.viewangles, oldangles); + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + CSQC_SetGlobals(frametime); + // clear renderable entity and light lists to prevent crashes if the + // CSQC_UpdateView function does not call R_ClearScene as it should + r_refdef.scene.numentities = 0; + r_refdef.scene.numlights = 0; + // pass in width and height as parameters (EXT_CSQC_1) + PRVM_G_FLOAT(OFS_PARM0) = vid.width; + PRVM_G_FLOAT(OFS_PARM1) = vid.height; + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_UpdateView), "QC function CSQC_UpdateView is missing"); + //VectorCopy(oldangles, cl.viewangles); + // Dresk : Reset Dmg Globals Here + CL_VM_UpdateDmgGlobals(0, 0, emptyvector); + r_refdef.view = csqc_main_r_refdef_view; + R_RenderView_UpdateViewVectors(); // we have to do this, as we undid the scene render doing this for us + CSQC_END + + R_TimeReport("UpdateView"); + return true; +} + +qboolean CL_VM_ConsoleCommand (const char *cmd) +{ + prvm_prog_t *prog = CLVM_prog; + int restorevm_tempstringsbuf_cursize; + qboolean r = false; + if(!cl.csqc_loaded) + return false; + CSQC_BEGIN + if (PRVM_clientfunction(CSQC_ConsoleCommand)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, cmd); + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_ConsoleCommand), "QC function CSQC_ConsoleCommand is missing"); + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + r = CSQC_RETURNVAL != 0; + } + CSQC_END + return r; +} + +qboolean CL_VM_Parse_TempEntity (void) +{ + prvm_prog_t *prog = CLVM_prog; + int t; + qboolean r = false; + if(!cl.csqc_loaded) + return false; + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Parse_TempEntity)) + { + t = cl_message.readcount; + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Parse_TempEntity), "QC function CSQC_Parse_TempEntity is missing"); + r = CSQC_RETURNVAL != 0; + if(!r) + { + cl_message.readcount = t; + cl_message.badread = false; + } + } + CSQC_END + return r; +} + +void CL_VM_Parse_StuffCmd (const char *msg) +{ + prvm_prog_t *prog = CLVM_prog; + int restorevm_tempstringsbuf_cursize; + if(msg[0] == 'c') + if(msg[1] == 's') + if(msg[2] == 'q') + if(msg[3] == 'c') + { + // if this is setting a csqc variable, deprotect csqc_progcrc + // temporarily so that it can be set by the cvar command, + // and then reprotect it afterwards + int crcflags = csqc_progcrc.flags; + int sizeflags = csqc_progcrc.flags; + csqc_progcrc.flags &= ~CVAR_READONLY; + csqc_progsize.flags &= ~CVAR_READONLY; + Cmd_ExecuteString (msg, src_command, true); + csqc_progcrc.flags = crcflags; + csqc_progsize.flags = sizeflags; + return; + } + + if(cls.demoplayback) + if(!strncmp(msg, "curl --clear_autodownload\ncurl --pak --forthismap --as ", 55)) + { + // special handling for map download commands + // run these commands IMMEDIATELY, instead of waiting for a client frame + // that way, there is no black screen when playing back demos + // I know this is a really ugly hack, but I can't think of any better way + // FIXME find the actual CAUSE of this, and make demo playback WAIT + // until all maps are loaded, then remove this hack + + char buf[MAX_INPUTLINE]; + const char *p, *q; + size_t l; + + p = msg; + + for(;;) + { + q = strchr(p, '\n'); + if(q) + l = q - p; + else + l = strlen(p); + if(l > sizeof(buf) - 1) + l = sizeof(buf) - 1; + strlcpy(buf, p, l + 1); // strlcpy needs a + 1 as it includes the newline! + + Cmd_ExecuteString(buf, src_command, true); + + p += l; + if(*p == '\n') + ++p; // skip the newline and continue + else + break; // end of string or overflow + } + Cmd_ExecuteString("curl --clear_autodownload", src_command, true); // don't inhibit CSQC loading + return; + } + + if(!cl.csqc_loaded) + { + Cbuf_AddText(msg); + return; + } + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Parse_StuffCmd)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, msg); + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Parse_StuffCmd), "QC function CSQC_Parse_StuffCmd is missing"); + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + } + else + Cbuf_AddText(msg); + CSQC_END +} + +static void CL_VM_Parse_Print (const char *msg) +{ + prvm_prog_t *prog = CLVM_prog; + int restorevm_tempstringsbuf_cursize; + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, msg); + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Parse_Print), "QC function CSQC_Parse_Print is missing"); + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; +} + +void CSQC_AddPrintText (const char *msg) +{ + prvm_prog_t *prog = CLVM_prog; + size_t i; + if(!cl.csqc_loaded) + { + Con_Print(msg); + return; + } + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Parse_Print)) + { + // FIXME: is this bugged? + i = strlen(msg)-1; + if(msg[i] != '\n' && msg[i] != '\r') + { + if(strlen(cl.csqc_printtextbuf)+i >= MAX_INPUTLINE) + { + CL_VM_Parse_Print(cl.csqc_printtextbuf); + cl.csqc_printtextbuf[0] = 0; + } + else + strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE); + return; + } + strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE); + CL_VM_Parse_Print(cl.csqc_printtextbuf); + cl.csqc_printtextbuf[0] = 0; + } + else + Con_Print(msg); + CSQC_END +} + +void CL_VM_Parse_CenterPrint (const char *msg) +{ + prvm_prog_t *prog = CLVM_prog; + int restorevm_tempstringsbuf_cursize; + if(!cl.csqc_loaded) + { + SCR_CenterPrint(msg); + return; + } + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Parse_CenterPrint)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, msg); + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Parse_CenterPrint), "QC function CSQC_Parse_CenterPrint is missing"); + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + } + else + SCR_CenterPrint(msg); + CSQC_END +} + +void CL_VM_UpdateIntermissionState (int intermission) +{ + prvm_prog_t *prog = CLVM_prog; + if(cl.csqc_loaded) + { + CSQC_BEGIN + PRVM_clientglobalfloat(intermission) = intermission; + CSQC_END + } +} +void CL_VM_UpdateShowingScoresState (int showingscores) +{ + prvm_prog_t *prog = CLVM_prog; + if(cl.csqc_loaded) + { + CSQC_BEGIN + PRVM_clientglobalfloat(sb_showscores) = showingscores; + CSQC_END + } +} +qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos, int flags, float speed) +{ + prvm_prog_t *prog = CLVM_prog; + qboolean r = false; + if(cl.csqc_loaded) + { + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Event_Sound)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + PRVM_G_FLOAT(OFS_PARM0) = ent; + PRVM_G_FLOAT(OFS_PARM1) = CHAN_ENGINE2USER(channel); + PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(prog, cl.sound_name[sound_num] ); + PRVM_G_FLOAT(OFS_PARM3) = volume; + PRVM_G_FLOAT(OFS_PARM4) = attenuation; + VectorCopy(pos, PRVM_G_VECTOR(OFS_PARM5) ); + PRVM_G_FLOAT(OFS_PARM6) = speed * 100.0f; + PRVM_G_FLOAT(OFS_PARM7) = flags; // flags + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Event_Sound), "QC function CSQC_Event_Sound is missing"); + r = CSQC_RETURNVAL != 0; + } + CSQC_END + } + + return r; +} +static void CL_VM_UpdateCoopDeathmatchGlobals (int gametype) +{ + prvm_prog_t *prog = CLVM_prog; + // Avoid global names for clean(er) coding + int localcoop; + int localdeathmatch; + + if(cl.csqc_loaded) + { + if(gametype == GAME_COOP) + { + localcoop = 1; + localdeathmatch = 0; + } + else + if(gametype == GAME_DEATHMATCH) + { + localcoop = 0; + localdeathmatch = 1; + } + else + { + // How did the ServerInfo send an unknown gametype? + // Better just assign the globals as 0... + localcoop = 0; + localdeathmatch = 0; + } + CSQC_BEGIN + PRVM_clientglobalfloat(coop) = localcoop; + PRVM_clientglobalfloat(deathmatch) = localdeathmatch; + CSQC_END + } +} +#if 0 +static float CL_VM_Event (float event) //[515]: needed ? I'd say "YES", but don't know for what :D +{ + prvm_prog_t *prog = CLVM_prog; + float r = 0; + if(!cl.csqc_loaded) + return 0; + CSQC_BEGIN + if(PRVM_clientfunction(CSQC_Event)) + { + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; + PRVM_G_FLOAT(OFS_PARM0) = event; + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Event), "QC function CSQC_Event is missing"); + r = CSQC_RETURNVAL; + } + CSQC_END + return r; +} +#endif + +void CSQC_ReadEntities (void) +{ + prvm_prog_t *prog = CLVM_prog; + unsigned short entnum, oldself, realentnum; + if(!cl.csqc_loaded) + { + Host_Error ("CSQC_ReadEntities: CSQC is not loaded"); + return; + } + + CSQC_BEGIN + PRVM_clientglobalfloat(time) = cl.time; + oldself = PRVM_clientglobaledict(self); + while(1) + { + entnum = MSG_ReadShort(&cl_message); + if(!entnum || cl_message.badread) + break; + realentnum = entnum & 0x7FFF; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum]; + if(entnum & 0x8000) + { + if(PRVM_clientglobaledict(self)) + { + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Ent_Remove), "QC function CSQC_Ent_Remove is missing"); + cl.csqc_server2csqcentitynumber[realentnum] = 0; + } + else + { + // LordHavoc: removing an entity that is already gone on + // the csqc side is possible for legitimate reasons (such + // as a repeat of the remove message), so no warning is + // needed + //Con_Printf("Bad csqc_server2csqcentitynumber map\n"); //[515]: never happens ? + } + } + else + { + if(!PRVM_clientglobaledict(self)) + { + if(!PRVM_clientfunction(CSQC_Ent_Spawn)) + { + prvm_edict_t *ed; + ed = PRVM_ED_Alloc(prog); + PRVM_clientedictfloat(ed, entnum) = realentnum; + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT_TO_PROG(ed); + } + else + { + // entity( float entnum ) CSQC_Ent_Spawn; + // the qc function should set entnum, too (this way it also can return world [2/1/2008 Andreas] + PRVM_G_FLOAT(OFS_PARM0) = (float) realentnum; + // make sure no one gets wrong ideas + PRVM_clientglobaledict(self) = 0; + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Ent_Spawn), "QC function CSQC_Ent_Spawn is missing"); + PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT( PRVM_G_INT( OFS_RETURN ) ); + } + PRVM_G_FLOAT(OFS_PARM0) = 1; + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Ent_Update), "QC function CSQC_Ent_Update is missing"); + } + else { + PRVM_G_FLOAT(OFS_PARM0) = 0; + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Ent_Update), "QC function CSQC_Ent_Update is missing"); + } + } + } + PRVM_clientglobaledict(self) = oldself; + CSQC_END +} + +static void CLVM_begin_increase_edicts(prvm_prog_t *prog) +{ + // links don't survive the transition, so unlink everything + World_UnlinkAll(&cl.world); +} + +static void CLVM_end_increase_edicts(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *ent; + + // link every entity except world + for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++) + if (!ent->priv.server->free) + CL_LinkEdict(ent); +} + +static void CLVM_init_edict(prvm_prog_t *prog, prvm_edict_t *e) +{ + int edictnum = PRVM_NUM_FOR_EDICT(e); + entity_render_t *entrender; + CL_ExpandCSQCRenderEntities(edictnum); + entrender = cl.csqcrenderentities + edictnum; + e->priv.server->move = false; // don't move on first frame + memset(entrender, 0, sizeof(*entrender)); + entrender->shadertime = cl.time; +} + +static void CLVM_free_edict(prvm_prog_t *prog, prvm_edict_t *ed) +{ + entity_render_t *entrender = cl.csqcrenderentities + PRVM_NUM_FOR_EDICT(ed); + R_DecalSystem_Reset(&entrender->decalsystem); + memset(entrender, 0, sizeof(*entrender)); + World_UnlinkEdict(ed); + memset(ed->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); + VM_RemoveEdictSkeleton(prog, ed); + World_Physics_RemoveFromEntity(&cl.world, ed); + World_Physics_RemoveJointFromEntity(&cl.world, ed); +} + +static void CLVM_count_edicts(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *ent; + int active = 0, models = 0, solid = 0; + + for (i=0 ; inum_edicts ; i++) + { + ent = PRVM_EDICT_NUM(i); + if (ent->priv.server->free) + continue; + active++; + if (PRVM_clientedictfloat(ent, solid)) + solid++; + if (PRVM_clientedictstring(ent, model)) + models++; + } + + Con_Printf("num_edicts:%3i\n", prog->num_edicts); + Con_Printf("active :%3i\n", active); + Con_Printf("view :%3i\n", models); + Con_Printf("touch :%3i\n", solid); +} + +static qboolean CLVM_load_edict(prvm_prog_t *prog, prvm_edict_t *ent) +{ + return true; +} + +// returns true if the packet is valid, false if end of file is reached +// used for dumping the CSQC download into demo files +qboolean MakeDownloadPacket(const char *filename, unsigned char *data, size_t len, int crc, int cnt, sizebuf_t *buf, int protocol) +{ + int packetsize = buf->maxsize - 7; // byte short long + int npackets = (len + packetsize - 1) / (packetsize); + char vabuf[1024]; + + if(protocol == PROTOCOL_QUAKEWORLD) + return false; // CSQC can't run in QW anyway + + SZ_Clear(buf); + if(cnt == 0) + { + MSG_WriteByte(buf, svc_stufftext); + MSG_WriteString(buf, va(vabuf, sizeof(vabuf), "\ncl_downloadbegin %lu %s\n", (unsigned long)len, filename)); + return true; + } + else if(cnt >= 1 && cnt <= npackets) + { + unsigned long thispacketoffset = (cnt - 1) * packetsize; + int thispacketsize = len - thispacketoffset; + if(thispacketsize > packetsize) + thispacketsize = packetsize; + + MSG_WriteByte(buf, svc_downloaddata); + MSG_WriteLong(buf, thispacketoffset); + MSG_WriteShort(buf, thispacketsize); + SZ_Write(buf, data + thispacketoffset, thispacketsize); + + return true; + } + else if(cnt == npackets + 1) + { + MSG_WriteByte(buf, svc_stufftext); + MSG_WriteString(buf, va(vabuf, sizeof(vabuf), "\ncl_downloadfinished %lu %d\n", (unsigned long)len, crc)); + return true; + } + return false; +} + +extern cvar_t csqc_usedemoprogs; +void CL_VM_Init (void) +{ + prvm_prog_t *prog = CLVM_prog; + const char* csprogsfn = NULL; + unsigned char *csprogsdata = NULL; + fs_offset_t csprogsdatasize = 0; + int csprogsdatacrc, requiredcrc; + int requiredsize; + char vabuf[1024]; + + // reset csqc_progcrc after reading it, so that changing servers doesn't + // expect csqc on the next server + requiredcrc = csqc_progcrc.integer; + requiredsize = csqc_progsize.integer; + Cvar_SetValueQuick(&csqc_progcrc, -1); + Cvar_SetValueQuick(&csqc_progsize, -1); + + // if the server is not requesting a csprogs, then we're done here + if (requiredcrc < 0) + return; + + // see if the requested csprogs.dat file matches the requested crc + if (!cls.demoplayback || csqc_usedemoprogs.integer) + { + csprogsfn = va(vabuf, sizeof(vabuf), "dlcache/%s.%i.%i", csqc_progname.string, requiredsize, requiredcrc); + if(cls.caughtcsprogsdata && cls.caughtcsprogsdatasize == requiredsize && CRC_Block(cls.caughtcsprogsdata, (size_t)cls.caughtcsprogsdatasize) == requiredcrc) + { + Con_DPrintf("Using buffered \"%s\"\n", csprogsfn); + csprogsdata = cls.caughtcsprogsdata; + csprogsdatasize = cls.caughtcsprogsdatasize; + cls.caughtcsprogsdata = NULL; + cls.caughtcsprogsdatasize = 0; + } + else + { + Con_DPrintf("Not using buffered \"%s\" (buffered: %p, %d)\n", csprogsfn, cls.caughtcsprogsdata, (int) cls.caughtcsprogsdatasize); + csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize); + } + } + if (!csprogsdata) + { + csprogsfn = csqc_progname.string; + csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize); + } + if (csprogsdata) + { + csprogsdatacrc = CRC_Block(csprogsdata, (size_t)csprogsdatasize); + if (csprogsdatacrc != requiredcrc || csprogsdatasize != requiredsize) + { + if (cls.demoplayback) + { + Con_Printf("^1Warning: Your %s is not the same version as the demo was recorded with (CRC/size are %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize); + // Mem_Free(csprogsdata); + // return; + // We WANT to continue here, and play the demo with different csprogs! + // After all, this is just a warning. Sure things may go wrong from here. + } + else + { + Mem_Free(csprogsdata); + Con_Printf("^1Your %s is not the same version as the server (CRC is %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize); + CL_Disconnect(); + return; + } + } + } + else + { + if (requiredcrc >= 0) + { + if (cls.demoplayback) + Con_Printf("CL_VM_Init: demo requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string); + else + Con_Printf("CL_VM_Init: server requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string); + CL_Disconnect(); + } + return; + } + + PRVM_Prog_Init(prog); + + // allocate the mempools + prog->progs_mempool = Mem_AllocPool(csqc_progname.string, 0, NULL); + prog->edictprivate_size = 0; // no private struct used + prog->name = "client"; + prog->num_edicts = 1; + prog->max_edicts = 512; + prog->limit_edicts = CL_MAX_EDICTS; + prog->reserved_edicts = 0; + prog->edictprivate_size = sizeof(edict_engineprivate_t); + // TODO: add a shared extension string #define and add real support for csqc extension strings [12/5/2007 Black] + prog->extensionstring = vm_sv_extensions; + prog->builtins = vm_cl_builtins; + prog->numbuiltins = vm_cl_numbuiltins; + + // all callbacks must be defined (pointers are not checked before calling) + prog->begin_increase_edicts = CLVM_begin_increase_edicts; + prog->end_increase_edicts = CLVM_end_increase_edicts; + prog->init_edict = CLVM_init_edict; + prog->free_edict = CLVM_free_edict; + prog->count_edicts = CLVM_count_edicts; + prog->load_edict = CLVM_load_edict; + prog->init_cmd = CLVM_init_cmd; + prog->reset_cmd = CLVM_reset_cmd; + prog->error_cmd = Host_Error; + prog->ExecuteProgram = CLVM_ExecuteProgram; + + PRVM_Prog_Load(prog, csprogsfn, csprogsdata, csprogsdatasize, cl_numrequiredfunc, cl_required_func, CL_REQFIELDS, cl_reqfields, CL_REQGLOBALS, cl_reqglobals); + + if (!prog->loaded) + { + Host_Error("CSQC %s ^2failed to load\n", csprogsfn); + if(!sv.active) + CL_Disconnect(); + Mem_Free(csprogsdata); + return; + } + + Con_DPrintf("CSQC %s ^5loaded (crc=%i, size=%i)\n", csprogsfn, csprogsdatacrc, (int)csprogsdatasize); + + if(cls.demorecording) + { + if(cls.demo_lastcsprogssize != csprogsdatasize || cls.demo_lastcsprogscrc != csprogsdatacrc) + { + int i; + static char buf[NET_MAXMESSAGE]; + sizebuf_t sb; + unsigned char *demobuf; fs_offset_t demofilesize; + + sb.data = (unsigned char *) buf; + sb.maxsize = sizeof(buf); + i = 0; + + CL_CutDemo(&demobuf, &demofilesize); + while(MakeDownloadPacket(csqc_progname.string, csprogsdata, (size_t)csprogsdatasize, csprogsdatacrc, i++, &sb, cls.protocol)) + CL_WriteDemoMessage(&sb); + CL_PasteDemo(&demobuf, &demofilesize); + + cls.demo_lastcsprogssize = csprogsdatasize; + cls.demo_lastcsprogscrc = csprogsdatacrc; + } + } + Mem_Free(csprogsdata); + + // check if OP_STATE animation is possible in this dat file + if (prog->fieldoffsets.nextthink >= 0 && prog->fieldoffsets.frame >= 0 && prog->fieldoffsets.think >= 0 && prog->globaloffsets.self >= 0) + prog->flag |= PRVM_OP_STATE; + + // set time + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = 0; + + PRVM_clientglobalstring(mapname) = PRVM_SetEngineString(prog, cl.worldname); + PRVM_clientglobalfloat(player_localnum) = cl.realplayerentity - 1; + PRVM_clientglobalfloat(player_localentnum) = cl.viewentity; + + // set map description (use world entity 0) + PRVM_clientedictstring(prog->edicts, message) = PRVM_SetEngineString(prog, cl.worldmessage); + VectorCopy(cl.world.mins, PRVM_clientedictvector(prog->edicts, mins)); + VectorCopy(cl.world.maxs, PRVM_clientedictvector(prog->edicts, maxs)); + + // call the prog init + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Init), "QC function CSQC_Init is missing"); + + cl.csqc_loaded = true; + + cl.csqc_vidvars.drawcrosshair = false; + cl.csqc_vidvars.drawenginesbar = false; + + // Update Coop and Deathmatch Globals (at this point the client knows them from ServerInfo) + CL_VM_UpdateCoopDeathmatchGlobals(cl.gametype); +} + +void CL_VM_ShutDown (void) +{ + prvm_prog_t *prog = CLVM_prog; + Cmd_ClearCsqcFuncs(); + //Cvar_SetValueQuick(&csqc_progcrc, -1); + //Cvar_SetValueQuick(&csqc_progsize, -1); + if(!cl.csqc_loaded) + return; + CSQC_BEGIN + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = 0; + if (PRVM_clientfunction(CSQC_Shutdown)) + prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Shutdown), "QC function CSQC_Shutdown is missing"); + PRVM_Prog_Reset(prog); + CSQC_END + Con_DPrint("CSQC ^1unloaded\n"); + cl.csqc_loaded = false; +} + +qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out) +{ + prvm_prog_t *prog = CLVM_prog; + prvm_edict_t *ed; + dp_model_t *mod; + matrix4x4_t matrix; + qboolean r = 0; + + CSQC_BEGIN; + + // FIXME consider attachments here! + + ed = PRVM_EDICT_NUM(entnum - MAX_EDICTS); + + if(!ed->priv.required->free) + { + mod = CL_GetModelFromEdict(ed); + VectorCopy(PRVM_clientedictvector(ed, origin), out); + if(CL_GetTagMatrix(prog, &matrix, ed, 0) == 0) + Matrix4x4_OriginFromMatrix(&matrix, out); + if (mod && mod->soundfromcenter) + VectorMAMAM(1.0f, out, 0.5f, mod->normalmins, 0.5f, mod->normalmaxs, out); + r = 1; + } + + CSQC_END; + + return r; +} + +qboolean CL_VM_TransformView(int entnum, matrix4x4_t *viewmatrix, mplane_t *clipplane, vec3_t visorigin) +{ + prvm_prog_t *prog = CLVM_prog; + qboolean ret = false; + prvm_edict_t *ed; + vec3_t forward, left, up, origin, ang; + matrix4x4_t mat, matq; + + CSQC_BEGIN + ed = PRVM_EDICT_NUM(entnum); + // camera: + // camera_transform + if(PRVM_clientedictfunction(ed, camera_transform)) + { + ret = true; + if(viewmatrix || clipplane || visorigin) + { + Matrix4x4_ToVectors(viewmatrix, forward, left, up, origin); + AnglesFromVectors(ang, forward, up, false); + PRVM_clientglobalfloat(time) = cl.time; + PRVM_clientglobaledict(self) = entnum; + VectorCopy(origin, PRVM_G_VECTOR(OFS_PARM0)); + VectorCopy(ang, PRVM_G_VECTOR(OFS_PARM1)); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorScale(left, -1, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_clientglobalvector(trace_endpos)); + prog->ExecuteProgram(prog, PRVM_clientedictfunction(ed, camera_transform), "QC function e.camera_transform is missing"); + VectorCopy(PRVM_G_VECTOR(OFS_RETURN), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorScale(PRVM_clientglobalvector(v_right), -1, left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + VectorCopy(PRVM_clientglobalvector(trace_endpos), visorigin); + Matrix4x4_Invert_Full(&mat, viewmatrix); + Matrix4x4_FromVectors(viewmatrix, forward, left, up, origin); + Matrix4x4_Concat(&matq, viewmatrix, &mat); + Matrix4x4_TransformPositivePlane(&matq, clipplane->normal[0], clipplane->normal[1], clipplane->normal[2], clipplane->dist, &clipplane->normal[0]); + } + } + CSQC_END + + return ret; +} + +int CL_VM_GetViewEntity(void) +{ + if(cl.csqc_server2csqcentitynumber[cl.viewentity]) + return cl.csqc_server2csqcentitynumber[cl.viewentity] + MAX_EDICTS; + return cl.viewentity; +} diff --git a/app/jni/csprogs.h b/app/jni/csprogs.h new file mode 100644 index 0000000..02fa972 --- /dev/null +++ b/app/jni/csprogs.h @@ -0,0 +1,121 @@ +#ifndef CSPROGS_H +#define CSPROGS_H + +// LordHavoc: changed to match MAX_EDICTS +#define CL_MAX_EDICTS MAX_EDICTS + +#define ENTMASK_ENGINE 1 +#define ENTMASK_ENGINEVIEWMODELS 2 +#define ENTMASK_NORMAL 4 + +#define VF_MIN 1 //(vector) +#define VF_MIN_X 2 //(float) +#define VF_MIN_Y 3 //(float) +#define VF_SIZE 4 //(vector) (viewport size) +#define VF_SIZE_X 5 //(float) +#define VF_SIZE_Y 6 //(float) +#define VF_VIEWPORT 7 //(vector, vector) +#define VF_FOV 8 //(vector) +#define VF_FOVX 9 //(float) +#define VF_FOVY 10 //(float) +#define VF_ORIGIN 11 //(vector) +#define VF_ORIGIN_X 12 //(float) +#define VF_ORIGIN_Y 13 //(float) +#define VF_ORIGIN_Z 14 //(float) +#define VF_ANGLES 15 //(vector) +#define VF_ANGLES_X 16 //(float) +#define VF_ANGLES_Y 17 //(float) +#define VF_ANGLES_Z 18 //(float) + +#define VF_DRAWWORLD 19 //(float) //actually world model and sky +#define VF_DRAWENGINESBAR 20 //(float) +#define VF_DRAWCROSSHAIR 21 //(float) + +#define VF_CL_VIEWANGLES 33 //(vector) //sweet thing for RPGs/... +#define VF_CL_VIEWANGLES_X 34 //(float) +#define VF_CL_VIEWANGLES_Y 35 //(float) +#define VF_CL_VIEWANGLES_Z 36 //(float) + +// FTEQW's extension range +#define VF_PERSPECTIVE 200 //(float) + +// what is this doing here? This is a DP extension introduced by Black, should be in 4xx range +#define VF_CLEARSCREEN 201 //(float) + +// what is this doing here? This is a DP extension introduced by VorteX, should be in 4xx range +#define VF_FOG_DENSITY 202 //(float) +#define VF_FOG_COLOR 203 //(vector) +#define VF_FOG_COLOR_R 204 //(float) +#define VF_FOG_COLOR_G 205 //(float) +#define VF_FOG_COLOR_B 206 //(float) +#define VF_FOG_ALPHA 207 //(float) +#define VF_FOG_START 208 //(float) +#define VF_FOG_END 209 //(float) +#define VF_FOG_HEIGHT 210 //(float) +#define VF_FOG_FADEDEPTH 211 //(float) + +// DP's extension range +#define VF_MAINVIEW 400 //(float) +#define VF_MINFPS_QUALITY 401 //(float) + +#define RF_VIEWMODEL 1 // The entity is never drawn in mirrors. In engines with realtime lighting, it casts no shadows. +#define RF_EXTERNALMODEL 2 // The entity is appears in mirrors but not in the normal view. It does still cast shadows in engines with realtime lighting. +#define RF_DEPTHHACK 4 // The entity appears closer to the view than normal, either by scaling it wierdly or by just using a depthrange. This will usually be found in conjunction with RF_VIEWMODEL +#define RF_ADDITIVE 8 // Add the entity acording to it's alpha values instead of the normal blend +#define RF_USEAXIS 16 // When set, the entity will use the v_forward, v_right and v_up globals instead of it's angles field for orientation. Angles will be ignored compleatly. + // Note that to use this properly, you'll NEED to use the predraw function to set the globals. +//#define RF_DOUBLESIDED 32 +#define RF_USETRANSPARENTOFFSET 64 // Allows QC to customize origin used for transparent sorting via transparent_origin global, helps to fix transparent sorting bugs on a very large entities +#define RF_WORLDOBJECT 128 // for large outdoor entities that should not be culled +#define RF_MODELLIGHT 4096 // CSQC-set model light +#define RF_DYNAMICMODELLIGHT 8192 // origin-dependent model light + +#define RF_FULLBRIGHT 256 +#define RF_NOSHADOW 512 + +extern cvar_t csqc_progname; //[515]: csqc crc check and right csprogs name according to progs.dat +extern cvar_t csqc_progcrc; +extern cvar_t csqc_progsize; + +void CL_VM_PreventInformationLeaks(void); + +qboolean MakeDownloadPacket(const char *filename, unsigned char *data, size_t len, int crc, int cnt, sizebuf_t *buf, int protocol); + +qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out); + +qboolean CL_VM_TransformView(int entnum, matrix4x4_t *viewmatrix, mplane_t *clipplane, vec3_t visorigin); + +void CL_VM_Init(void); +void CL_VM_ShutDown(void); +void CL_VM_UpdateIntermissionState(int intermission); +void CL_VM_UpdateShowingScoresState(int showingscores); +qboolean CL_VM_InputEvent(int eventtype, int x, int y); +qboolean CL_VM_ConsoleCommand(const char *cmd); +void CL_VM_UpdateDmgGlobals(int dmg_take, int dmg_save, vec3_t dmg_origin); +void CL_VM_UpdateIntermissionState(int intermission); +qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos, int flags, float speed); +qboolean CL_VM_Parse_TempEntity(void); +void CL_VM_Parse_StuffCmd(const char *msg); +void CL_VM_Parse_CenterPrint(const char *msg); +int CL_GetPitchSign(prvm_prog_t *prog, prvm_edict_t *ent); +int CL_GetTagMatrix(prvm_prog_t *prog, matrix4x4_t *out, prvm_edict_t *ent, int tagindex); +void CL_GetEntityMatrix(prvm_prog_t *prog, prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix); +/* VMs exposing the polygon calls must call this on Init/Reset */ +void VM_Polygons_Reset(prvm_prog_t *prog); +void QW_CL_StartUpload(unsigned char *data, int size); + +void CSQC_UpdateNetworkTimes(double newtime, double oldtime); +void CSQC_AddPrintText(const char *msg); +void CSQC_ReadEntities(void); +void CSQC_RelinkAllEntities(int drawmask); +void CSQC_RelinkCSQCEntities(void); +void CSQC_Predraw(prvm_edict_t *ed); +void CSQC_Think(prvm_edict_t *ed); +qboolean CSQC_AddRenderEdict(prvm_edict_t *ed, int edictnum);//csprogs.c +void CSQC_R_RecalcView(void); + +dp_model_t *CL_GetModelByIndex(int modelindex); + +int CL_VM_GetViewEntity(void); + +#endif diff --git a/app/jni/curves.c b/app/jni/curves.c new file mode 100644 index 0000000..cf7569d --- /dev/null +++ b/app/jni/curves.c @@ -0,0 +1,440 @@ + +/* +this code written by Forest Hale, on 2004-10-17, and placed into public domain +this implements Quadratic BSpline surfaces as seen in Quake3 by id Software + +a small rant on misuse of the name 'bezier': many people seem to think that +bezier is a generic term for splines, but it is not, it is a term for a +specific type of bspline (4 control points, cubic bspline), bsplines are the +generalization of the bezier spline to support dimensions other than cubic. + +example equations for 1-5 control point bsplines being sampled as t=0...1 +1: flat (0th dimension) +o = a +2: linear (1st dimension) +o = a * (1 - t) + b * t +3: quadratic bspline (2nd dimension) +o = a * (1 - t) * (1 - t) + 2 * b * (1 - t) * t + c * t * t +4: cubic (bezier) bspline (3rd dimension) +o = a * (1 - t) * (1 - t) * (1 - t) + 3 * b * (1 - t) * (1 - t) * t + 3 * c * (1 - t) * t * t + d * t * t * t +5: quartic bspline (4th dimension) +o = a * (1 - t) * (1 - t) * (1 - t) * (1 - t) + 4 * b * (1 - t) * (1 - t) * (1 - t) * t + 6 * c * (1 - t) * (1 - t) * t * t + 4 * d * (1 - t) * t * t * t + e * t * t * t * t + +arbitrary dimension bspline +double factorial(int n) +{ + int i; + double f; + f = 1; + for (i = 1;i < n;i++) + f = f * i; + return f; +} +double bsplinesample(int dimensions, double t, double *param) +{ + double o = 0; + for (i = 0;i < dimensions + 1;i++) + o += param[i] * factorial(dimensions)/(factorial(i)*factorial(dimensions-i)) * pow(t, i) * pow(1 - t, dimensions - i); + return o; +} +*/ + +#include "quakedef.h" +#include "mathlib.h" + +#include +#include "curves.h" + +// Calculate number of resulting vertex rows/columns by given patch size and tesselation factor +// tess=0 means that we reduce detalization of base 3x3 patches by removing middle row and column of vertices +// "DimForTess" is "DIMension FOR TESSelation factor" +// NB: tess=0 actually means that tess must be 0.5, but obviously it can't because it is of int type. (so "a*tess"-like code is replaced by "a/2" if tess=0) +int Q3PatchDimForTess(int size, int tess) +{ + if (tess > 0) + return (size - 1) * tess + 1; + else if (tess == 0) + return (size - 1) / 2 + 1; + else + return 0; // Maybe warn about wrong tess here? +} + +// usage: +// to expand a 5x5 patch to 21x21 vertices (4x4 tesselation), one might use this call: +// Q3PatchSubdivideFloat(3, sizeof(float[3]), outvertices, 5, 5, sizeof(float[3]), patchvertices, 4, 4); +void Q3PatchTesselateFloat(int numcomponents, int outputstride, float *outputvertices, int patchwidth, int patchheight, int inputstride, float *patchvertices, int tesselationwidth, int tesselationheight) +{ + int k, l, x, y, component, outputwidth = Q3PatchDimForTess(patchwidth, tesselationwidth); + float px, py, *v, a, b, c, *cp[3][3], temp[3][64]; + int xmax = max(1, 2*tesselationwidth); + int ymax = max(1, 2*tesselationheight); + + // iterate over the individual 3x3 quadratic spline surfaces one at a time + // expanding them to fill the output array (with some overlap to ensure + // the edges are filled) + for (k = 0;k < patchheight-1;k += 2) + { + for (l = 0;l < patchwidth-1;l += 2) + { + // set up control point pointers for quicker lookup later + for (y = 0;y < 3;y++) + for (x = 0;x < 3;x++) + cp[y][x] = (float *)((unsigned char *)patchvertices + ((k+y)*patchwidth+(l+x)) * inputstride); + // for each row... + for (y = 0;y <= ymax;y++) + { + // calculate control points for this row by collapsing the 3 + // rows of control points to one row using py + py = (float)y / (float)ymax; + // calculate quadratic spline weights for py + a = ((1.0f - py) * (1.0f - py)); + b = ((1.0f - py) * (2.0f * py)); + c = (( py) * ( py)); + for (component = 0;component < numcomponents;component++) + { + temp[0][component] = cp[0][0][component] * a + cp[1][0][component] * b + cp[2][0][component] * c; + temp[1][component] = cp[0][1][component] * a + cp[1][1][component] * b + cp[2][1][component] * c; + temp[2][component] = cp[0][2][component] * a + cp[1][2][component] * b + cp[2][2][component] * c; + } + // fetch a pointer to the beginning of the output vertex row + v = (float *)((unsigned char *)outputvertices + ((k * ymax / 2 + y) * outputwidth + l * xmax / 2) * outputstride); + // for each column of the row... + for (x = 0;x <= xmax;x++) + { + // calculate point based on the row control points + px = (float)x / (float)xmax; + // calculate quadratic spline weights for px + // (could be precalculated) + a = ((1.0f - px) * (1.0f - px)); + b = ((1.0f - px) * (2.0f * px)); + c = (( px) * ( px)); + for (component = 0;component < numcomponents;component++) + v[component] = temp[0][component] * a + temp[1][component] * b + temp[2][component] * c; + // advance to next output vertex using outputstride + // (the next vertex may not be directly following this + // one, as this may be part of a larger structure) + v = (float *)((unsigned char *)v + outputstride); + } + } + } + } +#if 0 + // enable this if you want results printed out + printf("vertices[%i][%i] =\n{\n", (patchheight-1)*tesselationheight+1, (patchwidth-1)*tesselationwidth+1); + for (y = 0;y < (patchheight-1)*tesselationheight+1;y++) + { + for (x = 0;x < (patchwidth-1)*tesselationwidth+1;x++) + { + printf("("); + for (component = 0;component < numcomponents;component++) + printf("%f ", outputvertices[(y*((patchwidth-1)*tesselationwidth+1)+x)*numcomponents+component]); + printf(") "); + } + printf("\n"); + } + printf("}\n"); +#endif +} + +static int Q3PatchTesselation(float largestsquared3xcurvearea, float tolerance) +{ + float f; + // f is actually a squared 2x curve area... so the formula had to be adjusted to give roughly the same subdivisions + f = pow(largestsquared3xcurvearea / 64.0f, 0.25f) / tolerance; + //if(f < 0.25) // VERY flat patches + if(f < 0.0001) // TOTALLY flat patches + return 0; + else if(f < 2) + return 1; + else + return (int) floor(log(f) / log(2.0f)) + 1; + // this is always at least 2 + // maps [0.25..0.5[ to -1 (actually, 1 is returned) + // maps [0.5..1[ to 0 (actually, 1 is returned) + // maps [1..2[ to 1 + // maps [2..4[ to 2 + // maps [4..8[ to 4 +} + +static float Squared3xCurveArea(const float *a, const float *control, const float *b, int components) +{ +#if 0 + // mimicing the old behaviour with the new code... + + float deviation; + float quartercurvearea = 0; + int c; + for (c = 0;c < components;c++) + { + deviation = control[c] * 0.5f - a[c] * 0.25f - b[c] * 0.25f; + quartercurvearea += deviation*deviation; + } + + // But as the new code now works on the squared 2x curve area, let's scale the value + return quartercurvearea * quartercurvearea * 64.0; + +#else + // ideally, we'd like the area between the spline a->control->b and the line a->b. + // but as this is hard to calculate, let's calculate an upper bound of it: + // the area of the triangle a->control->b->a. + // + // one can prove that the area of a quadratic spline = 2/3 * the area of + // the triangle of its control points! + // to do it, first prove it for the spline through (0,0), (1,1), (2,0) + // (which is a parabola) and then note that moving the control point + // left/right is just shearing and keeps the area of both the spline and + // the triangle invariant. + // + // why are we going for the spline area anyway? + // we know that: + // + // the area between the spline and the line a->b is a measure of the + // error of approximation of the spline by the line. + // + // also, on circle-like or parabola-like curves, you easily get that the + // double amount of line approximation segments reduces the error to its quarter + // (also, easy to prove for splines by doing it for one specific one, and using + // affine transforms to get all other splines) + // + // so... + // + // let's calculate the area! but we have to avoid the cross product, as + // components is not necessarily 3 + // + // the area of a triangle spanned by vectors a and b is + // + // 0.5 * |a| |b| sin gamma + // + // now, cos gamma is + // + // a.b / (|a| |b|) + // + // so the area is + // + // 0.5 * sqrt(|a|^2 |b|^2 - (a.b)^2) + int c; + float aa = 0, bb = 0, ab = 0; + for (c = 0;c < components;c++) + { + float xa = a[c] - control[c]; + float xb = b[c] - control[c]; + aa += xa * xa; + ab += xa * xb; + bb += xb * xb; + } + // area is 0.5 * sqrt(aa*bb - ab*ab) + // 2x TRIANGLE area is sqrt(aa*bb - ab*ab) + // 3x CURVE area is sqrt(aa*bb - ab*ab) + return aa * bb - ab * ab; +#endif +} + +// returns how much tesselation of each segment is needed to remain under tolerance +int Q3PatchTesselationOnX(int patchwidth, int patchheight, int components, const float *in, float tolerance) +{ + int x, y; + const float *patch; + float squared3xcurvearea, largestsquared3xcurvearea; + largestsquared3xcurvearea = 0; + for (y = 0;y < patchheight;y++) + { + for (x = 0;x < patchwidth-1;x += 2) + { + patch = in + ((y * patchwidth) + x) * components; + squared3xcurvearea = Squared3xCurveArea(&patch[0], &patch[components], &patch[2*components], components); + if (largestsquared3xcurvearea < squared3xcurvearea) + largestsquared3xcurvearea = squared3xcurvearea; + } + } + return Q3PatchTesselation(largestsquared3xcurvearea, tolerance); +} + +// returns how much tesselation of each segment is needed to remain under tolerance +int Q3PatchTesselationOnY(int patchwidth, int patchheight, int components, const float *in, float tolerance) +{ + int x, y; + const float *patch; + float squared3xcurvearea, largestsquared3xcurvearea; + largestsquared3xcurvearea = 0; + for (y = 0;y < patchheight-1;y += 2) + { + for (x = 0;x < patchwidth;x++) + { + patch = in + ((y * patchwidth) + x) * components; + squared3xcurvearea = Squared3xCurveArea(&patch[0], &patch[patchwidth*components], &patch[2*patchwidth*components], components); + if (largestsquared3xcurvearea < squared3xcurvearea) + largestsquared3xcurvearea = squared3xcurvearea; + } + } + return Q3PatchTesselation(largestsquared3xcurvearea, tolerance); +} + +// Find an equal vertex in array. Check only vertices with odd X and Y +static int FindEqualOddVertexInArray(int numcomponents, float *vertex, float *vertices, int width, int height) +{ + int x, y, j; + for (y=0; y 0.05) + // div0: this is notably smaller than the smallest radiant grid + // but large enough so we don't need to get scared of roundoff + // errors + { + found = false; + break; + } + if(found) + return y*width+x; + vertices += numcomponents*2; + } + vertices += numcomponents*(width-1); + } + return -1; +} + +#define SIDE_INVALID -1 +#define SIDE_X 0 +#define SIDE_Y 1 + +static int GetSide(int p1, int p2, int width, int height, int *pointdist) +{ + int x1 = p1 % width, y1 = p1 / width; + int x2 = p2 % width, y2 = p2 / width; + if (p1 < 0 || p2 < 0) + return SIDE_INVALID; + if (x1 == x2) + { + if (y1 != y2) + { + *pointdist = abs(y2 - y1); + return SIDE_Y; + } + else + return SIDE_INVALID; + } + else if (y1 == y2) + { + *pointdist = abs(x2 - x1); + return SIDE_X; + } + else + return SIDE_INVALID; +} + +// Increase tesselation of one of two touching patches to make a seamless connection between them +// Returns 0 in case if patches were not modified, otherwise 1 +int Q3PatchAdjustTesselation(int numcomponents, patchinfo_t *patch1, float *patchvertices1, patchinfo_t *patch2, float *patchvertices2) +{ + // what we are doing here is: + // we take for each corner of one patch + // and check if the other patch contains that corner + // once we have a pair of such matches + + struct {int id1,id2;} commonverts[8]; + int i, j, k, side1, side2, *tess1, *tess2; + int dist1 = 0, dist2 = 0; + qboolean modified = false; + + // Potential paired vertices (corners of the first patch) + commonverts[0].id1 = 0; + commonverts[1].id1 = patch1->xsize-1; + commonverts[2].id1 = patch1->xsize*(patch1->ysize-1); + commonverts[3].id1 = patch1->xsize*patch1->ysize-1; + for (i=0;i<4;++i) + commonverts[i].id2 = FindEqualOddVertexInArray(numcomponents, patchvertices1+numcomponents*commonverts[i].id1, patchvertices2, patch2->xsize, patch2->ysize); + + // Corners of the second patch + commonverts[4].id2 = 0; + commonverts[5].id2 = patch2->xsize-1; + commonverts[6].id2 = patch2->xsize*(patch2->ysize-1); + commonverts[7].id2 = patch2->xsize*patch2->ysize-1; + for (i=4;i<8;++i) + commonverts[i].id1 = FindEqualOddVertexInArray(numcomponents, patchvertices2+numcomponents*commonverts[i].id2, patchvertices1, patch1->xsize, patch1->ysize); + + for (i=0;i<8;++i) + for (j=i+1;j<8;++j) + { + side1 = GetSide(commonverts[i].id1,commonverts[j].id1,patch1->xsize,patch1->ysize,&dist1); + side2 = GetSide(commonverts[i].id2,commonverts[j].id2,patch2->xsize,patch2->ysize,&dist2); + + if (side1 == SIDE_INVALID || side2 == SIDE_INVALID) + continue; + + if(dist1 != dist2) + { + // no patch welding if the resolutions mismatch + continue; + } + + // Update every lod level + for (k=0;klods[k].xtess : &patch1->lods[k].ytess; + tess2 = side2 == SIDE_X ? &patch2->lods[k].xtess : &patch2->lods[k].ytess; + if (*tess1 != *tess2) + { + if (*tess1 < *tess2) + *tess1 = *tess2; + else + *tess2 = *tess1; + modified = true; + } + } + } + + return modified; +} + +#undef SIDE_INVALID +#undef SIDE_X +#undef SIDE_Y + +// calculates elements for a grid of vertices +// (such as those produced by Q3PatchTesselate) +// (note: width and height are the actual vertex size, this produces +// (width-1)*(height-1)*2 triangles, 3 elements each) +void Q3PatchTriangleElements(int *elements, int width, int height, int firstvertex) +{ + int x, y, row0, row1; + for (y = 0;y < height - 1;y++) + { + if(y % 2) + { + // swap the triangle order in odd rows as optimization for collision stride + row0 = firstvertex + (y + 0) * width + width - 2; + row1 = firstvertex + (y + 1) * width + width - 2; + for (x = 0;x < width - 1;x++) + { + *elements++ = row1; + *elements++ = row1 + 1; + *elements++ = row0 + 1; + *elements++ = row0; + *elements++ = row1; + *elements++ = row0 + 1; + row0--; + row1--; + } + } + else + { + row0 = firstvertex + (y + 0) * width; + row1 = firstvertex + (y + 1) * width; + for (x = 0;x < width - 1;x++) + { + *elements++ = row0; + *elements++ = row1; + *elements++ = row0 + 1; + *elements++ = row1; + *elements++ = row1 + 1; + *elements++ = row0 + 1; + row0++; + row1++; + } + } + } +} + diff --git a/app/jni/curves.h b/app/jni/curves.h new file mode 100644 index 0000000..6555a5a --- /dev/null +++ b/app/jni/curves.h @@ -0,0 +1,39 @@ + +#ifndef CURVES_H +#define CURVES_H + +#define PATCH_LODS_NUM 2 +#define PATCH_LOD_COLLISION 0 +#define PATCH_LOD_VISUAL 1 + +typedef struct patchinfo_s +{ + int xsize, ysize; + struct { + int xtess, ytess; + } lods[PATCH_LODS_NUM]; +} patchinfo_t; + +// Calculate number of resulting vertex rows/columns by given patch size and tesselation factor +// When tess=0 it means that we reduce detalization of base 3x3 patches by removing middle row and column +// "DimForTess" is "DIMension FOR TESSelation factor" +int Q3PatchDimForTess(int size, int tess); + +// usage: +// to expand a 5x5 patch to 21x21 vertices (4x4 tesselation), one might use this call: +// Q3PatchSubdivideFloat(3, sizeof(float[3]), outvertices, 5, 5, sizeof(float[3]), patchvertices, 4, 4); +void Q3PatchTesselateFloat(int numcomponents, int outputstride, float *outputvertices, int patchwidth, int patchheight, int inputstride, float *patchvertices, int tesselationwidth, int tesselationheight); +// returns how much tesselation of each segment is needed to remain under tolerance +int Q3PatchTesselationOnX(int patchwidth, int patchheight, int components, const float *in, float tolerance); +// returns how much tesselation of each segment is needed to remain under tolerance +int Q3PatchTesselationOnY(int patchwidth, int patchheight, int components, const float *in, float tolerance); +// calculates elements for a grid of vertices +// (such as those produced by Q3PatchTesselate) +// (note: width and height are the actual vertex size, this produces +// (width-1)*(height-1)*2 triangles, 3 elements each) +void Q3PatchTriangleElements(int *elements, int width, int height, int firstvertex); + +int Q3PatchAdjustTesselation(int numcomponents, patchinfo_t *patch1, float *patchvertices1, patchinfo_t *patch2, float *patchvertices2); + +#endif + diff --git a/app/jni/cvar.c b/app/jni/cvar.c new file mode 100644 index 0000000..b88aafd --- /dev/null +++ b/app/jni/cvar.c @@ -0,0 +1,1027 @@ +/* +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. + +*/ +// cvar.c -- dynamic variable tracking + +#include "quakedef.h" + +const char *cvar_dummy_description = "custom cvar"; + +cvar_t *cvar_vars = NULL; +cvar_t *cvar_hashtable[CVAR_HASHSIZE]; +const char *cvar_null_string = ""; + +/* +============ +Cvar_FindVar +============ +*/ +cvar_t *Cvar_FindVar (const char *var_name) +{ + int hashindex; + cvar_t *var; + + // use hash lookup to minimize search time + hashindex = CRC_Block((const unsigned char *)var_name, strlen(var_name)) % CVAR_HASHSIZE; + for (var = cvar_hashtable[hashindex];var;var = var->nextonhashchain) + if (!strcmp (var_name, var->name)) + return var; + + return NULL; +} + +cvar_t *Cvar_FindVarAfter (const char *prev_var_name, int neededflags) +{ + cvar_t *var; + + if (*prev_var_name) + { + var = Cvar_FindVar (prev_var_name); + if (!var) + return NULL; + var = var->next; + } + else + var = cvar_vars; + + // search for the next cvar matching the needed flags + while (var) + { + if ((var->flags & neededflags) || !neededflags) + break; + var = var->next; + } + return var; +} + +static cvar_t *Cvar_FindVarLink (const char *var_name, cvar_t **parent, cvar_t ***link, cvar_t **prev_alpha) +{ + int hashindex; + cvar_t *var; + + // use hash lookup to minimize search time + hashindex = CRC_Block((const unsigned char *)var_name, strlen(var_name)); + if(parent) *parent = NULL; + if(prev_alpha) *prev_alpha = NULL; + if(link) *link = &cvar_hashtable[hashindex]; + for (var = cvar_hashtable[hashindex];var;var = var->nextonhashchain) + { + if (!strcmp (var_name, var->name)) + { + if(!prev_alpha || var == cvar_vars) + return var; + + *prev_alpha = cvar_vars; + // if prev_alpha happens to become NULL then there has been some inconsistency elsewhere + // already - should I still insert '*prev_alpha &&' in the loop? + while((*prev_alpha)->next != var) + *prev_alpha = (*prev_alpha)->next; + return var; + } + if(parent) *parent = var; + } + + return NULL; +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValueOr (const char *var_name, float def) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return def; + return atof (var->string); +} + +float Cvar_VariableValue (const char *var_name) +{ + return Cvar_VariableValueOr(var_name, 0); +} + +/* +============ +Cvar_VariableString +============ +*/ +const char *Cvar_VariableStringOr (const char *var_name, const char *def) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return def; + return var->string; +} + +const char *Cvar_VariableString (const char *var_name) +{ + return Cvar_VariableStringOr(var_name, cvar_null_string); +} + +/* +============ +Cvar_VariableDefString +============ +*/ +const char *Cvar_VariableDefString (const char *var_name) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return cvar_null_string; + return var->defstring; +} + +/* +============ +Cvar_VariableDescription +============ +*/ +const char *Cvar_VariableDescription (const char *var_name) +{ + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return cvar_null_string; + return var->description; +} + + +/* +============ +Cvar_CompleteVariable +============ +*/ +const char *Cvar_CompleteVariable (const char *partial) +{ + cvar_t *cvar; + size_t len; + + len = strlen(partial); + + if (!len) + return NULL; + +// check functions + for (cvar=cvar_vars ; cvar ; cvar=cvar->next) + if (!strncasecmp (partial,cvar->name, len)) + return cvar->name; + + return NULL; +} + + +/* + CVar_CompleteCountPossible + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + +*/ +int Cvar_CompleteCountPossible (const char *partial) +{ + cvar_t *cvar; + size_t len; + int h; + + h = 0; + len = strlen(partial); + + if (!len) + return 0; + + // Loop through the cvars and count all possible matches + for (cvar = cvar_vars; cvar; cvar = cvar->next) + if (!strncasecmp(partial, cvar->name, len)) + h++; + + return h; +} + +/* + CVar_CompleteBuildList + + New function for tab-completion system + Added by EvilTypeGuy + Thanks to Fett erich@heintz.com + Thanks to taniwha + +*/ +const char **Cvar_CompleteBuildList (const char *partial) +{ + const cvar_t *cvar; + size_t len = 0; + size_t bpos = 0; + size_t sizeofbuf = (Cvar_CompleteCountPossible (partial) + 1) * sizeof (const char *); + const char **buf; + + len = strlen(partial); + buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); + // Loop through the alias list and print all matches + for (cvar = cvar_vars; cvar; cvar = cvar->next) + if (!strncasecmp(partial, cvar->name, len)) + buf[bpos++] = cvar->name; + + buf[bpos] = NULL; + return buf; +} + +// written by LordHavoc +void Cvar_CompleteCvarPrint (const char *partial) +{ + cvar_t *cvar; + size_t len = strlen(partial); + // Loop through the command list and print all matches + for (cvar = cvar_vars; cvar; cvar = cvar->next) + if (!strncasecmp(partial, cvar->name, len)) + Con_Printf ("^3%s^7 is \"%s\" [\"%s\"] %s\n", cvar->name, cvar->string, cvar->defstring, cvar->description); +} + +// we assume that prog is already set to the target progs +static void Cvar_UpdateAutoCvar(cvar_t *var) +{ + int i; + int j; + const char *s; + vec3_t v; + prvm_prog_t *prog; + for (i = 0;i < PRVM_PROG_MAX;i++) + { + prog = &prvm_prog_list[i]; + if (prog->loaded && var->globaldefindex_progid[i] == prog->id) + { + // MUST BE SYNCED WITH prvm_edict.c PRVM_LoadProgs + switch(prog->globaldefs[var->globaldefindex[i]].type & ~DEF_SAVEGLOBAL) + { + case ev_float: + PRVM_GLOBALFIELDFLOAT(prog->globaldefs[var->globaldefindex[i]].ofs) = var->value; + break; + case ev_vector: + s = var->string; + VectorClear(v); + for (j = 0;j < 3;j++) + { + while (*s && ISWHITESPACE(*s)) + s++; + if (!*s) + break; + v[j] = atof(s); + while (!ISWHITESPACE(*s)) + s++; + if (!*s) + break; + } + VectorCopy(v, PRVM_GLOBALFIELDVECTOR(prog->globaldefs[var->globaldefindex[i]].ofs)); + break; + case ev_string: + PRVM_ChangeEngineString(prog, var->globaldefindex_stringno[i], var->string); + PRVM_GLOBALFIELDSTRING(prog->globaldefs[var->globaldefindex[i]].ofs) = var->globaldefindex_stringno[i]; + break; + } + } + } +} + +// called after loading a savegame +void Cvar_UpdateAllAutoCvars(void) +{ + cvar_t *var; + for (var = cvar_vars ; var ; var = var->next) + Cvar_UpdateAutoCvar(var); +} + +/* +============ +Cvar_Set +============ +*/ +extern cvar_t sv_disablenotify; +static void Cvar_SetQuick_Internal (cvar_t *var, const char *value) +{ + qboolean changed; + size_t valuelen; + char vabuf[1024]; + + changed = strcmp(var->string, value) != 0; + // LordHavoc: don't reallocate when there is no change + if (!changed) + return; + + // LordHavoc: don't reallocate when the buffer is the same size + valuelen = strlen(value); + if (!var->string || strlen(var->string) != valuelen) + { + Z_Free ((char *)var->string); // free the old value string + + var->string = (char *)Z_Malloc (valuelen + 1); + } + memcpy ((char *)var->string, value, valuelen + 1); + var->value = atof (var->string); + var->integer = (int) var->value; + if ((var->flags & CVAR_NOTIFY) && changed && sv.active && !sv_disablenotify.integer) + SV_BroadcastPrintf("\"%s\" changed to \"%s\"\n", var->name, var->string); +#if 0 + // TODO: add infostring support to the server? + if ((var->flags & CVAR_SERVERINFO) && changed && sv.active) + { + InfoString_SetValue(svs.serverinfo, sizeof(svs.serverinfo), var->name, var->string); + if (sv.active) + { + MSG_WriteByte (&sv.reliable_datagram, svc_serverinfostring); + MSG_WriteString (&sv.reliable_datagram, var->name); + MSG_WriteString (&sv.reliable_datagram, var->string); + } + } +#endif + if ((var->flags & CVAR_USERINFO) && cls.state != ca_dedicated) + CL_SetInfo(var->name, var->string, true, false, false, false); + else if ((var->flags & CVAR_NQUSERINFOHACK) && cls.state != ca_dedicated) + { + // update the cls.userinfo to have proper values for the + // silly nq config variables. + // + // this is done when these variables are changed rather than at + // connect time because if the user or code checks the userinfo and it + // holds weird values it may cause confusion... + if (!strcmp(var->name, "_cl_color")) + { + int top = (var->integer >> 4) & 15, bottom = var->integer & 15; + CL_SetInfo("topcolor", va(vabuf, sizeof(vabuf), "%i", top), true, false, false, false); + CL_SetInfo("bottomcolor", va(vabuf, sizeof(vabuf), "%i", bottom), true, false, false, false); + if (cls.protocol != PROTOCOL_QUAKEWORLD && cls.netcon) + { + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "color %i %i", top, bottom)); + } + } + else if (!strcmp(var->name, "_cl_rate")) + CL_SetInfo("rate", va(vabuf, sizeof(vabuf), "%i", var->integer), true, false, false, false); + else if (!strcmp(var->name, "_cl_playerskin")) + CL_SetInfo("playerskin", var->string, true, false, false, false); + else if (!strcmp(var->name, "_cl_playermodel")) + CL_SetInfo("playermodel", var->string, true, false, false, false); + else if (!strcmp(var->name, "_cl_name")) + CL_SetInfo("name", var->string, true, false, false, false); + else if (!strcmp(var->name, "rcon_secure")) + { + // whenever rcon_secure is changed to 0, clear rcon_password for + // security reasons (prevents a send-rcon-password-as-plaintext + // attack based on NQ protocol session takeover and svc_stufftext) + if(var->integer <= 0) + Cvar_Set("rcon_password", ""); + } + else if (!strcmp(var->name, "net_slist_favorites")) + NetConn_UpdateFavorites(); + } + + Cvar_UpdateAutoCvar(var); +} + +void Cvar_SetQuick (cvar_t *var, const char *value) +{ + if (var == NULL) + { + Con_Print("Cvar_SetQuick: var == NULL\n"); + return; + } + + if (developer_extra.integer) + Con_DPrintf("Cvar_SetQuick({\"%s\", \"%s\", %i, \"%s\"}, \"%s\");\n", var->name, var->string, var->flags, var->defstring, value); + + Cvar_SetQuick_Internal(var, value); +} + +void Cvar_Set (const char *var_name, const char *value) +{ + cvar_t *var; + var = Cvar_FindVar (var_name); + if (var == NULL) + { + Con_Printf("Cvar_Set: variable %s not found\n", var_name); + return; + } + Cvar_SetQuick(var, value); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValueQuick(cvar_t *var, float value) +{ + char val[MAX_INPUTLINE]; + + if ((float)((int)value) == value) + dpsnprintf(val, sizeof(val), "%i", (int)value); + else + dpsnprintf(val, sizeof(val), "%f", value); + Cvar_SetQuick(var, val); +} + +void Cvar_SetValue(const char *var_name, float value) +{ + char val[MAX_INPUTLINE]; + + if ((float)((int)value) == value) + dpsnprintf(val, sizeof(val), "%i", (int)value); + else + dpsnprintf(val, sizeof(val), "%f", value); + Cvar_Set(var_name, val); +} + +/* +============ +Cvar_RegisterVariable + +Adds a freestanding variable to the variable list. +============ +*/ +void Cvar_RegisterVariable (cvar_t *variable) +{ + int hashindex; + cvar_t *current, *next, *cvar; + char *oldstr; + size_t alloclen; + + if (developer_extra.integer) + Con_DPrintf("Cvar_RegisterVariable({\"%s\", \"%s\", %i});\n", variable->name, variable->string, variable->flags); + +// first check to see if it has already been defined + cvar = Cvar_FindVar (variable->name); + if (cvar) + { + if (cvar->flags & CVAR_ALLOCATED) + { + if (developer_extra.integer) + Con_DPrintf("... replacing existing allocated cvar {\"%s\", \"%s\", %i}\n", cvar->name, cvar->string, cvar->flags); + // fixed variables replace allocated ones + // (because the engine directly accesses fixed variables) + // NOTE: this isn't actually used currently + // (all cvars are registered before config parsing) + variable->flags |= (cvar->flags & ~CVAR_ALLOCATED); + // cvar->string is now owned by variable instead + variable->string = cvar->string; + variable->defstring = cvar->defstring; + variable->value = atof (variable->string); + variable->integer = (int) variable->value; + // replace cvar with this one... + variable->next = cvar->next; + if (cvar_vars == cvar) + { + // head of the list is easy to change + cvar_vars = variable; + } + else + { + // otherwise find it somewhere in the list + for (current = cvar_vars;current->next != cvar;current = current->next) + ; + current->next = variable; + } + + // get rid of old allocated cvar + // (but not cvar->string and cvar->defstring, because we kept those) + Z_Free((char *)cvar->name); + Z_Free(cvar); + } + else + Con_DPrintf("Can't register variable %s, already defined\n", variable->name); + return; + } + +// check for overlap with a command + if (Cmd_Exists (variable->name)) + { + Con_Printf("Cvar_RegisterVariable: %s is a command\n", variable->name); + return; + } + +// copy the value off, because future sets will Z_Free it + oldstr = (char *)variable->string; + alloclen = strlen(variable->string) + 1; + variable->string = (char *)Z_Malloc (alloclen); + memcpy ((char *)variable->string, oldstr, alloclen); + variable->defstring = (char *)Z_Malloc (alloclen); + memcpy ((char *)variable->defstring, oldstr, alloclen); + variable->value = atof (variable->string); + variable->integer = (int) variable->value; + +// link the variable in +// alphanumerical order + for( current = NULL, next = cvar_vars ; next && strcmp( next->name, variable->name ) < 0 ; current = next, next = next->next ) + ; + if( current ) { + current->next = variable; + } else { + cvar_vars = variable; + } + variable->next = next; + + // link to head of list in this hash table index + hashindex = CRC_Block((const unsigned char *)variable->name, strlen(variable->name)) % CVAR_HASHSIZE; + variable->nextonhashchain = cvar_hashtable[hashindex]; + cvar_hashtable[hashindex] = variable; +} + +/* +============ +Cvar_Get + +Adds a newly allocated variable to the variable list or sets its value. +============ +*/ +cvar_t *Cvar_Get (const char *name, const char *value, int flags, const char *newdescription) +{ + int hashindex; + cvar_t *current, *next, *cvar; + + if (developer_extra.integer) + Con_DPrintf("Cvar_Get(\"%s\", \"%s\", %i);\n", name, value, flags); + +// first check to see if it has already been defined + cvar = Cvar_FindVar (name); + if (cvar) + { + cvar->flags |= flags; + Cvar_SetQuick_Internal (cvar, value); + if(newdescription && (cvar->flags & CVAR_ALLOCATED)) + { + if(cvar->description != cvar_dummy_description) + Z_Free((char *)cvar->description); + + if(*newdescription) + cvar->description = (char *)Mem_strdup(zonemempool, newdescription); + else + cvar->description = cvar_dummy_description; + } + return cvar; + } + +// check for pure evil + if (!*name) + { + Con_Printf("Cvar_Get: invalid variable name\n"); + return NULL; + } + +// check for overlap with a command + if (Cmd_Exists (name)) + { + Con_Printf("Cvar_Get: %s is a command\n", name); + return NULL; + } + +// allocate a new cvar, cvar name, and cvar string +// TODO: factorize the following code with the one at the end of Cvar_RegisterVariable() +// FIXME: these never get Z_Free'd + cvar = (cvar_t *)Z_Malloc(sizeof(cvar_t)); + cvar->flags = flags | CVAR_ALLOCATED; + cvar->name = (char *)Mem_strdup(zonemempool, name); + cvar->string = (char *)Mem_strdup(zonemempool, value); + cvar->defstring = (char *)Mem_strdup(zonemempool, value); + cvar->value = atof (cvar->string); + cvar->integer = (int) cvar->value; + + if(newdescription && *newdescription) + cvar->description = (char *)Mem_strdup(zonemempool, newdescription); + else + cvar->description = cvar_dummy_description; // actually checked by VM_cvar_type + +// link the variable in +// alphanumerical order + for( current = NULL, next = cvar_vars ; next && strcmp( next->name, cvar->name ) < 0 ; current = next, next = next->next ) + ; + if( current ) + current->next = cvar; + else + cvar_vars = cvar; + cvar->next = next; + + // link to head of list in this hash table index + hashindex = CRC_Block((const unsigned char *)cvar->name, strlen(cvar->name)) % CVAR_HASHSIZE; + cvar->nextonhashchain = cvar_hashtable[hashindex]; + cvar_hashtable[hashindex] = cvar; + + return cvar; +} + + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +qboolean Cvar_Command (void) +{ + cvar_t *v; + +// check variables + v = Cvar_FindVar (Cmd_Argv(0)); + if (!v) + return false; + +// perform a variable print or set + if (Cmd_Argc() == 1) + { + Con_Printf("\"%s\" is \"%s\" [\"%s\"]\n", v->name, ((v->flags & CVAR_PRIVATE) ? "********"/*hunter2*/ : v->string), v->defstring); + return true; + } + + if (developer_extra.integer) + Con_DPrint("Cvar_Command: "); + + if (v->flags & CVAR_READONLY) + { + Con_Printf("%s is read-only\n", v->name); + return true; + } + Cvar_Set (v->name, Cmd_Argv(1)); + if (developer_extra.integer) + Con_DPrint("\n"); + return true; +} + + +void Cvar_UnlockDefaults (void) +{ + cvar_t *var; + // unlock the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + var->flags &= ~CVAR_DEFAULTSET; +} + + +void Cvar_LockDefaults_f (void) +{ + cvar_t *var; + // lock in the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + { + if (!(var->flags & CVAR_DEFAULTSET)) + { + size_t alloclen; + + //Con_Printf("locking cvar %s (%s -> %s)\n", var->name, var->string, var->defstring); + var->flags |= CVAR_DEFAULTSET; + Z_Free((char *)var->defstring); + alloclen = strlen(var->string) + 1; + var->defstring = (char *)Z_Malloc(alloclen); + memcpy((char *)var->defstring, var->string, alloclen); + } + } +} + +void Cvar_SaveInitState(void) +{ + cvar_t *c; + for (c = cvar_vars;c;c = c->next) + { + c->initstate = true; + c->initflags = c->flags; + c->initdefstring = Mem_strdup(zonemempool, c->defstring); + c->initstring = Mem_strdup(zonemempool, c->string); + c->initvalue = c->value; + c->initinteger = c->integer; + VectorCopy(c->vector, c->initvector); + } +} + +void Cvar_RestoreInitState(void) +{ + int hashindex; + cvar_t *c, **cp; + cvar_t *c2, **cp2; + for (cp = &cvar_vars;(c = *cp);) + { + if (c->initstate) + { + // restore this cvar, it existed at init + if (((c->flags ^ c->initflags) & CVAR_MAXFLAGSVAL) + || strcmp(c->defstring ? c->defstring : "", c->initdefstring ? c->initdefstring : "") + || strcmp(c->string ? c->string : "", c->initstring ? c->initstring : "")) + { + Con_DPrintf("Cvar_RestoreInitState: Restoring cvar \"%s\"\n", c->name); + if (c->defstring) + Z_Free((char *)c->defstring); + c->defstring = Mem_strdup(zonemempool, c->initdefstring); + if (c->string) + Z_Free((char *)c->string); + c->string = Mem_strdup(zonemempool, c->initstring); + } + c->flags = c->initflags; + c->value = c->initvalue; + c->integer = c->initinteger; + VectorCopy(c->initvector, c->vector); + cp = &c->next; + } + else + { + if (!(c->flags & CVAR_ALLOCATED)) + { + Con_DPrintf("Cvar_RestoreInitState: Unable to destroy cvar \"%s\", it was registered after init!\n", c->name); + cp = &c->next; + continue; + } + // remove this cvar, it did not exist at init + Con_DPrintf("Cvar_RestoreInitState: Destroying cvar \"%s\"\n", c->name); + // unlink struct from hash + hashindex = CRC_Block((const unsigned char *)c->name, strlen(c->name)) % CVAR_HASHSIZE; + for (cp2 = &cvar_hashtable[hashindex];(c2 = *cp2);) + { + if (c2 == c) + { + *cp2 = c2->nextonhashchain; + break; + } + else + cp2 = &c2->nextonhashchain; + } + // unlink struct from main list + *cp = c->next; + // free strings + if (c->defstring) + Z_Free((char *)c->defstring); + if (c->string) + Z_Free((char *)c->string); + if (c->description && c->description != cvar_dummy_description) + Z_Free((char *)c->description); + // free struct + Z_Free(c); + } + } +} + +void Cvar_ResetToDefaults_All_f (void) +{ + cvar_t *var; + // restore the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + if((var->flags & CVAR_NORESETTODEFAULTS) == 0) + Cvar_SetQuick(var, var->defstring); +} + + +void Cvar_ResetToDefaults_NoSaveOnly_f (void) +{ + cvar_t *var; + // restore the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + if ((var->flags & (CVAR_NORESETTODEFAULTS | CVAR_SAVE)) == 0) + Cvar_SetQuick(var, var->defstring); +} + + +void Cvar_ResetToDefaults_SaveOnly_f (void) +{ + cvar_t *var; + // restore the default values of all cvars + for (var = cvar_vars ; var ; var = var->next) + if ((var->flags & (CVAR_NORESETTODEFAULTS | CVAR_SAVE)) == CVAR_SAVE) + Cvar_SetQuick(var, var->defstring); +} + + +/* +============ +Cvar_WriteVariables + +Writes lines containing "set variable value" for all variables +with the archive flag set to true. +============ +*/ +void Cvar_WriteVariables (qfile_t *f) +{ + cvar_t *var; + char buf1[MAX_INPUTLINE], buf2[MAX_INPUTLINE]; + + // don't save cvars that match their default value + for (var = cvar_vars ; var ; var = var->next) + if ((var->flags & CVAR_SAVE) && (strcmp(var->string, var->defstring) || ((var->flags & CVAR_ALLOCATED) && !(var->flags & CVAR_DEFAULTSET)))) + { + Cmd_QuoteString(buf1, sizeof(buf1), var->name, "\"\\$", false); + Cmd_QuoteString(buf2, sizeof(buf2), var->string, "\"\\$", false); + FS_Printf(f, "%s\"%s\" \"%s\"\n", var->flags & CVAR_ALLOCATED ? "seta " : "", buf1, buf2); + } +} + + +// Added by EvilTypeGuy eviltypeguy@qeradiant.com +// 2000-01-09 CvarList command By Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ +/* +========= +Cvar_List +========= +*/ +void Cvar_List_f (void) +{ + cvar_t *cvar; + const char *partial; + size_t len; + int count; + qboolean ispattern; + + if (Cmd_Argc() > 1) + { + partial = Cmd_Argv (1); + len = strlen(partial); + ispattern = (strchr(partial, '*') || strchr(partial, '?')); + } + else + { + partial = NULL; + len = 0; + ispattern = false; + } + + count = 0; + for (cvar = cvar_vars; cvar; cvar = cvar->next) + { + if (len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp (partial,cvar->name,len))) + continue; + + Con_Printf("%s is \"%s\" [\"%s\"] %s\n", cvar->name, ((cvar->flags & CVAR_PRIVATE) ? "********"/*hunter2*/ : cvar->string), cvar->defstring, cvar->description); + count++; + } + + if (len) + { + if(ispattern) + Con_Printf("%i cvar%s matching \"%s\"\n", count, (count > 1) ? "s" : "", partial); + else + Con_Printf("%i cvar%s beginning with \"%s\"\n", count, (count > 1) ? "s" : "", partial); + } + else + Con_Printf("%i cvar(s)\n", count); +} +// 2000-01-09 CvarList command by Maddes + +void Cvar_Set_f (void) +{ + cvar_t *cvar; + + // make sure it's the right number of parameters + if (Cmd_Argc() < 3) + { + Con_Printf("Set: wrong number of parameters, usage: set []\n"); + return; + } + + // check if it's read-only + cvar = Cvar_FindVar(Cmd_Argv(1)); + if (cvar && cvar->flags & CVAR_READONLY) + { + Con_Printf("Set: %s is read-only\n", cvar->name); + return; + } + + if (developer_extra.integer) + Con_DPrint("Set: "); + + // all looks ok, create/modify the cvar + Cvar_Get(Cmd_Argv(1), Cmd_Argv(2), 0, Cmd_Argc() > 3 ? Cmd_Argv(3) : NULL); +} + +void Cvar_SetA_f (void) +{ + cvar_t *cvar; + + // make sure it's the right number of parameters + if (Cmd_Argc() < 3) + { + Con_Printf("SetA: wrong number of parameters, usage: seta []\n"); + return; + } + + // check if it's read-only + cvar = Cvar_FindVar(Cmd_Argv(1)); + if (cvar && cvar->flags & CVAR_READONLY) + { + Con_Printf("SetA: %s is read-only\n", cvar->name); + return; + } + + if (developer_extra.integer) + Con_DPrint("SetA: "); + + // all looks ok, create/modify the cvar + Cvar_Get(Cmd_Argv(1), Cmd_Argv(2), CVAR_SAVE, Cmd_Argc() > 3 ? Cmd_Argv(3) : NULL); +} + +void Cvar_Del_f (void) +{ + int i; + cvar_t *cvar, *parent, **link, *prev; + + if(Cmd_Argc() < 2) + { + Con_Printf("Del: wrong number of parameters, useage: unset [ ...]\n"); + return; + } + for(i = 1; i < Cmd_Argc(); ++i) + { + cvar = Cvar_FindVarLink(Cmd_Argv(i), &parent, &link, &prev); + if(!cvar) + { + Con_Printf("Del: %s is not defined\n", Cmd_Argv(i)); + continue; + } + if(cvar->flags & CVAR_READONLY) + { + Con_Printf("Del: %s is read-only\n", cvar->name); + continue; + } + if(!(cvar->flags & CVAR_ALLOCATED)) + { + Con_Printf("Del: %s is static and cannot be deleted\n", cvar->name); + continue; + } + if(cvar == cvar_vars) + { + cvar_vars = cvar->next; + } + else + { + // in this case, prev must be set, otherwise there has been some inconsistensy + // elsewhere already... should I still check for prev != NULL? + prev->next = cvar->next; + } + + if(parent) + parent->nextonhashchain = cvar->nextonhashchain; + else if(link) + *link = cvar->nextonhashchain; + + if(cvar->description != cvar_dummy_description) + Z_Free((char *)cvar->description); + + Z_Free((char *)cvar->name); + Z_Free((char *)cvar->string); + Z_Free((char *)cvar->defstring); + Z_Free(cvar); + } +} + +#ifdef FILLALLCVARSWITHRUBBISH +void Cvar_FillAll_f() +{ + char *buf, *p, *q; + int n, i; + cvar_t *var; + qboolean verify; + if(Cmd_Argc() != 2) + { + Con_Printf("Usage: %s length to plant rubbish\n", Cmd_Argv(0)); + Con_Printf("Usage: %s -length to verify that the rubbish is still there\n", Cmd_Argv(0)); + return; + } + n = atoi(Cmd_Argv(1)); + verify = (n < 0); + if(verify) + n = -n; + buf = Z_Malloc(n + 1); + buf[n] = 0; + for(var = cvar_vars; var; var = var->next) + { + for(i = 0, p = buf, q = var->name; i < n; ++i) + { + *p++ = *q++; + if(!*q) + q = var->name; + } + if(verify && strcmp(var->string, buf)) + { + Con_Printf("\n%s does not contain the right rubbish, either this is the first run or a possible overrun was detected, or something changed it intentionally; it DOES contain: %s\n", var->name, var->string); + } + Cvar_SetQuick(var, buf); + } + Z_Free(buf); +} +#endif /* FILLALLCVARSWITHRUBBISH */ diff --git a/app/jni/cvar.h b/app/jni/cvar.h new file mode 100644 index 0000000..4fd177d --- /dev/null +++ b/app/jni/cvar.h @@ -0,0 +1,245 @@ +/* +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. + +*/ +// cvar.h + +/* + +cvar_t variables are used to hold scalar or string variables that can be changed or displayed at the console or prog code as well as accessed directly +in C code. + +it is sufficient to initialize a cvar_t with just the first two fields, or +you can add a ,true flag for variables that you want saved to the configuration +file when the game is quit: + +cvar_t r_draworder = {"r_draworder","1"}; +cvar_t scr_screensize = {"screensize","1",true}; + +Cvars must be registered before use, or they will have a 0 value instead of the float interpretation of the string. Generally, all cvar_t declarations should be registered in the apropriate init function before any console commands are executed: +Cvar_RegisterVariable (&host_framerate); + + +C code usually just references a cvar in place: +if ( r_draworder.value ) + +It could optionally ask for the value to be looked up for a string name: +if (Cvar_VariableValue ("r_draworder")) + +Interpreted prog code can access cvars with the cvar(name) or +cvar_set (name, value) internal functions: +teamplay = cvar("teamplay"); +cvar_set ("registered", "1"); + +The user can access cvars from the console in two ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. +*/ + +#ifndef CVAR_H +#define CVAR_H + +// cvar flags + +#define CVAR_SAVE 1 +#define CVAR_NOTIFY 2 +#define CVAR_READONLY 4 +#define CVAR_SERVERINFO 8 +#define CVAR_USERINFO 16 +// CVAR_PRIVATE means do not $ expand or sendcvar this cvar under any circumstances (rcon_password uses this) +#define CVAR_PRIVATE 32 +// this means that this cvar should update a userinfo key but the name does not correspond directly to the userinfo key to update, and may require additional conversion ("_cl_color" for example should update "topcolor" and "bottomcolor") +#define CVAR_NQUSERINFOHACK 64 +// used to determine if flags is valid +#define CVAR_NORESETTODEFAULTS 128 +// for engine-owned cvars that must not be reset on gametype switch (e.g. scr_screenshot_name, which otherwise isn't set to the mod name properly) +#define CVAR_MAXFLAGSVAL 255 +// for internal use only! +#define CVAR_DEFAULTSET (1<<30) +#define CVAR_ALLOCATED (1<<31) + +/* +// type of a cvar for menu purposes +#define CVARMENUTYPE_FLOAT 1 +#define CVARMENUTYPE_INTEGER 2 +#define CVARMENUTYPE_SLIDER 3 +#define CVARMENUTYPE_BOOL 4 +#define CVARMENUTYPE_STRING 5 +#define CVARMENUTYPE_OPTION 6 + +// which menu to put a cvar in +#define CVARMENU_GRAPHICS 1 +#define CVARMENU_SOUND 2 +#define CVARMENU_INPUT 3 +#define CVARMENU_NETWORK 4 +#define CVARMENU_SERVER 5 + +#define MAX_CVAROPTIONS 16 + +typedef struct cvaroption_s +{ + int value; + const char *name; +} +cvaroption_t; + +typedef struct menucvar_s +{ + int type; + float valuemin, valuemax, valuestep; + int numoptions; + cvaroption_t optionlist[MAX_CVAROPTIONS]; +} +menucvar_t; +*/ + +typedef struct cvar_s +{ + int flags; + + const char *name; + + const char *string; + const char *description; + int integer; + float value; + float vector[3]; + + const char *defstring; + + // values at init (for Cvar_RestoreInitState) + qboolean initstate; // indicates this existed at init + int initflags; + const char *initstring; + const char *initdescription; + int initinteger; + float initvalue; + float initvector[3]; + const char *initdefstring; + + unsigned int globaldefindex_progid[3]; + int globaldefindex[3]; + int globaldefindex_stringno[3]; + + //menucvar_t menuinfo; + struct cvar_s *next; + struct cvar_s *nextonhashchain; +} cvar_t; + +/* +void Cvar_MenuSlider(cvar_t *variable, int menu, float slider_min, float slider_max, float slider_step); +void Cvar_MenuBool(cvar_t *variable, int menu, const char *name_false, const char *name_true); +void Cvar_MenuFloat(cvar_t *variable, int menu, float range_min, float range_max); +void Cvar_MenuInteger(cvar_t *variable, int menu, int range_min, int range_max); +void Cvar_MenuString(cvar_t *variable, int menu); +void Cvar_MenuOption(cvar_t *variable, int menu, int value[16], const char *name[16]); +*/ + +/// registers a cvar that already has the name, string, and optionally the +/// archive elements set. +void Cvar_RegisterVariable (cvar_t *variable); + +/// equivelant to " " typed at the console +void Cvar_Set (const char *var_name, const char *value); + +/// expands value to a string and calls Cvar_Set +void Cvar_SetValue (const char *var_name, float value); + +void Cvar_SetQuick (cvar_t *var, const char *value); +void Cvar_SetValueQuick (cvar_t *var, float value); + +float Cvar_VariableValueOr (const char *var_name, float def); +// returns def if not defined + +float Cvar_VariableValue (const char *var_name); +// returns 0 if not defined or non numeric + +const char *Cvar_VariableStringOr (const char *var_name, const char *def); +// returns def if not defined + +const char *Cvar_VariableString (const char *var_name); +// returns an empty string if not defined + +const char *Cvar_VariableDefString (const char *var_name); +// returns an empty string if not defined + +const char *Cvar_VariableDescription (const char *var_name); +// returns an empty string if not defined + +const char *Cvar_CompleteVariable (const char *partial); +// attempts to match a partial variable name for command line completion +// returns NULL if nothing fits + +void Cvar_CompleteCvarPrint (const char *partial); + +qboolean Cvar_Command (void); +// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known +// command. Returns true if the command was a variable reference that +// was handled. (print or change) + +void Cvar_SaveInitState(void); +void Cvar_RestoreInitState(void); + +void Cvar_UnlockDefaults (void); +void Cvar_LockDefaults_f (void); +void Cvar_ResetToDefaults_All_f (void); +void Cvar_ResetToDefaults_NoSaveOnly_f (void); +void Cvar_ResetToDefaults_SaveOnly_f (void); + +void Cvar_WriteVariables (qfile_t *f); +// Writes lines containing "set variable value" for all variables +// with the archive flag set to true. + +cvar_t *Cvar_FindVar (const char *var_name); +cvar_t *Cvar_FindVarAfter (const char *prev_var_name, int neededflags); + +int Cvar_CompleteCountPossible (const char *partial); +const char **Cvar_CompleteBuildList (const char *partial); +// Added by EvilTypeGuy - functions for tab completion system +// Thanks to Fett erich@heintz.com +// Thanks to taniwha + +/// Prints a list of Cvars including a count of them to the user console +/// Referenced in cmd.c in Cmd_Init hence it's inclusion here. +/// Added by EvilTypeGuy eviltypeguy@qeradiant.com +/// Thanks to Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ +void Cvar_List_f (void); + +void Cvar_Set_f (void); +void Cvar_SetA_f (void); +void Cvar_Del_f (void); +// commands to create new cvars (or set existing ones) +// seta creates an archived cvar (saved to config) + +/// allocates a cvar by name and returns its address, +/// or merely sets its value if it already exists. +cvar_t *Cvar_Get (const char *name, const char *value, int flags, const char *newdescription); + +extern const char *cvar_dummy_description; // ALWAYS the same pointer +extern cvar_t *cvar_vars; // used to list all cvars + +void Cvar_UpdateAllAutoCvars(void); // updates ALL autocvars of the active prog to the cvar values (savegame loading) + +#ifdef FILLALLCVARSWITHRUBBISH +void Cvar_FillAll_f(); +#endif /* FILLALLCVARSWITHRUBBISH */ + +#endif + diff --git a/app/jni/dpsoftrast.c b/app/jni/dpsoftrast.c new file mode 100644 index 0000000..039e7fc --- /dev/null +++ b/app/jni/dpsoftrast.c @@ -0,0 +1,5690 @@ +#include +#include +#define _USE_MATH_DEFINES +#include +#include "quakedef.h" +#include "thread.h" +#include "dpsoftrast.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4324) +#endif + +#ifndef __cplusplus +typedef qboolean bool; +#endif + +#define ALIGN_SIZE 16 +#define ATOMIC_SIZE 4 + +#ifdef SSE_POSSIBLE + #if defined(__APPLE__) + #include + #define ALIGN(var) var __attribute__((__aligned__(16))) + #define ATOMIC(var) var __attribute__((__aligned__(4))) + #define MEMORY_BARRIER (_mm_sfence()) + #define ATOMIC_COUNTER volatile int32_t + #define ATOMIC_INCREMENT(counter) (OSAtomicIncrement32Barrier(&(counter))) + #define ATOMIC_DECREMENT(counter) (OSAtomicDecrement32Barrier(&(counter))) + #define ATOMIC_ADD(counter, val) ((void)OSAtomicAdd32Barrier((val), &(counter))) + #elif defined(__GNUC__) && defined(WIN32) + #define ALIGN(var) var __attribute__((__aligned__(16))) + #define ATOMIC(var) var __attribute__((__aligned__(4))) + #define MEMORY_BARRIER (_mm_sfence()) + //(__sync_synchronize()) + #define ATOMIC_COUNTER volatile LONG + // this LONG * cast serves to fix an issue with broken mingw + // packages on Ubuntu; these only declare the function to take + // a LONG *, causing a compile error here. This seems to be + // error- and warn-free on platforms that DO declare + // InterlockedIncrement correctly, like mingw on Windows. + #define ATOMIC_INCREMENT(counter) (InterlockedIncrement((LONG *) &(counter))) + #define ATOMIC_DECREMENT(counter) (InterlockedDecrement((LONG *) &(counter))) + #define ATOMIC_ADD(counter, val) ((void)InterlockedExchangeAdd((LONG *) &(counter), (val))) + #elif defined(__GNUC__) + #define ALIGN(var) var __attribute__((__aligned__(16))) + #define ATOMIC(var) var __attribute__((__aligned__(4))) + #define MEMORY_BARRIER (_mm_sfence()) + //(__sync_synchronize()) + #define ATOMIC_COUNTER volatile int + #define ATOMIC_INCREMENT(counter) (__sync_add_and_fetch(&(counter), 1)) + #define ATOMIC_DECREMENT(counter) (__sync_add_and_fetch(&(counter), -1)) + #define ATOMIC_ADD(counter, val) ((void)__sync_fetch_and_add(&(counter), (val))) + #elif defined(_MSC_VER) + #define ALIGN(var) __declspec(align(16)) var + #define ATOMIC(var) __declspec(align(4)) var + #define MEMORY_BARRIER (_mm_sfence()) + //(MemoryBarrier()) + #define ATOMIC_COUNTER volatile LONG + #define ATOMIC_INCREMENT(counter) (InterlockedIncrement(&(counter))) + #define ATOMIC_DECREMENT(counter) (InterlockedDecrement(&(counter))) + #define ATOMIC_ADD(counter, val) ((void)InterlockedExchangeAdd(&(counter), (val))) + #endif +#endif + +#ifndef ALIGN +#define ALIGN(var) var +#endif +#ifndef ATOMIC +#define ATOMIC(var) var +#endif +#ifndef MEMORY_BARRIER +#define MEMORY_BARRIER ((void)0) +#endif +#ifndef ATOMIC_COUNTER +#define ATOMIC_COUNTER int +#endif +#ifndef ATOMIC_INCREMENT +#define ATOMIC_INCREMENT(counter) (++(counter)) +#endif +#ifndef ATOMIC_DECREMENT +#define ATOMIC_DECREMENT(counter) (--(counter)) +#endif +#ifndef ATOMIC_ADD +#define ATOMIC_ADD(counter, val) ((void)((counter) += (val))) +#endif + +#ifdef SSE_POSSIBLE +#include + +#if defined(__GNUC__) && (__GNUC < 4 || __GNUC_MINOR__ < 6) && !defined(__clang__) + #define _mm_cvtss_f32(val) (__builtin_ia32_vec_ext_v4sf ((__v4sf)(val), 0)) +#endif + +#define MM_MALLOC(size) _mm_malloc(size, ALIGN_SIZE) + +static void *MM_CALLOC(size_t nmemb, size_t size) +{ + void *ptr = _mm_malloc(nmemb*size, ALIGN_SIZE); + if (ptr != NULL) memset(ptr, 0, nmemb*size); + return ptr; +} + +#define MM_FREE _mm_free +#else +#define MM_MALLOC(size) malloc(size) +#define MM_CALLOC(nmemb, size) calloc(nmemb, size) +#define MM_FREE free +#endif + +typedef enum DPSOFTRAST_ARRAY_e +{ + DPSOFTRAST_ARRAY_POSITION, + DPSOFTRAST_ARRAY_COLOR, + DPSOFTRAST_ARRAY_TEXCOORD0, + DPSOFTRAST_ARRAY_TEXCOORD1, + DPSOFTRAST_ARRAY_TEXCOORD2, + DPSOFTRAST_ARRAY_TEXCOORD3, + DPSOFTRAST_ARRAY_TEXCOORD4, + DPSOFTRAST_ARRAY_TEXCOORD5, + DPSOFTRAST_ARRAY_TEXCOORD6, + DPSOFTRAST_ARRAY_TEXCOORD7, + DPSOFTRAST_ARRAY_TOTAL +} +DPSOFTRAST_ARRAY; + +typedef struct DPSOFTRAST_Texture_s +{ + int flags; + int width; + int height; + int depth; + int sides; + DPSOFTRAST_TEXTURE_FILTER filter; + int mipmaps; + int size; + ATOMIC_COUNTER binds; + unsigned char *bytes; + int mipmap[DPSOFTRAST_MAXMIPMAPS][5]; +} +DPSOFTRAST_Texture; + +#define COMMAND_SIZE ALIGN_SIZE +#define COMMAND_ALIGN(var) ALIGN(var) + +typedef COMMAND_ALIGN(struct DPSOFTRAST_Command_s +{ + unsigned char opcode; + unsigned short commandsize; +} +DPSOFTRAST_Command); + +enum { DPSOFTRAST_OPCODE_Reset = 0 }; + +#define DEFCOMMAND(opcodeval, name, fields) \ + enum { DPSOFTRAST_OPCODE_##name = opcodeval }; \ + typedef COMMAND_ALIGN(struct DPSOFTRAST_Command_##name##_s \ + { \ + unsigned char opcode; \ + unsigned short commandsize; \ + fields \ + } DPSOFTRAST_Command_##name ); + +#define DPSOFTRAST_DRAW_MAXCOMMANDPOOL 2097152 +#define DPSOFTRAST_DRAW_MAXCOMMANDSIZE 16384 + +typedef ALIGN(struct DPSOFTRAST_State_Command_Pool_s +{ + int freecommand; + int usedcommands; + ALIGN(unsigned char commands[DPSOFTRAST_DRAW_MAXCOMMANDPOOL]); +} +DPSOFTRAST_State_Command_Pool); + +typedef ALIGN(struct DPSOFTRAST_State_Triangle_s +{ + unsigned char mip[DPSOFTRAST_MAXTEXTUREUNITS]; // texcoord to screen space density values (for picking mipmap of textures) + float w[3]; + ALIGN(float attribs[DPSOFTRAST_ARRAY_TOTAL][3][4]); +} +DPSOFTRAST_State_Triangle); + +#define DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex) { \ + slope = _mm_load_ps((triangle)->attribs[arrayindex][0]); \ + data = _mm_add_ps(_mm_load_ps((triangle)->attribs[arrayindex][2]), \ + _mm_add_ps(_mm_mul_ps(_mm_set1_ps((span)->x), slope), \ + _mm_mul_ps(_mm_set1_ps((span)->y), _mm_load_ps((triangle)->attribs[arrayindex][1])))); \ +} +#define DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex) { \ + slope[0] = (triangle)->attribs[arrayindex][0][0]; \ + slope[1] = (triangle)->attribs[arrayindex][0][1]; \ + slope[2] = (triangle)->attribs[arrayindex][0][2]; \ + slope[3] = (triangle)->attribs[arrayindex][0][3]; \ + data[0] = (triangle)->attribs[arrayindex][2][0] + (span->x)*slope[0] + (span->y)*(triangle)->attribs[arrayindex][1][0]; \ + data[1] = (triangle)->attribs[arrayindex][2][1] + (span->x)*slope[1] + (span->y)*(triangle)->attribs[arrayindex][1][1]; \ + data[2] = (triangle)->attribs[arrayindex][2][2] + (span->x)*slope[2] + (span->y)*(triangle)->attribs[arrayindex][1][2]; \ + data[3] = (triangle)->attribs[arrayindex][2][3] + (span->x)*slope[3] + (span->y)*(triangle)->attribs[arrayindex][1][3]; \ +} + +#define DPSOFTRAST_DRAW_MAXSUBSPAN 16 + +typedef ALIGN(struct DPSOFTRAST_State_Span_s +{ + int triangle; // triangle this span was generated by + int x; // framebuffer x coord + int y; // framebuffer y coord + int startx; // usable range (according to pixelmask) + int endx; // usable range (according to pixelmask) + unsigned char *pixelmask; // true for pixels that passed depth test, false for others + int depthbase; // depthbuffer value at x (add depthslope*startx to get first pixel's depthbuffer value) + int depthslope; // depthbuffer value pixel delta +} +DPSOFTRAST_State_Span); + +#define DPSOFTRAST_DRAW_MAXSPANS 1024 +#define DPSOFTRAST_DRAW_MAXTRIANGLES 128 +#define DPSOFTRAST_DRAW_MAXSPANLENGTH 256 + +#define DPSOFTRAST_VALIDATE_FB 1 +#define DPSOFTRAST_VALIDATE_DEPTHFUNC 2 +#define DPSOFTRAST_VALIDATE_BLENDFUNC 4 +#define DPSOFTRAST_VALIDATE_DRAW (DPSOFTRAST_VALIDATE_FB | DPSOFTRAST_VALIDATE_DEPTHFUNC | DPSOFTRAST_VALIDATE_BLENDFUNC) + +typedef enum DPSOFTRAST_BLENDMODE_e +{ + DPSOFTRAST_BLENDMODE_OPAQUE, + DPSOFTRAST_BLENDMODE_ALPHA, + DPSOFTRAST_BLENDMODE_ADDALPHA, + DPSOFTRAST_BLENDMODE_ADD, + DPSOFTRAST_BLENDMODE_INVMOD, + DPSOFTRAST_BLENDMODE_MUL, + DPSOFTRAST_BLENDMODE_MUL2, + DPSOFTRAST_BLENDMODE_SUBALPHA, + DPSOFTRAST_BLENDMODE_PSEUDOALPHA, + DPSOFTRAST_BLENDMODE_INVADD, + DPSOFTRAST_BLENDMODE_TOTAL +} +DPSOFTRAST_BLENDMODE; + +typedef ALIGN(struct DPSOFTRAST_State_Thread_s +{ + void *thread; + int index; + + int cullface; + int colormask[4]; + int blendfunc[2]; + int blendsubtract; + int depthmask; + int depthtest; + int depthfunc; + int scissortest; + int viewport[4]; + int scissor[4]; + float depthrange[2]; + float polygonoffset[2]; + float clipplane[4]; + ALIGN(float fb_clipplane[4]); + + int shader_mode; + int shader_permutation; + int shader_exactspecularmath; + + DPSOFTRAST_Texture *texbound[DPSOFTRAST_MAXTEXTUREUNITS]; + + ALIGN(float uniform4f[DPSOFTRAST_UNIFORM_TOTAL*4]); + int uniform1i[DPSOFTRAST_UNIFORM_TOTAL]; + + // DPSOFTRAST_VALIDATE_ flags + int validate; + + // derived values (DPSOFTRAST_VALIDATE_FB) + int fb_colormask; + int fb_scissor[4]; + ALIGN(float fb_viewportcenter[4]); + ALIGN(float fb_viewportscale[4]); + + // derived values (DPSOFTRAST_VALIDATE_DEPTHFUNC) + int fb_depthfunc; + + // derived values (DPSOFTRAST_VALIDATE_BLENDFUNC) + int fb_blendmode; + + // band boundaries + int miny1; + int maxy1; + int miny2; + int maxy2; + + ATOMIC(volatile int commandoffset); + + volatile bool waiting; + volatile bool starving; + void *waitcond; + void *drawcond; + void *drawmutex; + + int numspans; + int numtriangles; + DPSOFTRAST_State_Span spans[DPSOFTRAST_DRAW_MAXSPANS]; + DPSOFTRAST_State_Triangle triangles[DPSOFTRAST_DRAW_MAXTRIANGLES]; + unsigned char pixelmaskarray[DPSOFTRAST_DRAW_MAXSPANLENGTH+4]; // LordHavoc: padded to allow some termination bytes +} +DPSOFTRAST_State_Thread); + +typedef ALIGN(struct DPSOFTRAST_State_s +{ + int fb_width; + int fb_height; + unsigned int *fb_depthpixels; + unsigned int *fb_colorpixels[4]; + + int viewport[4]; + ALIGN(float fb_viewportcenter[4]); + ALIGN(float fb_viewportscale[4]); + + float color[4]; + ALIGN(float uniform4f[DPSOFTRAST_UNIFORM_TOTAL*4]); + int uniform1i[DPSOFTRAST_UNIFORM_TOTAL]; + + const float *pointer_vertex3f; + const float *pointer_color4f; + const unsigned char *pointer_color4ub; + const float *pointer_texcoordf[DPSOFTRAST_MAXTEXCOORDARRAYS]; + int stride_vertex; + int stride_color; + int stride_texcoord[DPSOFTRAST_MAXTEXCOORDARRAYS]; + int components_texcoord[DPSOFTRAST_MAXTEXCOORDARRAYS]; + DPSOFTRAST_Texture *texbound[DPSOFTRAST_MAXTEXTUREUNITS]; + + int firstvertex; + int numvertices; + float *post_array4f[DPSOFTRAST_ARRAY_TOTAL]; + float *screencoord4f; + int drawstarty; + int drawendy; + int drawclipped; + + int shader_mode; + int shader_permutation; + int shader_exactspecularmath; + + int texture_max; + int texture_end; + int texture_firstfree; + DPSOFTRAST_Texture *texture; + + int bigendian; + + // error reporting + const char *errorstring; + + bool usethreads; + int interlace; + int numthreads; + DPSOFTRAST_State_Thread *threads; + + ATOMIC(volatile int drawcommand); + + DPSOFTRAST_State_Command_Pool commandpool; +} +DPSOFTRAST_State); + +DPSOFTRAST_State dpsoftrast; + +#define DPSOFTRAST_DEPTHSCALE (1024.0f*1048576.0f) +#define DPSOFTRAST_DEPTHOFFSET (128.0f) +#define DPSOFTRAST_BGRA8_FROM_RGBA32F(r,g,b,a) (((int)(r * 255.0f + 0.5f) << 16) | ((int)(g * 255.0f + 0.5f) << 8) | (int)(b * 255.0f + 0.5f) | ((int)(a * 255.0f + 0.5f) << 24)) +#define DPSOFTRAST_DEPTH32_FROM_DEPTH32F(d) ((int)(DPSOFTRAST_DEPTHSCALE * (1-d))) + +static void DPSOFTRAST_Draw_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_State_Span *span); +static void DPSOFTRAST_Draw_DepthWrite(const DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Span *span); + +static void DPSOFTRAST_RecalcViewport(const int *viewport, float *fb_viewportcenter, float *fb_viewportscale) +{ + fb_viewportcenter[1] = viewport[0] + 0.5f * viewport[2] - 0.5f; + fb_viewportcenter[2] = dpsoftrast.fb_height - viewport[1] - 0.5f * viewport[3] - 0.5f; + fb_viewportcenter[3] = 0.5f; + fb_viewportcenter[0] = 0.0f; + fb_viewportscale[1] = 0.5f * viewport[2]; + fb_viewportscale[2] = -0.5f * viewport[3]; + fb_viewportscale[3] = 0.5f; + fb_viewportscale[0] = 1.0f; +} + +static void DPSOFTRAST_RecalcThread(DPSOFTRAST_State_Thread *thread) +{ + if (dpsoftrast.interlace) + { + thread->miny1 = (thread->index*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); + thread->maxy1 = ((thread->index+1)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); + thread->miny2 = ((dpsoftrast.numthreads+thread->index)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); + thread->maxy2 = ((dpsoftrast.numthreads+thread->index+1)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); + } + else + { + thread->miny1 = thread->miny2 = (thread->index*dpsoftrast.fb_height)/dpsoftrast.numthreads; + thread->maxy1 = thread->maxy2 = ((thread->index+1)*dpsoftrast.fb_height)/dpsoftrast.numthreads; + } +} + +static void DPSOFTRAST_RecalcClipPlane(DPSOFTRAST_State_Thread *thread) +{ + thread->fb_clipplane[0] = thread->clipplane[0] / thread->fb_viewportscale[1]; + thread->fb_clipplane[1] = thread->clipplane[1] / thread->fb_viewportscale[2]; + thread->fb_clipplane[2] = thread->clipplane[2] / thread->fb_viewportscale[3]; + thread->fb_clipplane[3] = thread->clipplane[3] / thread->fb_viewportscale[0]; + thread->fb_clipplane[3] -= thread->fb_viewportcenter[1]*thread->fb_clipplane[0] + thread->fb_viewportcenter[2]*thread->fb_clipplane[1] + thread->fb_viewportcenter[3]*thread->fb_clipplane[2] + thread->fb_viewportcenter[0]*thread->fb_clipplane[3]; +} + +static void DPSOFTRAST_RecalcFB(DPSOFTRAST_State_Thread *thread) +{ + // calculate framebuffer scissor, viewport, viewport clipped by scissor, + // and viewport projection values + int x1, x2; + int y1, y2; + x1 = thread->scissor[0]; + x2 = thread->scissor[0] + thread->scissor[2]; + y1 = dpsoftrast.fb_height - thread->scissor[1] - thread->scissor[3]; + y2 = dpsoftrast.fb_height - thread->scissor[1]; + if (!thread->scissortest) {x1 = 0;y1 = 0;x2 = dpsoftrast.fb_width;y2 = dpsoftrast.fb_height;} + if (x1 < 0) x1 = 0; + if (x2 > dpsoftrast.fb_width) x2 = dpsoftrast.fb_width; + if (y1 < 0) y1 = 0; + if (y2 > dpsoftrast.fb_height) y2 = dpsoftrast.fb_height; + thread->fb_scissor[0] = x1; + thread->fb_scissor[1] = y1; + thread->fb_scissor[2] = x2 - x1; + thread->fb_scissor[3] = y2 - y1; + + DPSOFTRAST_RecalcViewport(thread->viewport, thread->fb_viewportcenter, thread->fb_viewportscale); + DPSOFTRAST_RecalcClipPlane(thread); + DPSOFTRAST_RecalcThread(thread); +} + +static void DPSOFTRAST_RecalcDepthFunc(DPSOFTRAST_State_Thread *thread) +{ + thread->fb_depthfunc = thread->depthtest ? thread->depthfunc : GL_ALWAYS; +} + +static void DPSOFTRAST_RecalcBlendFunc(DPSOFTRAST_State_Thread *thread) +{ + if (thread->blendsubtract) + { + switch ((thread->blendfunc[0]<<16)|thread->blendfunc[1]) + { + #define BLENDFUNC(sfactor, dfactor, blendmode) \ + case (sfactor<<16)|dfactor: thread->fb_blendmode = blendmode; break; + BLENDFUNC(GL_SRC_ALPHA, GL_ONE, DPSOFTRAST_BLENDMODE_SUBALPHA) + default: thread->fb_blendmode = DPSOFTRAST_BLENDMODE_OPAQUE; break; + } + } + else + { + switch ((thread->blendfunc[0]<<16)|thread->blendfunc[1]) + { + BLENDFUNC(GL_ONE, GL_ZERO, DPSOFTRAST_BLENDMODE_OPAQUE) + BLENDFUNC(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, DPSOFTRAST_BLENDMODE_ALPHA) + BLENDFUNC(GL_SRC_ALPHA, GL_ONE, DPSOFTRAST_BLENDMODE_ADDALPHA) + BLENDFUNC(GL_ONE, GL_ONE, DPSOFTRAST_BLENDMODE_ADD) + BLENDFUNC(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, DPSOFTRAST_BLENDMODE_INVMOD) + BLENDFUNC(GL_ZERO, GL_SRC_COLOR, DPSOFTRAST_BLENDMODE_MUL) + BLENDFUNC(GL_DST_COLOR, GL_ZERO, DPSOFTRAST_BLENDMODE_MUL) + BLENDFUNC(GL_DST_COLOR, GL_SRC_COLOR, DPSOFTRAST_BLENDMODE_MUL2) + BLENDFUNC(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, DPSOFTRAST_BLENDMODE_PSEUDOALPHA) + BLENDFUNC(GL_ONE_MINUS_DST_COLOR, GL_ONE, DPSOFTRAST_BLENDMODE_INVADD) + default: thread->fb_blendmode = DPSOFTRAST_BLENDMODE_OPAQUE; break; + } + } +} + +#define DPSOFTRAST_ValidateQuick(thread, f) ((thread->validate & (f)) ? (DPSOFTRAST_Validate(thread, f), 0) : 0) + +static void DPSOFTRAST_Validate(DPSOFTRAST_State_Thread *thread, int mask) +{ + mask &= thread->validate; + if (!mask) + return; + if (mask & DPSOFTRAST_VALIDATE_FB) + { + thread->validate &= ~DPSOFTRAST_VALIDATE_FB; + DPSOFTRAST_RecalcFB(thread); + } + if (mask & DPSOFTRAST_VALIDATE_DEPTHFUNC) + { + thread->validate &= ~DPSOFTRAST_VALIDATE_DEPTHFUNC; + DPSOFTRAST_RecalcDepthFunc(thread); + } + if (mask & DPSOFTRAST_VALIDATE_BLENDFUNC) + { + thread->validate &= ~DPSOFTRAST_VALIDATE_BLENDFUNC; + DPSOFTRAST_RecalcBlendFunc(thread); + } +} + +static DPSOFTRAST_Texture *DPSOFTRAST_Texture_GetByIndex(int index) +{ + if (index >= 1 && index < dpsoftrast.texture_end && dpsoftrast.texture[index].bytes) + return &dpsoftrast.texture[index]; + return NULL; +} + +static void DPSOFTRAST_Texture_Grow(void) +{ + DPSOFTRAST_Texture *oldtexture = dpsoftrast.texture; + DPSOFTRAST_State_Thread *thread; + int i; + int j; + DPSOFTRAST_Flush(); + // expand texture array as needed + if (dpsoftrast.texture_max < 1024) + dpsoftrast.texture_max = 1024; + else + dpsoftrast.texture_max *= 2; + dpsoftrast.texture = (DPSOFTRAST_Texture *)realloc(dpsoftrast.texture, dpsoftrast.texture_max * sizeof(DPSOFTRAST_Texture)); + for (i = 0; i < DPSOFTRAST_MAXTEXTUREUNITS; i++) + if (dpsoftrast.texbound[i]) + dpsoftrast.texbound[i] = dpsoftrast.texture + (dpsoftrast.texbound[i] - oldtexture); + for (j = 0; j < dpsoftrast.numthreads; j++) + { + thread = &dpsoftrast.threads[j]; + for (i = 0; i < DPSOFTRAST_MAXTEXTUREUNITS; i++) + if (thread->texbound[i]) + thread->texbound[i] = dpsoftrast.texture + (thread->texbound[i] - oldtexture); + } +} + +int DPSOFTRAST_Texture_New(int flags, int width, int height, int depth) +{ + int w; + int h; + int d; + int size; + int s; + int texnum; + int mipmaps; + int sides = (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) ? 6 : 1; + int texformat = flags & DPSOFTRAST_TEXTURE_FORMAT_COMPAREMASK; + DPSOFTRAST_Texture *texture; + if (width*height*depth < 1) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: width, height or depth is less than 1"; + return 0; + } + if (width > DPSOFTRAST_TEXTURE_MAXSIZE || height > DPSOFTRAST_TEXTURE_MAXSIZE || depth > DPSOFTRAST_TEXTURE_MAXSIZE) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: texture size is too large"; + return 0; + } + switch(texformat) + { + case DPSOFTRAST_TEXTURE_FORMAT_BGRA8: + case DPSOFTRAST_TEXTURE_FORMAT_RGBA8: + case DPSOFTRAST_TEXTURE_FORMAT_ALPHA8: + break; + case DPSOFTRAST_TEXTURE_FORMAT_DEPTH: + if (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH only permitted on 2D textures"; + return 0; + } + if (depth != 1) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH only permitted on 2D textures"; + return 0; + } + if ((flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP) && (texformat == DPSOFTRAST_TEXTURE_FORMAT_DEPTH)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH does not permit mipmaps"; + return 0; + } + break; + } + if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_CUBEMAP can not be used on 3D textures"; + return 0; + } + if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on 3D textures"; + return 0; + } + if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on 3D textures"; + return 0; + } + if ((flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on cubemap textures"; + return 0; + } + if ((width & (width-1)) || (height & (height-1)) || (depth & (depth-1))) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: dimensions are not power of two"; + return 0; + } + // find first empty slot in texture array + for (texnum = dpsoftrast.texture_firstfree;texnum < dpsoftrast.texture_end;texnum++) + if (!dpsoftrast.texture[texnum].bytes) + break; + dpsoftrast.texture_firstfree = texnum + 1; + if (dpsoftrast.texture_max <= texnum) + DPSOFTRAST_Texture_Grow(); + if (dpsoftrast.texture_end <= texnum) + dpsoftrast.texture_end = texnum + 1; + texture = &dpsoftrast.texture[texnum]; + memset(texture, 0, sizeof(*texture)); + texture->flags = flags; + texture->width = width; + texture->height = height; + texture->depth = depth; + texture->sides = sides; + texture->binds = 0; + w = width; + h = height; + d = depth; + size = 0; + mipmaps = 0; + for (;;) + { + s = w * h * d * sides * 4; + texture->mipmap[mipmaps][0] = size; + texture->mipmap[mipmaps][1] = s; + texture->mipmap[mipmaps][2] = w; + texture->mipmap[mipmaps][3] = h; + texture->mipmap[mipmaps][4] = d; + size += s; + mipmaps++; + if (w * h * d == 1 || !(flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) + break; + if (w > 1) w >>= 1; + if (h > 1) h >>= 1; + if (d > 1) d >>= 1; + } + texture->mipmaps = mipmaps; + texture->size = size; + + // allocate the pixels now + texture->bytes = (unsigned char *)MM_CALLOC(1, size); + + return texnum; +} +void DPSOFTRAST_Texture_Free(int index) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (texture->binds) + DPSOFTRAST_Flush(); + if (texture->bytes) + MM_FREE(texture->bytes); + texture->bytes = NULL; + memset(texture, 0, sizeof(*texture)); + // adjust the free range and used range + if (dpsoftrast.texture_firstfree > index) + dpsoftrast.texture_firstfree = index; + while (dpsoftrast.texture_end > 0 && dpsoftrast.texture[dpsoftrast.texture_end-1].bytes == NULL) + dpsoftrast.texture_end--; +} +static void DPSOFTRAST_Texture_CalculateMipmaps(int index) +{ + int i, x, y, z, w, layer0, layer1, row0, row1; + unsigned char *o, *i0, *i1, *i2, *i3; + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (texture->mipmaps <= 1) + return; + for (i = 1;i < texture->mipmaps;i++) + { + for (z = 0;z < texture->mipmap[i][4];z++) + { + layer0 = z*2; + layer1 = z*2+1; + if (layer1 >= texture->mipmap[i-1][4]) + layer1 = texture->mipmap[i-1][4]-1; + for (y = 0;y < texture->mipmap[i][3];y++) + { + row0 = y*2; + row1 = y*2+1; + if (row1 >= texture->mipmap[i-1][3]) + row1 = texture->mipmap[i-1][3]-1; + o = texture->bytes + texture->mipmap[i ][0] + 4*((texture->mipmap[i ][3] * z + y ) * texture->mipmap[i ][2]); + i0 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer0 + row0) * texture->mipmap[i-1][2]); + i1 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer0 + row1) * texture->mipmap[i-1][2]); + i2 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer1 + row0) * texture->mipmap[i-1][2]); + i3 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer1 + row1) * texture->mipmap[i-1][2]); + w = texture->mipmap[i][2]; + if (layer1 > layer0) + { + if (texture->mipmap[i-1][2] > 1) + { + // average 3D texture + for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8, i2 += 8, i3 += 8) + { + o[0] = (i0[0] + i0[4] + i1[0] + i1[4] + i2[0] + i2[4] + i3[0] + i3[4] + 4) >> 3; + o[1] = (i0[1] + i0[5] + i1[1] + i1[5] + i2[1] + i2[5] + i3[1] + i3[5] + 4) >> 3; + o[2] = (i0[2] + i0[6] + i1[2] + i1[6] + i2[2] + i2[6] + i3[2] + i3[6] + 4) >> 3; + o[3] = (i0[3] + i0[7] + i1[3] + i1[7] + i2[3] + i2[7] + i3[3] + i3[7] + 4) >> 3; + } + } + else + { + // average 3D mipmap with parent width == 1 + for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8) + { + o[0] = (i0[0] + i1[0] + i2[0] + i3[0] + 2) >> 2; + o[1] = (i0[1] + i1[1] + i2[1] + i3[1] + 2) >> 2; + o[2] = (i0[2] + i1[2] + i2[2] + i3[2] + 2) >> 2; + o[3] = (i0[3] + i1[3] + i2[3] + i3[3] + 2) >> 2; + } + } + } + else + { + if (texture->mipmap[i-1][2] > 1) + { + // average 2D texture (common case) + for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8) + { + o[0] = (i0[0] + i0[4] + i1[0] + i1[4] + 2) >> 2; + o[1] = (i0[1] + i0[5] + i1[1] + i1[5] + 2) >> 2; + o[2] = (i0[2] + i0[6] + i1[2] + i1[6] + 2) >> 2; + o[3] = (i0[3] + i0[7] + i1[3] + i1[7] + 2) >> 2; + } + } + else + { + // 2D texture with parent width == 1 + o[0] = (i0[0] + i1[0] + 1) >> 1; + o[1] = (i0[1] + i1[1] + 1) >> 1; + o[2] = (i0[2] + i1[2] + 1) >> 1; + o[3] = (i0[3] + i1[3] + 1) >> 1; + } + } + } + } + } +} +void DPSOFTRAST_Texture_UpdatePartial(int index, int mip, const unsigned char *pixels, int blockx, int blocky, int blockwidth, int blockheight) +{ + DPSOFTRAST_Texture *texture; + unsigned char *dst; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (texture->binds) + DPSOFTRAST_Flush(); + if (pixels) + { + dst = texture->bytes + texture->mipmap[0][1] +(-blocky * texture->mipmap[0][2] + blockx) * 4; + while (blockheight > 0) + { + dst -= texture->mipmap[0][2] * 4; + memcpy(dst, pixels, blockwidth * 4); + pixels += blockwidth * 4; + blockheight--; + } + } + DPSOFTRAST_Texture_CalculateMipmaps(index); +} +void DPSOFTRAST_Texture_UpdateFull(int index, const unsigned char *pixels) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (texture->binds) + DPSOFTRAST_Flush(); + if (pixels) + { + int i, stride = texture->mipmap[0][2]*4; + unsigned char *dst = texture->bytes + texture->mipmap[0][1]; + for (i = texture->mipmap[0][3];i > 0;i--) + { + dst -= stride; + memcpy(dst, pixels, stride); + pixels += stride; + } + } + DPSOFTRAST_Texture_CalculateMipmaps(index); +} +int DPSOFTRAST_Texture_GetWidth(int index, int mip) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; + return texture->mipmap[mip][2]; +} +int DPSOFTRAST_Texture_GetHeight(int index, int mip) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; + return texture->mipmap[mip][3]; +} +int DPSOFTRAST_Texture_GetDepth(int index, int mip) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; + return texture->mipmap[mip][4]; +} +unsigned char *DPSOFTRAST_Texture_GetPixelPointer(int index, int mip) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; + if (texture->binds) + DPSOFTRAST_Flush(); + return texture->bytes + texture->mipmap[mip][0]; +} +void DPSOFTRAST_Texture_Filter(int index, DPSOFTRAST_TEXTURE_FILTER filter) +{ + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (!(texture->flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP) && filter > DPSOFTRAST_TEXTURE_FILTER_LINEAR) + { + dpsoftrast.errorstring = "DPSOFTRAST_Texture_Filter: requested filter mode requires mipmaps"; + return; + } + if (texture->binds) + DPSOFTRAST_Flush(); + texture->filter = filter; +} + +static void DPSOFTRAST_Draw_FlushThreads(void); + +static void DPSOFTRAST_Draw_SyncCommands(void) +{ + if(dpsoftrast.usethreads) MEMORY_BARRIER; + dpsoftrast.drawcommand = dpsoftrast.commandpool.freecommand; +} + +static void DPSOFTRAST_Draw_FreeCommandPool(int space) +{ + DPSOFTRAST_State_Thread *thread; + int i; + int freecommand = dpsoftrast.commandpool.freecommand; + int usedcommands = dpsoftrast.commandpool.usedcommands; + if (usedcommands <= DPSOFTRAST_DRAW_MAXCOMMANDPOOL-space) + return; + DPSOFTRAST_Draw_SyncCommands(); + for(;;) + { + int waitindex = -1; + int commandoffset; + usedcommands = 0; + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + commandoffset = freecommand - thread->commandoffset; + if (commandoffset < 0) + commandoffset += DPSOFTRAST_DRAW_MAXCOMMANDPOOL; + if (commandoffset > usedcommands) + { + waitindex = i; + usedcommands = commandoffset; + } + } + if (usedcommands <= DPSOFTRAST_DRAW_MAXCOMMANDPOOL-space || waitindex < 0) + break; + thread = &dpsoftrast.threads[waitindex]; + Thread_LockMutex(thread->drawmutex); + if (thread->commandoffset != dpsoftrast.drawcommand) + { + thread->waiting = true; + if (thread->starving) Thread_CondSignal(thread->drawcond); + Thread_CondWait(thread->waitcond, thread->drawmutex); + thread->waiting = false; + } + Thread_UnlockMutex(thread->drawmutex); + } + dpsoftrast.commandpool.usedcommands = usedcommands; +} + +#define DPSOFTRAST_ALIGNCOMMAND(size) \ + ((size) + ((COMMAND_SIZE - ((size)&(COMMAND_SIZE-1))) & (COMMAND_SIZE-1))) +#define DPSOFTRAST_ALLOCATECOMMAND(name) \ + ((DPSOFTRAST_Command_##name *) DPSOFTRAST_AllocateCommand( DPSOFTRAST_OPCODE_##name , DPSOFTRAST_ALIGNCOMMAND(sizeof( DPSOFTRAST_Command_##name )))) + +static void *DPSOFTRAST_AllocateCommand(int opcode, int size) +{ + DPSOFTRAST_Command *command; + int freecommand = dpsoftrast.commandpool.freecommand; + int usedcommands = dpsoftrast.commandpool.usedcommands; + int extra = sizeof(DPSOFTRAST_Command); + if (DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand < size) + extra += DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand; + if (usedcommands > DPSOFTRAST_DRAW_MAXCOMMANDPOOL - (size + extra)) + { + if (dpsoftrast.usethreads) + DPSOFTRAST_Draw_FreeCommandPool(size + extra); + else + DPSOFTRAST_Draw_FlushThreads(); + freecommand = dpsoftrast.commandpool.freecommand; + usedcommands = dpsoftrast.commandpool.usedcommands; + } + if (DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand < size) + { + command = (DPSOFTRAST_Command *) &dpsoftrast.commandpool.commands[freecommand]; + command->opcode = DPSOFTRAST_OPCODE_Reset; + usedcommands += DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand; + freecommand = 0; + } + command = (DPSOFTRAST_Command *) &dpsoftrast.commandpool.commands[freecommand]; + command->opcode = opcode; + command->commandsize = size; + freecommand += size; + if (freecommand >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) + freecommand = 0; + dpsoftrast.commandpool.freecommand = freecommand; + dpsoftrast.commandpool.usedcommands = usedcommands + size; + return command; +} + +static void DPSOFTRAST_UndoCommand(int size) +{ + int freecommand = dpsoftrast.commandpool.freecommand; + int usedcommands = dpsoftrast.commandpool.usedcommands; + freecommand -= size; + if (freecommand < 0) + freecommand += DPSOFTRAST_DRAW_MAXCOMMANDPOOL; + usedcommands -= size; + dpsoftrast.commandpool.freecommand = freecommand; + dpsoftrast.commandpool.usedcommands = usedcommands; +} + +DEFCOMMAND(1, Viewport, int x; int y; int width; int height;) +static void DPSOFTRAST_Interpret_Viewport(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_Viewport *command) +{ + thread->viewport[0] = command->x; + thread->viewport[1] = command->y; + thread->viewport[2] = command->width; + thread->viewport[3] = command->height; + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_Viewport(int x, int y, int width, int height) +{ + DPSOFTRAST_Command_Viewport *command = DPSOFTRAST_ALLOCATECOMMAND(Viewport); + command->x = x; + command->y = y; + command->width = width; + command->height = height; + + dpsoftrast.viewport[0] = x; + dpsoftrast.viewport[1] = y; + dpsoftrast.viewport[2] = width; + dpsoftrast.viewport[3] = height; + DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); +} + +DEFCOMMAND(2, ClearColor, float r; float g; float b; float a;) +static void DPSOFTRAST_Interpret_ClearColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_ClearColor *command) +{ + int i, x1, y1, x2, y2, w, h, x, y; + int miny1, maxy1, miny2, maxy2; + int bandy; + unsigned int *p; + unsigned int c; + DPSOFTRAST_Validate(thread, DPSOFTRAST_VALIDATE_FB); + miny1 = thread->miny1; + maxy1 = thread->maxy1; + miny2 = thread->miny2; + maxy2 = thread->maxy2; + x1 = thread->fb_scissor[0]; + y1 = thread->fb_scissor[1]; + x2 = thread->fb_scissor[0] + thread->fb_scissor[2]; + y2 = thread->fb_scissor[1] + thread->fb_scissor[3]; + if (y1 < miny1) y1 = miny1; + if (y2 > maxy2) y2 = maxy2; + w = x2 - x1; + h = y2 - y1; + if (w < 1 || h < 1) + return; + // FIXME: honor fb_colormask? + c = DPSOFTRAST_BGRA8_FROM_RGBA32F(command->r,command->g,command->b,command->a); + for (i = 0;i < 4;i++) + { + if (!dpsoftrast.fb_colorpixels[i]) + continue; + for (y = y1, bandy = min(y2, maxy1); y < y2; bandy = min(y2, maxy2), y = max(y, miny2)) + for (;y < bandy;y++) + { + p = dpsoftrast.fb_colorpixels[i] + y * dpsoftrast.fb_width; + for (x = x1;x < x2;x++) + p[x] = c; + } + } +} +void DPSOFTRAST_ClearColor(float r, float g, float b, float a) +{ + DPSOFTRAST_Command_ClearColor *command = DPSOFTRAST_ALLOCATECOMMAND(ClearColor); + command->r = r; + command->g = g; + command->b = b; + command->a = a; +} + +DEFCOMMAND(3, ClearDepth, float depth;) +static void DPSOFTRAST_Interpret_ClearDepth(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ClearDepth *command) +{ + int x1, y1, x2, y2, w, h, x, y; + int miny1, maxy1, miny2, maxy2; + int bandy; + unsigned int *p; + unsigned int c; + DPSOFTRAST_Validate(thread, DPSOFTRAST_VALIDATE_FB); + miny1 = thread->miny1; + maxy1 = thread->maxy1; + miny2 = thread->miny2; + maxy2 = thread->maxy2; + x1 = thread->fb_scissor[0]; + y1 = thread->fb_scissor[1]; + x2 = thread->fb_scissor[0] + thread->fb_scissor[2]; + y2 = thread->fb_scissor[1] + thread->fb_scissor[3]; + if (y1 < miny1) y1 = miny1; + if (y2 > maxy2) y2 = maxy2; + w = x2 - x1; + h = y2 - y1; + if (w < 1 || h < 1) + return; + c = DPSOFTRAST_DEPTH32_FROM_DEPTH32F(command->depth); + for (y = y1, bandy = min(y2, maxy1); y < y2; bandy = min(y2, maxy2), y = max(y, miny2)) + for (;y < bandy;y++) + { + p = dpsoftrast.fb_depthpixels + y * dpsoftrast.fb_width; + for (x = x1;x < x2;x++) + p[x] = c; + } +} +void DPSOFTRAST_ClearDepth(float d) +{ + DPSOFTRAST_Command_ClearDepth *command = DPSOFTRAST_ALLOCATECOMMAND(ClearDepth); + command->depth = d; +} + +DEFCOMMAND(4, ColorMask, int r; int g; int b; int a;) +static void DPSOFTRAST_Interpret_ColorMask(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ColorMask *command) +{ + thread->colormask[0] = command->r != 0; + thread->colormask[1] = command->g != 0; + thread->colormask[2] = command->b != 0; + thread->colormask[3] = command->a != 0; + thread->fb_colormask = ((-thread->colormask[0]) & 0x00FF0000) | ((-thread->colormask[1]) & 0x0000FF00) | ((-thread->colormask[2]) & 0x000000FF) | ((-thread->colormask[3]) & 0xFF000000); +} +void DPSOFTRAST_ColorMask(int r, int g, int b, int a) +{ + DPSOFTRAST_Command_ColorMask *command = DPSOFTRAST_ALLOCATECOMMAND(ColorMask); + command->r = r; + command->g = g; + command->b = b; + command->a = a; +} + +DEFCOMMAND(5, DepthTest, int enable;) +static void DPSOFTRAST_Interpret_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthTest *command) +{ + thread->depthtest = command->enable; + thread->validate |= DPSOFTRAST_VALIDATE_DEPTHFUNC; +} +void DPSOFTRAST_DepthTest(int enable) +{ + DPSOFTRAST_Command_DepthTest *command = DPSOFTRAST_ALLOCATECOMMAND(DepthTest); + command->enable = enable; +} + +DEFCOMMAND(6, ScissorTest, int enable;) +static void DPSOFTRAST_Interpret_ScissorTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ScissorTest *command) +{ + thread->scissortest = command->enable; + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_ScissorTest(int enable) +{ + DPSOFTRAST_Command_ScissorTest *command = DPSOFTRAST_ALLOCATECOMMAND(ScissorTest); + command->enable = enable; +} + +DEFCOMMAND(7, Scissor, float x; float y; float width; float height;) +static void DPSOFTRAST_Interpret_Scissor(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Scissor *command) +{ + thread->scissor[0] = command->x; + thread->scissor[1] = command->y; + thread->scissor[2] = command->width; + thread->scissor[3] = command->height; + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_Scissor(float x, float y, float width, float height) +{ + DPSOFTRAST_Command_Scissor *command = DPSOFTRAST_ALLOCATECOMMAND(Scissor); + command->x = x; + command->y = y; + command->width = width; + command->height = height; +} + +DEFCOMMAND(8, BlendFunc, int sfactor; int dfactor;) +static void DPSOFTRAST_Interpret_BlendFunc(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_BlendFunc *command) +{ + thread->blendfunc[0] = command->sfactor; + thread->blendfunc[1] = command->dfactor; + thread->validate |= DPSOFTRAST_VALIDATE_BLENDFUNC; +} +void DPSOFTRAST_BlendFunc(int sfactor, int dfactor) +{ + DPSOFTRAST_Command_BlendFunc *command = DPSOFTRAST_ALLOCATECOMMAND(BlendFunc); + command->sfactor = sfactor; + command->dfactor = dfactor; +} + +DEFCOMMAND(9, BlendSubtract, int enable;) +static void DPSOFTRAST_Interpret_BlendSubtract(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_BlendSubtract *command) +{ + thread->blendsubtract = command->enable; + thread->validate |= DPSOFTRAST_VALIDATE_BLENDFUNC; +} +void DPSOFTRAST_BlendSubtract(int enable) +{ + DPSOFTRAST_Command_BlendSubtract *command = DPSOFTRAST_ALLOCATECOMMAND(BlendSubtract); + command->enable = enable; +} + +DEFCOMMAND(10, DepthMask, int enable;) +static void DPSOFTRAST_Interpret_DepthMask(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthMask *command) +{ + thread->depthmask = command->enable; +} +void DPSOFTRAST_DepthMask(int enable) +{ + DPSOFTRAST_Command_DepthMask *command = DPSOFTRAST_ALLOCATECOMMAND(DepthMask); + command->enable = enable; +} + +DEFCOMMAND(11, DepthFunc, int func;) +static void DPSOFTRAST_Interpret_DepthFunc(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthFunc *command) +{ + thread->depthfunc = command->func; +} +void DPSOFTRAST_DepthFunc(int func) +{ + DPSOFTRAST_Command_DepthFunc *command = DPSOFTRAST_ALLOCATECOMMAND(DepthFunc); + command->func = func; +} + +DEFCOMMAND(12, DepthRange, float nearval; float farval;) +static void DPSOFTRAST_Interpret_DepthRange(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthRange *command) +{ + thread->depthrange[0] = command->nearval; + thread->depthrange[1] = command->farval; +} +void DPSOFTRAST_DepthRange(float nearval, float farval) +{ + DPSOFTRAST_Command_DepthRange *command = DPSOFTRAST_ALLOCATECOMMAND(DepthRange); + command->nearval = nearval; + command->farval = farval; +} + +DEFCOMMAND(13, PolygonOffset, float alongnormal; float intoview;) +static void DPSOFTRAST_Interpret_PolygonOffset(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_PolygonOffset *command) +{ + thread->polygonoffset[0] = command->alongnormal; + thread->polygonoffset[1] = command->intoview; +} +void DPSOFTRAST_PolygonOffset(float alongnormal, float intoview) +{ + DPSOFTRAST_Command_PolygonOffset *command = DPSOFTRAST_ALLOCATECOMMAND(PolygonOffset); + command->alongnormal = alongnormal; + command->intoview = intoview; +} + +DEFCOMMAND(14, CullFace, int mode;) +static void DPSOFTRAST_Interpret_CullFace(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_CullFace *command) +{ + thread->cullface = command->mode; +} +void DPSOFTRAST_CullFace(int mode) +{ + DPSOFTRAST_Command_CullFace *command = DPSOFTRAST_ALLOCATECOMMAND(CullFace); + command->mode = mode; +} + +void DPSOFTRAST_Color4f(float r, float g, float b, float a) +{ + dpsoftrast.color[0] = r; + dpsoftrast.color[1] = g; + dpsoftrast.color[2] = b; + dpsoftrast.color[3] = a; +} + +void DPSOFTRAST_GetPixelsBGRA(int blockx, int blocky, int blockwidth, int blockheight, unsigned char *outpixels) +{ + int outstride = blockwidth * 4; + int instride = dpsoftrast.fb_width * 4; + int bx1 = blockx; + int by1 = blocky; + int bx2 = blockx + blockwidth; + int by2 = blocky + blockheight; + int bw; + int x; + int y; + unsigned char *inpixels; + unsigned char *b; + unsigned char *o; + DPSOFTRAST_Flush(); + if (bx1 < 0) bx1 = 0; + if (by1 < 0) by1 = 0; + if (bx2 > dpsoftrast.fb_width) bx2 = dpsoftrast.fb_width; + if (by2 > dpsoftrast.fb_height) by2 = dpsoftrast.fb_height; + bw = bx2 - bx1; + inpixels = (unsigned char *)dpsoftrast.fb_colorpixels[0]; + if (dpsoftrast.bigendian) + { + for (y = by1;y < by2;y++) + { + b = (unsigned char *)inpixels + (dpsoftrast.fb_height - 1 - y) * instride + 4 * bx1; + o = (unsigned char *)outpixels + (y - by1) * outstride; + for (x = bx1;x < bx2;x++) + { + o[0] = b[3]; + o[1] = b[2]; + o[2] = b[1]; + o[3] = b[0]; + o += 4; + b += 4; + } + } + } + else + { + for (y = by1;y < by2;y++) + { + b = (unsigned char *)inpixels + (dpsoftrast.fb_height - 1 - y) * instride + 4 * bx1; + o = (unsigned char *)outpixels + (y - by1) * outstride; + memcpy(o, b, bw*4); + } + } + +} +void DPSOFTRAST_CopyRectangleToTexture(int index, int mip, int tx, int ty, int sx, int sy, int width, int height) +{ + int tx1 = tx; + int ty1 = ty; + int tx2 = tx + width; + int ty2 = ty + height; + int sx1 = sx; + int sy1 = sy; + int sx2 = sx + width; + int sy2 = sy + height; + int swidth; + int sheight; + int twidth; + int theight; + int sw; + int sh; + int tw; + int th; + int y; + unsigned int *spixels; + unsigned int *tpixels; + DPSOFTRAST_Texture *texture; + texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; + if (mip < 0 || mip >= texture->mipmaps) return; + DPSOFTRAST_Flush(); + spixels = dpsoftrast.fb_colorpixels[0]; + swidth = dpsoftrast.fb_width; + sheight = dpsoftrast.fb_height; + tpixels = (unsigned int *)(texture->bytes + texture->mipmap[mip][0]); + twidth = texture->mipmap[mip][2]; + theight = texture->mipmap[mip][3]; + if (tx1 < 0) tx1 = 0; + if (ty1 < 0) ty1 = 0; + if (tx2 > twidth) tx2 = twidth; + if (ty2 > theight) ty2 = theight; + if (sx1 < 0) sx1 = 0; + if (sy1 < 0) sy1 = 0; + if (sx2 > swidth) sx2 = swidth; + if (sy2 > sheight) sy2 = sheight; + tw = tx2 - tx1; + th = ty2 - ty1; + sw = sx2 - sx1; + sh = sy2 - sy1; + if (tw > sw) tw = sw; + if (th > sh) th = sh; + if (tw < 1 || th < 1) + return; + sy1 = sheight - sy1 - th; + ty1 = theight - ty1 - th; + for (y = 0;y < th;y++) + memcpy(tpixels + ((ty1 + y) * twidth + tx1), spixels + ((sy1 + y) * swidth + sx1), tw*4); + if (texture->mipmaps > 1) + DPSOFTRAST_Texture_CalculateMipmaps(index); +} + +DEFCOMMAND(17, SetTexture, int unitnum; DPSOFTRAST_Texture *texture;) +static void DPSOFTRAST_Interpret_SetTexture(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_SetTexture *command) +{ + if (thread->texbound[command->unitnum]) + ATOMIC_DECREMENT(thread->texbound[command->unitnum]->binds); + thread->texbound[command->unitnum] = command->texture; +} +void DPSOFTRAST_SetTexture(int unitnum, int index) +{ + DPSOFTRAST_Command_SetTexture *command; + DPSOFTRAST_Texture *texture; + if (unitnum < 0 || unitnum >= DPSOFTRAST_MAXTEXTUREUNITS) + { + dpsoftrast.errorstring = "DPSOFTRAST_SetTexture: invalid unit number"; + return; + } + texture = DPSOFTRAST_Texture_GetByIndex(index); + if (index && !texture) + { + dpsoftrast.errorstring = "DPSOFTRAST_SetTexture: invalid texture handle"; + return; + } + + command = DPSOFTRAST_ALLOCATECOMMAND(SetTexture); + command->unitnum = unitnum; + command->texture = texture; + + dpsoftrast.texbound[unitnum] = texture; + if (texture) + ATOMIC_ADD(texture->binds, dpsoftrast.numthreads); +} + +void DPSOFTRAST_SetVertexPointer(const float *vertex3f, size_t stride) +{ + dpsoftrast.pointer_vertex3f = vertex3f; + dpsoftrast.stride_vertex = stride; +} +void DPSOFTRAST_SetColorPointer(const float *color4f, size_t stride) +{ + dpsoftrast.pointer_color4f = color4f; + dpsoftrast.pointer_color4ub = NULL; + dpsoftrast.stride_color = stride; +} +void DPSOFTRAST_SetColorPointer4ub(const unsigned char *color4ub, size_t stride) +{ + dpsoftrast.pointer_color4f = NULL; + dpsoftrast.pointer_color4ub = color4ub; + dpsoftrast.stride_color = stride; +} +void DPSOFTRAST_SetTexCoordPointer(int unitnum, int numcomponents, size_t stride, const float *texcoordf) +{ + dpsoftrast.pointer_texcoordf[unitnum] = texcoordf; + dpsoftrast.components_texcoord[unitnum] = numcomponents; + dpsoftrast.stride_texcoord[unitnum] = stride; +} + +DEFCOMMAND(18, SetShader, int mode; int permutation; int exactspecularmath;) +static void DPSOFTRAST_Interpret_SetShader(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_SetShader *command) +{ + thread->shader_mode = command->mode; + thread->shader_permutation = command->permutation; + thread->shader_exactspecularmath = command->exactspecularmath; +} +void DPSOFTRAST_SetShader(int mode, int permutation, int exactspecularmath) +{ + DPSOFTRAST_Command_SetShader *command = DPSOFTRAST_ALLOCATECOMMAND(SetShader); + command->mode = mode; + command->permutation = permutation; + command->exactspecularmath = exactspecularmath; + + dpsoftrast.shader_mode = mode; + dpsoftrast.shader_permutation = permutation; + dpsoftrast.shader_exactspecularmath = exactspecularmath; +} + +DEFCOMMAND(19, Uniform4f, DPSOFTRAST_UNIFORM index; float val[4];) +static void DPSOFTRAST_Interpret_Uniform4f(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Uniform4f *command) +{ + memcpy(&thread->uniform4f[command->index*4], command->val, sizeof(command->val)); +} +void DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM index, float v0, float v1, float v2, float v3) +{ + DPSOFTRAST_Command_Uniform4f *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform4f); + command->index = index; + command->val[0] = v0; + command->val[1] = v1; + command->val[2] = v2; + command->val[3] = v3; + + dpsoftrast.uniform4f[index*4+0] = v0; + dpsoftrast.uniform4f[index*4+1] = v1; + dpsoftrast.uniform4f[index*4+2] = v2; + dpsoftrast.uniform4f[index*4+3] = v3; +} +void DPSOFTRAST_Uniform4fv(DPSOFTRAST_UNIFORM index, const float *v) +{ + DPSOFTRAST_Command_Uniform4f *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform4f); + command->index = index; + memcpy(command->val, v, sizeof(command->val)); + + memcpy(&dpsoftrast.uniform4f[index*4], v, sizeof(float[4])); +} + +DEFCOMMAND(20, UniformMatrix4f, DPSOFTRAST_UNIFORM index; ALIGN(float val[16]);) +static void DPSOFTRAST_Interpret_UniformMatrix4f(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_UniformMatrix4f *command) +{ + memcpy(&thread->uniform4f[command->index*4], command->val, sizeof(command->val)); +} +void DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM uniform, int arraysize, int transpose, const float *v) +{ +#ifdef SSE_POSSIBLE + int i, index; + for (i = 0, index = (int)uniform;i < arraysize;i++, index += 4, v += 16) + { + __m128 m0, m1, m2, m3; + DPSOFTRAST_Command_UniformMatrix4f *command = DPSOFTRAST_ALLOCATECOMMAND(UniformMatrix4f); + command->index = (DPSOFTRAST_UNIFORM)index; + if (((size_t)v)&(ALIGN_SIZE-1)) + { + m0 = _mm_loadu_ps(v); + m1 = _mm_loadu_ps(v+4); + m2 = _mm_loadu_ps(v+8); + m3 = _mm_loadu_ps(v+12); + } + else + { + m0 = _mm_load_ps(v); + m1 = _mm_load_ps(v+4); + m2 = _mm_load_ps(v+8); + m3 = _mm_load_ps(v+12); + } + if (transpose) + { + __m128 t0, t1, t2, t3; + t0 = _mm_unpacklo_ps(m0, m1); + t1 = _mm_unpacklo_ps(m2, m3); + t2 = _mm_unpackhi_ps(m0, m1); + t3 = _mm_unpackhi_ps(m2, m3); + m0 = _mm_movelh_ps(t0, t1); + m1 = _mm_movehl_ps(t1, t0); + m2 = _mm_movelh_ps(t2, t3); + m3 = _mm_movehl_ps(t3, t2); + } + _mm_store_ps(command->val, m0); + _mm_store_ps(command->val+4, m1); + _mm_store_ps(command->val+8, m2); + _mm_store_ps(command->val+12, m3); + _mm_store_ps(&dpsoftrast.uniform4f[index*4+0], m0); + _mm_store_ps(&dpsoftrast.uniform4f[index*4+4], m1); + _mm_store_ps(&dpsoftrast.uniform4f[index*4+8], m2); + _mm_store_ps(&dpsoftrast.uniform4f[index*4+12], m3); + } +#endif +} + +DEFCOMMAND(21, Uniform1i, DPSOFTRAST_UNIFORM index; int val;) +static void DPSOFTRAST_Interpret_Uniform1i(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Uniform1i *command) +{ + thread->uniform1i[command->index] = command->val; +} +void DPSOFTRAST_Uniform1i(DPSOFTRAST_UNIFORM index, int i0) +{ + DPSOFTRAST_Command_Uniform1i *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform1i); + command->index = index; + command->val = i0; + + dpsoftrast.uniform1i[command->index] = i0; +} + +DEFCOMMAND(24, ClipPlane, float clipplane[4];) +static void DPSOFTRAST_Interpret_ClipPlane(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ClipPlane *command) +{ + memcpy(thread->clipplane, command->clipplane, 4*sizeof(float)); + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_ClipPlane(float x, float y, float z, float w) +{ + DPSOFTRAST_Command_ClipPlane *command = DPSOFTRAST_ALLOCATECOMMAND(ClipPlane); + command->clipplane[0] = x; + command->clipplane[1] = y; + command->clipplane[2] = z; + command->clipplane[3] = w; +} + +#ifdef SSE_POSSIBLE +static void DPSOFTRAST_Load4fTo4f(float *dst, const unsigned char *src, int size, int stride) +{ + float *end = dst + size*4; + if ((((size_t)src)|stride)&(ALIGN_SIZE - 1)) // check for alignment + { + while (dst < end) + { + _mm_store_ps(dst, _mm_loadu_ps((const float *)src)); + dst += 4; + src += stride; + } + } + else + { + while (dst < end) + { + _mm_store_ps(dst, _mm_load_ps((const float *)src)); + dst += 4; + src += stride; + } + } +} + +static void DPSOFTRAST_Load3fTo4f(float *dst, const unsigned char *src, int size, int stride) +{ + float *end = dst + size*4; + if (stride == sizeof(float[3])) + { + float *end4 = dst + (size&~3)*4; + if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment + { + while (dst < end4) + { + __m128 v1 = _mm_loadu_ps((const float *)src), v2 = _mm_loadu_ps((const float *)src + 4), v3 = _mm_loadu_ps((const float *)src + 8), dv; + dv = _mm_shuffle_ps(v1, v1, _MM_SHUFFLE(2, 1, 0, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_shuffle_ps(v1, v2, _MM_SHUFFLE(1, 0, 3, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 4, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_shuffle_ps(v2, v3, _MM_SHUFFLE(0, 0, 3, 2)); + dv = _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(2, 1, 0, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 8, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_move_ss(v3, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 12, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dst += 16; + src += 4*sizeof(float[3]); + } + } + else + { + while (dst < end4) + { + __m128 v1 = _mm_load_ps((const float *)src), v2 = _mm_load_ps((const float *)src + 4), v3 = _mm_load_ps((const float *)src + 8), dv; + dv = _mm_shuffle_ps(v1, v1, _MM_SHUFFLE(2, 1, 0, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_shuffle_ps(v1, v2, _MM_SHUFFLE(1, 0, 3, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 4, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_shuffle_ps(v2, v3, _MM_SHUFFLE(0, 0, 3, 2)); + dv = _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(2, 1, 0, 3)); + dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 8, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dv = _mm_move_ss(v3, _mm_set_ss(1.0f)); + _mm_store_ps(dst + 12, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); + dst += 16; + src += 4*sizeof(float[3]); + } + } + } + if ((((size_t)src)|stride)&(ALIGN_SIZE - 1)) + { + while (dst < end) + { + __m128 v = _mm_loadu_ps((const float *)src); + v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3)); + v = _mm_move_ss(v, _mm_set_ss(1.0f)); + v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ps(dst, v); + dst += 4; + src += stride; + } + } + else + { + while (dst < end) + { + __m128 v = _mm_load_ps((const float *)src); + v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3)); + v = _mm_move_ss(v, _mm_set_ss(1.0f)); + v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 3, 2, 1)); + _mm_store_ps(dst, v); + dst += 4; + src += stride; + } + } +} + +static void DPSOFTRAST_Load2fTo4f(float *dst, const unsigned char *src, int size, int stride) +{ + float *end = dst + size*4; + __m128 v2 = _mm_setr_ps(0.0f, 0.0f, 0.0f, 1.0f); + if (stride == sizeof(float[2])) + { + float *end2 = dst + (size&~1)*4; + if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment + { + while (dst < end2) + { + __m128 v = _mm_loadu_ps((const float *)src); + _mm_store_ps(dst, _mm_shuffle_ps(v, v2, _MM_SHUFFLE(3, 2, 1, 0))); + _mm_store_ps(dst + 4, _mm_movehl_ps(v2, v)); + dst += 8; + src += 2*sizeof(float[2]); + } + } + else + { + while (dst < end2) + { + __m128 v = _mm_load_ps((const float *)src); + _mm_store_ps(dst, _mm_shuffle_ps(v, v2, _MM_SHUFFLE(3, 2, 1, 0))); + _mm_store_ps(dst + 4, _mm_movehl_ps(v2, v)); + dst += 8; + src += 2*sizeof(float[2]); + } + } + } + while (dst < end) + { + _mm_store_ps(dst, _mm_loadl_pi(v2, (__m64 *)src)); + dst += 4; + src += stride; + } +} + +static void DPSOFTRAST_Load4bTo4f(float *dst, const unsigned char *src, int size, int stride) +{ + float *end = dst + size*4; + __m128 scale = _mm_set1_ps(1.0f/255.0f); + if (stride == sizeof(unsigned char[4])) + { + float *end4 = dst + (size&~3)*4; + if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment + { + while (dst < end4) + { + __m128i v = _mm_loadu_si128((const __m128i *)src), v1 = _mm_unpacklo_epi8(v, _mm_setzero_si128()), v2 = _mm_unpackhi_epi8(v, _mm_setzero_si128()); + _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v1, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 4, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v1, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 8, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v2, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 12, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v2, _mm_setzero_si128())), scale)); + dst += 16; + src += 4*sizeof(unsigned char[4]); + } + } + else + { + while (dst < end4) + { + __m128i v = _mm_load_si128((const __m128i *)src), v1 = _mm_unpacklo_epi8(v, _mm_setzero_si128()), v2 = _mm_unpackhi_epi8(v, _mm_setzero_si128()); + _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v1, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 4, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v1, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 8, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v2, _mm_setzero_si128())), scale)); + _mm_store_ps(dst + 12, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v2, _mm_setzero_si128())), scale)); + dst += 16; + src += 4*sizeof(unsigned char[4]); + } + } + } + while (dst < end) + { + __m128i v = _mm_cvtsi32_si128(*(const int *)src); + _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(_mm_unpacklo_epi8(v, _mm_setzero_si128()), _mm_setzero_si128())), scale)); + dst += 4; + src += stride; + } +} + +static void DPSOFTRAST_Fill4f(float *dst, const float *src, int size) +{ + float *end = dst + 4*size; + __m128 v = _mm_loadu_ps(src); + while (dst < end) + { + _mm_store_ps(dst, v); + dst += 4; + } +} +#endif + +static void DPSOFTRAST_Vertex_Transform(float *out4f, const float *in4f, int numitems, const float *inmatrix16f) +{ +#ifdef SSE_POSSIBLE + static const float identitymatrix[4][4] = {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}; + __m128 m0, m1, m2, m3; + float *end; + if (!memcmp(identitymatrix, inmatrix16f, sizeof(float[16]))) + { + // fast case for identity matrix + if (out4f != in4f) memcpy(out4f, in4f, numitems * sizeof(float[4])); + return; + } + end = out4f + numitems*4; + m0 = _mm_loadu_ps(inmatrix16f); + m1 = _mm_loadu_ps(inmatrix16f + 4); + m2 = _mm_loadu_ps(inmatrix16f + 8); + m3 = _mm_loadu_ps(inmatrix16f + 12); + if (((size_t)in4f)&(ALIGN_SIZE-1)) // check alignment + { + while (out4f < end) + { + __m128 v = _mm_loadu_ps(in4f); + _mm_store_ps(out4f, + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)), m0), + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 1, 1, 1)), m1), + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 2, 2, 2)), m2), + _mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 3, 3, 3)), m3))))); + out4f += 4; + in4f += 4; + } + } + else + { + while (out4f < end) + { + __m128 v = _mm_load_ps(in4f); + _mm_store_ps(out4f, + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)), m0), + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 1, 1, 1)), m1), + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 2, 2, 2)), m2), + _mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 3, 3, 3)), m3))))); + out4f += 4; + in4f += 4; + } + } +#endif +} + +#if 0 +static void DPSOFTRAST_Vertex_Copy(float *out4f, const float *in4f, int numitems) +{ + memcpy(out4f, in4f, numitems * sizeof(float[4])); +} +#endif + +#ifdef SSE_POSSIBLE +#define DPSOFTRAST_PROJECTVERTEX(out, in, viewportcenter, viewportscale) \ +{ \ + __m128 p = (in), w = _mm_shuffle_ps(p, p, _MM_SHUFFLE(3, 3, 3, 3)); \ + p = _mm_move_ss(_mm_shuffle_ps(p, p, _MM_SHUFFLE(2, 1, 0, 3)), _mm_set_ss(1.0f)); \ + p = _mm_add_ps(viewportcenter, _mm_div_ps(_mm_mul_ps(viewportscale, p), w)); \ + out = _mm_shuffle_ps(p, p, _MM_SHUFFLE(0, 3, 2, 1)); \ +} + +#define DPSOFTRAST_TRANSFORMVERTEX(out, in, m0, m1, m2, m3) \ +{ \ + __m128 p = (in); \ + out = _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(0, 0, 0, 0)), m0), \ + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(1, 1, 1, 1)), m1), \ + _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(2, 2, 2, 2)), m2), \ + _mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(3, 3, 3, 3)), m3)))); \ +} + +static int DPSOFTRAST_Vertex_BoundY(int *starty, int *endy, const float *minposf, const float *maxposf, const float *inmatrix16f) +{ + int clipmask = 0xFF; + __m128 viewportcenter = _mm_load_ps(dpsoftrast.fb_viewportcenter), viewportscale = _mm_load_ps(dpsoftrast.fb_viewportscale); + __m128 bb[8], clipdist[8], minproj = _mm_set_ss(2.0f), maxproj = _mm_set_ss(-2.0f); + __m128 m0 = _mm_loadu_ps(inmatrix16f), m1 = _mm_loadu_ps(inmatrix16f + 4), m2 = _mm_loadu_ps(inmatrix16f + 8), m3 = _mm_loadu_ps(inmatrix16f + 12); + __m128 minpos = _mm_load_ps(minposf), maxpos = _mm_load_ps(maxposf); + m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(3, 2, 0, 1)); + m1 = _mm_shuffle_ps(m1, m1, _MM_SHUFFLE(3, 2, 0, 1)); + m2 = _mm_shuffle_ps(m2, m2, _MM_SHUFFLE(3, 2, 0, 1)); + m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(3, 2, 0, 1)); + #define BBFRONT(k, pos) \ + { \ + DPSOFTRAST_TRANSFORMVERTEX(bb[k], pos, m0, m1, m2, m3); \ + clipdist[k] = _mm_add_ss(_mm_shuffle_ps(bb[k], bb[k], _MM_SHUFFLE(2, 2, 2, 2)), _mm_shuffle_ps(bb[k], bb[k], _MM_SHUFFLE(3, 3, 3, 3))); \ + if (_mm_ucomige_ss(clipdist[k], _mm_setzero_ps())) \ + { \ + __m128 proj; \ + clipmask &= ~(1<= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; + DPSOFTRAST_Vertex_Transform(data, data, dpsoftrast.numvertices, inmatrix16f); + return data; +} + +#if 0 +static float *DPSOFTRAST_Array_Project(int outarray, int inarray) +{ +#ifdef SSE_POSSIBLE + float *data = inarray >= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; + dpsoftrast.drawclipped = DPSOFTRAST_Vertex_Project(data, dpsoftrast.screencoord4f, &dpsoftrast.drawstarty, &dpsoftrast.drawendy, data, dpsoftrast.numvertices); + return data; +#else + return NULL; +#endif +} +#endif + +static float *DPSOFTRAST_Array_TransformProject(int outarray, int inarray, const float *inmatrix16f) +{ +#ifdef SSE_POSSIBLE + float *data = inarray >= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; + dpsoftrast.drawclipped = DPSOFTRAST_Vertex_TransformProject(data, dpsoftrast.screencoord4f, &dpsoftrast.drawstarty, &dpsoftrast.drawendy, data, dpsoftrast.numvertices, inmatrix16f); + return data; +#else + return NULL; +#endif +} + +static void DPSOFTRAST_Draw_Span_Begin(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *zf) +{ + int x; + int startx = span->startx; + int endx = span->endx; + float wslope = triangle->w[0]; + float w = triangle->w[2] + span->x*wslope + span->y*triangle->w[1]; + float endz = 1.0f / (w + wslope * startx); + if (triangle->w[0] == 0) + { + // LordHavoc: fast flat polygons (HUD/menu) + for (x = startx;x < endx;x++) + zf[x] = endz; + return; + } + for (x = startx;x < endx;) + { + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + float z = endz, dz; + if (nextsub >= endx) nextsub = endsub = endx-1; + endz = 1.0f / (w + wslope * nextsub); + dz = x < nextsub ? (endz - z) / (nextsub - x) : 0.0f; + for (; x <= endsub; x++, z += dz) + zf[x] = z; + } +} + +static void DPSOFTRAST_Draw_Span_FinishBGRA8(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, const unsigned char* RESTRICT in4ub) +{ +#ifdef SSE_POSSIBLE + int x; + int startx = span->startx; + int endx = span->endx; + int maskx; + int subx; + const unsigned int * RESTRICT ini = (const unsigned int *)in4ub; + unsigned char * RESTRICT pixelmask = span->pixelmask; + unsigned int * RESTRICT pixeli = (unsigned int *)dpsoftrast.fb_colorpixels[0]; + if (!pixeli) + return; + pixeli += span->y * dpsoftrast.fb_width + span->x; + // handle alphatest now (this affects depth writes too) + if (thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) + for (x = startx;x < endx;x++) + if (in4ub[x*4+3] < 128) + pixelmask[x] = false; + // LordHavoc: clear pixelmask for some pixels in alphablend cases, this + // helps sprites, text and hud artwork + switch(thread->fb_blendmode) + { + case DPSOFTRAST_BLENDMODE_ALPHA: + case DPSOFTRAST_BLENDMODE_ADDALPHA: + case DPSOFTRAST_BLENDMODE_SUBALPHA: + maskx = startx; + for (x = startx;x < endx;x++) + { + if (in4ub[x*4+3] >= 1) + { + startx = x; + for (;;) + { + while (++x < endx && in4ub[x*4+3] >= 1) ; + maskx = x; + if (x >= endx) break; + ++x; + while (++x < endx && in4ub[x*4+3] < 1) pixelmask[x] = false; + if (x >= endx) break; + } + break; + } + } + endx = maskx; + break; + case DPSOFTRAST_BLENDMODE_OPAQUE: + case DPSOFTRAST_BLENDMODE_ADD: + case DPSOFTRAST_BLENDMODE_INVMOD: + case DPSOFTRAST_BLENDMODE_MUL: + case DPSOFTRAST_BLENDMODE_MUL2: + case DPSOFTRAST_BLENDMODE_PSEUDOALPHA: + case DPSOFTRAST_BLENDMODE_INVADD: + break; + } + // put some special values at the end of the mask to ensure the loops end + pixelmask[endx] = 1; + pixelmask[endx+1] = 0; + // LordHavoc: use a double loop to identify subspans, this helps the + // optimized copy/blend loops to perform at their best, most triangles + // have only one run of pixels, and do the search using wide reads... + x = startx; + while (x < endx) + { + // if this pixel is masked off, it's probably not alone... + if (!pixelmask[x]) + { + x++; +#if 1 + if (x + 8 < endx) + { + // the 4-item search must be aligned or else it stalls badly + if ((x & 3) && !pixelmask[x]) + { + if(pixelmask[x]) goto endmasked; + x++; + if (x & 3) + { + if(pixelmask[x]) goto endmasked; + x++; + if (x & 3) + { + if(pixelmask[x]) goto endmasked; + x++; + } + } + } + while (*(unsigned int *)&pixelmask[x] == 0x00000000) + x += 4; + } +#endif + for (;!pixelmask[x];x++) + ; + // rather than continue the loop, just check the end variable + if (x >= endx) + break; + } + endmasked: + // find length of subspan + subx = x + 1; +#if 1 + if (subx + 8 < endx) + { + if (subx & 3) + { + if(!pixelmask[subx]) goto endunmasked; + subx++; + if (subx & 3) + { + if(!pixelmask[subx]) goto endunmasked; + subx++; + if (subx & 3) + { + if(!pixelmask[subx]) goto endunmasked; + subx++; + } + } + } + while (*(unsigned int *)&pixelmask[subx] == 0x01010101) + subx += 4; + } +#endif + for (;pixelmask[subx];subx++) + ; + // the checks can overshoot, so make sure to clip it... + if (subx > endx) + subx = endx; + endunmasked: + // now that we know the subspan length... process! + switch(thread->fb_blendmode) + { + case DPSOFTRAST_BLENDMODE_OPAQUE: +#if 0 + if (subx - x >= 16) + { + memcpy(pixeli + x, ini + x, (subx - x) * sizeof(pixeli[x])); + x = subx; + } + else +#elif 1 + while (x + 16 <= subx) + { + _mm_storeu_si128((__m128i *)&pixeli[x], _mm_loadu_si128((const __m128i *)&ini[x])); + _mm_storeu_si128((__m128i *)&pixeli[x+4], _mm_loadu_si128((const __m128i *)&ini[x+4])); + _mm_storeu_si128((__m128i *)&pixeli[x+8], _mm_loadu_si128((const __m128i *)&ini[x+8])); + _mm_storeu_si128((__m128i *)&pixeli[x+12], _mm_loadu_si128((const __m128i *)&ini[x+12])); + x += 16; + } +#endif + { + while (x + 4 <= subx) + { + _mm_storeu_si128((__m128i *)&pixeli[x], _mm_loadu_si128((const __m128i *)&ini[x])); + x += 4; + } + if (x + 2 <= subx) + { + pixeli[x] = ini[x]; + pixeli[x+1] = ini[x+1]; + x += 2; + } + if (x < subx) + { + pixeli[x] = ini[x]; + x++; + } + } + break; + case DPSOFTRAST_BLENDMODE_ALPHA: + #define FINISHBLEND(blend2, blend1) \ + for (;x + 1 < subx;x += 2) \ + { \ + __m128i src, dst; \ + src = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ini[x]), _mm_setzero_si128()); \ + dst = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&pixeli[x]), _mm_setzero_si128()); \ + blend2; \ + _mm_storel_epi64((__m128i *)&pixeli[x], _mm_packus_epi16(dst, dst)); \ + } \ + if (x < subx) \ + { \ + __m128i src, dst; \ + src = _mm_unpacklo_epi8(_mm_cvtsi32_si128(ini[x]), _mm_setzero_si128()); \ + dst = _mm_unpacklo_epi8(_mm_cvtsi32_si128(pixeli[x]), _mm_setzero_si128()); \ + blend1; \ + pixeli[x] = _mm_cvtsi128_si32(_mm_packus_epi16(dst, dst)); \ + x++; \ + } + FINISHBLEND({ + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(src, dst), 4), _mm_slli_epi16(blend, 4))); + }, { + __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(src, dst), 4), _mm_slli_epi16(blend, 4))); + }); + break; + case DPSOFTRAST_BLENDMODE_ADDALPHA: + FINISHBLEND({ + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); + }, { + __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); + }); + break; + case DPSOFTRAST_BLENDMODE_ADD: + FINISHBLEND({ dst = _mm_add_epi16(src, dst); }, { dst = _mm_add_epi16(src, dst); }); + break; + case DPSOFTRAST_BLENDMODE_INVMOD: + FINISHBLEND({ + dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, src), 8)); + }, { + dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, src), 8)); + }); + break; + case DPSOFTRAST_BLENDMODE_MUL: + FINISHBLEND({ dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 8); }, { dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 8); }); + break; + case DPSOFTRAST_BLENDMODE_MUL2: + FINISHBLEND({ dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 7); }, { dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 7); }); + break; + case DPSOFTRAST_BLENDMODE_SUBALPHA: + FINISHBLEND({ + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); + }, { + __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); + }); + break; + case DPSOFTRAST_BLENDMODE_PSEUDOALPHA: + FINISHBLEND({ + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(src, _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, blend), 8))); + }, { + __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); + dst = _mm_add_epi16(src, _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, blend), 8))); + }); + break; + case DPSOFTRAST_BLENDMODE_INVADD: + FINISHBLEND({ + dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(_mm_set1_epi16(255), dst), 4), _mm_slli_epi16(src, 4))); + }, { + dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(_mm_set1_epi16(255), dst), 4), _mm_slli_epi16(src, 4))); + }); + break; + } + } +#endif +} + +static void DPSOFTRAST_Texture2DBGRA8(DPSOFTRAST_Texture *texture, int mip, float x, float y, unsigned char c[4]) + // warning: this is SLOW, only use if the optimized per-span functions won't do +{ + const unsigned char * RESTRICT pixelbase; + const unsigned char * RESTRICT pixel[4]; + int width = texture->mipmap[mip][2], height = texture->mipmap[mip][3]; + int wrapmask[2] = { width-1, height-1 }; + pixelbase = (unsigned char *)texture->bytes + texture->mipmap[mip][0] + texture->mipmap[mip][1] - 4*width; + if(texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR) + { + unsigned int tc[2] = { x * (width<<12) - 2048, y * (height<<12) - 2048}; + unsigned int frac[2] = { tc[0]&0xFFF, tc[1]&0xFFF }; + unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; + unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; + int tci[2] = { tc[0]>>12, tc[1]>>12 }; + int tci1[2] = { tci[0] + 1, tci[1] + 1 }; + if (texture->flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + tci[0] = tci[0] >= 0 ? (tci[0] <= wrapmask[0] ? tci[0] : wrapmask[0]) : 0; + tci[1] = tci[1] >= 0 ? (tci[1] <= wrapmask[1] ? tci[1] : wrapmask[1]) : 0; + tci1[0] = tci1[0] >= 0 ? (tci1[0] <= wrapmask[0] ? tci1[0] : wrapmask[0]) : 0; + tci1[1] = tci1[1] >= 0 ? (tci1[1] <= wrapmask[1] ? tci1[1] : wrapmask[1]) : 0; + } + else + { + tci[0] &= wrapmask[0]; + tci[1] &= wrapmask[1]; + tci1[0] &= wrapmask[0]; + tci1[1] &= wrapmask[1]; + } + pixel[0] = pixelbase + 4 * (tci[0] - tci[1]*width); + pixel[1] = pixelbase + 4 * (tci[0] - tci[1]*width); + pixel[2] = pixelbase + 4 * (tci[0] - tci1[1]*width); + pixel[3] = pixelbase + 4 * (tci[0] - tci1[1]*width); + c[0] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3])>>24; + c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3])>>24; + c[2] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3])>>24; + c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3])>>24; + } + else + { + int tci[2] = { x * width, y * height }; + if (texture->flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + tci[0] = tci[0] >= 0 ? (tci[0] <= wrapmask[0] ? tci[0] : wrapmask[0]) : 0; + tci[1] = tci[1] >= 0 ? (tci[1] <= wrapmask[1] ? tci[1] : wrapmask[1]) : 0; + } + else + { + tci[0] &= wrapmask[0]; + tci[1] &= wrapmask[1]; + } + pixel[0] = pixelbase + 4 * (tci[0] - tci[1]*width); + c[0] = pixel[0][0]; + c[1] = pixel[0][1]; + c[2] = pixel[0][2]; + c[3] = pixel[0][3]; + } +} + +#if 0 +static void DPSOFTRAST_Draw_Span_Texture2DVarying(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float * RESTRICT out4f, int texunitindex, int arrayindex, const float * RESTRICT zf) +{ + int x; + int startx = span->startx; + int endx = span->endx; + int flags; + float c[4]; + float data[4]; + float slope[4]; + float tc[2], endtc[2]; + float tcscale[2]; + unsigned int tci[2]; + unsigned int tci1[2]; + unsigned int tcimin[2]; + unsigned int tcimax[2]; + int tciwrapmask[2]; + int tciwidth; + int filter; + int mip; + const unsigned char * RESTRICT pixelbase; + const unsigned char * RESTRICT pixel[4]; + DPSOFTRAST_Texture *texture = thread->texbound[texunitindex]; + // if no texture is bound, just fill it with white + if (!texture) + { + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = 1.0f; + out4f[x*4+1] = 1.0f; + out4f[x*4+2] = 1.0f; + out4f[x*4+3] = 1.0f; + } + return; + } + mip = triangle->mip[texunitindex]; + pixelbase = (unsigned char *)texture->bytes + texture->mipmap[mip][0] + texture->mipmap[mip][1] - 4*texture->mipmap[mip][2]; + // if this mipmap of the texture is 1 pixel, just fill it with that color + if (texture->mipmap[mip][1] == 4) + { + c[0] = texture->bytes[2] * (1.0f/255.0f); + c[1] = texture->bytes[1] * (1.0f/255.0f); + c[2] = texture->bytes[0] * (1.0f/255.0f); + c[3] = texture->bytes[3] * (1.0f/255.0f); + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + return; + } + filter = texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR; + DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); + flags = texture->flags; + tcscale[0] = texture->mipmap[mip][2]; + tcscale[1] = texture->mipmap[mip][3]; + tciwidth = -texture->mipmap[mip][2]; + tcimin[0] = 0; + tcimin[1] = 0; + tcimax[0] = texture->mipmap[mip][2]-1; + tcimax[1] = texture->mipmap[mip][3]-1; + tciwrapmask[0] = texture->mipmap[mip][2]-1; + tciwrapmask[1] = texture->mipmap[mip][3]-1; + endtc[0] = (data[0] + slope[0]*startx) * zf[startx] * tcscale[0]; + endtc[1] = (data[1] + slope[1]*startx) * zf[startx] * tcscale[1]; + if (filter) + { + endtc[0] -= 0.5f; + endtc[1] -= 0.5f; + } + for (x = startx;x < endx;) + { + unsigned int subtc[2]; + unsigned int substep[2]; + float subscale = 4096.0f/DPSOFTRAST_DRAW_MAXSUBSPAN; + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + if (nextsub >= endx) + { + nextsub = endsub = endx-1; + if (x < nextsub) subscale = 4096.0f / (nextsub - x); + } + tc[0] = endtc[0]; + tc[1] = endtc[1]; + endtc[0] = (data[0] + slope[0]*nextsub) * zf[nextsub] * tcscale[0]; + endtc[1] = (data[1] + slope[1]*nextsub) * zf[nextsub] * tcscale[1]; + if (filter) + { + endtc[0] -= 0.5f; + endtc[1] -= 0.5f; + } + substep[0] = (endtc[0] - tc[0]) * subscale; + substep[1] = (endtc[1] - tc[1]) * subscale; + subtc[0] = tc[0] * (1<<12); + subtc[1] = tc[1] * (1<<12); + if (filter) + { + if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) + { + unsigned int frac[2] = { subtc[0]&0xFFF, subtc[1]&0xFFF }; + unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; + unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; + tci[0] = subtc[0]>>12; + tci[1] = subtc[1]>>12; + tci1[0] = tci[0] + 1; + tci1[1] = tci[1] + 1; + tci[0] = tci[0] >= tcimin[0] ? (tci[0] <= tcimax[0] ? tci[0] : tcimax[0]) : tcimin[0]; + tci[1] = tci[1] >= tcimin[1] ? (tci[1] <= tcimax[1] ? tci[1] : tcimax[1]) : tcimin[1]; + tci1[0] = tci1[0] >= tcimin[0] ? (tci1[0] <= tcimax[0] ? tci1[0] : tcimax[0]) : tcimin[0]; + tci1[1] = tci1[1] >= tcimin[1] ? (tci1[1] <= tcimax[1] ? tci1[1] : tcimax[1]) : tcimin[1]; + pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); + pixel[1] = pixelbase + 4 * (tci[1]*tciwidth+tci1[0]); + pixel[2] = pixelbase + 4 * (tci1[1]*tciwidth+tci[0]); + pixel[3] = pixelbase + 4 * (tci1[1]*tciwidth+tci1[0]); + c[0] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3]) * (1.0f / 0xFF000000); + c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3]) * (1.0f / 0xFF000000); + c[2] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3]) * (1.0f / 0xFF000000); + c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3]) * (1.0f / 0xFF000000); + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + } + else + { + for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) + { + unsigned int frac[2] = { subtc[0]&0xFFF, subtc[1]&0xFFF }; + unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; + unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; + tci[0] = subtc[0]>>12; + tci[1] = subtc[1]>>12; + tci1[0] = tci[0] + 1; + tci1[1] = tci[1] + 1; + tci[0] &= tciwrapmask[0]; + tci[1] &= tciwrapmask[1]; + tci1[0] &= tciwrapmask[0]; + tci1[1] &= tciwrapmask[1]; + pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); + pixel[1] = pixelbase + 4 * (tci[1]*tciwidth+tci1[0]); + pixel[2] = pixelbase + 4 * (tci1[1]*tciwidth+tci[0]); + pixel[3] = pixelbase + 4 * (tci1[1]*tciwidth+tci1[0]); + c[0] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3]) * (1.0f / 0xFF000000); + c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3]) * (1.0f / 0xFF000000); + c[2] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3]) * (1.0f / 0xFF000000); + c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3]) * (1.0f / 0xFF000000); + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + } + } + else if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) + { + tci[0] = subtc[0]>>12; + tci[1] = subtc[1]>>12; + tci[0] = tci[0] >= tcimin[0] ? (tci[0] <= tcimax[0] ? tci[0] : tcimax[0]) : tcimin[0]; + tci[1] = tci[1] >= tcimin[1] ? (tci[1] <= tcimax[1] ? tci[1] : tcimax[1]) : tcimin[1]; + pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); + c[0] = pixel[0][2] * (1.0f / 255.0f); + c[1] = pixel[0][1] * (1.0f / 255.0f); + c[2] = pixel[0][0] * (1.0f / 255.0f); + c[3] = pixel[0][3] * (1.0f / 255.0f); + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + } + else + { + for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) + { + tci[0] = subtc[0]>>12; + tci[1] = subtc[1]>>12; + tci[0] &= tciwrapmask[0]; + tci[1] &= tciwrapmask[1]; + pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); + c[0] = pixel[0][2] * (1.0f / 255.0f); + c[1] = pixel[0][1] * (1.0f / 255.0f); + c[2] = pixel[0][0] * (1.0f / 255.0f); + c[3] = pixel[0][3] * (1.0f / 255.0f); + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } + } + } +} +#endif + +static void DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char * RESTRICT out4ub, int texunitindex, int arrayindex, const float * RESTRICT zf) +{ +#ifdef SSE_POSSIBLE + int x; + int startx = span->startx; + int endx = span->endx; + int flags; + __m128 data, slope, tcscale; + __m128i tcsize, tcmask, tcoffset, tcmax; + __m128 tc, endtc; + __m128i subtc, substep, endsubtc; + int filter; + int mip; + int affine; // LordHavoc: optimized affine texturing case + unsigned int * RESTRICT outi = (unsigned int *)out4ub; + const unsigned char * RESTRICT pixelbase; + DPSOFTRAST_Texture *texture = thread->texbound[texunitindex]; + // if no texture is bound, just fill it with white + if (!texture) + { + memset(out4ub + startx*4, 255, (span->endx - span->startx)*4); + return; + } + mip = triangle->mip[texunitindex]; + pixelbase = (const unsigned char *)texture->bytes + texture->mipmap[mip][0] + texture->mipmap[mip][1] - 4*texture->mipmap[mip][2]; + // if this mipmap of the texture is 1 pixel, just fill it with that color + if (texture->mipmap[mip][1] == 4) + { + unsigned int k = *((const unsigned int *)pixelbase); + for (x = startx;x < endx;x++) + outi[x] = k; + return; + } + affine = zf[startx] == zf[endx-1]; + filter = texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR; + DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); + flags = texture->flags; + tcsize = _mm_shuffle_epi32(_mm_loadu_si128((const __m128i *)&texture->mipmap[mip][0]), _MM_SHUFFLE(3, 2, 3, 2)); + tcmask = _mm_sub_epi32(tcsize, _mm_set1_epi32(1)); + tcscale = _mm_cvtepi32_ps(tcsize); + data = _mm_mul_ps(_mm_movelh_ps(data, data), tcscale); + slope = _mm_mul_ps(_mm_movelh_ps(slope, slope), tcscale); + endtc = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); + if (filter) + endtc = _mm_sub_ps(endtc, _mm_set1_ps(0.5f)); + endsubtc = _mm_cvtps_epi32(_mm_mul_ps(endtc, _mm_set1_ps(65536.0f))); + tcoffset = _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_setzero_si128(), _mm_shuffle_epi32(tcsize, _MM_SHUFFLE(0, 0, 0, 0))), 18), _mm_set1_epi32(4)); + tcmax = _mm_packs_epi32(tcmask, tcmask); + for (x = startx;x < endx;) + { + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + __m128 subscale = _mm_set1_ps(65536.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); + if (nextsub >= endx || affine) + { + nextsub = endsub = endx-1; + if (x < nextsub) subscale = _mm_set1_ps(65536.0f / (nextsub - x)); + } + tc = endtc; + subtc = endsubtc; + endtc = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); + if (filter) + endtc = _mm_sub_ps(endtc, _mm_set1_ps(0.5f)); + substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endtc, tc), subscale)); + endsubtc = _mm_cvtps_epi32(_mm_mul_ps(endtc, _mm_set1_ps(65536.0f))); + subtc = _mm_unpacklo_epi64(subtc, _mm_add_epi32(subtc, substep)); + substep = _mm_slli_epi32(substep, 1); + if (filter) + { + __m128i tcrange = _mm_srai_epi32(_mm_unpacklo_epi64(subtc, _mm_add_epi32(endsubtc, substep)), 16); + if (_mm_movemask_epi8(_mm_andnot_si128(_mm_cmplt_epi32(tcrange, _mm_setzero_si128()), _mm_cmplt_epi32(tcrange, tcmask))) == 0xFFFF) + { + int stride = _mm_cvtsi128_si32(tcoffset)>>16; + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + const unsigned char * RESTRICT ptr1, * RESTRICT ptr2; + __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)), pix1, pix2, pix3, pix4, fracm; + tci = _mm_madd_epi16(tci, tcoffset); + ptr1 = pixelbase + _mm_cvtsi128_si32(tci); + ptr2 = pixelbase + _mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2))); + pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr1), _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr1 + stride)), _mm_setzero_si128()); + pix3 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr2), _mm_setzero_si128()); + pix4 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr2 + stride)), _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix3 = _mm_add_epi16(pix3, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), + _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); + pix2 = _mm_unpacklo_epi64(pix1, pix3); + pix4 = _mm_unpackhi_epi64(pix1, pix3); + pix2 = _mm_add_epi16(pix2, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), + _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); + _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); + } + if (x <= endsub) + { + const unsigned char * RESTRICT ptr1; + __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), pix1, pix2, fracm; + tci = _mm_madd_epi16(tci, tcoffset); + ptr1 = pixelbase + _mm_cvtsi128_si32(tci); + pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr1), _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr1 + stride)), _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); + outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + x++; + } + } + else if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, pix3, pix4, fracm; + tci = _mm_min_epi16(_mm_max_epi16(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), _mm_setzero_si128()), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + tci = _mm_shuffle_epi32(_mm_shufflehi_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 2, 3, 2)); + tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix3 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix4 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix3 = _mm_add_epi16(pix3, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), + _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); + pix2 = _mm_unpacklo_epi64(pix1, pix3); + pix4 = _mm_unpackhi_epi64(pix1, pix3); + pix2 = _mm_add_epi16(pix2, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), + _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); + _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); + } + if (x <= endsub) + { + __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, fracm; + tci = _mm_min_epi16(_mm_max_epi16(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), _mm_setzero_si128()), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); + outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + x++; + } + } + else + { + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, pix3, pix4, fracm; + tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + tci = _mm_shuffle_epi32(_mm_shufflehi_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 2, 3, 2)); + tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix3 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix4 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix3 = _mm_add_epi16(pix3, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), + _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); + pix2 = _mm_unpacklo_epi64(pix1, pix3); + pix4 = _mm_unpackhi_epi64(pix1, pix3); + pix2 = _mm_add_epi16(pix2, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), + _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); + _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); + } + if (x <= endsub) + { + __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, fracm; + tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), + _mm_setzero_si128()); + pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), + _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), + _mm_setzero_si128()); + fracm = _mm_srli_epi16(subtc, 1); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); + pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); + pix1 = _mm_add_epi16(pix1, + _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), + _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); + outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + x++; + } + } + } + else + { + if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) + { + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)); + tci = _mm_min_epi16(_mm_max_epi16(tci, _mm_setzero_si128()), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; + outi[x+1] = *(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]; + } + if (x <= endsub) + { + __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)); + tci =_mm_min_epi16(_mm_max_epi16(tci, _mm_setzero_si128()), tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; + x++; + } + } + else + { + for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) + { + __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)); + tci = _mm_and_si128(tci, tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; + outi[x+1] = *(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]; + } + if (x <= endsub) + { + __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)); + tci = _mm_and_si128(tci, tcmax); + tci = _mm_madd_epi16(tci, tcoffset); + outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; + x++; + } + } + } + } +#endif +} + +static void DPSOFTRAST_Draw_Span_TextureCubeVaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char * RESTRICT out4ub, int texunitindex, int arrayindex, const float * RESTRICT zf) +{ + // TODO: IMPLEMENT + memset(out4ub + span->startx*4, 255, (span->startx - span->endx)*4); +} + +static float DPSOFTRAST_SampleShadowmap(const float *vector) +{ + // TODO: IMPLEMENT + return 1.0f; +} + +#if 0 +static void DPSOFTRAST_Draw_Span_MultiplyVarying(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *in4f, int arrayindex, const float *zf) +{ + int x; + int startx = span->startx; + int endx = span->endx; + float c[4]; + float data[4]; + float slope[4]; + float z; + DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); + for (x = startx;x < endx;x++) + { + z = zf[x]; + c[0] = (data[0] + slope[0]*x) * z; + c[1] = (data[1] + slope[1]*x) * z; + c[2] = (data[2] + slope[2]*x) * z; + c[3] = (data[3] + slope[3]*x) * z; + out4f[x*4+0] = in4f[x*4+0] * c[0]; + out4f[x*4+1] = in4f[x*4+1] * c[1]; + out4f[x*4+2] = in4f[x*4+2] * c[2]; + out4f[x*4+3] = in4f[x*4+3] * c[3]; + } +} +#endif + +#if 0 +static void DPSOFTRAST_Draw_Span_Varying(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, int arrayindex, const float *zf) +{ + int x; + int startx = span->startx; + int endx = span->endx; + float c[4]; + float data[4]; + float slope[4]; + float z; + DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); + for (x = startx;x < endx;x++) + { + z = zf[x]; + c[0] = (data[0] + slope[0]*x) * z; + c[1] = (data[1] + slope[1]*x) * z; + c[2] = (data[2] + slope[2]*x) * z; + c[3] = (data[3] + slope[3]*x) * z; + out4f[x*4+0] = c[0]; + out4f[x*4+1] = c[1]; + out4f[x*4+2] = c[2]; + out4f[x*4+3] = c[3]; + } +} +#endif + +#if 0 +static void DPSOFTRAST_Draw_Span_AddBloom(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f, const float *subcolor) +{ + int x, startx = span->startx, endx = span->endx; + float c[4], localcolor[4]; + localcolor[0] = subcolor[0]; + localcolor[1] = subcolor[1]; + localcolor[2] = subcolor[2]; + localcolor[3] = subcolor[3]; + for (x = startx;x < endx;x++) + { + c[0] = inb4f[x*4+0] - localcolor[0];if (c[0] < 0.0f) c[0] = 0.0f; + c[1] = inb4f[x*4+1] - localcolor[1];if (c[1] < 0.0f) c[1] = 0.0f; + c[2] = inb4f[x*4+2] - localcolor[2];if (c[2] < 0.0f) c[2] = 0.0f; + c[3] = inb4f[x*4+3] - localcolor[3];if (c[3] < 0.0f) c[3] = 0.0f; + out4f[x*4+0] = ina4f[x*4+0] + c[0]; + out4f[x*4+1] = ina4f[x*4+1] + c[1]; + out4f[x*4+2] = ina4f[x*4+2] + c[2]; + out4f[x*4+3] = ina4f[x*4+3] + c[3]; + } +} +#endif + +#if 0 +static void DPSOFTRAST_Draw_Span_MultiplyBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) +{ + int x, startx = span->startx, endx = span->endx; + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = ina4f[x*4+0] * inb4f[x*4+0]; + out4f[x*4+1] = ina4f[x*4+1] * inb4f[x*4+1]; + out4f[x*4+2] = ina4f[x*4+2] * inb4f[x*4+2]; + out4f[x*4+3] = ina4f[x*4+3] * inb4f[x*4+3]; + } +} +#endif + +#if 0 +static void DPSOFTRAST_Draw_Span_AddBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) +{ + int x, startx = span->startx, endx = span->endx; + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = ina4f[x*4+0] + inb4f[x*4+0]; + out4f[x*4+1] = ina4f[x*4+1] + inb4f[x*4+1]; + out4f[x*4+2] = ina4f[x*4+2] + inb4f[x*4+2]; + out4f[x*4+3] = ina4f[x*4+3] + inb4f[x*4+3]; + } +} +#endif + +#if 0 +static void DPSOFTRAST_Draw_Span_MixBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) +{ + int x, startx = span->startx, endx = span->endx; + float a, b; + for (x = startx;x < endx;x++) + { + a = 1.0f - inb4f[x*4+3]; + b = inb4f[x*4+3]; + out4f[x*4+0] = ina4f[x*4+0] * a + inb4f[x*4+0] * b; + out4f[x*4+1] = ina4f[x*4+1] * a + inb4f[x*4+1] * b; + out4f[x*4+2] = ina4f[x*4+2] * a + inb4f[x*4+2] * b; + out4f[x*4+3] = ina4f[x*4+3] * a + inb4f[x*4+3] * b; + } +} +#endif + +#if 0 +static void DPSOFTRAST_Draw_Span_MixUniformColor(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *in4f, const float *color) +{ + int x, startx = span->startx, endx = span->endx; + float localcolor[4], ilerp, lerp; + localcolor[0] = color[0]; + localcolor[1] = color[1]; + localcolor[2] = color[2]; + localcolor[3] = color[3]; + ilerp = 1.0f - localcolor[3]; + lerp = localcolor[3]; + for (x = startx;x < endx;x++) + { + out4f[x*4+0] = in4f[x*4+0] * ilerp + localcolor[0] * lerp; + out4f[x*4+1] = in4f[x*4+1] * ilerp + localcolor[1] * lerp; + out4f[x*4+2] = in4f[x*4+2] * ilerp + localcolor[2] * lerp; + out4f[x*4+3] = in4f[x*4+3] * ilerp + localcolor[3] * lerp; + } +} +#endif + + + +static void DPSOFTRAST_Draw_Span_MultiplyVaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *in4ub, int arrayindex, const float *zf) +{ +#ifdef SSE_POSSIBLE + int x; + int startx = span->startx; + int endx = span->endx; + __m128 data, slope; + __m128 mod, endmod; + __m128i submod, substep, endsubmod; + DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); + data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); + slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); + endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); + endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(256.0f))); + for (x = startx; x < endx;) + { + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + __m128 subscale = _mm_set1_ps(256.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); + if (nextsub >= endx) + { + nextsub = endsub = endx-1; + if (x < nextsub) subscale = _mm_set1_ps(256.0f / (nextsub - x)); + } + mod = endmod; + submod = endsubmod; + endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); + substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endmod, mod), subscale)); + endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(256.0f))); + submod = _mm_packs_epi32(submod, _mm_add_epi32(submod, substep)); + substep = _mm_packs_epi32(substep, substep); + for (; x + 1 <= endsub; x += 2, submod = _mm_add_epi16(submod, substep)) + { + __m128i pix = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&in4ub[x*4])); + pix = _mm_mulhi_epu16(pix, submod); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); + } + if (x <= endsub) + { + __m128i pix = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&in4ub[x*4])); + pix = _mm_mulhi_epu16(pix, submod); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + x++; + } + } +#endif +} + +static void DPSOFTRAST_Draw_Span_VaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, int arrayindex, const float *zf) +{ +#ifdef SSE_POSSIBLE + int x; + int startx = span->startx; + int endx = span->endx; + __m128 data, slope; + __m128 mod, endmod; + __m128i submod, substep, endsubmod; + DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); + data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); + slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); + endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); + endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(4095.0f))); + for (x = startx; x < endx;) + { + int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; + __m128 subscale = _mm_set1_ps(4095.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); + if (nextsub >= endx) + { + nextsub = endsub = endx-1; + if (x < nextsub) subscale = _mm_set1_ps(4095.0f / (nextsub - x)); + } + mod = endmod; + submod = endsubmod; + endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); + substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endmod, mod), subscale)); + endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(4095.0f))); + submod = _mm_packs_epi32(submod, _mm_add_epi32(submod, substep)); + substep = _mm_packs_epi32(substep, substep); + for (; x + 1 <= endsub; x += 2, submod = _mm_add_epi16(submod, substep)) + { + __m128i pix = _mm_srai_epi16(submod, 4); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); + } + if (x <= endsub) + { + __m128i pix = _mm_srai_epi16(submod, 4); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + x++; + } + } +#endif +} + +static void DPSOFTRAST_Draw_Span_AddBloomBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub, const float *subcolor) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + __m128i localcolor = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(subcolor), _mm_set1_ps(255.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + localcolor = _mm_packs_epi32(localcolor, localcolor); + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); + pix1 = _mm_add_epi16(pix1, _mm_subs_epu16(pix2, localcolor)); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); + pix1 = _mm_add_epi16(pix1, _mm_subs_epu16(pix2, localcolor)); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +static void DPSOFTRAST_Draw_Span_MultiplyBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&inb4ub[x*4])); + pix1 = _mm_mulhi_epu16(pix1, pix2); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&inb4ub[x*4])); + pix1 = _mm_mulhi_epu16(pix1, pix2); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +static void DPSOFTRAST_Draw_Span_AddBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); + pix1 = _mm_add_epi16(pix1, pix2); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); + pix1 = _mm_add_epi16(pix1, pix2); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +#if 0 +static void DPSOFTRAST_Draw_Span_TintedAddBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub, const float *inbtintbgra) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + __m128i tint = _mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(inbtintbgra), _mm_set1_ps(256.0f))); + tint = _mm_packs_epi32(tint, tint); + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&inb4ub[x*4])); + pix1 = _mm_add_epi16(pix1, _mm_mulhi_epu16(tint, pix2)); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&inb4ub[x*4])); + pix1 = _mm_add_epi16(pix1, _mm_mulhi_epu16(tint, pix2)); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} +#endif + +static void DPSOFTRAST_Draw_Span_MixBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); + __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); + pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 4), _mm_slli_epi16(blend, 4))); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); + } + if (x < endx) + { + __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); + __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); + __m128i blend = _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 3, 3, 3)); + pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 4), _mm_slli_epi16(blend, 4))); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); + } +#endif +} + +static void DPSOFTRAST_Draw_Span_MixUniformColorBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *in4ub, const float *color) +{ +#ifdef SSE_POSSIBLE + int x, startx = span->startx, endx = span->endx; + __m128i localcolor = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(color), _mm_set1_ps(255.0f))), _MM_SHUFFLE(3, 0, 1, 2)), blend; + localcolor = _mm_packs_epi32(localcolor, localcolor); + blend = _mm_slli_epi16(_mm_shufflehi_epi16(_mm_shufflelo_epi16(localcolor, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)), 4); + for (x = startx;x+2 <= endx;x+=2) + { + __m128i pix = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&in4ub[x*4]), _mm_setzero_si128()); + pix = _mm_add_epi16(pix, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(localcolor, pix), 4), blend)); + _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); + } + if (x < endx) + { + __m128i pix = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&in4ub[x*4]), _mm_setzero_si128()); + pix = _mm_add_epi16(pix, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(localcolor, pix), 4), blend)); + *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } +#endif +} + + + +static void DPSOFTRAST_VertexShader_Generic(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_COLOR); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0); + if (dpsoftrast.shader_permutation & SHADERPERMUTATION_SPECULAR) + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); +} + +static void DPSOFTRAST_PixelShader_Generic(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_FIRST, 2, buffer_z); + DPSOFTRAST_Draw_Span_MultiplyVaryingBGRA8(triangle, span, buffer_FragColorbgra8, buffer_texture_colorbgra8, 1, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_SECOND, 2, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + // multiply + DPSOFTRAST_Draw_Span_MultiplyBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); + } + else if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + // add + DPSOFTRAST_Draw_Span_AddBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); + } + else if (thread->shader_permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) + { + // alphablend + DPSOFTRAST_Draw_Span_MixBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); + } + } + } + else + DPSOFTRAST_Draw_Span_VaryingBGRA8(triangle, span, buffer_FragColorbgra8, 1, buffer_z); + if(thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) + { + int x; + for (x = span->startx;x < span->endx;x++) + buffer_FragColorbgra8[x*4+3] = buffer_FragColorbgra8[x*4+3] * thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]; + } + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +static void DPSOFTRAST_VertexShader_PostProcess(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD4); +} + +static void DPSOFTRAST_PixelShader_PostProcess(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // TODO: optimize!! at the very least there is no reason to use texture sampling on the frame texture + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_FragColorbgra8, GL20TU_FIRST, 2, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_BLOOM) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_SECOND, 3, buffer_z); + DPSOFTRAST_Draw_Span_AddBloomBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_colorbgra8, thread->uniform4f + DPSOFTRAST_UNIFORM_BloomColorSubtract * 4); + } + DPSOFTRAST_Draw_Span_MixUniformColorBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, thread->uniform4f + DPSOFTRAST_UNIFORM_ViewTintColor * 4); + if (thread->shader_permutation & SHADERPERMUTATION_SATURATION) + { + // TODO: implement saturation + } + if (thread->shader_permutation & SHADERPERMUTATION_GAMMARAMPS) + { + // TODO: implement gammaramps + } + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +static void DPSOFTRAST_VertexShader_Depth_Or_Shadow(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +static void DPSOFTRAST_PixelShader_Depth_Or_Shadow(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // this is never called (because colormask is off when this shader is used) + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +static void DPSOFTRAST_VertexShader_FlatColor(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); +} + +static void DPSOFTRAST_PixelShader_FlatColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ +#ifdef SSE_POSSIBLE + unsigned char * RESTRICT pixelmask = span->pixelmask; + unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; + int x, startx = span->startx, endx = span->endx; + __m128i Color_Ambientm; + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, 2, buffer_z); + if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) + pixel = buffer_FragColorbgra8; + Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); + Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); + for (x = startx;x < endx;x++) + { + __m128i color, pix; + if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) + { + __m128i pix2; + color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); + pix = _mm_mulhi_epu16(Color_Ambientm, _mm_unpacklo_epi8(_mm_setzero_si128(), color)); + pix2 = _mm_mulhi_epu16(Color_Ambientm, _mm_unpackhi_epi8(_mm_setzero_si128(), color)); + _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); + x += 3; + continue; + } + if (!pixelmask[x]) + continue; + color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); + pix = _mm_mulhi_epu16(Color_Ambientm, color); + *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } + if (pixel == buffer_FragColorbgra8) + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +#endif +} + + + +static void DPSOFTRAST_VertexShader_VertexColor(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_COLOR); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); +} + +static void DPSOFTRAST_PixelShader_VertexColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ +#ifdef SSE_POSSIBLE + unsigned char * RESTRICT pixelmask = span->pixelmask; + unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; + int x, startx = span->startx, endx = span->endx; + __m128i Color_Ambientm, Color_Diffusem; + __m128 data, slope; + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + int arrayindex = DPSOFTRAST_ARRAY_COLOR; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, 2, buffer_z); + if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) + pixel = buffer_FragColorbgra8; + Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); + Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); + Color_Diffusem = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4]), _mm_set1_ps(4096.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Diffusem = _mm_and_si128(Color_Diffusem, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Diffusem = _mm_packs_epi32(Color_Diffusem, Color_Diffusem); + DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); + data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); + slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); + data = _mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))); + data = _mm_mul_ps(data, _mm_set1_ps(4096.0f)); + slope = _mm_mul_ps(slope, _mm_set1_ps(4096.0f)); + for (x = startx;x < endx;x++, data = _mm_add_ps(data, slope)) + { + __m128i color, mod, pix; + if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) + { + __m128i pix2, mod2; + __m128 z = _mm_loadu_ps(&buffer_z[x]); + color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); + mod = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(0, 0, 0, 0)))); + data = _mm_add_ps(data, slope); + mod = _mm_packs_epi32(mod, _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(1, 1, 1, 1))))); + data = _mm_add_ps(data, slope); + mod2 = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(2, 2, 2, 2)))); + data = _mm_add_ps(data, slope); + mod2 = _mm_packs_epi32(mod2, _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(3, 3, 3, 3))))); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, mod), Color_Ambientm), + _mm_unpacklo_epi8(_mm_setzero_si128(), color)); + pix2 = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, mod2), Color_Ambientm), + _mm_unpackhi_epi8(_mm_setzero_si128(), color)); + _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); + x += 3; + continue; + } + if (!pixelmask[x]) + continue; + color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); + mod = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_load1_ps(&buffer_z[x]))); + mod = _mm_packs_epi32(mod, mod); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(mod, Color_Diffusem), Color_Ambientm), color); + *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } + if (pixel == buffer_FragColorbgra8) + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +#endif +} + + + +static void DPSOFTRAST_VertexShader_Lightmap(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); +} + +static void DPSOFTRAST_PixelShader_Lightmap(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ +#ifdef SSE_POSSIBLE + unsigned char * RESTRICT pixelmask = span->pixelmask; + unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; + int x, startx = span->startx, endx = span->endx; + __m128i Color_Ambientm, Color_Diffusem, Color_Glowm, Color_AmbientGlowm; + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_glowbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) + pixel = buffer_FragColorbgra8; + Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); + Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); + Color_Diffusem = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Diffusem = _mm_and_si128(Color_Diffusem, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Diffusem = _mm_packs_epi32(Color_Diffusem, Color_Diffusem); + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glowbgra8, GL20TU_GLOW, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + Color_Glowm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); + Color_Glowm = _mm_and_si128(Color_Glowm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); + Color_Glowm = _mm_packs_epi32(Color_Glowm, Color_Glowm); + Color_AmbientGlowm = _mm_unpacklo_epi64(Color_Ambientm, Color_Glowm); + for (x = startx;x < endx;x++) + { + __m128i color, lightmap, glow, pix; + if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) + { + __m128i pix2; + color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); + lightmap = _mm_loadu_si128((const __m128i *)&buffer_texture_lightmapbgra8[x*4]); + glow = _mm_loadu_si128((const __m128i *)&buffer_texture_glowbgra8[x*4]); + pix = _mm_add_epi16(_mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpacklo_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), + _mm_unpacklo_epi8(_mm_setzero_si128(), color)), + _mm_mulhi_epu16(Color_Glowm, _mm_unpacklo_epi8(_mm_setzero_si128(), glow))); + pix2 = _mm_add_epi16(_mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpackhi_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), + _mm_unpackhi_epi8(_mm_setzero_si128(), color)), + _mm_mulhi_epu16(Color_Glowm, _mm_unpackhi_epi8(_mm_setzero_si128(), glow))); + _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); + x += 3; + continue; + } + if (!pixelmask[x]) + continue; + color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); + lightmap = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_lightmapbgra8[x*4])); + glow = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_glowbgra8[x*4])); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, lightmap), Color_AmbientGlowm), _mm_unpacklo_epi64(color, glow)); + pix = _mm_add_epi16(pix, _mm_shuffle_epi32(pix, _MM_SHUFFLE(3, 2, 3, 2))); + *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } + } + else + { + for (x = startx;x < endx;x++) + { + __m128i color, lightmap, pix; + if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) + { + __m128i pix2; + color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); + lightmap = _mm_loadu_si128((const __m128i *)&buffer_texture_lightmapbgra8[x*4]); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpacklo_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), + _mm_unpacklo_epi8(_mm_setzero_si128(), color)); + pix2 = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpackhi_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), + _mm_unpackhi_epi8(_mm_setzero_si128(), color)); + _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); + x += 3; + continue; + } + if (!pixelmask[x]) + continue; + color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); + lightmap = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_lightmapbgra8[x*4])); + pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(lightmap, Color_Diffusem), Color_Ambientm), color); + *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); + } + } + if (pixel == buffer_FragColorbgra8) + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +#endif +} + + +void DPSOFTRAST_VertexShader_LightDirection(void); +void DPSOFTRAST_PixelShader_LightDirection(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span); + +static void DPSOFTRAST_VertexShader_FakeLight(void) +{ + DPSOFTRAST_VertexShader_LightDirection(); +} + +static void DPSOFTRAST_PixelShader_FakeLight(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); +} + + + +static void DPSOFTRAST_VertexShader_LightDirectionMap_ModelSpace(void) +{ + DPSOFTRAST_VertexShader_LightDirection(); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); +} + +static void DPSOFTRAST_PixelShader_LightDirectionMap_ModelSpace(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); +} + + + +static void DPSOFTRAST_VertexShader_LightDirectionMap_TangentSpace(void) +{ + DPSOFTRAST_VertexShader_LightDirection(); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); +} + +static void DPSOFTRAST_PixelShader_LightDirectionMap_TangentSpace(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); +} + + + +void DPSOFTRAST_VertexShader_LightDirection(void) +{ + int i; + int numvertices = dpsoftrast.numvertices; + float LightDir[4]; + float LightVector[4]; + float EyePosition[4]; + float EyeVectorModelSpace[4]; + float EyeVector[4]; + float position[4]; + float svector[4]; + float tvector[4]; + float normal[4]; + LightDir[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+0]; + LightDir[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+1]; + LightDir[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+2]; + LightDir[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+3]; + EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; + EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; + EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; + EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); + for (i = 0;i < numvertices;i++) + { + position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; + position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; + position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; + svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; + svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; + svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; + tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; + tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; + tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; + normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; + normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; + normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; + LightVector[0] = svector[0] * LightDir[0] + svector[1] * LightDir[1] + svector[2] * LightDir[2]; + LightVector[1] = tvector[0] * LightDir[0] + tvector[1] * LightDir[1] + tvector[2] * LightDir[2]; + LightVector[2] = normal[0] * LightDir[0] + normal[1] * LightDir[1] + normal[2] * LightDir[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+0] = LightVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+1] = LightVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+2] = LightVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+3] = 0.0f; + EyeVectorModelSpace[0] = EyePosition[0] - position[0]; + EyeVectorModelSpace[1] = EyePosition[1] - position[1]; + EyeVectorModelSpace[2] = EyePosition[2] - position[2]; + EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; + EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; + EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+0] = EyeVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+1] = EyeVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+2] = EyeVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+3] = 0.0f; + } + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +#define DPSOFTRAST_Min(a,b) ((a) < (b) ? (a) : (b)) +#define DPSOFTRAST_Max(a,b) ((a) > (b) ? (a) : (b)) +#define DPSOFTRAST_Vector3Dot(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) +#define DPSOFTRAST_Vector3LengthSquared(v) (DPSOFTRAST_Vector3Dot((v),(v))) +#define DPSOFTRAST_Vector3Length(v) (sqrt(DPSOFTRAST_Vector3LengthSquared(v))) +#define DPSOFTRAST_Vector3Normalize(v)\ +do\ +{\ + float len = sqrt(DPSOFTRAST_Vector3Dot(v,v));\ + if (len)\ + {\ + len = 1.0f / len;\ + v[0] *= len;\ + v[1] *= len;\ + v[2] *= len;\ + }\ +}\ +while(0) + +void DPSOFTRAST_PixelShader_LightDirection(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_glossbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_glowbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_pantsbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_shirtbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_deluxemapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + int x, startx = span->startx, endx = span->endx; + float Color_Ambient[4], Color_Diffuse[4], Color_Specular[4], Color_Glow[4], Color_Pants[4], Color_Shirt[4], LightColor[4]; + float LightVectordata[4]; + float LightVectorslope[4]; + float EyeVectordata[4]; + float EyeVectorslope[4]; + float VectorSdata[4]; + float VectorSslope[4]; + float VectorTdata[4]; + float VectorTslope[4]; + float VectorRdata[4]; + float VectorRslope[4]; + float z; + float diffusetex[4]; + float glosstex[4]; + float surfacenormal[4]; + float lightnormal[4]; + float lightnormal_modelspace[4]; + float eyenormal[4]; + float specularnormal[4]; + float diffuse; + float specular; + float SpecularPower; + int d[4]; + Color_Glow[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+0]; + Color_Glow[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+1]; + Color_Glow[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+2]; + Color_Glow[3] = 0.0f; + Color_Ambient[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+0]; + Color_Ambient[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+1]; + Color_Ambient[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+2]; + Color_Ambient[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]; + Color_Pants[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+0]; + Color_Pants[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+1]; + Color_Pants[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+2]; + Color_Pants[3] = 0.0f; + Color_Shirt[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+0]; + Color_Shirt[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+1]; + Color_Shirt[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+2]; + Color_Shirt[3] = 0.0f; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_pantsbgra8, GL20TU_PANTS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_shirtbgra8, GL20TU_SHIRT, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + } + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glowbgra8, GL20TU_GLOW, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + } + if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) + { + Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; + Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; + Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; + Color_Diffuse[3] = 0.0f; + LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; + LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; + LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; + LightColor[3] = 0.0f; + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + Color_Specular[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+0]; + Color_Specular[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+1]; + Color_Specular[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+2]; + Color_Specular[3] = 0.0f; + SpecularPower = thread->uniform4f[DPSOFTRAST_UNIFORM_SpecularPower*4+0] * (1.0f / 255.0f); + DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glossbgra8, GL20TU_GLOSS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + + if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorSdata, VectorSslope, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorTdata, VectorTslope, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorRdata, VectorRslope, DPSOFTRAST_ARRAY_TEXCOORD3); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + } + else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + } + else if(thread->shader_mode == SHADERMODE_FAKELIGHT) + { + // nothing of this needed + } + else + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD5); + } + + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; + diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; + diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; + diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; + } + glosstex[0] = buffer_texture_glossbgra8[x*4+0]; + glosstex[1] = buffer_texture_glossbgra8[x*4+1]; + glosstex[2] = buffer_texture_glossbgra8[x*4+2]; + glosstex[3] = buffer_texture_glossbgra8[x*4+3]; + surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(surfacenormal); + + if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) + { + // myhalf3 lightnormal_modelspace = myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + myhalf3(-1.0, -1.0, -1.0);\n"; + lightnormal_modelspace[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + lightnormal_modelspace[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + lightnormal_modelspace[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + + // lightnormal.x = dot(lightnormal_modelspace, myhalf3(VectorS));\n" + lightnormal[0] = lightnormal_modelspace[0] * (VectorSdata[0] + VectorSslope[0] * x) + + lightnormal_modelspace[1] * (VectorSdata[1] + VectorSslope[1] * x) + + lightnormal_modelspace[2] * (VectorSdata[2] + VectorSslope[2] * x); + + // lightnormal.y = dot(lightnormal_modelspace, myhalf3(VectorT));\n" + lightnormal[1] = lightnormal_modelspace[0] * (VectorTdata[0] + VectorTslope[0] * x) + + lightnormal_modelspace[1] * (VectorTdata[1] + VectorTslope[1] * x) + + lightnormal_modelspace[2] * (VectorTdata[2] + VectorTslope[2] * x); + + // lightnormal.z = dot(lightnormal_modelspace, myhalf3(VectorR));\n" + lightnormal[2] = lightnormal_modelspace[0] * (VectorRdata[0] + VectorRslope[0] * x) + + lightnormal_modelspace[1] * (VectorRdata[1] + VectorRslope[1] * x) + + lightnormal_modelspace[2] * (VectorRdata[2] + VectorRslope[2] * x); + + // lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n" + DPSOFTRAST_Vector3Normalize(lightnormal); + + // myhalf3 lightcolor = myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n"; + { + float f = 1.0f / (256.0f * max(0.25f, lightnormal[2])); + LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; + LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; + LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; + } + } + else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) + { + lightnormal[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + lightnormal[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + lightnormal[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + { + float f = 1.0f / 256.0f; + LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; + LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; + LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; + } + } + else if(thread->shader_mode == SHADERMODE_FAKELIGHT) + { + lightnormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + lightnormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + lightnormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + + LightColor[0] = 1.0; + LightColor[1] = 1.0; + LightColor[2] = 1.0; + } + else + { + lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; + lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; + lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + } + + diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; + + if(thread->shader_exactspecularmath) + { + // reflect lightnormal at surfacenormal, take the negative of that + // i.e. we want (2*dot(N, i) * N - I) for N=surfacenormal, I=lightnormal + float f; + f = DPSOFTRAST_Vector3Dot(lightnormal, surfacenormal); + specularnormal[0] = 2*f*surfacenormal[0] - lightnormal[0]; + specularnormal[1] = 2*f*surfacenormal[1] - lightnormal[1]; + specularnormal[2] = 2*f*surfacenormal[2] - lightnormal[2]; + + // dot of this and normalize(EyeVectorFogDepth.xyz) + eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(eyenormal); + + specular = DPSOFTRAST_Vector3Dot(eyenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; + } + else + { + eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(eyenormal); + + specularnormal[0] = lightnormal[0] + eyenormal[0]; + specularnormal[1] = lightnormal[1] + eyenormal[1]; + specularnormal[2] = lightnormal[2] + eyenormal[2]; + DPSOFTRAST_Vector3Normalize(specularnormal); + + specular = DPSOFTRAST_Vector3Dot(surfacenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; + } + specular = pow(specular, 1.0f + SpecularPower * glosstex[3]); + + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * Color_Ambient[0] + (diffusetex[0] * Color_Diffuse[0] * diffuse + glosstex[0] * Color_Specular[0] * specular) * LightColor[0]);if (d[0] > 255) d[0] = 255; + d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * Color_Ambient[1] + (diffusetex[1] * Color_Diffuse[1] * diffuse + glosstex[1] * Color_Specular[1] * specular) * LightColor[1]);if (d[1] > 255) d[1] = 255; + d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * Color_Ambient[2] + (diffusetex[2] * Color_Diffuse[2] * diffuse + glosstex[2] * Color_Specular[2] * specular) * LightColor[2]);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)( diffusetex[0] * Color_Ambient[0] + (diffusetex[0] * Color_Diffuse[0] * diffuse + glosstex[0] * Color_Specular[0] * specular) * LightColor[0]);if (d[0] > 255) d[0] = 255; + d[1] = (int)( diffusetex[1] * Color_Ambient[1] + (diffusetex[1] * Color_Diffuse[1] * diffuse + glosstex[1] * Color_Specular[1] * specular) * LightColor[1]);if (d[1] > 255) d[1] = 255; + d[2] = (int)( diffusetex[2] * Color_Ambient[2] + (diffusetex[2] * Color_Diffuse[2] * diffuse + glosstex[2] * Color_Specular[2] * specular) * LightColor[2]);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; + } + + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + else if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) + { + Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; + Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; + Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; + Color_Diffuse[3] = 0.0f; + LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; + LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; + LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; + LightColor[3] = 0.0f; + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + + if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorSdata, VectorSslope, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorTdata, VectorTslope, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorRdata, VectorRslope, DPSOFTRAST_ARRAY_TEXCOORD3); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + } + else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); + } + else if(thread->shader_mode == SHADERMODE_FAKELIGHT) + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); + } + else + { + DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD5); + } + + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(surfacenormal); + + if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) + { + // myhalf3 lightnormal_modelspace = myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + myhalf3(-1.0, -1.0, -1.0);\n"; + lightnormal_modelspace[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + lightnormal_modelspace[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + lightnormal_modelspace[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + + // lightnormal.x = dot(lightnormal_modelspace, myhalf3(VectorS));\n" + lightnormal[0] = lightnormal_modelspace[0] * (VectorSdata[0] + VectorSslope[0] * x) + + lightnormal_modelspace[1] * (VectorSdata[1] + VectorSslope[1] * x) + + lightnormal_modelspace[2] * (VectorSdata[2] + VectorSslope[2] * x); + + // lightnormal.y = dot(lightnormal_modelspace, myhalf3(VectorT));\n" + lightnormal[1] = lightnormal_modelspace[0] * (VectorTdata[0] + VectorTslope[0] * x) + + lightnormal_modelspace[1] * (VectorTdata[1] + VectorTslope[1] * x) + + lightnormal_modelspace[2] * (VectorTdata[2] + VectorTslope[2] * x); + + // lightnormal.z = dot(lightnormal_modelspace, myhalf3(VectorR));\n" + lightnormal[2] = lightnormal_modelspace[0] * (VectorRdata[0] + VectorRslope[0] * x) + + lightnormal_modelspace[1] * (VectorRdata[1] + VectorRslope[1] * x) + + lightnormal_modelspace[2] * (VectorRdata[2] + VectorRslope[2] * x); + + // lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n" + DPSOFTRAST_Vector3Normalize(lightnormal); + + // myhalf3 lightcolor = myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n"; + { + float f = 1.0f / (256.0f * max(0.25f, lightnormal[2])); + LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; + LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; + LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; + } + } + else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) + { + lightnormal[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + lightnormal[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + lightnormal[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + { + float f = 1.0f / 256.0f; + LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; + LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; + LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; + } + } + else if(thread->shader_mode == SHADERMODE_FAKELIGHT) + { + lightnormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + lightnormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + lightnormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + + LightColor[0] = 1.0; + LightColor[1] = 1.0; + LightColor[2] = 1.0; + } + else + { + lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; + lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; + lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + } + + diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse * LightColor[0]));if (d[0] > 255) d[0] = 255; + d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse * LightColor[1]));if (d[1] > 255) d[1] = 255; + d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse * LightColor[2]));if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * (Color_Ambient[3] ));if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)( + diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse * LightColor[0]));if (d[0] > 255) d[0] = 255; + d[1] = (int)( + diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse * LightColor[1]));if (d[1] > 255) d[1] = 255; + d[2] = (int)( + diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse * LightColor[2]));if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * (Color_Ambient[3] ));if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + else + { + for (x = startx;x < endx;x++) + { + // z = buffer_z[x]; + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + + if (thread->shader_permutation & SHADERPERMUTATION_GLOW) + { + d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * Color_Ambient[0]);if (d[0] > 255) d[0] = 255; + d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * Color_Ambient[1]);if (d[1] > 255) d[1] = 255; + d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * Color_Ambient[2]);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)( diffusetex[0] * Color_Ambient[0]);if (d[0] > 255) d[0] = 255; + d[1] = (int)( diffusetex[1] * Color_Ambient[1]);if (d[1] > 255) d[1] = 255; + d[2] = (int)( diffusetex[2] * Color_Ambient[2]);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +static void DPSOFTRAST_VertexShader_LightSource(void) +{ + int i; + int numvertices = dpsoftrast.numvertices; + float LightPosition[4]; + float LightVector[4]; + float LightVectorModelSpace[4]; + float EyePosition[4]; + float EyeVectorModelSpace[4]; + float EyeVector[4]; + float position[4]; + float svector[4]; + float tvector[4]; + float normal[4]; + LightPosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+0]; + LightPosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+1]; + LightPosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+2]; + LightPosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+3]; + EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; + EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; + EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; + EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); + for (i = 0;i < numvertices;i++) + { + position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; + position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; + position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; + svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; + svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; + svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; + tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; + tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; + tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; + normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; + normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; + normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; + LightVectorModelSpace[0] = LightPosition[0] - position[0]; + LightVectorModelSpace[1] = LightPosition[1] - position[1]; + LightVectorModelSpace[2] = LightPosition[2] - position[2]; + LightVector[0] = svector[0] * LightVectorModelSpace[0] + svector[1] * LightVectorModelSpace[1] + svector[2] * LightVectorModelSpace[2]; + LightVector[1] = tvector[0] * LightVectorModelSpace[0] + tvector[1] * LightVectorModelSpace[1] + tvector[2] * LightVectorModelSpace[2]; + LightVector[2] = normal[0] * LightVectorModelSpace[0] + normal[1] * LightVectorModelSpace[1] + normal[2] * LightVectorModelSpace[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0] = LightVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1] = LightVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2] = LightVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+3] = 0.0f; + EyeVectorModelSpace[0] = EyePosition[0] - position[0]; + EyeVectorModelSpace[1] = EyePosition[1] - position[1]; + EyeVectorModelSpace[2] = EyePosition[2] - position[2]; + EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; + EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; + EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0] = EyeVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1] = EyeVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2] = EyeVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+3] = 0.0f; + } + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelToLightM1); +} + +static void DPSOFTRAST_PixelShader_LightSource(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ +#ifdef SSE_POSSIBLE + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_glossbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_cubebgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_pantsbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_texture_shirtbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + int x, startx = span->startx, endx = span->endx; + float Color_Ambient[4], Color_Diffuse[4], Color_Specular[4], /*Color_Glow[4],*/ Color_Pants[4], Color_Shirt[4], LightColor[4]; + float CubeVectordata[4]; + float CubeVectorslope[4]; + float LightVectordata[4]; + float LightVectorslope[4]; + float EyeVectordata[4]; + float EyeVectorslope[4]; + float z; + float diffusetex[4]; + float glosstex[4]; + float surfacenormal[4]; + float lightnormal[4]; + float eyenormal[4]; + float specularnormal[4]; + float diffuse; + float specular; + float SpecularPower; + float CubeVector[4]; + float attenuation; + int d[4]; +#if 0 + Color_Glow[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+0]; + Color_Glow[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+1]; + Color_Glow[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+2]; + Color_Glow[3] = 0.0f; +#endif + Color_Ambient[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+0]; + Color_Ambient[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+1]; + Color_Ambient[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+2]; + Color_Ambient[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]; + Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; + Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; + Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; + Color_Diffuse[3] = 0.0f; + Color_Specular[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+0]; + Color_Specular[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+1]; + Color_Specular[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+2]; + Color_Specular[3] = 0.0f; + Color_Pants[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+0]; + Color_Pants[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+1]; + Color_Pants[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+2]; + Color_Pants[3] = 0.0f; + Color_Shirt[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+0]; + Color_Shirt[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+1]; + Color_Shirt[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+2]; + Color_Shirt[3] = 0.0f; + LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; + LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; + LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; + LightColor[3] = 0.0f; + SpecularPower = thread->uniform4f[DPSOFTRAST_UNIFORM_SpecularPower*4+0] * (1.0f / 255.0f); + DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_CALCATTRIB4F(triangle, span, CubeVectordata, CubeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD3); + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + startx*4, 0, (endx-startx)*4); // clear first, because we skip writing black pixels, and there are a LOT of them... + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_pantsbgra8, GL20TU_PANTS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_shirtbgra8, GL20TU_SHIRT, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + } + if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) + DPSOFTRAST_Draw_Span_TextureCubeVaryingBGRA8(triangle, span, buffer_texture_cubebgra8, GL20TU_CUBE, DPSOFTRAST_ARRAY_TEXCOORD3, buffer_z); + if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glossbgra8, GL20TU_GLOSS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; + CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; + CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; + attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); + if (attenuation < 0.01f) + continue; + if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) + { + attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); + if (attenuation < 0.01f) + continue; + } + + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; + diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; + diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; + diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; + } + glosstex[0] = buffer_texture_glossbgra8[x*4+0]; + glosstex[1] = buffer_texture_glossbgra8[x*4+1]; + glosstex[2] = buffer_texture_glossbgra8[x*4+2]; + glosstex[3] = buffer_texture_glossbgra8[x*4+3]; + surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(surfacenormal); + + lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; + lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; + lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + + diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; + + if(thread->shader_exactspecularmath) + { + // reflect lightnormal at surfacenormal, take the negative of that + // i.e. we want (2*dot(N, i) * N - I) for N=surfacenormal, I=lightnormal + float f; + f = DPSOFTRAST_Vector3Dot(lightnormal, surfacenormal); + specularnormal[0] = 2*f*surfacenormal[0] - lightnormal[0]; + specularnormal[1] = 2*f*surfacenormal[1] - lightnormal[1]; + specularnormal[2] = 2*f*surfacenormal[2] - lightnormal[2]; + + // dot of this and normalize(EyeVectorFogDepth.xyz) + eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(eyenormal); + + specular = DPSOFTRAST_Vector3Dot(eyenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; + } + else + { + eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; + eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; + eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(eyenormal); + + specularnormal[0] = lightnormal[0] + eyenormal[0]; + specularnormal[1] = lightnormal[1] + eyenormal[1]; + specularnormal[2] = lightnormal[2] + eyenormal[2]; + DPSOFTRAST_Vector3Normalize(specularnormal); + + specular = DPSOFTRAST_Vector3Dot(surfacenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; + } + specular = pow(specular, 1.0f + SpecularPower * glosstex[3]); + + if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) + { + // scale down the attenuation to account for the cubefilter multiplying everything by 255 + attenuation *= (1.0f / 255.0f); + d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse) + glosstex[0] * Color_Specular[0] * specular) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse) + glosstex[1] * Color_Specular[1] * specular) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse) + glosstex[2] * Color_Specular[2] * specular) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse) + glosstex[0] * Color_Specular[0] * specular) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse) + glosstex[1] * Color_Specular[1] * specular) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse) + glosstex[2] * Color_Specular[2] * specular) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + else if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) + { + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; + CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; + CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; + attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); + if (attenuation < 0.01f) + continue; + if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) + { + attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); + if (attenuation < 0.01f) + continue; + } + + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; + diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; + diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; + diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; + } + surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(surfacenormal); + + lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; + lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; + lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; + DPSOFTRAST_Vector3Normalize(lightnormal); + + diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; + if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) + { + // scale down the attenuation to account for the cubefilter multiplying everything by 255 + attenuation *= (1.0f / 255.0f); + d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse)) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse)) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse)) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse)) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse)) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse)) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + else + { + for (x = startx;x < endx;x++) + { + z = buffer_z[x]; + CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; + CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; + CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; + attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); + if (attenuation < 0.01f) + continue; + if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) + { + attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); + if (attenuation < 0.01f) + continue; + } + + diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; + diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; + diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; + diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; + if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) + { + diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; + diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; + diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; + diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; + } + if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) + { + // scale down the attenuation to account for the cubefilter multiplying everything by 255 + attenuation *= (1.0f / 255.0f); + d[0] = (int)((diffusetex[0] * (Color_Ambient[0])) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1])) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2])) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + else + { + d[0] = (int)((diffusetex[0] * (Color_Ambient[0])) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; + d[1] = (int)((diffusetex[1] * (Color_Ambient[1])) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; + d[2] = (int)((diffusetex[2] * (Color_Ambient[2])) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; + d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; + } + buffer_FragColorbgra8[x*4+0] = d[0]; + buffer_FragColorbgra8[x*4+1] = d[1]; + buffer_FragColorbgra8[x*4+2] = d[2]; + buffer_FragColorbgra8[x*4+3] = d[3]; + } + } + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +#endif +} + + + +static void DPSOFTRAST_VertexShader_Refraction(void) +{ + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +static void DPSOFTRAST_PixelShader_Refraction(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + //float z; + int x, startx = span->startx, endx = span->endx; + + // texture reads + unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + + // varyings + float ModelViewProjectionPositiondata[4]; + float ModelViewProjectionPositionslope[4]; + + // uniforms + float ScreenScaleRefractReflect[2]; + float ScreenCenterRefractReflect[2]; + float DistortScaleRefractReflect[2]; + float RefractColor[4]; + + DPSOFTRAST_Texture *texture = thread->texbound[GL20TU_REFRACTION]; + if(!texture) return; + + // read textures + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + + // read varyings + DPSOFTRAST_CALCATTRIB4F(triangle, span, ModelViewProjectionPositiondata, ModelViewProjectionPositionslope, DPSOFTRAST_ARRAY_TEXCOORD4); + + // read uniforms + ScreenScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+0]; + ScreenScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+1]; + ScreenCenterRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+0]; + ScreenCenterRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+1]; + DistortScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+0]; + DistortScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+1]; + RefractColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+2]; + RefractColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+1]; + RefractColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+0]; + RefractColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+3]; + + // do stuff + for (x = startx;x < endx;x++) + { + float SafeScreenTexCoord[2]; + float ScreenTexCoord[2]; + float v[3]; + float iw; + unsigned char c[4]; + + //z = buffer_z[x]; + + // " vec2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n" + iw = 1.0f / (ModelViewProjectionPositiondata[3] + ModelViewProjectionPositionslope[3]*x); // / z + + // " vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n" + SafeScreenTexCoord[0] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[0] + ScreenCenterRefractReflect[0]; // * z (disappears) + SafeScreenTexCoord[1] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[1] + ScreenCenterRefractReflect[1]; // * z (disappears) + + // " vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(myhalf3(dp_texture2D(Texture_Normal, TexCoord)) - myhalf3(0.5))).xy * DistortScaleRefractReflect.zw;\n" + v[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + v[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + v[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(v); + ScreenTexCoord[0] = SafeScreenTexCoord[0] + v[0] * DistortScaleRefractReflect[0]; + ScreenTexCoord[1] = SafeScreenTexCoord[1] + v[1] * DistortScaleRefractReflect[1]; + + // " dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * RefractColor;\n" + DPSOFTRAST_Texture2DBGRA8(texture, 0, ScreenTexCoord[0], ScreenTexCoord[1], c); + + buffer_FragColorbgra8[x*4+0] = c[0] * RefractColor[0]; + buffer_FragColorbgra8[x*4+1] = c[1] * RefractColor[1]; + buffer_FragColorbgra8[x*4+2] = c[2] * RefractColor[2]; + buffer_FragColorbgra8[x*4+3] = min(RefractColor[3] * 256, 255); + } + + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +static void DPSOFTRAST_VertexShader_Water(void) +{ + int i; + int numvertices = dpsoftrast.numvertices; + float EyePosition[4]; + float EyeVectorModelSpace[4]; + float EyeVector[4]; + float position[4]; + float svector[4]; + float tvector[4]; + float normal[4]; + EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; + EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; + EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; + EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); + DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); + for (i = 0;i < numvertices;i++) + { + position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; + position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; + position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; + svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; + svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; + svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; + tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; + tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; + tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; + normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; + normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; + normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; + EyeVectorModelSpace[0] = EyePosition[0] - position[0]; + EyeVectorModelSpace[1] = EyePosition[1] - position[1]; + EyeVectorModelSpace[2] = EyePosition[2] - position[2]; + EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; + EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; + EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+0] = EyeVector[0]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+1] = EyeVector[1]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+2] = EyeVector[2]; + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+3] = 0.0f; + } + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); + DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); +} + + +static void DPSOFTRAST_PixelShader_Water(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + // float z; + int x, startx = span->startx, endx = span->endx; + + // texture reads + unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + + // varyings + float ModelViewProjectionPositiondata[4]; + float ModelViewProjectionPositionslope[4]; + float EyeVectordata[4]; + float EyeVectorslope[4]; + + // uniforms + float ScreenScaleRefractReflect[4]; + float ScreenCenterRefractReflect[4]; + float DistortScaleRefractReflect[4]; + float RefractColor[4]; + float ReflectColor[4]; + float ReflectFactor; + float ReflectOffset; + + DPSOFTRAST_Texture *texture_refraction = thread->texbound[GL20TU_REFRACTION]; + DPSOFTRAST_Texture *texture_reflection = thread->texbound[GL20TU_REFLECTION]; + if(!texture_refraction || !texture_reflection) return; + + // read textures + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); + + // read varyings + DPSOFTRAST_CALCATTRIB4F(triangle, span, ModelViewProjectionPositiondata, ModelViewProjectionPositionslope, DPSOFTRAST_ARRAY_TEXCOORD4); + DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); + + // read uniforms + ScreenScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+0]; + ScreenScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+1]; + ScreenScaleRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+2]; + ScreenScaleRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+3]; + ScreenCenterRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+0]; + ScreenCenterRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+1]; + ScreenCenterRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+2]; + ScreenCenterRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+3]; + DistortScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+0]; + DistortScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+1]; + DistortScaleRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+2]; + DistortScaleRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+3]; + RefractColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+2]; + RefractColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+1]; + RefractColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+0]; + RefractColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+3]; + ReflectColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+2]; + ReflectColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+1]; + ReflectColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+0]; + ReflectColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+3]; + ReflectFactor = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectFactor*4+0]; + ReflectOffset = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectOffset*4+0]; + + // do stuff + for (x = startx;x < endx;x++) + { + float SafeScreenTexCoord[4]; + float ScreenTexCoord[4]; + float v[3]; + float iw; + unsigned char c1[4]; + unsigned char c2[4]; + float Fresnel; + + // z = buffer_z[x]; + + // " vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n" + iw = 1.0f / (ModelViewProjectionPositiondata[3] + ModelViewProjectionPositionslope[3]*x); // / z + + // " vec4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" + SafeScreenTexCoord[0] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[0] + ScreenCenterRefractReflect[0]; // * z (disappears) + SafeScreenTexCoord[1] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[1] + ScreenCenterRefractReflect[1]; // * z (disappears) + SafeScreenTexCoord[2] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[2] + ScreenCenterRefractReflect[2]; // * z (disappears) + SafeScreenTexCoord[3] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[3] + ScreenCenterRefractReflect[3]; // * z (disappears) + + // " vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5))).xyxy * DistortScaleRefractReflect;\n" + v[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; + v[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; + v[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; + DPSOFTRAST_Vector3Normalize(v); + ScreenTexCoord[0] = SafeScreenTexCoord[0] + v[0] * DistortScaleRefractReflect[0]; + ScreenTexCoord[1] = SafeScreenTexCoord[1] + v[1] * DistortScaleRefractReflect[1]; + ScreenTexCoord[2] = SafeScreenTexCoord[2] + v[0] * DistortScaleRefractReflect[2]; + ScreenTexCoord[3] = SafeScreenTexCoord[3] + v[1] * DistortScaleRefractReflect[3]; + + // " float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * ReflectFactor + ReflectOffset;\n" + v[0] = (EyeVectordata[0] + EyeVectorslope[0] * x); // * z (disappears) + v[1] = (EyeVectordata[1] + EyeVectorslope[1] * x); // * z (disappears) + v[2] = (EyeVectordata[2] + EyeVectorslope[2] * x); // * z (disappears) + DPSOFTRAST_Vector3Normalize(v); + Fresnel = 1.0f - v[2]; + Fresnel = min(1.0f, Fresnel); + Fresnel = Fresnel * Fresnel * ReflectFactor + ReflectOffset; + + // " dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * RefractColor;\n" + // " dp_FragColor = mix(vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * RefractColor, vec4(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n" + DPSOFTRAST_Texture2DBGRA8(texture_refraction, 0, ScreenTexCoord[0], ScreenTexCoord[1], c1); + DPSOFTRAST_Texture2DBGRA8(texture_reflection, 0, ScreenTexCoord[2], ScreenTexCoord[3], c2); + + buffer_FragColorbgra8[x*4+0] = (c1[0] * RefractColor[0]) * (1.0f - Fresnel) + (c2[0] * ReflectColor[0]) * Fresnel; + buffer_FragColorbgra8[x*4+1] = (c1[1] * RefractColor[1]) * (1.0f - Fresnel) + (c2[1] * ReflectColor[1]) * Fresnel; + buffer_FragColorbgra8[x*4+2] = (c1[2] * RefractColor[2]) * (1.0f - Fresnel) + (c2[2] * ReflectColor[2]) * Fresnel; + buffer_FragColorbgra8[x*4+3] = min(( RefractColor[3] * (1.0f - Fresnel) + ReflectColor[3] * Fresnel) * 256, 255); + } + + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +static void DPSOFTRAST_VertexShader_DeferredGeometry(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +static void DPSOFTRAST_PixelShader_DeferredGeometry(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // TODO: IMPLEMENT + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +static void DPSOFTRAST_VertexShader_DeferredLightSource(void) +{ + DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); +} + +static void DPSOFTRAST_PixelShader_DeferredLightSource(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) +{ + // TODO: IMPLEMENT + float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; + unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; + DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); + memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); + DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); +} + + + +typedef struct DPSOFTRAST_ShaderModeInfo_s +{ + int lodarrayindex; + void (*Vertex)(void); + void (*Span)(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span); + unsigned char arrays[DPSOFTRAST_ARRAY_TOTAL]; + unsigned char texunits[DPSOFTRAST_MAXTEXTUREUNITS]; +} +DPSOFTRAST_ShaderModeInfo; + +static const DPSOFTRAST_ShaderModeInfo DPSOFTRAST_ShaderModeTable[SHADERMODE_COUNT] = +{ + {2, DPSOFTRAST_VertexShader_Generic, DPSOFTRAST_PixelShader_Generic, {DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, ~0}, {GL20TU_FIRST, GL20TU_SECOND, ~0}}, + {2, DPSOFTRAST_VertexShader_PostProcess, DPSOFTRAST_PixelShader_PostProcess, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, ~0}, {GL20TU_FIRST, GL20TU_SECOND, ~0}}, + {2, DPSOFTRAST_VertexShader_Depth_Or_Shadow, DPSOFTRAST_PixelShader_Depth_Or_Shadow, {~0}, {~0}}, + {2, DPSOFTRAST_VertexShader_FlatColor, DPSOFTRAST_PixelShader_FlatColor, {DPSOFTRAST_ARRAY_TEXCOORD0, ~0}, {GL20TU_COLOR, ~0}}, + {2, DPSOFTRAST_VertexShader_VertexColor, DPSOFTRAST_PixelShader_VertexColor, {DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, ~0}, {GL20TU_COLOR, ~0}}, + {2, DPSOFTRAST_VertexShader_Lightmap, DPSOFTRAST_PixelShader_Lightmap, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD4, ~0}, {GL20TU_COLOR, GL20TU_LIGHTMAP, GL20TU_GLOW, ~0}}, + {2, DPSOFTRAST_VertexShader_FakeLight, DPSOFTRAST_PixelShader_FakeLight, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, ~0}}, + {2, DPSOFTRAST_VertexShader_LightDirectionMap_ModelSpace, DPSOFTRAST_PixelShader_LightDirectionMap_ModelSpace, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_LIGHTMAP, GL20TU_DELUXEMAP, ~0}}, + {2, DPSOFTRAST_VertexShader_LightDirectionMap_TangentSpace, DPSOFTRAST_PixelShader_LightDirectionMap_TangentSpace, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_LIGHTMAP, GL20TU_DELUXEMAP, ~0}}, + {2, DPSOFTRAST_VertexShader_Lightmap, DPSOFTRAST_PixelShader_Lightmap, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD4, ~0}, {GL20TU_COLOR, GL20TU_LIGHTMAP, GL20TU_GLOW, ~0}}, + {2, DPSOFTRAST_VertexShader_VertexColor, DPSOFTRAST_PixelShader_VertexColor, {DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, ~0}, {GL20TU_COLOR, ~0}}, + {2, DPSOFTRAST_VertexShader_LightDirection, DPSOFTRAST_PixelShader_LightDirection, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, ~0}}, + {2, DPSOFTRAST_VertexShader_LightSource, DPSOFTRAST_PixelShader_LightSource, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, ~0}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_CUBE, ~0}}, + {2, DPSOFTRAST_VertexShader_Refraction, DPSOFTRAST_PixelShader_Refraction, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD4, ~0}, {GL20TU_NORMAL, GL20TU_REFRACTION, ~0}}, + {2, DPSOFTRAST_VertexShader_Water, DPSOFTRAST_PixelShader_Water, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD6, ~0}, {GL20TU_NORMAL, GL20TU_REFLECTION, GL20TU_REFRACTION, ~0}}, + {2, DPSOFTRAST_VertexShader_DeferredGeometry, DPSOFTRAST_PixelShader_DeferredGeometry, {~0}}, + {2, DPSOFTRAST_VertexShader_DeferredLightSource, DPSOFTRAST_PixelShader_DeferredLightSource, {~0}}, +}; + +static void DPSOFTRAST_Draw_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_State_Span *span) +{ + int x; + int startx; + int endx; + unsigned int *depthpixel; + int depth; + int depthslope; + unsigned int d; + unsigned char *pixelmask; + depthpixel = dpsoftrast.fb_depthpixels + span->y * dpsoftrast.fb_width + span->x; + startx = span->startx; + endx = span->endx; + depth = span->depthbase; + depthslope = span->depthslope; + pixelmask = thread->pixelmaskarray; + if (thread->depthtest && dpsoftrast.fb_depthpixels) + { + switch(thread->fb_depthfunc) + { + default: + case GL_ALWAYS: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = true; break; + case GL_LESS: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] < d; break; + case GL_LEQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] <= d; break; + case GL_EQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] == d; break; + case GL_GEQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] >= d; break; + case GL_GREATER: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] > d; break; + case GL_NEVER: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = false; break; + } + while (startx < endx && !pixelmask[startx]) + startx++; + while (endx > startx && !pixelmask[endx-1]) + endx--; + } + else + { + // no depth testing means we're just dealing with color... + memset(pixelmask + startx, 1, endx - startx); + } + span->pixelmask = pixelmask; + span->startx = startx; + span->endx = endx; +} + +static void DPSOFTRAST_Draw_DepthWrite(const DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Span *span) +{ + int x, d, depth, depthslope, startx, endx; + const unsigned char *pixelmask; + unsigned int *depthpixel; + if (thread->depthmask && thread->depthtest && dpsoftrast.fb_depthpixels) + { + depth = span->depthbase; + depthslope = span->depthslope; + pixelmask = span->pixelmask; + startx = span->startx; + endx = span->endx; + depthpixel = dpsoftrast.fb_depthpixels + span->y * dpsoftrast.fb_width + span->x; + for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) + if (pixelmask[x]) + depthpixel[x] = d; + } +} + +static void DPSOFTRAST_Draw_ProcessSpans(DPSOFTRAST_State_Thread *thread) +{ + int i; + DPSOFTRAST_State_Triangle *triangle; + DPSOFTRAST_State_Span *span; + for (i = 0; i < thread->numspans; i++) + { + span = &thread->spans[i]; + triangle = &thread->triangles[span->triangle]; + DPSOFTRAST_Draw_DepthTest(thread, span); + if (span->startx >= span->endx) + continue; + // run pixel shader if appropriate + // do this before running depthmask code, to allow the pixelshader + // to clear pixelmask values for alpha testing + if (dpsoftrast.fb_colorpixels[0] && thread->fb_colormask) + DPSOFTRAST_ShaderModeTable[thread->shader_mode].Span(thread, triangle, span); + DPSOFTRAST_Draw_DepthWrite(thread, span); + } + thread->numspans = 0; +} + +DEFCOMMAND(22, Draw, int datasize; int starty; int endy; ATOMIC_COUNTER refcount; int clipped; int firstvertex; int numvertices; int numtriangles; float *arrays; int *element3i; unsigned short *element3s;) + +static void DPSOFTRAST_Interpret_Draw(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Draw *command) +{ +#ifdef SSE_POSSIBLE + int cullface = thread->cullface; + int minx, maxx, miny, maxy; + int miny1, maxy1, miny2, maxy2; + __m128i fbmin, fbmax; + __m128 viewportcenter, viewportscale; + int firstvertex = command->firstvertex; + int numvertices = command->numvertices; + int numtriangles = command->numtriangles; + const int *element3i = command->element3i; + const unsigned short *element3s = command->element3s; + int clipped = command->clipped; + int i; + int j; + int k; + int y; + int e[3]; + __m128i screeny; + int starty, endy, bandy; + int numpoints; + int clipcase; + float clipdist[4]; + float clip0origin, clip0slope; + int clip0dir; + __m128 triangleedge1, triangleedge2, trianglenormal; + __m128 clipfrac[3]; + __m128 screen[4]; + DPSOFTRAST_State_Triangle *triangle; + DPSOFTRAST_Texture *texture; + DPSOFTRAST_ValidateQuick(thread, DPSOFTRAST_VALIDATE_DRAW); + miny = thread->fb_scissor[1]; + maxy = thread->fb_scissor[1] + thread->fb_scissor[3]; + miny1 = bound(miny, thread->miny1, maxy); + maxy1 = bound(miny, thread->maxy1, maxy); + miny2 = bound(miny, thread->miny2, maxy); + maxy2 = bound(miny, thread->maxy2, maxy); + if ((command->starty >= maxy1 || command->endy <= miny1) && (command->starty >= maxy2 || command->endy <= miny2)) + { + if (!ATOMIC_DECREMENT(command->refcount)) + { + if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) + MM_FREE(command->arrays); + } + return; + } + minx = thread->fb_scissor[0]; + maxx = thread->fb_scissor[0] + thread->fb_scissor[2]; + fbmin = _mm_setr_epi16(minx, miny1, minx, miny1, minx, miny1, minx, miny1); + fbmax = _mm_sub_epi16(_mm_setr_epi16(maxx, maxy2, maxx, maxy2, maxx, maxy2, maxx, maxy2), _mm_set1_epi16(1)); + viewportcenter = _mm_load_ps(thread->fb_viewportcenter); + viewportscale = _mm_load_ps(thread->fb_viewportscale); + screen[3] = _mm_setzero_ps(); + clipfrac[0] = clipfrac[1] = clipfrac[2] = _mm_setzero_ps(); + for (i = 0;i < numtriangles;i++) + { + const float *screencoord4f = command->arrays; + const float *arrays = screencoord4f + numvertices*4; + + // generate the 3 edges of this triangle + // generate spans for the triangle - switch based on left split or right split classification of triangle + if (element3s) + { + e[0] = element3s[i*3+0] - firstvertex; + e[1] = element3s[i*3+1] - firstvertex; + e[2] = element3s[i*3+2] - firstvertex; + } + else if (element3i) + { + e[0] = element3i[i*3+0] - firstvertex; + e[1] = element3i[i*3+1] - firstvertex; + e[2] = element3i[i*3+2] - firstvertex; + } + else + { + e[0] = i*3+0; + e[1] = i*3+1; + e[2] = i*3+2; + } + +#define SKIPBACKFACE \ + triangleedge1 = _mm_sub_ps(screen[0], screen[1]); \ + triangleedge2 = _mm_sub_ps(screen[2], screen[1]); \ + /* store normal in 2, 0, 1 order instead of 0, 1, 2 as it requires fewer shuffles and leaves z component accessible as scalar */ \ + trianglenormal = _mm_sub_ss(_mm_mul_ss(triangleedge1, _mm_shuffle_ps(triangleedge2, triangleedge2, _MM_SHUFFLE(3, 0, 2, 1))), \ + _mm_mul_ss(_mm_shuffle_ps(triangleedge1, triangleedge1, _MM_SHUFFLE(3, 0, 2, 1)), triangleedge2)); \ + switch(cullface) \ + { \ + case GL_BACK: \ + if (_mm_ucomilt_ss(trianglenormal, _mm_setzero_ps())) \ + continue; \ + break; \ + case GL_FRONT: \ + if (_mm_ucomigt_ss(trianglenormal, _mm_setzero_ps())) \ + continue; \ + break; \ + } + +#define CLIPPEDVERTEXLERP(k,p1, p2) \ + clipfrac[p1] = _mm_set1_ps(clipdist[p1] / (clipdist[p1] - clipdist[p2])); \ + { \ + __m128 v1 = _mm_load_ps(&arrays[e[p1]*4]), v2 = _mm_load_ps(&arrays[e[p2]*4]); \ + DPSOFTRAST_PROJECTVERTEX(screen[k], _mm_add_ps(v1, _mm_mul_ps(_mm_sub_ps(v2, v1), clipfrac[p1])), viewportcenter, viewportscale); \ + } +#define CLIPPEDVERTEXCOPY(k,p1) \ + screen[k] = _mm_load_ps(&screencoord4f[e[p1]*4]); + +#define GENATTRIBCOPY(attrib, p1) \ + attrib = _mm_load_ps(&arrays[e[p1]*4]); +#define GENATTRIBLERP(attrib, p1, p2) \ + { \ + __m128 v1 = _mm_load_ps(&arrays[e[p1]*4]), v2 = _mm_load_ps(&arrays[e[p2]*4]); \ + attrib = _mm_add_ps(v1, _mm_mul_ps(_mm_sub_ps(v2, v1), clipfrac[p1])); \ + } +#define GENATTRIBS(attrib0, attrib1, attrib2) \ + switch(clipcase) \ + { \ + default: \ + case 0: GENATTRIBCOPY(attrib0, 0); GENATTRIBCOPY(attrib1, 1); GENATTRIBCOPY(attrib2, 2); break; \ + case 1: GENATTRIBCOPY(attrib0, 0); GENATTRIBCOPY(attrib1, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ + case 2: GENATTRIBCOPY(attrib0, 0); GENATTRIBLERP(attrib1, 0, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ + case 3: GENATTRIBCOPY(attrib0, 0); GENATTRIBLERP(attrib1, 0, 1); GENATTRIBLERP(attrib2, 2, 0); break; \ + case 4: GENATTRIBLERP(attrib0, 0, 1); GENATTRIBCOPY(attrib1, 1); GENATTRIBCOPY(attrib2, 2); break; \ + case 5: GENATTRIBLERP(attrib0, 0, 1); GENATTRIBCOPY(attrib1, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ + case 6: GENATTRIBLERP(attrib0, 1, 2); GENATTRIBCOPY(attrib1, 2); GENATTRIBLERP(attrib2, 2, 0); break; \ + } + + if (! clipped) + goto notclipped; + + // calculate distance from nearplane + clipdist[0] = arrays[e[0]*4+2] + arrays[e[0]*4+3]; + clipdist[1] = arrays[e[1]*4+2] + arrays[e[1]*4+3]; + clipdist[2] = arrays[e[2]*4+2] + arrays[e[2]*4+3]; + if (clipdist[0] >= 0.0f) + { + if (clipdist[1] >= 0.0f) + { + if (clipdist[2] >= 0.0f) + { + notclipped: + // triangle is entirely in front of nearplane + CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXCOPY(2,2); + SKIPBACKFACE; + numpoints = 3; + clipcase = 0; + } + else + { + CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXLERP(2,1,2); CLIPPEDVERTEXLERP(3,2,0); + SKIPBACKFACE; + numpoints = 4; + clipcase = 1; + } + } + else + { + if (clipdist[2] >= 0.0f) + { + CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXLERP(1,0,1); CLIPPEDVERTEXLERP(2,1,2); CLIPPEDVERTEXCOPY(3,2); + SKIPBACKFACE; + numpoints = 4; + clipcase = 2; + } + else + { + CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXLERP(1,0,1); CLIPPEDVERTEXLERP(2,2,0); + SKIPBACKFACE; + numpoints = 3; + clipcase = 3; + } + } + } + else if (clipdist[1] >= 0.0f) + { + if (clipdist[2] >= 0.0f) + { + CLIPPEDVERTEXLERP(0,0,1); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXCOPY(2,2); CLIPPEDVERTEXLERP(3,2,0); + SKIPBACKFACE; + numpoints = 4; + clipcase = 4; + } + else + { + CLIPPEDVERTEXLERP(0,0,1); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXLERP(2,1,2); + SKIPBACKFACE; + numpoints = 3; + clipcase = 5; + } + } + else if (clipdist[2] >= 0.0f) + { + CLIPPEDVERTEXLERP(0,1,2); CLIPPEDVERTEXCOPY(1,2); CLIPPEDVERTEXLERP(2,2,0); + SKIPBACKFACE; + numpoints = 3; + clipcase = 6; + } + else continue; // triangle is entirely behind nearplane + + { + // calculate integer y coords for triangle points + __m128i screeni = _mm_packs_epi32(_mm_cvttps_epi32(_mm_movelh_ps(screen[0], screen[1])), _mm_cvttps_epi32(_mm_movelh_ps(screen[2], numpoints > 3 ? screen[3] : screen[2]))), + screenir = _mm_shuffle_epi32(screeni, _MM_SHUFFLE(1, 0, 3, 2)), + screenmin = _mm_min_epi16(screeni, screenir), + screenmax = _mm_max_epi16(screeni, screenir); + screenmin = _mm_min_epi16(screenmin, _mm_shufflelo_epi16(screenmin, _MM_SHUFFLE(1, 0, 3, 2))); + screenmax = _mm_max_epi16(screenmax, _mm_shufflelo_epi16(screenmax, _MM_SHUFFLE(1, 0, 3, 2))); + screenmin = _mm_max_epi16(screenmin, fbmin); + screenmax = _mm_min_epi16(screenmax, fbmax); + // skip offscreen triangles + if (_mm_cvtsi128_si32(_mm_cmplt_epi16(screenmax, screenmin))) + continue; + starty = _mm_extract_epi16(screenmin, 1); + endy = _mm_extract_epi16(screenmax, 1)+1; + if (starty >= maxy1 && endy <= miny2) + continue; + screeny = _mm_srai_epi32(screeni, 16); + } + + triangle = &thread->triangles[thread->numtriangles]; + + // calculate attribute plans for triangle data... + // okay, this triangle is going to produce spans, we'd better project + // the interpolants now (this is what gives perspective texturing), + // this consists of simply multiplying all arrays by the W coord + // (which is basically 1/Z), which will be undone per-pixel + // (multiplying by Z again) to get the perspective-correct array + // values + { + __m128 attribuvslope, attribuxslope, attribuyslope, attribvxslope, attribvyslope, attriborigin, attribedge1, attribedge2, attribxslope, attribyslope, w0, w1, w2, x1, y1; + __m128 mipedgescale, mipdensity; + attribuvslope = _mm_div_ps(_mm_movelh_ps(triangleedge1, triangleedge2), _mm_shuffle_ps(trianglenormal, trianglenormal, _MM_SHUFFLE(0, 0, 0, 0))); + attribuxslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(3, 3, 3, 3)); + attribuyslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(2, 2, 2, 2)); + attribvxslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(1, 1, 1, 1)); + attribvyslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(0, 0, 0, 0)); + w0 = _mm_shuffle_ps(screen[0], screen[0], _MM_SHUFFLE(3, 3, 3, 3)); + w1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(3, 3, 3, 3)); + w2 = _mm_shuffle_ps(screen[2], screen[2], _MM_SHUFFLE(3, 3, 3, 3)); + attribedge1 = _mm_sub_ss(w0, w1); + attribedge2 = _mm_sub_ss(w2, w1); + attribxslope = _mm_sub_ss(_mm_mul_ss(attribuxslope, attribedge1), _mm_mul_ss(attribvxslope, attribedge2)); + attribyslope = _mm_sub_ss(_mm_mul_ss(attribvyslope, attribedge2), _mm_mul_ss(attribuyslope, attribedge1)); + x1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(0, 0, 0, 0)); + y1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(1, 1, 1, 1)); + attriborigin = _mm_sub_ss(w1, _mm_add_ss(_mm_mul_ss(attribxslope, x1), _mm_mul_ss(attribyslope, y1))); + _mm_store_ss(&triangle->w[0], attribxslope); + _mm_store_ss(&triangle->w[1], attribyslope); + _mm_store_ss(&triangle->w[2], attriborigin); + + clip0origin = 0; + clip0slope = 0; + clip0dir = 0; + if(thread->fb_clipplane[0] || thread->fb_clipplane[1] || thread->fb_clipplane[2]) + { + float cliporigin, clipxslope, clipyslope; + attriborigin = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(2, 2, 2, 2)); + attribedge1 = _mm_sub_ss(_mm_shuffle_ps(screen[0], screen[0], _MM_SHUFFLE(2, 2, 2, 2)), attriborigin); + attribedge2 = _mm_sub_ss(_mm_shuffle_ps(screen[2], screen[2], _MM_SHUFFLE(2, 2, 2, 2)), attriborigin); + attribxslope = _mm_sub_ss(_mm_mul_ss(attribuxslope, attribedge1), _mm_mul_ss(attribvxslope, attribedge2)); + attribyslope = _mm_sub_ss(_mm_mul_ss(attribvyslope, attribedge2), _mm_mul_ss(attribuyslope, attribedge1)); + attriborigin = _mm_sub_ss(attriborigin, _mm_add_ss(_mm_mul_ss(attribxslope, x1), _mm_mul_ss(attribyslope, y1))); + cliporigin = _mm_cvtss_f32(attriborigin)*thread->fb_clipplane[2] + thread->fb_clipplane[3]; + clipxslope = thread->fb_clipplane[0] + _mm_cvtss_f32(attribxslope)*thread->fb_clipplane[2]; + clipyslope = thread->fb_clipplane[1] + _mm_cvtss_f32(attribyslope)*thread->fb_clipplane[2]; + if(clipxslope != 0) + { + clip0origin = -cliporigin/clipxslope; + clip0slope = -clipyslope/clipxslope; + clip0dir = clipxslope > 0 ? 1 : -1; + } + else if(clipyslope > 0) + { + clip0origin = dpsoftrast.fb_width*floor(cliporigin/clipyslope); + clip0slope = dpsoftrast.fb_width; + clip0dir = -1; + } + else if(clipyslope < 0) + { + clip0origin = dpsoftrast.fb_width*ceil(cliporigin/clipyslope); + clip0slope = -dpsoftrast.fb_width; + clip0dir = -1; + } + else if(clip0origin < 0) continue; + } + + mipedgescale = _mm_setzero_ps(); + for (j = 0;j < DPSOFTRAST_ARRAY_TOTAL; j++) + { + __m128 attrib0, attrib1, attrib2; + k = DPSOFTRAST_ShaderModeTable[thread->shader_mode].arrays[j]; + if (k >= DPSOFTRAST_ARRAY_TOTAL) + break; + arrays += numvertices*4; + GENATTRIBS(attrib0, attrib1, attrib2); + attriborigin = _mm_mul_ps(attrib1, w1); + attribedge1 = _mm_sub_ps(_mm_mul_ps(attrib0, w0), attriborigin); + attribedge2 = _mm_sub_ps(_mm_mul_ps(attrib2, w2), attriborigin); + attribxslope = _mm_sub_ps(_mm_mul_ps(attribuxslope, attribedge1), _mm_mul_ps(attribvxslope, attribedge2)); + attribyslope = _mm_sub_ps(_mm_mul_ps(attribvyslope, attribedge2), _mm_mul_ps(attribuyslope, attribedge1)); + attriborigin = _mm_sub_ps(attriborigin, _mm_add_ps(_mm_mul_ps(attribxslope, x1), _mm_mul_ps(attribyslope, y1))); + _mm_storeu_ps(triangle->attribs[k][0], attribxslope); + _mm_storeu_ps(triangle->attribs[k][1], attribyslope); + _mm_storeu_ps(triangle->attribs[k][2], attriborigin); + if (k == DPSOFTRAST_ShaderModeTable[thread->shader_mode].lodarrayindex) + { + mipedgescale = _mm_movelh_ps(triangleedge1, triangleedge2); + mipedgescale = _mm_mul_ps(mipedgescale, mipedgescale); + mipedgescale = _mm_rsqrt_ps(_mm_add_ps(mipedgescale, _mm_shuffle_ps(mipedgescale, mipedgescale, _MM_SHUFFLE(2, 3, 0, 1)))); + mipedgescale = _mm_mul_ps(_mm_sub_ps(_mm_movelh_ps(attrib0, attrib2), _mm_movelh_ps(attrib1, attrib1)), mipedgescale); + } + } + + memset(triangle->mip, 0, sizeof(triangle->mip)); + for (j = 0;j < DPSOFTRAST_MAXTEXTUREUNITS;j++) + { + int texunit = DPSOFTRAST_ShaderModeTable[thread->shader_mode].texunits[j]; + if (texunit >= DPSOFTRAST_MAXTEXTUREUNITS) + break; + texture = thread->texbound[texunit]; + if (texture && texture->filter > DPSOFTRAST_TEXTURE_FILTER_LINEAR) + { + mipdensity = _mm_mul_ps(mipedgescale, _mm_cvtepi32_ps(_mm_shuffle_epi32(_mm_loadl_epi64((const __m128i *)&texture->mipmap[0][2]), _MM_SHUFFLE(1, 0, 1, 0)))); + mipdensity = _mm_mul_ps(mipdensity, mipdensity); + mipdensity = _mm_add_ps(mipdensity, _mm_shuffle_ps(mipdensity, mipdensity, _MM_SHUFFLE(2, 3, 0, 1))); + mipdensity = _mm_min_ss(mipdensity, _mm_shuffle_ps(mipdensity, mipdensity, _MM_SHUFFLE(2, 2, 2, 2))); + // this will be multiplied in the texturing routine by the texture resolution + y = _mm_cvtss_si32(mipdensity); + if (y > 0) + { + y = (int)(log((float)y)*0.5f/M_LN2); + if (y > texture->mipmaps - 1) + y = texture->mipmaps - 1; + triangle->mip[texunit] = y; + } + } + } + } + + for (y = starty, bandy = min(endy, maxy1); y < endy; bandy = min(endy, maxy2), y = max(y, miny2)) + for (; y < bandy;) + { + __m128 xcoords, xslope; + __m128i ycc = _mm_cmpgt_epi32(_mm_set1_epi32(y), screeny); + int yccmask = _mm_movemask_epi8(ycc); + int edge0p, edge0n, edge1p, edge1n; + int nexty; + float w, wslope; + float clip0; + if (numpoints == 4) + { + switch(yccmask) + { + default: + case 0xFFFF: /*0000*/ y = endy; continue; + case 0xFFF0: /*1000*/ edge0p = 3;edge0n = 0;edge1p = 1;edge1n = 0;break; + case 0xFF0F: /*0100*/ edge0p = 0;edge0n = 1;edge1p = 2;edge1n = 1;break; + case 0xFF00: /*1100*/ edge0p = 3;edge0n = 0;edge1p = 2;edge1n = 1;break; + case 0xF0FF: /*0010*/ edge0p = 1;edge0n = 2;edge1p = 3;edge1n = 2;break; + case 0xF0F0: /*1010*/ edge0p = 1;edge0n = 2;edge1p = 3;edge1n = 2;break; // concave - nonsense + case 0xF00F: /*0110*/ edge0p = 0;edge0n = 1;edge1p = 3;edge1n = 2;break; + case 0xF000: /*1110*/ edge0p = 3;edge0n = 0;edge1p = 3;edge1n = 2;break; + case 0x0FFF: /*0001*/ edge0p = 2;edge0n = 3;edge1p = 0;edge1n = 3;break; + case 0x0FF0: /*1001*/ edge0p = 2;edge0n = 3;edge1p = 1;edge1n = 0;break; + case 0x0F0F: /*0101*/ edge0p = 2;edge0n = 3;edge1p = 2;edge1n = 1;break; // concave - nonsense + case 0x0F00: /*1101*/ edge0p = 2;edge0n = 3;edge1p = 2;edge1n = 1;break; + case 0x00FF: /*0011*/ edge0p = 1;edge0n = 2;edge1p = 0;edge1n = 3;break; + case 0x00F0: /*1011*/ edge0p = 1;edge0n = 2;edge1p = 1;edge1n = 0;break; + case 0x000F: /*0111*/ edge0p = 0;edge0n = 1;edge1p = 0;edge1n = 3;break; + case 0x0000: /*1111*/ y++; continue; + } + } + else + { + switch(yccmask) + { + default: + case 0xFFFF: /*000*/ y = endy; continue; + case 0xFFF0: /*100*/ edge0p = 2;edge0n = 0;edge1p = 1;edge1n = 0;break; + case 0xFF0F: /*010*/ edge0p = 0;edge0n = 1;edge1p = 2;edge1n = 1;break; + case 0xFF00: /*110*/ edge0p = 2;edge0n = 0;edge1p = 2;edge1n = 1;break; + case 0x00FF: /*001*/ edge0p = 1;edge0n = 2;edge1p = 0;edge1n = 2;break; + case 0x00F0: /*101*/ edge0p = 1;edge0n = 2;edge1p = 1;edge1n = 0;break; + case 0x000F: /*011*/ edge0p = 0;edge0n = 1;edge1p = 0;edge1n = 2;break; + case 0x0000: /*111*/ y++; continue; + } + } + ycc = _mm_max_epi16(_mm_srli_epi16(ycc, 1), screeny); + ycc = _mm_min_epi16(ycc, _mm_shuffle_epi32(ycc, _MM_SHUFFLE(1, 0, 3, 2))); + ycc = _mm_min_epi16(ycc, _mm_shuffle_epi32(ycc, _MM_SHUFFLE(2, 3, 0, 1))); + nexty = _mm_extract_epi16(ycc, 0); + if (nexty >= bandy) nexty = bandy-1; + xslope = _mm_sub_ps(_mm_movelh_ps(screen[edge0n], screen[edge1n]), _mm_movelh_ps(screen[edge0p], screen[edge1p])); + xslope = _mm_div_ps(xslope, _mm_shuffle_ps(xslope, xslope, _MM_SHUFFLE(3, 3, 1, 1))); + xcoords = _mm_add_ps(_mm_movelh_ps(screen[edge0p], screen[edge1p]), + _mm_mul_ps(xslope, _mm_sub_ps(_mm_set1_ps(y), _mm_shuffle_ps(screen[edge0p], screen[edge1p], _MM_SHUFFLE(1, 1, 1, 1))))); + xcoords = _mm_add_ps(xcoords, _mm_set1_ps(0.5f)); + if (_mm_ucomigt_ss(xcoords, _mm_shuffle_ps(xcoords, xcoords, _MM_SHUFFLE(1, 0, 3, 2)))) + { + xcoords = _mm_shuffle_ps(xcoords, xcoords, _MM_SHUFFLE(1, 0, 3, 2)); + xslope = _mm_shuffle_ps(xslope, xslope, _MM_SHUFFLE(1, 0, 3, 2)); + } + clip0 = clip0origin + (y+0.5f)*clip0slope + 0.5f; + for(; y <= nexty; y++, xcoords = _mm_add_ps(xcoords, xslope), clip0 += clip0slope) + { + int startx, endx, offset; + startx = _mm_cvtss_si32(xcoords); + endx = _mm_cvtss_si32(_mm_movehl_ps(xcoords, xcoords)); + if (startx < minx) startx = minx; + if (endx > maxx) endx = maxx; + if (startx >= endx) continue; + + if (clip0dir) + { + if (clip0dir > 0) + { + if (startx < clip0) + { + if(endx <= clip0) continue; + startx = (int)clip0; + } + } + else if (endx > clip0) + { + if(startx >= clip0) continue; + endx = (int)clip0; + } + } + + for (offset = startx; offset < endx;offset += DPSOFTRAST_DRAW_MAXSPANLENGTH) + { + DPSOFTRAST_State_Span *span = &thread->spans[thread->numspans]; + span->triangle = thread->numtriangles; + span->x = offset; + span->y = y; + span->startx = 0; + span->endx = min(endx - offset, DPSOFTRAST_DRAW_MAXSPANLENGTH); + if (span->startx >= span->endx) + continue; + wslope = triangle->w[0]; + w = triangle->w[2] + span->x*wslope + span->y*triangle->w[1]; + span->depthslope = (int)(wslope*DPSOFTRAST_DEPTHSCALE); + span->depthbase = (int)(w*DPSOFTRAST_DEPTHSCALE - DPSOFTRAST_DEPTHOFFSET*(thread->polygonoffset[1] + fabs(wslope)*thread->polygonoffset[0])); + if (++thread->numspans >= DPSOFTRAST_DRAW_MAXSPANS) + DPSOFTRAST_Draw_ProcessSpans(thread); + } + } + } + + if (++thread->numtriangles >= DPSOFTRAST_DRAW_MAXTRIANGLES) + { + DPSOFTRAST_Draw_ProcessSpans(thread); + thread->numtriangles = 0; + } + } + + if (!ATOMIC_DECREMENT(command->refcount)) + { + if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) + MM_FREE(command->arrays); + } + + if (thread->numspans > 0 || thread->numtriangles > 0) + { + DPSOFTRAST_Draw_ProcessSpans(thread); + thread->numtriangles = 0; + } +#endif +} + +static DPSOFTRAST_Command_Draw *DPSOFTRAST_Draw_AllocateDrawCommand(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s) +{ + int i; + int j; + int commandsize = DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw)); + int datasize = 2*numvertices*sizeof(float[4]); + DPSOFTRAST_Command_Draw *command; + unsigned char *data; + for (i = 0; i < DPSOFTRAST_ARRAY_TOTAL; i++) + { + j = DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].arrays[i]; + if (j >= DPSOFTRAST_ARRAY_TOTAL) + break; + datasize += numvertices*sizeof(float[4]); + } + if (element3s) + datasize += numtriangles*sizeof(unsigned short[3]); + else if (element3i) + datasize += numtriangles*sizeof(int[3]); + datasize = DPSOFTRAST_ALIGNCOMMAND(datasize); + if (commandsize + datasize > DPSOFTRAST_DRAW_MAXCOMMANDSIZE) + { + command = (DPSOFTRAST_Command_Draw *) DPSOFTRAST_AllocateCommand(DPSOFTRAST_OPCODE_Draw, commandsize); + data = (unsigned char *)MM_CALLOC(datasize, 1); + } + else + { + command = (DPSOFTRAST_Command_Draw *) DPSOFTRAST_AllocateCommand(DPSOFTRAST_OPCODE_Draw, commandsize + datasize); + data = (unsigned char *)command + commandsize; + } + command->firstvertex = firstvertex; + command->numvertices = numvertices; + command->numtriangles = numtriangles; + command->arrays = (float *)data; + memset(dpsoftrast.post_array4f, 0, sizeof(dpsoftrast.post_array4f)); + dpsoftrast.firstvertex = firstvertex; + dpsoftrast.numvertices = numvertices; + dpsoftrast.screencoord4f = (float *)data; + data += numvertices*sizeof(float[4]); + dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION] = (float *)data; + data += numvertices*sizeof(float[4]); + for (i = 0; i < DPSOFTRAST_ARRAY_TOTAL; i++) + { + j = DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].arrays[i]; + if (j >= DPSOFTRAST_ARRAY_TOTAL) + break; + dpsoftrast.post_array4f[j] = (float *)data; + data += numvertices*sizeof(float[4]); + } + command->element3i = NULL; + command->element3s = NULL; + if (element3s) + { + command->element3s = (unsigned short *)data; + memcpy(command->element3s, element3s, numtriangles*sizeof(unsigned short[3])); + } + else if (element3i) + { + command->element3i = (int *)data; + memcpy(command->element3i, element3i, numtriangles*sizeof(int[3])); + } + return command; +} + +void DPSOFTRAST_DrawTriangles(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s) +{ + DPSOFTRAST_Command_Draw *command = DPSOFTRAST_Draw_AllocateDrawCommand(firstvertex, numvertices, numtriangles, element3i, element3s); + DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].Vertex(); + command->starty = bound(0, dpsoftrast.drawstarty, dpsoftrast.fb_height); + command->endy = bound(0, dpsoftrast.drawendy, dpsoftrast.fb_height); + if (command->starty >= command->endy) + { + if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) + MM_FREE(command->arrays); + DPSOFTRAST_UndoCommand(command->commandsize); + return; + } + command->clipped = dpsoftrast.drawclipped; + command->refcount = dpsoftrast.numthreads; + + if (dpsoftrast.usethreads) + { + int i; + DPSOFTRAST_Draw_SyncCommands(); + for (i = 0; i < dpsoftrast.numthreads; i++) + { + DPSOFTRAST_State_Thread *thread = &dpsoftrast.threads[i]; + if (((command->starty < thread->maxy1 && command->endy > thread->miny1) || (command->starty < thread->maxy2 && command->endy > thread->miny2)) && thread->starving) + Thread_CondSignal(thread->drawcond); + } + } + else + { + DPSOFTRAST_Draw_FlushThreads(); + } +} + +DEFCOMMAND(23, SetRenderTargets, int width; int height;) +static void DPSOFTRAST_Interpret_SetRenderTargets(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_SetRenderTargets *command) +{ + thread->validate |= DPSOFTRAST_VALIDATE_FB; +} +void DPSOFTRAST_SetRenderTargets(int width, int height, unsigned int *depthpixels, unsigned int *colorpixels0, unsigned int *colorpixels1, unsigned int *colorpixels2, unsigned int *colorpixels3) +{ + DPSOFTRAST_Command_SetRenderTargets *command; + if (width != dpsoftrast.fb_width || height != dpsoftrast.fb_height || depthpixels != dpsoftrast.fb_depthpixels || + colorpixels0 != dpsoftrast.fb_colorpixels[0] || colorpixels1 != dpsoftrast.fb_colorpixels[1] || + colorpixels2 != dpsoftrast.fb_colorpixels[2] || colorpixels3 != dpsoftrast.fb_colorpixels[3]) + DPSOFTRAST_Flush(); + dpsoftrast.fb_width = width; + dpsoftrast.fb_height = height; + dpsoftrast.fb_depthpixels = depthpixels; + dpsoftrast.fb_colorpixels[0] = colorpixels0; + dpsoftrast.fb_colorpixels[1] = colorpixels1; + dpsoftrast.fb_colorpixels[2] = colorpixels2; + dpsoftrast.fb_colorpixels[3] = colorpixels3; + DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); + command = DPSOFTRAST_ALLOCATECOMMAND(SetRenderTargets); + command->width = width; + command->height = height; +} + +static void DPSOFTRAST_Draw_InterpretCommands(DPSOFTRAST_State_Thread *thread, int endoffset) +{ + int commandoffset = thread->commandoffset; + while (commandoffset != endoffset) + { + DPSOFTRAST_Command *command = (DPSOFTRAST_Command *)&dpsoftrast.commandpool.commands[commandoffset]; + switch (command->opcode) + { +#define INTERPCOMMAND(name) \ + case DPSOFTRAST_OPCODE_##name : \ + DPSOFTRAST_Interpret_##name (thread, (DPSOFTRAST_Command_##name *)command); \ + commandoffset += DPSOFTRAST_ALIGNCOMMAND(sizeof( DPSOFTRAST_Command_##name )); \ + if (commandoffset >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) \ + commandoffset = 0; \ + break; + INTERPCOMMAND(Viewport) + INTERPCOMMAND(ClearColor) + INTERPCOMMAND(ClearDepth) + INTERPCOMMAND(ColorMask) + INTERPCOMMAND(DepthTest) + INTERPCOMMAND(ScissorTest) + INTERPCOMMAND(Scissor) + INTERPCOMMAND(BlendFunc) + INTERPCOMMAND(BlendSubtract) + INTERPCOMMAND(DepthMask) + INTERPCOMMAND(DepthFunc) + INTERPCOMMAND(DepthRange) + INTERPCOMMAND(PolygonOffset) + INTERPCOMMAND(CullFace) + INTERPCOMMAND(SetTexture) + INTERPCOMMAND(SetShader) + INTERPCOMMAND(Uniform4f) + INTERPCOMMAND(UniformMatrix4f) + INTERPCOMMAND(Uniform1i) + INTERPCOMMAND(SetRenderTargets) + INTERPCOMMAND(ClipPlane) + + case DPSOFTRAST_OPCODE_Draw: + DPSOFTRAST_Interpret_Draw(thread, (DPSOFTRAST_Command_Draw *)command); + commandoffset += command->commandsize; + if (commandoffset >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) + commandoffset = 0; + thread->commandoffset = commandoffset; + break; + + case DPSOFTRAST_OPCODE_Reset: + commandoffset = 0; + break; + } + } + thread->commandoffset = commandoffset; +} + +static int DPSOFTRAST_Draw_Thread(void *data) +{ + DPSOFTRAST_State_Thread *thread = (DPSOFTRAST_State_Thread *)data; + while(thread->index >= 0) + { + if (thread->commandoffset != dpsoftrast.drawcommand) + { + DPSOFTRAST_Draw_InterpretCommands(thread, dpsoftrast.drawcommand); + } + else + { + Thread_LockMutex(thread->drawmutex); + if (thread->commandoffset == dpsoftrast.drawcommand && thread->index >= 0) + { + if (thread->waiting) Thread_CondSignal(thread->waitcond); + thread->starving = true; + Thread_CondWait(thread->drawcond, thread->drawmutex); + thread->starving = false; + } + Thread_UnlockMutex(thread->drawmutex); + } + } + return 0; +} + +static void DPSOFTRAST_Draw_FlushThreads(void) +{ + DPSOFTRAST_State_Thread *thread; + int i; + DPSOFTRAST_Draw_SyncCommands(); + if (dpsoftrast.usethreads) + { + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + if (thread->commandoffset != dpsoftrast.drawcommand) + { + Thread_LockMutex(thread->drawmutex); + if (thread->commandoffset != dpsoftrast.drawcommand && thread->starving) + Thread_CondSignal(thread->drawcond); + Thread_UnlockMutex(thread->drawmutex); + } + } + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + if (thread->commandoffset != dpsoftrast.drawcommand) + { + Thread_LockMutex(thread->drawmutex); + if (thread->commandoffset != dpsoftrast.drawcommand) + { + thread->waiting = true; + Thread_CondWait(thread->waitcond, thread->drawmutex); + thread->waiting = false; + } + Thread_UnlockMutex(thread->drawmutex); + } + } + } + else + { + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + if (thread->commandoffset != dpsoftrast.drawcommand) + DPSOFTRAST_Draw_InterpretCommands(thread, dpsoftrast.drawcommand); + } + } + dpsoftrast.commandpool.usedcommands = 0; +} + +void DPSOFTRAST_Flush(void) +{ + DPSOFTRAST_Draw_FlushThreads(); +} + +void DPSOFTRAST_Finish(void) +{ + DPSOFTRAST_Flush(); +} + +int DPSOFTRAST_Init(int width, int height, int numthreads, int interlace, unsigned int *colorpixels, unsigned int *depthpixels) +{ + int i; + union + { + int i; + unsigned char b[4]; + } + u; + u.i = 1; + memset(&dpsoftrast, 0, sizeof(dpsoftrast)); + dpsoftrast.bigendian = u.b[3]; + dpsoftrast.fb_width = width; + dpsoftrast.fb_height = height; + dpsoftrast.fb_depthpixels = depthpixels; + dpsoftrast.fb_colorpixels[0] = colorpixels; + dpsoftrast.fb_colorpixels[1] = NULL; + dpsoftrast.fb_colorpixels[1] = NULL; + dpsoftrast.fb_colorpixels[1] = NULL; + dpsoftrast.viewport[0] = 0; + dpsoftrast.viewport[1] = 0; + dpsoftrast.viewport[2] = dpsoftrast.fb_width; + dpsoftrast.viewport[3] = dpsoftrast.fb_height; + DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); + dpsoftrast.texture_firstfree = 1; + dpsoftrast.texture_end = 1; + dpsoftrast.texture_max = 0; + dpsoftrast.color[0] = 1; + dpsoftrast.color[1] = 1; + dpsoftrast.color[2] = 1; + dpsoftrast.color[3] = 1; + dpsoftrast.usethreads = numthreads > 0 && Thread_HasThreads(); + dpsoftrast.interlace = dpsoftrast.usethreads ? bound(0, interlace, 1) : 0; + dpsoftrast.numthreads = dpsoftrast.usethreads ? bound(1, numthreads, 64) : 1; + dpsoftrast.threads = (DPSOFTRAST_State_Thread *)MM_CALLOC(dpsoftrast.numthreads, sizeof(DPSOFTRAST_State_Thread)); + for (i = 0; i < dpsoftrast.numthreads; i++) + { + DPSOFTRAST_State_Thread *thread = &dpsoftrast.threads[i]; + thread->index = i; + thread->cullface = GL_BACK; + thread->colormask[0] = 1; + thread->colormask[1] = 1; + thread->colormask[2] = 1; + thread->colormask[3] = 1; + thread->blendfunc[0] = GL_ONE; + thread->blendfunc[1] = GL_ZERO; + thread->depthmask = true; + thread->depthtest = true; + thread->depthfunc = GL_LEQUAL; + thread->scissortest = false; + thread->viewport[0] = 0; + thread->viewport[1] = 0; + thread->viewport[2] = dpsoftrast.fb_width; + thread->viewport[3] = dpsoftrast.fb_height; + thread->scissor[0] = 0; + thread->scissor[1] = 0; + thread->scissor[2] = dpsoftrast.fb_width; + thread->scissor[3] = dpsoftrast.fb_height; + thread->depthrange[0] = 0; + thread->depthrange[1] = 1; + thread->polygonoffset[0] = 0; + thread->polygonoffset[1] = 0; + thread->clipplane[0] = 0; + thread->clipplane[1] = 0; + thread->clipplane[2] = 0; + thread->clipplane[3] = 1; + + thread->numspans = 0; + thread->numtriangles = 0; + thread->commandoffset = 0; + thread->waiting = false; + thread->starving = false; + + thread->validate = -1; + DPSOFTRAST_Validate(thread, -1); + + if (dpsoftrast.usethreads) + { + thread->waitcond = Thread_CreateCond(); + thread->drawcond = Thread_CreateCond(); + thread->drawmutex = Thread_CreateMutex(); + thread->thread = Thread_CreateThread(DPSOFTRAST_Draw_Thread, thread); + } + } + return 0; +} + +void DPSOFTRAST_Shutdown(void) +{ + int i; + if (dpsoftrast.usethreads && dpsoftrast.numthreads > 0) + { + DPSOFTRAST_State_Thread *thread; + for (i = 0; i < dpsoftrast.numthreads; i++) + { + thread = &dpsoftrast.threads[i]; + Thread_LockMutex(thread->drawmutex); + thread->index = -1; + Thread_CondSignal(thread->drawcond); + Thread_UnlockMutex(thread->drawmutex); + Thread_WaitThread(thread->thread, 0); + Thread_DestroyCond(thread->waitcond); + Thread_DestroyCond(thread->drawcond); + Thread_DestroyMutex(thread->drawmutex); + } + } + for (i = 0;i < dpsoftrast.texture_end;i++) + if (dpsoftrast.texture[i].bytes) + MM_FREE(dpsoftrast.texture[i].bytes); + if (dpsoftrast.texture) + free(dpsoftrast.texture); + if (dpsoftrast.threads) + MM_FREE(dpsoftrast.threads); + memset(&dpsoftrast, 0, sizeof(dpsoftrast)); +} + diff --git a/app/jni/dpsoftrast.h b/app/jni/dpsoftrast.h new file mode 100644 index 0000000..5bda9f3 --- /dev/null +++ b/app/jni/dpsoftrast.h @@ -0,0 +1,327 @@ + +#ifndef DPSOFTRAST_H +#define DPSOFTRAST_H + +#include + +#define DPSOFTRAST_MAXMIPMAPS 16 +#define DPSOFTRAST_TEXTURE_MAXSIZE (1<<(DPSOFTRAST_MAXMIPMAPS - 1)) +#define DPSOFTRAST_MAXTEXTUREUNITS 16 +#define DPSOFTRAST_MAXTEXCOORDARRAYS 8 + +// type of pixels in texture (some of these are converted to BGRA8 on update) +#define DPSOFTRAST_TEXTURE_FORMAT_BGRA8 0 +#define DPSOFTRAST_TEXTURE_FORMAT_DEPTH 1 +#define DPSOFTRAST_TEXTURE_FORMAT_RGBA8 2 +#define DPSOFTRAST_TEXTURE_FORMAT_ALPHA8 3 +#define DPSOFTRAST_TEXTURE_FORMAT_RGBA16F 4 +#define DPSOFTRAST_TEXTURE_FORMAT_RGBA32F 5 +#define DPSOFTRAST_TEXTURE_FORMAT_COMPAREMASK 0x0F + +// modifier flags for texture (can not be changed after creation) +#define DPSOFTRAST_TEXTURE_FLAG_MIPMAP 0x10 +#define DPSOFTRAST_TEXTURE_FLAG_CUBEMAP 0x20 +#define DPSOFTRAST_TEXTURE_FLAG_USEALPHA 0x40 +#define DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE 0x80 + +typedef enum DPSOFTRAST_TEXTURE_FILTER_e +{ + DPSOFTRAST_TEXTURE_FILTER_NEAREST = 0, + DPSOFTRAST_TEXTURE_FILTER_LINEAR = 1, + DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE = 2, + DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE = 3, +} +DPSOFTRAST_TEXTURE_FILTER; + +int DPSOFTRAST_Init(int width, int height, int numthreads, int interlace, unsigned int *colorpixels, unsigned int *depthpixels); +void DPSOFTRAST_Shutdown(void); +void DPSOFTRAST_Flush(void); +void DPSOFTRAST_Finish(void); + +int DPSOFTRAST_Texture_New(int flags, int width, int height, int depth); +void DPSOFTRAST_Texture_Free(int index); +void DPSOFTRAST_Texture_UpdatePartial(int index, int mip, const unsigned char *pixels, int blockx, int blocky, int blockwidth, int blockheight); +void DPSOFTRAST_Texture_UpdateFull(int index, const unsigned char *pixels); +int DPSOFTRAST_Texture_GetWidth(int index, int mip); +int DPSOFTRAST_Texture_GetHeight(int index, int mip); +int DPSOFTRAST_Texture_GetDepth(int index, int mip); +unsigned char *DPSOFTRAST_Texture_GetPixelPointer(int index, int mip); +void DPSOFTRAST_Texture_Filter(int index, DPSOFTRAST_TEXTURE_FILTER filter); + +void DPSOFTRAST_SetRenderTargets(int width, int height, unsigned int *depthpixels, unsigned int *colorpixels0, unsigned int *colorpixels1, unsigned int *colorpixels2, unsigned int *colorpixels3); +void DPSOFTRAST_Viewport(int x, int y, int width, int height); +void DPSOFTRAST_ClearColor(float r, float g, float b, float a); +void DPSOFTRAST_ClearDepth(float d); +void DPSOFTRAST_ColorMask(int r, int g, int b, int a); +void DPSOFTRAST_DepthTest(int enable); +void DPSOFTRAST_ScissorTest(int enable); +void DPSOFTRAST_Scissor(float x, float y, float width, float height); +void DPSOFTRAST_ClipPlane(float x, float y, float z, float w); + +void DPSOFTRAST_BlendFunc(int smodulate, int dmodulate); +void DPSOFTRAST_BlendSubtract(int enable); +void DPSOFTRAST_DepthMask(int enable); +void DPSOFTRAST_DepthFunc(int comparemode); +void DPSOFTRAST_DepthRange(float range0, float range1); +void DPSOFTRAST_PolygonOffset(float alongnormal, float intoview); +void DPSOFTRAST_CullFace(int mode); +void DPSOFTRAST_Color4f(float r, float g, float b, float a); +void DPSOFTRAST_GetPixelsBGRA(int blockx, int blocky, int blockwidth, int blockheight, unsigned char *outpixels); +void DPSOFTRAST_CopyRectangleToTexture(int index, int mip, int tx, int ty, int sx, int sy, int width, int height); +void DPSOFTRAST_SetTexture(int unitnum, int index); + +void DPSOFTRAST_SetVertexPointer(const float *vertex3f, size_t stride); +void DPSOFTRAST_SetColorPointer(const float *color4f, size_t stride); +void DPSOFTRAST_SetColorPointer4ub(const unsigned char *color4ub, size_t stride); +void DPSOFTRAST_SetTexCoordPointer(int unitnum, int numcomponents, size_t stride, const float *texcoordf); + +typedef enum gl20_texunit_e +{ + // postprocess shaders, and generic shaders: + GL20TU_FIRST = 0, + GL20TU_SECOND = 1, + GL20TU_GAMMARAMPS = 2, + // standard material properties + GL20TU_NORMAL = 0, + GL20TU_COLOR = 1, + GL20TU_GLOSS = 2, + GL20TU_GLOW = 3, + // material properties for a second material + GL20TU_SECONDARY_NORMAL = 4, + GL20TU_SECONDARY_COLOR = 5, + GL20TU_SECONDARY_GLOSS = 6, + GL20TU_SECONDARY_GLOW = 7, + // material properties for a colormapped material + // conflicts with secondary material + GL20TU_PANTS = 4, + GL20TU_SHIRT = 7, + // fog fade in the distance + GL20TU_FOGMASK = 8, + // compiled ambient lightmap and deluxemap + GL20TU_LIGHTMAP = 9, + GL20TU_DELUXEMAP = 10, + // refraction, used by water shaders + GL20TU_REFRACTION = 3, + // reflection, used by water shaders, also with normal material rendering + // conflicts with secondary material + GL20TU_REFLECTION = 7, + // rtlight attenuation (distance fade) and cubemap filter (projection texturing) + // conflicts with lightmap/deluxemap + GL20TU_ATTENUATION = 9, + GL20TU_CUBE = 10, + GL20TU_SHADOWMAP2D = 15, + GL20TU_CUBEPROJECTION = 12, + // rtlight prepass data (screenspace depth and normalmap) +// GL20TU_UNUSED1 = 13, + GL20TU_SCREENNORMALMAP = 14, + // lightmap prepass data (screenspace diffuse and specular from lights) + GL20TU_SCREENDIFFUSE = 11, + GL20TU_SCREENSPECULAR = 12, + // fake reflections + GL20TU_REFLECTMASK = 5, + GL20TU_REFLECTCUBE = 6, + GL20TU_FOGHEIGHTTEXTURE = 14 +} +gl20_texunit; + +typedef enum glsl_attrib_e +{ + GLSLATTRIB_POSITION = 0, + GLSLATTRIB_COLOR = 1, + GLSLATTRIB_TEXCOORD0 = 2, + GLSLATTRIB_TEXCOORD1 = 3, + GLSLATTRIB_TEXCOORD2 = 4, + GLSLATTRIB_TEXCOORD3 = 5, + GLSLATTRIB_TEXCOORD4 = 6, + GLSLATTRIB_TEXCOORD5 = 7, + GLSLATTRIB_TEXCOORD6 = 8, + GLSLATTRIB_TEXCOORD7 = 9, +} +glsl_attrib; + +// this enum selects which of the glslshadermodeinfo entries should be used +typedef enum shadermode_e +{ + SHADERMODE_GENERIC, ///< (particles/HUD/etc) vertex color, optionally multiplied by one texture + SHADERMODE_POSTPROCESS, ///< postprocessing shader (r_glsl_postprocess) + SHADERMODE_DEPTH_OR_SHADOW, ///< (depthfirst/shadows) vertex shader only + SHADERMODE_FLATCOLOR, ///< (lightmap) modulate texture by uniform color (q1bsp, q3bsp) + SHADERMODE_VERTEXCOLOR, ///< (lightmap) modulate texture by vertex colors (q3bsp) + SHADERMODE_LIGHTMAP, ///< (lightmap) modulate texture by lightmap texture (q1bsp, q3bsp) + SHADERMODE_FAKELIGHT, ///< (fakelight) modulate texture by "fake" lighting (no lightmaps, no nothing) + SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE, ///< (lightmap) use directional pixel shading from texture containing modelspace light directions (q3bsp deluxemap) + SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE, ///< (lightmap) use directional pixel shading from texture containing tangentspace light directions (q1bsp deluxemap) + SHADERMODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP, // forced deluxemapping for lightmapped surfaces + SHADERMODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR, // forced deluxemapping for vertexlit surfaces + SHADERMODE_LIGHTDIRECTION, ///< (lightmap) use directional pixel shading from fixed light direction (q3bsp) + SHADERMODE_LIGHTSOURCE, ///< (lightsource) use directional pixel shading from light source (rtlight) + SHADERMODE_REFRACTION, ///< refract background (the material is rendered normally after this pass) + SHADERMODE_WATER, ///< refract background and reflection (the material is rendered normally after this pass) + SHADERMODE_DEFERREDGEOMETRY, ///< (deferred) render material properties to screenspace geometry buffers + SHADERMODE_DEFERREDLIGHTSOURCE, ///< (deferred) use directional pixel shading from light source (rtlight) on screenspace geometry buffers + SHADERMODE_COUNT +} +shadermode_t; + +typedef enum shaderpermutation_e +{ + SHADERPERMUTATION_DIFFUSE = 1<<0, ///< (lightsource) whether to use directional shading + SHADERPERMUTATION_VERTEXTEXTUREBLEND = 1<<1, ///< indicates this is a two-layer material blend based on vertex alpha (q3bsp) + SHADERPERMUTATION_VIEWTINT = 1<<2, ///< view tint (postprocessing only), use vertex colors (generic only) + SHADERPERMUTATION_COLORMAPPING = 1<<3, ///< indicates this is a colormapped skin + SHADERPERMUTATION_SATURATION = 1<<4, ///< saturation (postprocessing only) + SHADERPERMUTATION_FOGINSIDE = 1<<5, ///< tint the color by fog color or black if using additive blend mode + SHADERPERMUTATION_FOGOUTSIDE = 1<<6, ///< tint the color by fog color or black if using additive blend mode + SHADERPERMUTATION_FOGHEIGHTTEXTURE = 1<<7, ///< fog color and density determined by texture mapped on vertical axis + SHADERPERMUTATION_FOGALPHAHACK = 1<<8, ///< fog color and density determined by texture mapped on vertical axis + SHADERPERMUTATION_GAMMARAMPS = 1<<9, ///< gamma (postprocessing only) + SHADERPERMUTATION_CUBEFILTER = 1<<10, ///< (lightsource) use cubemap light filter + SHADERPERMUTATION_GLOW = 1<<11, ///< (lightmap) blend in an additive glow texture + SHADERPERMUTATION_BLOOM = 1<<12, ///< bloom (postprocessing only) + SHADERPERMUTATION_SPECULAR = 1<<13, ///< (lightsource or deluxemapping) render specular effects + SHADERPERMUTATION_POSTPROCESSING = 1<<14, ///< user defined postprocessing (postprocessing only) + SHADERPERMUTATION_REFLECTION = 1<<15, ///< normalmap-perturbed reflection of the scene infront of the surface, preformed as an overlay on the surface + SHADERPERMUTATION_OFFSETMAPPING = 1<<16, ///< adjust texcoords to roughly simulate a displacement mapped surface + SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING = 1<<17, ///< adjust texcoords to accurately simulate a displacement mapped surface (requires OFFSETMAPPING to also be set!) + SHADERPERMUTATION_SHADOWMAP2D = 1<<18, ///< (lightsource) use shadowmap texture as light filter + SHADERPERMUTATION_SHADOWMAPVSDCT = 1<<19, ///< (lightsource) use virtual shadow depth cube texture for shadowmap indexing + SHADERPERMUTATION_SHADOWMAPORTHO = 1<<20, ///< (lightsource) use orthographic shadowmap projection + SHADERPERMUTATION_DEFERREDLIGHTMAP = 1<<21, ///< (lightmap) read Texture_ScreenDiffuse/Specular textures and add them on top of lightmapping + SHADERPERMUTATION_ALPHAKILL = 1<<22, ///< (deferredgeometry) discard pixel if diffuse texture alpha below 0.5, (generic) apply global alpha + SHADERPERMUTATION_REFLECTCUBE = 1<<23, ///< fake reflections using global cubemap (not HDRI light probe) + SHADERPERMUTATION_NORMALMAPSCROLLBLEND = 1<<24, ///< (water) counter-direction normalmaps scrolling + SHADERPERMUTATION_BOUNCEGRID = 1<<25, ///< (lightmap) use Texture_BounceGrid as an additional source of ambient light + SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL = 1<<26, ///< (lightmap) use 16-component pixels in bouncegrid texture for directional lighting rather than standard 4-component + SHADERPERMUTATION_TRIPPY = 1<<27, ///< use trippy vertex shader effect + SHADERPERMUTATION_DEPTHRGB = 1<<28, ///< read/write depth values in RGB color coded format for older hardware without depth samplers + SHADERPERMUTATION_ALPHAGEN_VERTEX = 1<<29, ///< alphaGen vertex + SHADERPERMUTATION_SKELETAL = 1<<30, ///< (skeletal models) use skeletal matrices to deform vertices (gpu-skinning) + SHADERPERMUTATION_COUNT = 31 ///< size of shaderpermutationinfo array +} +shaderpermutation_t; + +typedef enum DPSOFTRAST_UNIFORM_e +{ + DPSOFTRAST_UNIFORM_Texture_First, + DPSOFTRAST_UNIFORM_Texture_Second, + DPSOFTRAST_UNIFORM_Texture_GammaRamps, + DPSOFTRAST_UNIFORM_Texture_Normal, + DPSOFTRAST_UNIFORM_Texture_Color, + DPSOFTRAST_UNIFORM_Texture_Gloss, + DPSOFTRAST_UNIFORM_Texture_Glow, + DPSOFTRAST_UNIFORM_Texture_SecondaryNormal, + DPSOFTRAST_UNIFORM_Texture_SecondaryColor, + DPSOFTRAST_UNIFORM_Texture_SecondaryGloss, + DPSOFTRAST_UNIFORM_Texture_SecondaryGlow, + DPSOFTRAST_UNIFORM_Texture_Pants, + DPSOFTRAST_UNIFORM_Texture_Shirt, + DPSOFTRAST_UNIFORM_Texture_FogHeightTexture, + DPSOFTRAST_UNIFORM_Texture_FogMask, + DPSOFTRAST_UNIFORM_Texture_Lightmap, + DPSOFTRAST_UNIFORM_Texture_Deluxemap, + DPSOFTRAST_UNIFORM_Texture_Attenuation, + DPSOFTRAST_UNIFORM_Texture_Cube, + DPSOFTRAST_UNIFORM_Texture_Refraction, + DPSOFTRAST_UNIFORM_Texture_Reflection, + DPSOFTRAST_UNIFORM_Texture_ShadowMap2D, + DPSOFTRAST_UNIFORM_Texture_CubeProjection, + DPSOFTRAST_UNIFORM_Texture_ScreenNormalMap, + DPSOFTRAST_UNIFORM_Texture_ScreenDiffuse, + DPSOFTRAST_UNIFORM_Texture_ScreenSpecular, + DPSOFTRAST_UNIFORM_Texture_ReflectMask, + DPSOFTRAST_UNIFORM_Texture_ReflectCube, + DPSOFTRAST_UNIFORM_Alpha, + DPSOFTRAST_UNIFORM_BloomBlur_Parameters, + DPSOFTRAST_UNIFORM_ClientTime, + DPSOFTRAST_UNIFORM_Color_Ambient, + DPSOFTRAST_UNIFORM_Color_Diffuse, + DPSOFTRAST_UNIFORM_Color_Specular, + DPSOFTRAST_UNIFORM_Color_Glow, + DPSOFTRAST_UNIFORM_Color_Pants, + DPSOFTRAST_UNIFORM_Color_Shirt, + DPSOFTRAST_UNIFORM_DeferredColor_Ambient, + DPSOFTRAST_UNIFORM_DeferredColor_Diffuse, + DPSOFTRAST_UNIFORM_DeferredColor_Specular, + DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, + DPSOFTRAST_UNIFORM_DeferredMod_Specular, + DPSOFTRAST_UNIFORM_DistortScaleRefractReflect, + DPSOFTRAST_UNIFORM_EyePosition, + DPSOFTRAST_UNIFORM_FogColor, + DPSOFTRAST_UNIFORM_FogHeightFade, + DPSOFTRAST_UNIFORM_FogPlane, + DPSOFTRAST_UNIFORM_FogPlaneViewDist, + DPSOFTRAST_UNIFORM_FogRangeRecip, + DPSOFTRAST_UNIFORM_LightColor, + DPSOFTRAST_UNIFORM_LightDir, + DPSOFTRAST_UNIFORM_LightPosition, + DPSOFTRAST_UNIFORM_OffsetMapping_ScaleSteps, + DPSOFTRAST_UNIFORM_PixelSize, + DPSOFTRAST_UNIFORM_ReflectColor, + DPSOFTRAST_UNIFORM_ReflectFactor, + DPSOFTRAST_UNIFORM_ReflectOffset, + DPSOFTRAST_UNIFORM_RefractColor, + DPSOFTRAST_UNIFORM_Saturation, + DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect, + DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect, + DPSOFTRAST_UNIFORM_ScreenToDepth, + DPSOFTRAST_UNIFORM_ShadowMap_Parameters, + DPSOFTRAST_UNIFORM_ShadowMap_TextureScale, + DPSOFTRAST_UNIFORM_SpecularPower, + DPSOFTRAST_UNIFORM_UserVec1, + DPSOFTRAST_UNIFORM_UserVec2, + DPSOFTRAST_UNIFORM_UserVec3, + DPSOFTRAST_UNIFORM_UserVec4, + DPSOFTRAST_UNIFORM_ViewTintColor, + DPSOFTRAST_UNIFORM_ViewToLightM1, + DPSOFTRAST_UNIFORM_ViewToLightM2, + DPSOFTRAST_UNIFORM_ViewToLightM3, + DPSOFTRAST_UNIFORM_ViewToLightM4, + DPSOFTRAST_UNIFORM_ModelToLightM1, + DPSOFTRAST_UNIFORM_ModelToLightM2, + DPSOFTRAST_UNIFORM_ModelToLightM3, + DPSOFTRAST_UNIFORM_ModelToLightM4, + DPSOFTRAST_UNIFORM_TexMatrixM1, + DPSOFTRAST_UNIFORM_TexMatrixM2, + DPSOFTRAST_UNIFORM_TexMatrixM3, + DPSOFTRAST_UNIFORM_TexMatrixM4, + DPSOFTRAST_UNIFORM_BackgroundTexMatrixM1, + DPSOFTRAST_UNIFORM_BackgroundTexMatrixM2, + DPSOFTRAST_UNIFORM_BackgroundTexMatrixM3, + DPSOFTRAST_UNIFORM_BackgroundTexMatrixM4, + DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, + DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM2, + DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM3, + DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM4, + DPSOFTRAST_UNIFORM_ModelViewMatrixM1, + DPSOFTRAST_UNIFORM_ModelViewMatrixM2, + DPSOFTRAST_UNIFORM_ModelViewMatrixM3, + DPSOFTRAST_UNIFORM_ModelViewMatrixM4, + DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, + DPSOFTRAST_UNIFORM_ModelToReflectCubeM1, + DPSOFTRAST_UNIFORM_ModelToReflectCubeM2, + DPSOFTRAST_UNIFORM_ModelToReflectCubeM3, + DPSOFTRAST_UNIFORM_ModelToReflectCubeM4, + DPSOFTRAST_UNIFORM_ShadowMapMatrixM1, + DPSOFTRAST_UNIFORM_ShadowMapMatrixM2, + DPSOFTRAST_UNIFORM_ShadowMapMatrixM3, + DPSOFTRAST_UNIFORM_ShadowMapMatrixM4, + DPSOFTRAST_UNIFORM_BloomColorSubtract, + DPSOFTRAST_UNIFORM_NormalmapScrollBlend, + DPSOFTRAST_UNIFORM_OffsetMapping_LodDistance, + DPSOFTRAST_UNIFORM_OffsetMapping_Bias, + DPSOFTRAST_UNIFORM_TOTAL +} +DPSOFTRAST_UNIFORM; + +void DPSOFTRAST_SetShader(int mode, int permutation, int exactspecularmath); +#define DPSOFTRAST_Uniform1f(index, v0) DPSOFTRAST_Uniform4f(index, v0, 0, 0, 0) +#define DPSOFTRAST_Uniform2f(index, v0, v1) DPSOFTRAST_Uniform4f(index, v0, v1, 0, 0) +#define DPSOFTRAST_Uniform3f(index, v0, v1, v2) DPSOFTRAST_Uniform4f(index, v0, v1, v2, 0) +void DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM index, float v0, float v1, float v2, float v3); +void DPSOFTRAST_Uniform4fv(DPSOFTRAST_UNIFORM index, const float *v); +void DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM index, int arraysize, int transpose, const float *v); +void DPSOFTRAST_Uniform1i(DPSOFTRAST_UNIFORM index, int i0); + +void DPSOFTRAST_DrawTriangles(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s); + +#endif // DPSOFTRAST_H diff --git a/app/jni/dpvsimpledecode.c b/app/jni/dpvsimpledecode.c new file mode 100644 index 0000000..ed89260 --- /dev/null +++ b/app/jni/dpvsimpledecode.c @@ -0,0 +1,656 @@ +#include "quakedef.h" +#include "dpvsimpledecode.h" + +#define HZREADERROR_OK 0 +#define HZREADERROR_EOF 1 +#define HZREADERROR_MALLOCFAILED 2 + +//#define HZREADBLOCKSIZE 16000 +#define HZREADBLOCKSIZE 1048576 + +typedef struct hz_bitstream_read_s +{ + qfile_t *file; + int endoffile; +} +hz_bitstream_read_t; + +typedef struct hz_bitstream_readblock_s +{ + struct hz_bitstream_readblock_s *next; + unsigned int size; + unsigned char data[HZREADBLOCKSIZE]; +} +hz_bitstream_readblock_t; + +typedef struct hz_bitstream_readblocks_s +{ + hz_bitstream_readblock_t *blocks; + hz_bitstream_readblock_t *current; + unsigned int position; + unsigned int store; + int count; +} +hz_bitstream_readblocks_t; + +static hz_bitstream_read_t *hz_bitstream_read_open(char *filename) +{ + qfile_t *file; + hz_bitstream_read_t *stream; + if ((file = FS_OpenVirtualFile(filename, false))) + { + stream = (hz_bitstream_read_t *)Z_Malloc(sizeof(hz_bitstream_read_t)); + memset(stream, 0, sizeof(*stream)); + stream->file = file; + return stream; + } + else + return NULL; +} + +static void hz_bitstream_read_close(hz_bitstream_read_t *stream) +{ + if (stream) + { + FS_Close(stream->file); + Z_Free(stream); + } +} + +static hz_bitstream_readblocks_t *hz_bitstream_read_blocks_new(void) +{ + hz_bitstream_readblocks_t *blocks; + blocks = (hz_bitstream_readblocks_t *)Z_Malloc(sizeof(hz_bitstream_readblocks_t)); + if (blocks == NULL) + return NULL; + memset(blocks, 0, sizeof(hz_bitstream_readblocks_t)); + return blocks; +} + +static void hz_bitstream_read_blocks_free(hz_bitstream_readblocks_t *blocks) +{ + hz_bitstream_readblock_t *b, *n; + if (blocks == NULL) + return; + for (b = blocks->blocks;b;b = n) + { + n = b->next; + Z_Free(b); + } + Z_Free(blocks); +} + +static void hz_bitstream_read_flushbits(hz_bitstream_readblocks_t *blocks) +{ + blocks->store = 0; + blocks->count = 0; +} + +static int hz_bitstream_read_blocks_read(hz_bitstream_readblocks_t *blocks, hz_bitstream_read_t *stream, unsigned int size) +{ + int s; + hz_bitstream_readblock_t *b, *p; + s = size; + p = NULL; + b = blocks->blocks; + while (s > 0) + { + if (b == NULL) + { + b = (hz_bitstream_readblock_t *)Z_Malloc(sizeof(hz_bitstream_readblock_t)); + if (b == NULL) + return HZREADERROR_MALLOCFAILED; + b->next = NULL; + b->size = 0; + if (p != NULL) + p->next = b; + else + blocks->blocks = b; + } + if (s > HZREADBLOCKSIZE) + b->size = HZREADBLOCKSIZE; + else + b->size = s; + s -= b->size; + if (FS_Read(stream->file, b->data, b->size) != (fs_offset_t)b->size) + { + stream->endoffile = 1; + break; + } + p = b; + b = b->next; + } + while (b) + { + b->size = 0; + b = b->next; + } + blocks->current = blocks->blocks; + blocks->position = 0; + hz_bitstream_read_flushbits(blocks); + if (stream->endoffile) + return HZREADERROR_EOF; + return HZREADERROR_OK; +} + +static unsigned int hz_bitstream_read_blocks_getbyte(hz_bitstream_readblocks_t *blocks) +{ + while (blocks->current != NULL && blocks->position >= blocks->current->size) + { + blocks->position = 0; + blocks->current = blocks->current->next; + } + if (blocks->current == NULL) + return 0; + return blocks->current->data[blocks->position++]; +} + +static int hz_bitstream_read_bit(hz_bitstream_readblocks_t *blocks) +{ + if (!blocks->count) + { + blocks->count += 8; + blocks->store <<= 8; + blocks->store |= hz_bitstream_read_blocks_getbyte(blocks) & 0xFF; + } + blocks->count--; + return (blocks->store >> blocks->count) & 1; +} + +static unsigned int hz_bitstream_read_bits(hz_bitstream_readblocks_t *blocks, int size) +{ + unsigned int num = 0; + // we can only handle about 24 bits at a time safely + // (there might be up to 7 bits more than we need in the bit store) + if (size > 24) + { + size -= 8; + num |= hz_bitstream_read_bits(blocks, 8) << size; + } + while (blocks->count < size) + { + blocks->count += 8; + blocks->store <<= 8; + blocks->store |= hz_bitstream_read_blocks_getbyte(blocks) & 0xFF; + } + blocks->count -= size; + num |= (blocks->store >> blocks->count) & ((1 << size) - 1); + return num; +} + +static unsigned int hz_bitstream_read_byte(hz_bitstream_readblocks_t *blocks) +{ + return hz_bitstream_read_blocks_getbyte(blocks); +} + +static unsigned int hz_bitstream_read_short(hz_bitstream_readblocks_t *blocks) +{ + return (hz_bitstream_read_byte(blocks) << 8) + | (hz_bitstream_read_byte(blocks)); +} + +static unsigned int hz_bitstream_read_int(hz_bitstream_readblocks_t *blocks) +{ + return (hz_bitstream_read_byte(blocks) << 24) + | (hz_bitstream_read_byte(blocks) << 16) + | (hz_bitstream_read_byte(blocks) << 8) + | (hz_bitstream_read_byte(blocks)); +} + +static void hz_bitstream_read_bytes(hz_bitstream_readblocks_t *blocks, void *outdata, unsigned int size) +{ + unsigned char *out; + out = (unsigned char *)outdata; + while (size--) + *out++ = hz_bitstream_read_byte(blocks); +} + +#define BLOCKSIZE 8 + +typedef struct dpvsimpledecodestream_s +{ + hz_bitstream_read_t *bitstream; + hz_bitstream_readblocks_t *framedatablocks; + + int error; + + double info_framerate; + unsigned int info_frames; + + unsigned int info_imagewidth; + unsigned int info_imageheight; + unsigned int info_imagebpp; + unsigned int info_imageRloss; + unsigned int info_imageRmask; + unsigned int info_imageRshift; + unsigned int info_imageGloss; + unsigned int info_imageGmask; + unsigned int info_imageGshift; + unsigned int info_imageBloss; + unsigned int info_imageBmask; + unsigned int info_imageBshift; + unsigned int info_imagesize; + double info_aspectratio; + + // current video frame (needed because of delta compression) + int videoframenum; + // current video frame data (needed because of delta compression) + unsigned int *videopixels; + + // channel the sound file is being played on + int sndchan; +} +dpvsimpledecodestream_t; + +static int dpvsimpledecode_setpixelformat(dpvsimpledecodestream_t *s, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel) +{ + int Rshift, Rbits, Gshift, Gbits, Bshift, Bbits; + if (!Rmask) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDRMASK; + return s->error; + } + if (!Gmask) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDGMASK; + return s->error; + } + if (!Bmask) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDBMASK; + return s->error; + } + if (Rmask & Gmask || Rmask & Bmask || Gmask & Bmask) + { + s->error = DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP; + return s->error; + } + switch (bytesperpixel) + { + case 2: + if ((Rmask | Gmask | Bmask) > 65536) + { + s->error = DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP; + return s->error; + } + break; + case 4: + break; + default: + s->error = DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP; + return s->error; + } + for (Rshift = 0;!(Rmask & 1);Rshift++, Rmask >>= 1); + for (Gshift = 0;!(Gmask & 1);Gshift++, Gmask >>= 1); + for (Bshift = 0;!(Bmask & 1);Bshift++, Bmask >>= 1); + if (((Rmask + 1) & Rmask) != 0) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDRMASK; + return s->error; + } + if (((Gmask + 1) & Gmask) != 0) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDGMASK; + return s->error; + } + if (((Bmask + 1) & Bmask) != 0) + { + s->error = DPVSIMPLEDECODEERROR_INVALIDBMASK; + return s->error; + } + for (Rbits = 0;Rmask & 1;Rbits++, Rmask >>= 1); + for (Gbits = 0;Gmask & 1;Gbits++, Gmask >>= 1); + for (Bbits = 0;Bmask & 1;Bbits++, Bmask >>= 1); + if (Rbits > 8) + { + Rshift += (Rbits - 8); + Rbits = 8; + } + if (Gbits > 8) + { + Gshift += (Gbits - 8); + Gbits = 8; + } + if (Bbits > 8) + { + Bshift += (Bbits - 8); + Bbits = 8; + } + s->info_imagebpp = bytesperpixel; + s->info_imageRloss = 16 + (8 - Rbits); + s->info_imageGloss = 8 + (8 - Gbits); + s->info_imageBloss = 0 + (8 - Bbits); + s->info_imageRmask = (1 << Rbits) - 1; + s->info_imageGmask = (1 << Gbits) - 1; + s->info_imageBmask = (1 << Bbits) - 1; + s->info_imageRshift = Rshift; + s->info_imageGshift = Gshift; + s->info_imageBshift = Bshift; + s->info_imagesize = s->info_imagewidth * s->info_imageheight * s->info_imagebpp; + return s->error; +} + +// opening and closing streams + +// opens a stream +void *dpvsimpledecode_open(clvideo_t *video, char *filename, const char **errorstring) +{ + dpvsimpledecodestream_t *s; + char t[8], *wavename; + if (errorstring != NULL) + *errorstring = NULL; + s = (dpvsimpledecodestream_t *)Z_Malloc(sizeof(dpvsimpledecodestream_t)); + if (s != NULL) + { + s->bitstream = hz_bitstream_read_open(filename); + if (s->bitstream != NULL) + { + // check file identification + s->framedatablocks = hz_bitstream_read_blocks_new(); + if (s->framedatablocks != NULL) + { + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 8); + hz_bitstream_read_bytes(s->framedatablocks, t, 8); + if (!memcmp(t, "DPVideo", 8)) + { + // check version number + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 2); + if (hz_bitstream_read_short(s->framedatablocks) == 1) + { + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 12); + s->info_imagewidth = hz_bitstream_read_short(s->framedatablocks); + s->info_imageheight = hz_bitstream_read_short(s->framedatablocks); + s->info_framerate = (double) hz_bitstream_read_int(s->framedatablocks) * (1.0 / 65536.0); + s->info_aspectratio = (double)s->info_imagewidth / (double)s->info_imageheight; + + if (s->info_framerate > 0.0) + { + s->videopixels = (unsigned int *)Z_Malloc(s->info_imagewidth * s->info_imageheight * sizeof(*s->videopixels)); + if (s->videopixels != NULL) + { + size_t namelen; + + namelen = strlen(filename) + 10; + wavename = (char *)Z_Malloc(namelen); + if (wavename) + { + sfx_t* sfx; + + FS_StripExtension(filename, wavename, namelen); + strlcat(wavename, ".wav", namelen); + sfx = S_PrecacheSound (wavename, false, false); + if (sfx != NULL) + s->sndchan = S_StartSound (-1, 0, sfx, vec3_origin, 1.0f, 0); + else + s->sndchan = -1; + Z_Free(wavename); + } + // all is well... + // set the module functions + s->videoframenum = -10000; + video->close = dpvsimpledecode_close; + video->getwidth = dpvsimpledecode_getwidth; + video->getheight = dpvsimpledecode_getheight; + video->getframerate = dpvsimpledecode_getframerate; + video->decodeframe = dpvsimpledecode_video; + video->getaspectratio = dpvsimpledecode_getaspectratio; + + return s; + } + else if (errorstring != NULL) + *errorstring = "unable to allocate video image buffer"; + } + else if (errorstring != NULL) + *errorstring = "error in video info chunk"; + } + else if (errorstring != NULL) + *errorstring = "read error"; + } + else if (errorstring != NULL) + *errorstring = "not a dpvideo file"; + hz_bitstream_read_blocks_free(s->framedatablocks); + } + else if (errorstring != NULL) + *errorstring = "unable to allocate memory for reading buffer"; + hz_bitstream_read_close(s->bitstream); + } + else if (errorstring != NULL) + *errorstring = "unable to open file"; + Z_Free(s); + } + else if (errorstring != NULL) + *errorstring = "unable to allocate memory for stream info structure"; + return NULL; +} + +// closes a stream +void dpvsimpledecode_close(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + if (s == NULL) + return; + if (s->videopixels) + Z_Free(s->videopixels); + if (s->sndchan != -1) + S_StopChannel (s->sndchan, true, true); + if (s->framedatablocks) + hz_bitstream_read_blocks_free(s->framedatablocks); + if (s->bitstream) + hz_bitstream_read_close(s->bitstream); + Z_Free(s); +} + +// utilitarian functions + +// returns the current error number for the stream, and resets the error +// number to DPVSIMPLEDECODEERROR_NONE +// if the supplied string pointer variable is not NULL, it will be set to the +// error message +int dpvsimpledecode_error(void *stream, const char **errorstring) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + int e; + e = s->error; + s->error = 0; + if (errorstring) + { + switch (e) + { + case DPVSIMPLEDECODEERROR_NONE: + *errorstring = "no error"; + break; + case DPVSIMPLEDECODEERROR_EOF: + *errorstring = "end of file reached (this is not an error)"; + break; + case DPVSIMPLEDECODEERROR_READERROR: + *errorstring = "read error (corrupt or incomplete file)"; + break; + case DPVSIMPLEDECODEERROR_SOUNDBUFFERTOOSMALL: + *errorstring = "sound buffer is too small for decoding frame (please allocate it as large as dpvsimpledecode_getneededsoundbufferlength suggests)"; + break; + case DPVSIMPLEDECODEERROR_INVALIDRMASK: + *errorstring = "invalid red bits mask"; + break; + case DPVSIMPLEDECODEERROR_INVALIDGMASK: + *errorstring = "invalid green bits mask"; + break; + case DPVSIMPLEDECODEERROR_INVALIDBMASK: + *errorstring = "invalid blue bits mask"; + break; + case DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP: + *errorstring = "color bit masks overlap"; + break; + case DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP: + *errorstring = "color masks too big for specified bytes per pixel"; + break; + case DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP: + *errorstring = "unsupported bytes per pixel (must be 2 for 16bit, or 4 for 32bit)"; + break; + default: + *errorstring = "unknown error"; + break; + } + } + return e; +} + +// returns the width of the image data +unsigned int dpvsimpledecode_getwidth(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + return s->info_imagewidth; +} + +// returns the height of the image data +unsigned int dpvsimpledecode_getheight(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + return s->info_imageheight; +} + +// returns the framerate of the stream +double dpvsimpledecode_getframerate(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + return s->info_framerate; +} + +// return aspect ratio of the stream +double dpvsimpledecode_getaspectratio(void *stream) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + return s->info_aspectratio; +} + +static int dpvsimpledecode_convertpixels(dpvsimpledecodestream_t *s, void *imagedata, int imagebytesperrow) +{ + unsigned int a, x, y, width, height; + unsigned int Rloss, Rmask, Rshift, Gloss, Gmask, Gshift, Bloss, Bmask, Bshift; + unsigned int *in; + + width = s->info_imagewidth; + height = s->info_imageheight; + + Rloss = s->info_imageRloss; + Rmask = s->info_imageRmask; + Rshift = s->info_imageRshift; + Gloss = s->info_imageGloss; + Gmask = s->info_imageGmask; + Gshift = s->info_imageGshift; + Bloss = s->info_imageBloss; + Bmask = s->info_imageBmask; + Bshift = s->info_imageBshift; + + in = s->videopixels; + if (s->info_imagebpp == 4) + { + unsigned int *outrow; + for (y = 0;y < height;y++) + { + outrow = (unsigned int *)((unsigned char *)imagedata + y * imagebytesperrow); + for (x = 0;x < width;x++) + { + a = *in++; + outrow[x] = (((a >> Rloss) & Rmask) << Rshift) | (((a >> Gloss) & Gmask) << Gshift) | (((a >> Bloss) & Bmask) << Bshift); + } + } + } + else + { + unsigned short *outrow; + for (y = 0;y < height;y++) + { + outrow = (unsigned short *)((unsigned char *)imagedata + y * imagebytesperrow); + if (Rloss == 19 && Gloss == 10 && Bloss == 3 && Rshift == 11 && Gshift == 5 && Bshift == 0) + { + // optimized + for (x = 0;x < width;x++) + { + a = *in++; + outrow[x] = ((a >> 8) & 0xF800) | ((a >> 5) & 0x07E0) | ((a >> 3) & 0x001F); + } + } + else + { + for (x = 0;x < width;x++) + { + a = *in++; + outrow[x] = (((a >> Rloss) & Rmask) << Rshift) | (((a >> Gloss) & Gmask) << Gshift) | (((a >> Bloss) & Bmask) << Bshift); + } + } + } + } + return s->error; +} + +static int dpvsimpledecode_decompressimage(dpvsimpledecodestream_t *s) +{ + int i, a, b, colors, g, x1, y1, bw, bh, width, height, palettebits; + unsigned int palette[256], *outrow, *out; + g = BLOCKSIZE; + width = s->info_imagewidth; + height = s->info_imageheight; + for (y1 = 0;y1 < height;y1 += g) + { + outrow = s->videopixels + y1 * width; + bh = g; + if (y1 + bh > height) + bh = height - y1; + for (x1 = 0;x1 < width;x1 += g) + { + out = outrow + x1; + bw = g; + if (x1 + bw > width) + bw = width - x1; + if (hz_bitstream_read_bit(s->framedatablocks)) + { + // updated block + palettebits = hz_bitstream_read_bits(s->framedatablocks, 3); + colors = 1 << palettebits; + for (i = 0;i < colors;i++) + palette[i] = hz_bitstream_read_bits(s->framedatablocks, 24); + if (palettebits) + { + for (b = 0;b < bh;b++, out += width) + for (a = 0;a < bw;a++) + out[a] = palette[hz_bitstream_read_bits(s->framedatablocks, palettebits)]; + } + else + { + for (b = 0;b < bh;b++, out += width) + for (a = 0;a < bw;a++) + out[a] = palette[0]; + } + } + } + } + return s->error; +} + +// decodes a video frame to the supplied output pixels +int dpvsimpledecode_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) +{ + dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; + unsigned int framedatasize; + char t[4]; + s->error = DPVSIMPLEDECODEERROR_NONE; + if (dpvsimpledecode_setpixelformat(s, Rmask, Gmask, Bmask, bytesperpixel)) + return s->error; + + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 8); + hz_bitstream_read_bytes(s->framedatablocks, t, 4); + if (memcmp(t, "VID0", 4)) + { + if (t[0] == 0) + return (s->error = DPVSIMPLEDECODEERROR_EOF); + else + return (s->error = DPVSIMPLEDECODEERROR_READERROR); + } + framedatasize = hz_bitstream_read_int(s->framedatablocks); + hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, framedatasize); + if (dpvsimpledecode_decompressimage(s)) + return s->error; + + dpvsimpledecode_convertpixels(s, imagedata, imagebytesperrow); + return s->error; +} diff --git a/app/jni/dpvsimpledecode.h b/app/jni/dpvsimpledecode.h new file mode 100644 index 0000000..0b0ac35 --- /dev/null +++ b/app/jni/dpvsimpledecode.h @@ -0,0 +1,49 @@ + +#ifndef DPVSIMPLEDECODE_H +#define DPVSIMPLEDECODE_H + +#include "cl_video.h" + +#define DPVSIMPLEDECODEERROR_NONE 0 +#define DPVSIMPLEDECODEERROR_EOF 1 +#define DPVSIMPLEDECODEERROR_READERROR 2 +#define DPVSIMPLEDECODEERROR_SOUNDBUFFERTOOSMALL 3 +#define DPVSIMPLEDECODEERROR_INVALIDRMASK 4 +#define DPVSIMPLEDECODEERROR_INVALIDGMASK 5 +#define DPVSIMPLEDECODEERROR_INVALIDBMASK 6 +#define DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP 7 +#define DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP 8 +#define DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP 9 + +// opening and closing streams + +// opens a stream +void *dpvsimpledecode_open(clvideo_t *video, char *filename, const char **errorstring); + +// closes a stream +void dpvsimpledecode_close(void *stream); + +// utilitarian functions + +// returns the current error number for the stream, and resets the error +// number to DPVDECODEERROR_NONE +// if the supplied string pointer variable is not NULL, it will be set to the +// error message +int dpvsimpledecode_error(void *stream, const char **errorstring); + +// returns the width of the image data +unsigned int dpvsimpledecode_getwidth(void *stream); + +// returns the height of the image data +unsigned int dpvsimpledecode_getheight(void *stream); + +// returns the framerate of the stream +double dpvsimpledecode_getframerate(void *stream); + +// returns aspect ratio of the stream +double dpvsimpledecode_getaspectratio(void *stream); + +// decodes a video frame to the supplied output pixels +int dpvsimpledecode_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); + +#endif diff --git a/app/jni/draw.h b/app/jni/draw.h new file mode 100644 index 0000000..316a696 --- /dev/null +++ b/app/jni/draw.h @@ -0,0 +1,207 @@ +/* +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. + +*/ + +// draw.h -- these are the only functions outside the refresh allowed +// to touch the vid buffer + +#ifndef DRAW_H +#define DRAW_H + +// FIXME: move this stuff to cl_screen +typedef struct cachepic_s +{ + // size of pic + int width, height; + // this flag indicates that it should be loaded and unloaded on demand + int autoload; + // texture flags to upload with + int texflags; + // texture may be freed after a while + int lastusedframe; + // renderer texture to use + rtexture_t *tex; + // used for hash lookups + struct cachepic_s *chain; + // flags - CACHEPICFLAG_NEWPIC for example + unsigned int flags; + // has alpha? + qboolean hasalpha; + // name of pic + char name[MAX_QPATH]; + // allow to override/free the texture + qboolean allow_free_tex; +} +cachepic_t; + +typedef enum cachepicflags_e +{ + CACHEPICFLAG_NOTPERSISTENT = 1, + CACHEPICFLAG_QUIET = 2, + CACHEPICFLAG_NOCOMPRESSION = 4, + CACHEPICFLAG_NOCLAMP = 8, + CACHEPICFLAG_NEWPIC = 16, // disables matching texflags check, because a pic created with Draw_NewPic should not be subject to that + CACHEPICFLAG_MIPMAP = 32, + CACHEPICFLAG_NEAREST = 64 // force nearest filtering instead of linear +} +cachepicflags_t; + +void Draw_Init (void); +void Draw_Frame (void); +cachepic_t *Draw_CachePic_Flags (const char *path, unsigned int cachepicflags); +cachepic_t *Draw_CachePic (const char *path); // standard function with no options, used throughout engine +// create or update a pic's image +cachepic_t *Draw_NewPic(const char *picname, int width, int height, int alpha, unsigned char *pixels); +// free the texture memory used by a pic +void Draw_FreePic(const char *picname); + +// a triangle mesh.. +// each vertex is 3 floats +// each texcoord is 2 floats +// each color is 4 floats +typedef struct drawqueuemesh_s +{ + rtexture_t *texture; + int num_triangles; + int num_vertices; + int *data_element3i; + unsigned short *data_element3s; + float *data_vertex3f; + float *data_texcoord2f; + float *data_color4f; +} +drawqueuemesh_t; + +enum drawqueue_drawflag_e { +DRAWFLAG_NORMAL, +DRAWFLAG_ADDITIVE, +DRAWFLAG_MODULATE, +DRAWFLAG_2XMODULATE, +DRAWFLAG_SCREEN, +DRAWFLAG_NUMFLAGS, +DRAWFLAG_MASK = 0xFF, // ONLY R_BeginPolygon() +DRAWFLAG_MIPMAP = 0x100, // ONLY R_BeginPolygon() +DRAWFLAG_NOGAMMA = 0x200 // ONLY R_DrawQSuperPic() +}; +#define DRAWFLAGS_BLEND 0xFF /* this matches all blending flags */ + +typedef struct ft2_settings_s +{ + float scale, voffset; + // cvar parameters (only read on loadfont command) + int antialias, hinting; + float outline, blur, shadowx, shadowy, shadowz; +} ft2_settings_t; + +#define MAX_FONT_SIZES 16 +#define MAX_FONT_FALLBACKS 3 +typedef struct dp_font_s +{ + rtexture_t *tex; + float width_of[256]; // width_of[0] == max width of any char; 1.0f is base width (1/16 of texture width); therefore, all widths have to be <= 1 (does not include scale) + float maxwidth; // precalculated max width of the font (includes scale) + char texpath[MAX_QPATH]; + char title[MAX_QPATH]; + + int req_face; // requested face index, usually 0 + float req_sizes[MAX_FONT_SIZES]; // sizes to render the font with, 0 still defaults to 16 (backward compatibility when loadfont doesn't get a size parameter) and -1 = disabled + char fallbacks[MAX_FONT_FALLBACKS][MAX_QPATH]; + int fallback_faces[MAX_FONT_FALLBACKS]; + struct ft2_font_s *ft2; + + ft2_settings_t settings; +} +dp_font_t; + +typedef struct dp_fonts_s +{ + dp_font_t *f; + int maxsize; +} +dp_fonts_t; +extern dp_fonts_t dp_fonts; + +#define MAX_FONTS 16 // fonts at the start +#define FONTS_EXPAND 8 // fonts grow when no free slots +#define FONT_DEFAULT (&dp_fonts.f[0]) // should be fixed width +#define FONT_CONSOLE (&dp_fonts.f[1]) // REALLY should be fixed width (ls!) +#define FONT_SBAR (&dp_fonts.f[2]) // must be fixed width +#define FONT_NOTIFY (&dp_fonts.f[3]) // free +#define FONT_CHAT (&dp_fonts.f[4]) // free +#define FONT_CENTERPRINT (&dp_fonts.f[5]) // free +#define FONT_INFOBAR (&dp_fonts.f[6]) // free +#define FONT_MENU (&dp_fonts.f[7]) // should be fixed width +#define FONT_USER(i) (&dp_fonts.f[8+i]) // userdefined fonts +#define MAX_USERFONTS (dp_fonts.maxsize - 8) + +// shared color tag printing constants +#define STRING_COLOR_TAG '^' +#define STRING_COLOR_DEFAULT 7 +#define STRING_COLOR_DEFAULT_STR "^7" +#define STRING_COLOR_RGB_TAG_CHAR 'x' +#define STRING_COLOR_RGB_TAG "^x" + +// all of these functions will set r_defdef.draw2dstage if not in 2D rendering mode (and of course prepare for 2D rendering in that case) + +// draw an image (or a filled rectangle if pic == NULL) +void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags); +// draw a rotated image +void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags); +// draw a filled rectangle (slightly faster than DrawQ_Pic with pic = NULL) +void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags); +// draw a text string, +// with optional color tag support, +// returns final unclipped x coordinate +// if outcolor is provided the initial color is read from it, and it is updated at the end with the new value at the end of the text (not at the end of the clipped part) +// the color is tinted by the provided base color +// if r_textshadow is not zero, an additional instance of the text is drawn first at an offset with an inverted shade of gray (black text produces a white shadow, brightly colored text produces a black shadow) +extern float DrawQ_Color[4]; +float DrawQ_String(float x, float y, const char *text, size_t maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt); +float DrawQ_String_Scale(float x, float y, const char *text, size_t maxlen, float sizex, float sizey, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt); +float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt); +float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth); +float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); +float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); +// draw a very fancy pic (per corner texcoord/color control), the order is tl, tr, bl, br +void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags); +// draw a triangle mesh +void DrawQ_Mesh(drawqueuemesh_t *mesh, int flags, qboolean hasalpha); +// set the clipping area +void DrawQ_SetClipArea(float x, float y, float width, float height); +// reset the clipping area +void DrawQ_ResetClipArea(void); +// draw a line +void DrawQ_Line(float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags); +// draw a lot of lines (call R_Mesh_PrepareVertices_Generic first) +void DrawQ_Lines(float width, int numlines, int flags, qboolean hasalpha); +// draw a line loop +void DrawQ_LineLoop(drawqueuemesh_t *mesh, int flags); +// resets r_refdef.draw2dstage +void DrawQ_Finish(void); +void DrawQ_ProcessDrawFlag(int flags, qboolean alpha); // sets GL_DepthMask and GL_BlendFunc +void DrawQ_RecalcView(void); // use this when changing r_refdef.view.* from e.g. csqc + +rtexture_t *Draw_GetPicTexture(cachepic_t *pic); + +void R_DrawGamma(void); + +extern rtexturepool_t *drawtexturepool; // used by ft2.c + +#endif + diff --git a/app/jni/filematch.c b/app/jni/filematch.c new file mode 100644 index 0000000..9971167 --- /dev/null +++ b/app/jni/filematch.c @@ -0,0 +1,204 @@ + +#ifdef WIN32 +#include +#else +#include +#endif + +#include "quakedef.h" + +// LordHavoc: some portable directory listing code I wrote for lmp2pcx, now used in darkplaces to load id1/*.pak and such... + +int matchpattern(const char *in, const char *pattern, int caseinsensitive) +{ + return matchpattern_with_separator(in, pattern, caseinsensitive, "/\\:", false); +} + +// wildcard_least_one: if true * matches 1 or more characters +// if false * matches 0 or more characters +int matchpattern_with_separator(const char *in, const char *pattern, int caseinsensitive, const char *separators, qboolean wildcard_least_one) +{ + int c1, c2; + while (*pattern) + { + switch (*pattern) + { + case 0: + return 1; // end of pattern + case '?': // match any single character + if (*in == 0 || strchr(separators, *in)) + return 0; // no match + in++; + pattern++; + break; + case '*': // match anything until following string + if(wildcard_least_one) + { + if (*in == 0 || strchr(separators, *in)) + return 0; // no match + in++; + } + pattern++; + while (*in) + { + if (strchr(separators, *in)) + break; + // see if pattern matches at this offset + if (matchpattern_with_separator(in, pattern, caseinsensitive, separators, wildcard_least_one)) + return 1; + // nope, advance to next offset + in++; + } + break; + default: + if (*in != *pattern) + { + if (!caseinsensitive) + return 0; // no match + c1 = *in; + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + c2 = *pattern; + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + if (c1 != c2) + return 0; // no match + } + in++; + pattern++; + break; + } + } + if (*in) + return 0; // reached end of pattern but not end of input + return 1; // success +} + +// a little strings system +void stringlistinit(stringlist_t *list) +{ + memset(list, 0, sizeof(*list)); +} + +void stringlistfreecontents(stringlist_t *list) +{ + int i; + for (i = 0;i < list->numstrings;i++) + { + if (list->strings[i]) + Z_Free(list->strings[i]); + list->strings[i] = NULL; + } + list->numstrings = 0; + list->maxstrings = 0; + if (list->strings) + Z_Free(list->strings); + list->strings = NULL; +} + +void stringlistappend(stringlist_t *list, const char *text) +{ + size_t textlen; + char **oldstrings; + + if (list->numstrings >= list->maxstrings) + { + oldstrings = list->strings; + list->maxstrings += 4096; + list->strings = (char **) Z_Malloc(list->maxstrings * sizeof(*list->strings)); + if (list->numstrings) + memcpy(list->strings, oldstrings, list->numstrings * sizeof(*list->strings)); + if (oldstrings) + Z_Free(oldstrings); + } + textlen = strlen(text) + 1; + list->strings[list->numstrings] = (char *) Z_Malloc(textlen); + memcpy(list->strings[list->numstrings], text, textlen); + list->numstrings++; +} + +static int stringlistsort_cmp(const void *a, const void *b) +{ + return strcasecmp(*(const char **)a, *(const char **)b); +} + +void stringlistsort(stringlist_t *list, qboolean uniq) +{ + int i, j; + if(list->numstrings < 1) + return; + qsort(&list->strings[0], list->numstrings, sizeof(list->strings[0]), stringlistsort_cmp); + if(uniq) + { + // i: the item to read + // j: the item last written + for (i = 1, j = 0; i < list->numstrings; ++i) + { + char *save; + if(!strcasecmp(list->strings[i], list->strings[j])) + continue; + ++j; + save = list->strings[j]; + list->strings[j] = list->strings[i]; + list->strings[i] = save; + } + for(i = j+1; i < list->numstrings; ++i) + { + if (list->strings[i]) + Z_Free(list->strings[i]); + } + list->numstrings = j+1; + } +} + +// operating system specific code +static void adddirentry(stringlist_t *list, const char *path, const char *name) +{ + if (strcmp(name, ".") && strcmp(name, "..")) + { + char temp[MAX_OSPATH]; + dpsnprintf( temp, sizeof( temp ), "%s%s", path, name ); + stringlistappend(list, temp); + } +} +#ifdef WIN32 +void listdirectory(stringlist_t *list, const char *basepath, const char *path) +{ + int i; + char pattern[4096], *c; + WIN32_FIND_DATA n_file; + HANDLE hFile; + strlcpy (pattern, basepath, sizeof(pattern)); + strlcat (pattern, path, sizeof (pattern)); + strlcat (pattern, "*", sizeof (pattern)); + // ask for the directory listing handle + hFile = FindFirstFile(pattern, &n_file); + if(hFile == INVALID_HANDLE_VALUE) + return; + do { + adddirentry(list, path, n_file.cFileName); + } while (FindNextFile(hFile, &n_file) != 0); + FindClose(hFile); + + // convert names to lowercase because windows does not care, but pattern matching code often does + for (i = 0;i < list->numstrings;i++) + for (c = list->strings[i];*c;c++) + if (*c >= 'A' && *c <= 'Z') + *c += 'a' - 'A'; +} +#else +void listdirectory(stringlist_t *list, const char *basepath, const char *path) +{ + char fullpath[MAX_OSPATH]; + DIR *dir; + struct dirent *ent; + dpsnprintf(fullpath, sizeof(fullpath), "%s%s", basepath, *path ? path : "./"); + dir = opendir(fullpath); + if (!dir) + return; + while ((ent = readdir(dir))) + adddirentry(list, path, ent->d_name); + closedir(dir); +} +#endif + diff --git a/app/jni/fractalnoise.c b/app/jni/fractalnoise.c new file mode 100644 index 0000000..5d68d19 --- /dev/null +++ b/app/jni/fractalnoise.c @@ -0,0 +1,226 @@ + +#include "quakedef.h" + +void fractalnoise(unsigned char *noise, int size, int startgrid) +{ + int x, y, g, g2, amplitude, min, max, size1 = size - 1, sizepower, gridpower; + int *noisebuf; +#define n(x,y) noisebuf[((y)&size1)*size+((x)&size1)] + + for (sizepower = 0;(1 << sizepower) < size;sizepower++); + if (size != (1 << sizepower)) + { + Con_Printf("fractalnoise: size must be power of 2\n"); + return; + } + + for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++); + if (startgrid != (1 << gridpower)) + { + Con_Printf("fractalnoise: grid must be power of 2\n"); + return; + } + + startgrid = bound(0, startgrid, size); + + amplitude = 0xFFFF; // this gets halved before use + noisebuf = (int *)Mem_Alloc(tempmempool, size*size*sizeof(int)); + memset(noisebuf, 0, size*size*sizeof(int)); + + for (g2 = startgrid;g2;g2 >>= 1) + { + // brownian motion (at every smaller level there is random behavior) + amplitude >>= 1; + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + n(x,y) += (rand()&litude); + + g = g2 >> 1; + if (g) + { + // subdivide, diamond-square algorithm (really this has little to do with squares) + // diamond + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + n(x+g,y+g) = (n(x,y) + n(x+g2,y) + n(x,y+g2) + n(x+g2,y+g2)) >> 2; + // square + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + { + n(x+g,y) = (n(x,y) + n(x+g2,y) + n(x+g,y-g) + n(x+g,y+g)) >> 2; + n(x,y+g) = (n(x,y) + n(x,y+g2) + n(x-g,y+g) + n(x+g,y+g)) >> 2; + } + } + } + // find range of noise values + min = max = 0; + for (y = 0;y < size;y++) + for (x = 0;x < size;x++) + { + if (n(x,y) < min) min = n(x,y); + if (n(x,y) > max) max = n(x,y); + } + max -= min; + max++; + // normalize noise and copy to output + for (y = 0;y < size;y++) + for (x = 0;x < size;x++) + *noise++ = (unsigned char) (((n(x,y) - min) * 256) / max); + Mem_Free(noisebuf); +#undef n +} + +// unnormalized, used for explosions mainly, does not allocate/free memory (hence the name quick) +void fractalnoisequick(unsigned char *noise, int size, int startgrid) +{ + int x, y, g, g2, amplitude, size1 = size - 1, sizepower, gridpower; +#define n(x,y) noise[((y)&size1)*size+((x)&size1)] + + for (sizepower = 0;(1 << sizepower) < size;sizepower++); + if (size != (1 << sizepower)) + { + Con_Printf("fractalnoise: size must be power of 2\n"); + return; + } + + for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++); + if (startgrid != (1 << gridpower)) + { + Con_Printf("fractalnoise: grid must be power of 2\n"); + return; + } + + startgrid = bound(0, startgrid, size); + + amplitude = 255; // this gets halved before use + memset(noise, 0, size*size); + + for (g2 = startgrid;g2;g2 >>= 1) + { + // brownian motion (at every smaller level there is random behavior) + amplitude >>= 1; + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + n(x,y) += (rand()&litude); + + g = g2 >> 1; + if (g) + { + // subdivide, diamond-square algorithm (really this has little to do with squares) + // diamond + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + n(x+g,y+g) = (unsigned char) (((int) n(x,y) + (int) n(x+g2,y) + (int) n(x,y+g2) + (int) n(x+g2,y+g2)) >> 2); + // square + for (y = 0;y < size;y += g2) + for (x = 0;x < size;x += g2) + { + n(x+g,y) = (unsigned char) (((int) n(x,y) + (int) n(x+g2,y) + (int) n(x+g,y-g) + (int) n(x+g,y+g)) >> 2); + n(x,y+g) = (unsigned char) (((int) n(x,y) + (int) n(x,y+g2) + (int) n(x-g,y+g) + (int) n(x+g,y+g)) >> 2); + } + } + } +#undef n +} + +#define NOISE_SIZE 256 +#define NOISE_MASK 255 +float noise4f(float x, float y, float z, float w) +{ + int i; + int index[4][2]; + float frac[4][2]; + float v[4]; + static float noisetable[NOISE_SIZE]; + static int r[NOISE_SIZE]; + // LordHavoc: this is inspired by code I saw in Quake3, however I think my + // version is much cleaner and substantially faster as well + // + // the following changes were made: + // 1. for the permutation indexing (r[] array in this code) I substituted + // the ^ operator (which never overflows) for the original addition and + // masking code, this should not have any effect on quality. + // 2. removed the outermost randomization array lookup. + // (it really wasn't necessary, it's fine if X indexes the array + // directly without permutation indexing) + // 3. reimplemented the blending using frac[] arrays rather than a macro. + // (the original macro read one parameter twice - not good) + // 4. cleaned up the code by using 4 nested loops to make it read nicer + // (but then I unrolled it completely for speed, it still looks nicer). + if (!noisetable[0]) + { + // noisetable is a random-ish series of float values in +/- 1 range + for (i = 0;i < NOISE_SIZE;i++) + noisetable[i] = (rand() / (double)RAND_MAX) * 2 - 1; + // r is a remapping table to make each dimension of the index have different indexing behavior + for (i = 0;i < NOISE_SIZE;i++) + r[i] = (int)(rand() * (double)NOISE_SIZE / ((double)RAND_MAX + 1)) & NOISE_MASK; + // that & is only needed if RAND_MAX is > the range of double, which isn't the case on most platforms + } + frac[0][1] = x - floor(x);index[0][0] = ((int)floor(x)) & NOISE_MASK; + frac[1][1] = y - floor(y);index[1][0] = ((int)floor(y)) & NOISE_MASK; + frac[2][1] = z - floor(z);index[2][0] = ((int)floor(z)) & NOISE_MASK; + frac[3][1] = w - floor(w);index[3][0] = ((int)floor(w)) & NOISE_MASK; + for (i = 0;i < 4;i++) + frac[i][0] = 1 - frac[i][1]; + for (i = 0;i < 4;i++) + index[i][1] = (index[i][0] < NOISE_SIZE - 1) ? (index[i][0] + 1) : 0; +#if 1 + // short version + v[0] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]); + v[1] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]); + v[2] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]); + v[3] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]); + return frac[3][0] * (frac[2][0] * v[0] + frac[2][1] * v[1]) + frac[3][1] * (frac[2][0] * v[2] + frac[2][1] * v[3]); +#elif 1 + // longer version + v[ 0] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]]; + v[ 1] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]; + v[ 2] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]]; + v[ 3] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]; + v[ 4] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]]; + v[ 5] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]; + v[ 6] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]]; + v[ 7] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]; + v[ 8] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]]; + v[ 9] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]; + v[10] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]]; + v[11] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]; + v[12] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]]; + v[13] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]; + v[14] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]]; + v[15] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]; + v[16] = frac[0][0] * v[ 0] + frac[0][1] * v[ 1]; + v[17] = frac[0][0] * v[ 2] + frac[0][1] * v[ 3]; + v[18] = frac[0][0] * v[ 4] + frac[0][1] * v[ 5]; + v[19] = frac[0][0] * v[ 6] + frac[0][1] * v[ 7]; + v[20] = frac[0][0] * v[ 8] + frac[0][1] * v[ 9]; + v[21] = frac[0][0] * v[10] + frac[0][1] * v[11]; + v[22] = frac[0][0] * v[12] + frac[0][1] * v[13]; + v[23] = frac[0][0] * v[14] + frac[0][1] * v[15]; + v[24] = frac[1][0] * v[16] + frac[1][1] * v[17]; + v[25] = frac[1][0] * v[18] + frac[1][1] * v[19]; + v[26] = frac[1][0] * v[20] + frac[1][1] * v[21]; + v[27] = frac[1][0] * v[22] + frac[1][1] * v[23]; + v[28] = frac[2][0] * v[24] + frac[2][1] * v[25]; + v[29] = frac[2][0] * v[26] + frac[2][1] * v[27]; + return frac[3][0] * v[28] + frac[3][1] * v[29]; +#else + // the algorithm... + for (l = 0;l < 2;l++) + { + for (k = 0;k < 2;k++) + { + for (j = 0;j < 2;j++) + { + for (i = 0;i < 2;i++) + v[l][k][j][i] = noisetable[r[r[r[index[l][3]] ^ index[k][2]] ^ index[j][1]] ^ index[i][0]]; + v[l][k][j][2] = frac[0][0] * v[l][k][j][0] + frac[0][1] * v[l][k][j][1]; + } + v[l][k][2][2] = frac[1][0] * v[l][k][0][2] + frac[1][1] * v[l][k][1][2]; + } + v[l][2][2][2] = frac[2][0] * v[l][0][2][2] + frac[2][1] * v[l][1][2][2]; + } + v[2][2][2][2] = frac[3][0] * v[0][2][2][2] + frac[3][1] * v[1][2][2][2]; +#endif +} diff --git a/app/jni/fs.c b/app/jni/fs.c new file mode 100644 index 0000000..be8d70f --- /dev/null +++ b/app/jni/fs.c @@ -0,0 +1,3995 @@ +/* + DarkPlaces file system + + Copyright (C) 2003-2006 Mathieu Olivier + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + +#ifdef __APPLE__ +// include SDL for IPHONEOS code +# include +# if TARGET_OS_IPHONE +# include +# endif +#endif + +#include +#include + +#ifdef WIN32 +# include +# include +# include +# include +# include +#else +# include +# include +# include +#endif + +#include "quakedef.h" +#include "thread.h" + +#include "fs.h" +#include "wad.h" + +// Win32 requires us to add O_BINARY, but the other OSes don't have it +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +// In case the system doesn't support the O_NONBLOCK flag +#ifndef O_NONBLOCK +# define O_NONBLOCK 0 +#endif + +// largefile support for Win32 +#ifdef WIN32 +#undef lseek +# define lseek _lseeki64 +#endif + +// suppress deprecated warnings +#if _MSC_VER >= 1400 +# define read _read +# define write _write +# define close _close +# define unlink _unlink +# define dup _dup +#endif + +/** \page fs File System + +All of Quake's data access is through a hierchal file system, but the contents +of the file system can be transparently merged from several sources. + +The "base directory" is the path to the directory holding the quake.exe and +all game directories. The sys_* files pass this to host_init in +quakeparms_t->basedir. This can be overridden with the "-basedir" command +line parm to allow code debugging in a different directory. The base +directory is only used during filesystem initialization. + +The "game directory" is the first tree on the search path and directory that +all generated files (savegames, screenshots, demos, config files) will be +saved to. This can be overridden with the "-game" command line parameter. +The game directory can never be changed while quake is executing. This is a +precaution against having a malicious server instruct clients to write files +over areas they shouldn't. + +*/ + + +/* +============================================================================= + +CONSTANTS + +============================================================================= +*/ + +// Magic numbers of a ZIP file (big-endian format) +#define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4" +#define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2" +#define ZIP_END_HEADER 0x504B0506 // "PK\5\6" + +// Other constants for ZIP files +#define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF) +#define ZIP_END_CDIR_SIZE 22 +#define ZIP_CDIR_CHUNK_BASE_SIZE 46 +#define ZIP_LOCAL_CHUNK_BASE_SIZE 30 + +#ifdef LINK_TO_ZLIB +#include + +#define qz_inflate inflate +#define qz_inflateEnd inflateEnd +#define qz_inflateInit2_ inflateInit2_ +#define qz_inflateReset inflateReset +#define qz_deflateInit2_ deflateInit2_ +#define qz_deflateEnd deflateEnd +#define qz_deflate deflate +#define Z_MEMLEVEL_DEFAULT 8 +#else + +// Zlib constants (from zlib.h) +#define Z_SYNC_FLUSH 2 +#define MAX_WBITS 15 +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define ZLIB_VERSION "1.2.3" + +#define Z_BINARY 0 +#define Z_DEFLATED 8 +#define Z_MEMLEVEL_DEFAULT 8 + +#define Z_NULL 0 +#define Z_DEFAULT_COMPRESSION (-1) +#define Z_NO_FLUSH 0 +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 + +// Uncomment the following line if the zlib DLL you have still uses +// the 1.1.x series calling convention on Win32 (WINAPI) +//#define ZLIB_USES_WINAPI + + +/* +============================================================================= + +TYPES + +============================================================================= +*/ + +/*! Zlib stream (from zlib.h) + * \warning: some pointers we don't use directly have + * been cast to "void*" for a matter of simplicity + */ +typedef struct +{ + unsigned char *next_in; ///< next input byte + unsigned int avail_in; ///< number of bytes available at next_in + unsigned long total_in; ///< total nb of input bytes read so far + + unsigned char *next_out; ///< next output byte should be put there + unsigned int avail_out; ///< remaining free space at next_out + unsigned long total_out; ///< total nb of bytes output so far + + char *msg; ///< last error message, NULL if no error + void *state; ///< not visible by applications + + void *zalloc; ///< used to allocate the internal state + void *zfree; ///< used to free the internal state + void *opaque; ///< private data object passed to zalloc and zfree + + int data_type; ///< best guess about the data type: ascii or binary + unsigned long adler; ///< adler32 value of the uncompressed data + unsigned long reserved; ///< reserved for future use +} z_stream; +#endif + + +/// inside a package (PAK or PK3) +#define QFILE_FLAG_PACKED (1 << 0) +/// file is compressed using the deflate algorithm (PK3 only) +#define QFILE_FLAG_DEFLATED (1 << 1) +/// file is actually already loaded data +#define QFILE_FLAG_DATA (1 << 2) +/// real file will be removed on close +#define QFILE_FLAG_REMOVE (1 << 3) + +#define FILE_BUFF_SIZE 2048 +typedef struct +{ + z_stream zstream; + size_t comp_length; ///< length of the compressed file + size_t in_ind, in_len; ///< input buffer current index and length + size_t in_position; ///< position in the compressed file + unsigned char input [FILE_BUFF_SIZE]; +} ztoolkit_t; + +struct qfile_s +{ + int flags; + int handle; ///< file descriptor + fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode) + fs_offset_t position; ///< current position in the file + fs_offset_t offset; ///< offset into the package (0 if external file) + int ungetc; ///< single stored character from ungetc, cleared to EOF when read + + // Contents buffer + fs_offset_t buff_ind, buff_len; ///< buffer current index and length + unsigned char buff [FILE_BUFF_SIZE]; + + ztoolkit_t* ztk; ///< For zipped files. + + const unsigned char *data; ///< For data files. + + const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise +}; + + +// ------ PK3 files on disk ------ // + +// You can get the complete ZIP format description from PKWARE website + +typedef struct pk3_endOfCentralDir_s +{ + unsigned int signature; + unsigned short disknum; + unsigned short cdir_disknum; ///< number of the disk with the start of the central directory + unsigned short localentries; ///< number of entries in the central directory on this disk + unsigned short nbentries; ///< total number of entries in the central directory on this disk + unsigned int cdir_size; ///< size of the central directory + unsigned int cdir_offset; ///< with respect to the starting disk number + unsigned short comment_size; + fs_offset_t prepended_garbage; +} pk3_endOfCentralDir_t; + + +// ------ PAK files on disk ------ // +typedef struct dpackfile_s +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct dpackheader_s +{ + char id[4]; + int dirofs; + int dirlen; +} dpackheader_t; + + +/*! \name Packages in memory + * @{ + */ +/// the offset in packfile_t is the true contents offset +#define PACKFILE_FLAG_TRUEOFFS (1 << 0) +/// file compressed using the deflate algorithm +#define PACKFILE_FLAG_DEFLATED (1 << 1) +/// file is a symbolic link +#define PACKFILE_FLAG_SYMLINK (1 << 2) + +typedef struct packfile_s +{ + char name [MAX_QPATH]; + int flags; + fs_offset_t offset; + fs_offset_t packsize; ///< size in the package + fs_offset_t realsize; ///< real file size (uncompressed) +} packfile_t; + +typedef struct pack_s +{ + char filename [MAX_OSPATH]; + char shortname [MAX_QPATH]; + int handle; + int ignorecase; ///< PK3 ignores case + int numfiles; + qboolean vpack; + packfile_t *files; +} pack_t; +//@} + +/// Search paths for files (including packages) +typedef struct searchpath_s +{ + // only one of filename / pack will be used + char filename[MAX_OSPATH]; + pack_t *pack; + struct searchpath_s *next; +} searchpath_t; + + +/* +============================================================================= + +FUNCTION PROTOTYPES + +============================================================================= +*/ + +void FS_Dir_f(void); +void FS_Ls_f(void); +void FS_Which_f(void); + +static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet); +static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack, + fs_offset_t offset, fs_offset_t packsize, + fs_offset_t realsize, int flags); + + +/* +============================================================================= + +VARIABLES + +============================================================================= +*/ + +mempool_t *fs_mempool; +void *fs_mutex = NULL; + +searchpath_t *fs_searchpaths = NULL; +const char *const fs_checkgamedir_missing = "missing"; + +#define MAX_FILES_IN_PACK 65536 + +char fs_userdir[MAX_OSPATH]; +char fs_gamedir[MAX_OSPATH]; +char fs_basedir[MAX_OSPATH]; +static pack_t *fs_selfpack = NULL; + +// list of active game directories (empty if not running a mod) +int fs_numgamedirs = 0; +char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH]; + +// list of all gamedirs with modinfo.txt +gamedir_t *fs_all_gamedirs = NULL; +int fs_all_gamedirs_count = 0; + +cvar_t scr_screenshot_name = {CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"}; +cvar_t fs_empty_files_in_pack_mark_deletions = {0, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"}; +cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"}; + + +/* +============================================================================= + +PRIVATE FUNCTIONS - PK3 HANDLING + +============================================================================= +*/ + +#ifndef LINK_TO_ZLIB +// Functions exported from zlib +#if defined(WIN32) && defined(ZLIB_USES_WINAPI) +# define ZEXPORT WINAPI +#else +# define ZEXPORT +#endif + +static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush); +static int (ZEXPORT *qz_inflateEnd) (z_stream* strm); +static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size); +static int (ZEXPORT *qz_inflateReset) (z_stream* strm); +static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size); +static int (ZEXPORT *qz_deflateEnd) (z_stream* strm); +static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush); +#endif + +#define qz_inflateInit2(strm, windowBits) \ + qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream)) + +#ifndef LINK_TO_ZLIB +// qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) + +static dllfunction_t zlibfuncs[] = +{ + {"inflate", (void **) &qz_inflate}, + {"inflateEnd", (void **) &qz_inflateEnd}, + {"inflateInit2_", (void **) &qz_inflateInit2_}, + {"inflateReset", (void **) &qz_inflateReset}, + {"deflateInit2_", (void **) &qz_deflateInit2_}, + {"deflateEnd", (void **) &qz_deflateEnd}, + {"deflate", (void **) &qz_deflate}, + {NULL, NULL} +}; + +/// Handle for Zlib DLL +static dllhandle_t zlib_dll = NULL; +#endif + +#ifdef WIN32 +static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath); +static dllfunction_t shfolderfuncs[] = +{ + {"SHGetFolderPathA", (void **) &qSHGetFolderPath}, + {NULL, NULL} +}; +static const char* shfolderdllnames [] = +{ + "shfolder.dll", // IE 4, or Win NT and higher + NULL +}; +static dllhandle_t shfolder_dll = NULL; + +const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}}; +#define qREFKNOWNFOLDERID const GUID * +#define qKF_FLAG_CREATE 0x8000 +#define qKF_FLAG_NO_ALIAS 0x1000 +static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); +static dllfunction_t shell32funcs[] = +{ + {"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath}, + {NULL, NULL} +}; +static const char* shell32dllnames [] = +{ + "shell32.dll", // Vista and higher + NULL +}; +static dllhandle_t shell32_dll = NULL; + +static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit); +static void (WINAPI *qCoUninitialize)(void); +static void (WINAPI *qCoTaskMemFree)(LPVOID pv); +static dllfunction_t ole32funcs[] = +{ + {"CoInitializeEx", (void **) &qCoInitializeEx}, + {"CoUninitialize", (void **) &qCoUninitialize}, + {"CoTaskMemFree", (void **) &qCoTaskMemFree}, + {NULL, NULL} +}; +static const char* ole32dllnames [] = +{ + "ole32.dll", // 2000 and higher + NULL +}; +static dllhandle_t ole32_dll = NULL; +#endif + +/* +==================== +PK3_CloseLibrary + +Unload the Zlib DLL +==================== +*/ +static void PK3_CloseLibrary (void) +{ +#ifndef LINK_TO_ZLIB + Sys_UnloadLibrary (&zlib_dll); +#endif +} + + +/* +==================== +PK3_OpenLibrary + +Try to load the Zlib DLL +==================== +*/ +static qboolean PK3_OpenLibrary (void) +{ +#ifdef LINK_TO_ZLIB + return true; +#else + const char* dllnames [] = + { +#if defined(WIN32) +# ifdef ZLIB_USES_WINAPI + "zlibwapi.dll", + "zlib.dll", +# else + "zlib1.dll", +# endif +#elif defined(MACOSX) + "libz.dylib", +#else + "libz.so.1", + "libz.so", +#endif + NULL + }; + + // Already loaded? + if (zlib_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs); +#endif +} + +/* +==================== +FS_HasZlib + +See if zlib is available +==================== +*/ +qboolean FS_HasZlib(void) +{ +#ifdef LINK_TO_ZLIB + return true; +#else + PK3_OpenLibrary(); // to be safe + return (zlib_dll != 0); +#endif +} + +/* +==================== +PK3_GetEndOfCentralDir + +Extract the end of the central directory from a PK3 package +==================== +*/ +static qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd) +{ + fs_offset_t filesize, maxsize; + unsigned char *buffer, *ptr; + int ind; + + // Get the package size + filesize = lseek (packhandle, 0, SEEK_END); + if (filesize < ZIP_END_CDIR_SIZE) + return false; + + // Load the end of the file in memory + if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE) + maxsize = filesize; + else + maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE; + buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize); + lseek (packhandle, filesize - maxsize, SEEK_SET); + if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize) + { + Mem_Free (buffer); + return false; + } + + // Look for the end of central dir signature around the end of the file + maxsize -= ZIP_END_CDIR_SIZE; + ptr = &buffer[maxsize]; + ind = 0; + while (BuffBigLong (ptr) != ZIP_END_HEADER) + { + if (ind == maxsize) + { + Mem_Free (buffer); + return false; + } + + ind++; + ptr--; + } + + memcpy (eocd, ptr, ZIP_END_CDIR_SIZE); + eocd->signature = LittleLong (eocd->signature); + eocd->disknum = LittleShort (eocd->disknum); + eocd->cdir_disknum = LittleShort (eocd->cdir_disknum); + eocd->localentries = LittleShort (eocd->localentries); + eocd->nbentries = LittleShort (eocd->nbentries); + eocd->cdir_size = LittleLong (eocd->cdir_size); + eocd->cdir_offset = LittleLong (eocd->cdir_offset); + eocd->comment_size = LittleShort (eocd->comment_size); + eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files + eocd->cdir_offset += eocd->prepended_garbage; + + Mem_Free (buffer); + + return true; +} + + +/* +==================== +PK3_BuildFileList + +Extract the file list from a PK3 file +==================== +*/ +static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd) +{ + unsigned char *central_dir, *ptr; + unsigned int ind; + fs_offset_t remaining; + + // Load the central directory in memory + central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size); + lseek (pack->handle, eocd->cdir_offset, SEEK_SET); + if(read (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size) + { + Mem_Free (central_dir); + return -1; + } + + // Extract the files properties + // The parsing is done "by hand" because some fields have variable sizes and + // the constant part isn't 4-bytes aligned, which makes the use of structs difficult + remaining = eocd->cdir_size; + pack->numfiles = 0; + ptr = central_dir; + for (ind = 0; ind < eocd->nbentries; ind++) + { + fs_offset_t namesize, count; + + // Checking the remaining size + if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE) + { + Mem_Free (central_dir); + return -1; + } + remaining -= ZIP_CDIR_CHUNK_BASE_SIZE; + + // Check header + if (BuffBigLong (ptr) != ZIP_CDIR_HEADER) + { + Mem_Free (central_dir); + return -1; + } + + namesize = BuffLittleShort (&ptr[28]); // filename length + + // Check encryption, compression, and attributes + // 1st uint8 : general purpose bit flag + // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?)) + // + // LordHavoc: bit 3 would be a problem if we were scanning the archive + // but is not a problem in the central directory where the values are + // always real. + // + // bit 3 seems to always be set by the standard Mac OSX zip maker + // + // 2nd uint8 : external file attributes + // Check bits 3 (file is a directory) and 5 (file is a volume (?)) + if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0) + { + // Still enough bytes for the name? + if (remaining < namesize || namesize >= (int)sizeof (*pack->files)) + { + Mem_Free (central_dir); + return -1; + } + + // WinZip doesn't use the "directory" attribute, so we need to check the name directly + if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/') + { + char filename [sizeof (pack->files[0].name)]; + fs_offset_t offset, packsize, realsize; + int flags; + + // Extract the name (strip it if necessary) + namesize = min(namesize, (int)sizeof (filename) - 1); + memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize); + filename[namesize] = '\0'; + + if (BuffLittleShort (&ptr[10])) + flags = PACKFILE_FLAG_DEFLATED; + else + flags = 0; + offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage); + packsize = (unsigned int)BuffLittleLong (&ptr[20]); + realsize = (unsigned int)BuffLittleLong (&ptr[24]); + + switch(ptr[5]) // C_VERSION_MADE_BY_1 + { + case 3: // UNIX_ + case 2: // VMS_ + case 16: // BEOS_ + if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000) + // can't use S_ISLNK here, as this has to compile on non-UNIX too + flags |= PACKFILE_FLAG_SYMLINK; + break; + } + + FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags); + } + } + + // Skip the name, additionnal field, and comment + // 1er uint16 : extra field length + // 2eme uint16 : file comment length + count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]); + ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count; + remaining -= count; + } + + // If the package is empty, central_dir is NULL here + if (central_dir != NULL) + Mem_Free (central_dir); + return pack->numfiles; +} + + +/* +==================== +FS_LoadPackPK3 + +Create a package entry associated with a PK3 file +==================== +*/ +static pack_t *FS_LoadPackPK3FromFD (const char *packfile, int packhandle, qboolean silent) +{ + pk3_endOfCentralDir_t eocd; + pack_t *pack; + int real_nb_files; + + if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd)) + { + if(!silent) + Con_Printf ("%s is not a PK3 file\n", packfile); + close(packhandle); + return NULL; + } + + // Multi-volume ZIP archives are NOT allowed + if (eocd.disknum != 0 || eocd.cdir_disknum != 0) + { + Con_Printf ("%s is a multi-volume ZIP archive\n", packfile); + close(packhandle); + return NULL; + } + + // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535 + // since eocd.nbentries is an unsigned 16 bits integer +#if MAX_FILES_IN_PACK < 65535 + if (eocd.nbentries > MAX_FILES_IN_PACK) + { + Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries); + close(packhandle); + return NULL; + } +#endif + + // Create a package structure in memory + pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); + pack->ignorecase = true; // PK3 ignores case + strlcpy (pack->filename, packfile, sizeof (pack->filename)); + pack->handle = packhandle; + pack->numfiles = eocd.nbentries; + pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t)); + + real_nb_files = PK3_BuildFileList (pack, &eocd); + if (real_nb_files < 0) + { + Con_Printf ("%s is not a valid PK3 file\n", packfile); + close(pack->handle); + Mem_Free(pack); + return NULL; + } + + Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files); + return pack; +} +static pack_t *FS_LoadPackPK3 (const char *packfile) +{ + int packhandle; + packhandle = FS_SysOpenFD (packfile, "rb", false); + if (packhandle < 0) + return NULL; + return FS_LoadPackPK3FromFD(packfile, packhandle, false); +} + + +/* +==================== +PK3_GetTrueFileOffset + +Find where the true file data offset is +==================== +*/ +static qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack) +{ + unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE]; + fs_offset_t count; + + // Already found? + if (pfile->flags & PACKFILE_FLAG_TRUEOFFS) + return true; + + // Load the local file description + lseek (pack->handle, pfile->offset, SEEK_SET); + count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE); + if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER) + { + Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename); + return false; + } + + // Skip name and extra field + pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE; + + pfile->flags |= PACKFILE_FLAG_TRUEOFFS; + return true; +} + + +/* +============================================================================= + +OTHER PRIVATE FUNCTIONS + +============================================================================= +*/ + + +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack, + fs_offset_t offset, fs_offset_t packsize, + fs_offset_t realsize, int flags) +{ + int (*strcmp_funct) (const char* str1, const char* str2); + int left, right, middle; + packfile_t *pfile; + + strcmp_funct = pack->ignorecase ? strcasecmp : strcmp; + + // Look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + while (left <= right) + { + int diff; + + middle = (left + right) / 2; + diff = strcmp_funct (pack->files[middle].name, name); + + // If we found the file, there's a problem + if (!diff) + Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name); + + // If we're too far in the list + if (diff > 0) + right = middle - 1; + else + left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile)); + pack->numfiles++; + + strlcpy (pfile->name, name, sizeof (pfile->name)); + pfile->offset = offset; + pfile->packsize = packsize; + pfile->realsize = realsize; + pfile->flags = flags; + + return pfile; +} + + +/* +============ +FS_CreatePath + +Only used for FS_OpenRealFile. +============ +*/ +void FS_CreatePath (char *path) +{ + char *ofs, save; + + for (ofs = path+1 ; *ofs ; ofs++) + { + if (*ofs == '/' || *ofs == '\\') + { + // create the directory + save = *ofs; + *ofs = 0; + FS_mkdir (path); + *ofs = save; + } + } +} + + +/* +============ +FS_Path_f + +============ +*/ +static void FS_Path_f (void) +{ + searchpath_t *s; + + Con_Print("Current search path:\n"); + for (s=fs_searchpaths ; s ; s=s->next) + { + if (s->pack) + { + if(s->pack->vpack) + Con_Printf("%sdir (virtual pack)\n", s->pack->filename); + else + Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles); + } + else + Con_Printf("%s\n", s->filename); + } +} + + +/* +================= +FS_LoadPackPAK +================= +*/ +/*! Takes an explicit (not game tree related) path to a pak file. + *Loads the header and directory, adding the files at the beginning + *of the list so they override previous pack files. + */ +static pack_t *FS_LoadPackPAK (const char *packfile) +{ + dpackheader_t header; + int i, numpackfiles; + int packhandle; + pack_t *pack; + dpackfile_t *info; + + packhandle = FS_SysOpenFD(packfile, "rb", false); + if (packhandle < 0) + return NULL; + if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header)) + { + Con_Printf ("%s is not a packfile\n", packfile); + close(packhandle); + return NULL; + } + if (memcmp(header.id, "PACK", 4)) + { + Con_Printf ("%s is not a packfile\n", packfile); + close(packhandle); + return NULL; + } + header.dirofs = LittleLong (header.dirofs); + header.dirlen = LittleLong (header.dirlen); + + if (header.dirlen % sizeof(dpackfile_t)) + { + Con_Printf ("%s has an invalid directory size\n", packfile); + close(packhandle); + return NULL; + } + + numpackfiles = header.dirlen / sizeof(dpackfile_t); + + if (numpackfiles > MAX_FILES_IN_PACK) + { + Con_Printf ("%s has %i files\n", packfile, numpackfiles); + close(packhandle); + return NULL; + } + + info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles); + lseek (packhandle, header.dirofs, SEEK_SET); + if(header.dirlen != read (packhandle, (void *)info, header.dirlen)) + { + Con_Printf("%s is an incomplete PAK, not loading\n", packfile); + Mem_Free(info); + close(packhandle); + return NULL; + } + + pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); + pack->ignorecase = false; // PAK is case sensitive + strlcpy (pack->filename, packfile, sizeof (pack->filename)); + pack->handle = packhandle; + pack->numfiles = 0; + pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t)); + + // parse the directory + for (i = 0;i < numpackfiles;i++) + { + fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos); + fs_offset_t size = (unsigned int)LittleLong (info[i].filelen); + + FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS); + } + + Mem_Free(info); + + Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles); + return pack; +} + +/* +==================== +FS_LoadPackVirtual + +Create a package entry associated with a directory file +==================== +*/ +static pack_t *FS_LoadPackVirtual (const char *dirname) +{ + pack_t *pack; + pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); + pack->vpack = true; + pack->ignorecase = false; + strlcpy (pack->filename, dirname, sizeof(pack->filename)); + pack->handle = -1; + pack->numfiles = -1; + pack->files = NULL; + Con_DPrintf("Added packfile %s (virtual pack)\n", dirname); + return pack; +} + +/* +================ +FS_AddPack_Fullpath +================ +*/ +/*! Adds the given pack to the search path. + * The pack type is autodetected by the file extension. + * + * Returns true if the file was successfully added to the + * search path or if it was already included. + * + * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of + * plain directories. + * + */ +static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = FS_FileExtension(pakfile); + size_t l; + + for(search = fs_searchpaths; search; search = search->next) + { + if(search->pack && !strcasecmp(search->pack->filename, pakfile)) + { + if(already_loaded) + *already_loaded = true; + return true; // already loaded + } + } + + if(already_loaded) + *already_loaded = false; + + if(!strcasecmp(ext, "pk3dir")) + pak = FS_LoadPackVirtual (pakfile); + else if(!strcasecmp(ext, "pak")) + pak = FS_LoadPackPAK (pakfile); + else if(!strcasecmp(ext, "pk3")) + pak = FS_LoadPackPK3 (pakfile); + else + Con_Printf("\"%s\" does not have a pack extension\n", pakfile); + + if(pak) + { + strlcpy(pak->shortname, shortname, sizeof(pak->shortname)); + + //Con_DPrintf(" Registered pack with short name %s\n", shortname); + if(keep_plain_dirs) + { + // find the first item whose next one is a pack or NULL + searchpath_t *insertion_point = 0; + if(fs_searchpaths && !fs_searchpaths->pack) + { + insertion_point = fs_searchpaths; + for(;;) + { + if(!insertion_point->next) + break; + if(insertion_point->next->pack) + break; + insertion_point = insertion_point->next; + } + } + // If insertion_point is NULL, this means that either there is no + // item in the list yet, or that the very first item is a pack. In + // that case, we want to insert at the beginning... + if(!insertion_point) + { + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + search->next = fs_searchpaths; + fs_searchpaths = search; + } + else + // otherwise we want to append directly after insertion_point. + { + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + search->next = insertion_point->next; + insertion_point->next = search; + } + } + else + { + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + search->next = fs_searchpaths; + fs_searchpaths = search; + } + search->pack = pak; + if(pak->vpack) + { + dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile); + // if shortname ends with "pk3dir", strip that suffix to make it just "pk3" + // same goes for the name inside the pack structure + l = strlen(pak->shortname); + if(l >= 7) + if(!strcasecmp(pak->shortname + l - 7, ".pk3dir")) + pak->shortname[l - 3] = 0; + l = strlen(pak->filename); + if(l >= 7) + if(!strcasecmp(pak->filename + l - 7, ".pk3dir")) + pak->filename[l - 3] = 0; + } + return true; + } + else + { + Con_Printf("unable to load pak \"%s\"\n", pakfile); + return false; + } +} + + +/* +================ +FS_AddPack +================ +*/ +/*! Adds the given pack to the search path and searches for it in the game path. + * The pack type is autodetected by the file extension. + * + * Returns true if the file was successfully added to the + * search path or if it was already included. + * + * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of + * plain directories. + */ +qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs) +{ + char fullpath[MAX_OSPATH]; + int index; + searchpath_t *search; + + if(already_loaded) + *already_loaded = false; + + // then find the real name... + search = FS_FindFile(pakfile, &index, true); + if(!search || search->pack) + { + Con_Printf("could not find pak \"%s\"\n", pakfile); + return false; + } + + dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile); + + return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs); +} + + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads and adds pak1.pak pak2.pak ... +================ +*/ +static void FS_AddGameDirectory (const char *dir) +{ + int i; + stringlist_t list; + searchpath_t *search; + + strlcpy (fs_gamedir, dir, sizeof (fs_gamedir)); + + stringlistinit(&list); + listdirectory(&list, "", dir); + stringlistsort(&list, false); + + // add any PAK package in the directory + for (i = 0;i < list.numstrings;i++) + { + if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak")) + { + FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false); + } + } + + // add any PK3 package in the directory + for (i = 0;i < list.numstrings;i++) + { + if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir")) + { + FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false); + } + } + + stringlistfreecontents(&list); + + // Add the directory to the search path + // (unpacked files have the priority over packed files) + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + strlcpy (search->filename, dir, sizeof (search->filename)); + search->next = fs_searchpaths; + fs_searchpaths = search; +} + + +/* +================ +FS_AddGameHierarchy +================ +*/ +static void FS_AddGameHierarchy (const char *dir) +{ + char vabuf[1024]; + // Add the common game directory + FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir)); + + if (*fs_userdir) + FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir)); +} + + +/* +============ +FS_FileExtension +============ +*/ +const char *FS_FileExtension (const char *in) +{ + const char *separator, *backslash, *colon, *dot; + + separator = strrchr(in, '/'); + backslash = strrchr(in, '\\'); + if (!separator || separator < backslash) + separator = backslash; + colon = strrchr(in, ':'); + if (!separator || separator < colon) + separator = colon; + + dot = strrchr(in, '.'); + if (dot == NULL || (separator && (dot < separator))) + return ""; + + return dot + 1; +} + + +/* +============ +FS_FileWithoutPath +============ +*/ +const char *FS_FileWithoutPath (const char *in) +{ + const char *separator, *backslash, *colon; + + separator = strrchr(in, '/'); + backslash = strrchr(in, '\\'); + if (!separator || separator < backslash) + separator = backslash; + colon = strrchr(in, ':'); + if (!separator || separator < colon) + separator = colon; + return separator ? separator + 1 : in; +} + + +/* +================ +FS_ClearSearchPath +================ +*/ +static void FS_ClearSearchPath (void) +{ + // unload all packs and directory information, close all pack files + // (if a qfile is still reading a pack it won't be harmed because it used + // dup() to get its own handle already) + while (fs_searchpaths) + { + searchpath_t *search = fs_searchpaths; + fs_searchpaths = search->next; + if (search->pack && search->pack != fs_selfpack) + { + if(!search->pack->vpack) + { + // close the file + close(search->pack->handle); + // free any memory associated with it + if (search->pack->files) + Mem_Free(search->pack->files); + } + Mem_Free(search->pack); + } + Mem_Free(search); + } +} + +static void FS_AddSelfPack(void) +{ + if(fs_selfpack) + { + searchpath_t *search; + search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); + search->next = fs_searchpaths; + search->pack = fs_selfpack; + fs_searchpaths = search; + } +} + + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan (void) +{ + int i; + qboolean fs_modified = false; + qboolean reset = false; + char gamedirbuf[MAX_INPUTLINE]; + char vabuf[1024]; + + if (fs_searchpaths) + reset = true; + FS_ClearSearchPath(); + + // automatically activate gamemode for the gamedirs specified + if (reset) + COM_ChangeGameTypeForGameDirs(); + + // add the game-specific paths + // gamedirname1 (typically id1) + FS_AddGameHierarchy (gamedirname1); + // update the com_modname (used for server info) + if (gamedirname2 && gamedirname2[0]) + strlcpy(com_modname, gamedirname2, sizeof(com_modname)); + else + strlcpy(com_modname, gamedirname1, sizeof(com_modname)); + + // add the game-specific path, if any + // (only used for mission packs and the like, which should set fs_modified) + if (gamedirname2 && gamedirname2[0]) + { + fs_modified = true; + FS_AddGameHierarchy (gamedirname2); + } + + // -game + // Adds basedir/gamedir as an override game + // LordHavoc: now supports multiple -game directories + // set the com_modname (reported in server info) + *gamedirbuf = 0; + for (i = 0;i < fs_numgamedirs;i++) + { + fs_modified = true; + FS_AddGameHierarchy (fs_gamedirs[i]); + // update the com_modname (used server info) + strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname)); + if(i) + strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf)); + else + strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf)); + } + Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it + + // add back the selfpack as new first item + FS_AddSelfPack(); + + // set the default screenshot name to either the mod name or the + // gamemode screenshot name + if (strcmp(com_modname, gamedirname1)) + Cvar_SetQuick (&scr_screenshot_name, com_modname); + else + Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname); + + if((i = COM_CheckParm("-modname")) && i < com_argc - 1) + strlcpy(com_modname, com_argv[i+1], sizeof(com_modname)); + + // If "-condebug" is in the command line, remove the previous log file + if (COM_CheckParm ("-condebug") != 0) + unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir)); + + // look for the pop.lmp file and set registered to true if it is found + if (FS_FileExists("gfx/pop.lmp")) + Cvar_Set ("registered", "1"); + switch(gamemode) + { + case GAME_NORMAL: + case GAME_HIPNOTIC: + case GAME_ROGUE: + if (!registered.integer) + { + if (fs_modified) + Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n"); + else + Con_Print("Playing shareware version.\n"); + } + else + Con_Print("Playing registered version.\n"); + break; + case GAME_STEELSTORM: + if (registered.integer) + Con_Print("Playing registered version.\n"); + else + Con_Print("Playing shareware version.\n"); + break; + default: + break; + } + + // unload all wads so that future queries will return the new data + W_UnloadAll(); +} + +static void FS_Rescan_f(void) +{ + FS_Rescan(); +} + +/* +================ +FS_ChangeGameDirs +================ +*/ +extern qboolean vid_opened; +qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing) +{ + int i; + const char *p; + + if (fs_numgamedirs == numgamedirs) + { + for (i = 0;i < numgamedirs;i++) + if (strcasecmp(fs_gamedirs[i], gamedirs[i])) + break; + if (i == numgamedirs) + return true; // already using this set of gamedirs, do nothing + } + + if (numgamedirs > MAX_GAMEDIRS) + { + if (complain) + Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS); + return false; // too many gamedirs + } + + for (i = 0;i < numgamedirs;i++) + { + // if string is nasty, reject it + p = FS_CheckGameDir(gamedirs[i]); + if(!p) + { + if (complain) + Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]); + return false; // nasty gamedirs + } + if(p == fs_checkgamedir_missing && failmissing) + { + if (complain) + Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]); + return false; // missing gamedirs + } + } + + Host_SaveConfig(); + + fs_numgamedirs = numgamedirs; + for (i = 0;i < fs_numgamedirs;i++) + strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i])); + + // reinitialize filesystem to detect the new paks + FS_Rescan(); + + if (cls.demoplayback) + { + CL_Disconnect_f(); + cls.demonum = 0; + } + + // unload all sounds so they will be reloaded from the new files as needed + S_UnloadAllSounds_f(); + + // close down the video subsystem, it will start up again when the config finishes... + VID_Stop(); + vid_opened = false; + + // restart the video subsystem after the config is executed + Cbuf_InsertText("\nloadconfig\nvid_restart\n\n"); + + return true; +} + +/* +================ +FS_GameDir_f +================ +*/ +static void FS_GameDir_f (void) +{ + int i; + int numgamedirs; + char gamedirs[MAX_GAMEDIRS][MAX_QPATH]; + + if (Cmd_Argc() < 2) + { + Con_Printf("gamedirs active:"); + for (i = 0;i < fs_numgamedirs;i++) + Con_Printf(" %s", fs_gamedirs[i]); + Con_Printf("\n"); + return; + } + + numgamedirs = Cmd_Argc() - 1; + if (numgamedirs > MAX_GAMEDIRS) + { + Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS); + return; + } + + for (i = 0;i < numgamedirs;i++) + strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i])); + + if ((cls.state == ca_connected && !cls.demoplayback) || sv.active) + { + // actually, changing during game would work fine, but would be stupid + Con_Printf("Can not change gamedir while client is connected or server is running!\n"); + return; + } + + // halt demo playback to close the file + CL_Disconnect(); + + FS_ChangeGameDirs(numgamedirs, gamedirs, true, true); +} + +static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength) +{ + qboolean success; + qfile_t *f; + stringlist_t list; + fs_offset_t n; + char vabuf[1024]; + + stringlistinit(&list); + listdirectory(&list, gamedir, ""); + success = list.numstrings > 0; + stringlistfreecontents(&list); + + if(success) + { + f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false); + if(f) + { + n = FS_Read (f, buf, buflength - 1); + if(n >= 0) + buf[n] = 0; + else + *buf = 0; + FS_Close(f); + } + else + *buf = 0; + return buf; + } + + return NULL; +} + +/* +================ +FS_CheckGameDir +================ +*/ +const char *FS_CheckGameDir(const char *gamedir) +{ + const char *ret; + char buf[8192]; + char vabuf[1024]; + + if (FS_CheckNastyPath(gamedir, true)) + return NULL; + + ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf)); + if(ret) + { + if(!*ret) + { + // get description from basedir + ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf)); + if(ret) + return ret; + return ""; + } + return ret; + } + + ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf)); + if(ret) + return ret; + + return fs_checkgamedir_missing; +} + +static void FS_ListGameDirs(void) +{ + stringlist_t list, list2; + int i; + const char *info; + char vabuf[1024]; + + fs_all_gamedirs_count = 0; + if(fs_all_gamedirs) + Mem_Free(fs_all_gamedirs); + + stringlistinit(&list); + listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), ""); + listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), ""); + stringlistsort(&list, false); + + stringlistinit(&list2); + for(i = 0; i < list.numstrings; ++i) + { + if(i) + if(!strcmp(list.strings[i-1], list.strings[i])) + continue; + info = FS_CheckGameDir(list.strings[i]); + if(!info) + continue; + if(info == fs_checkgamedir_missing) + continue; + if(!*info) + continue; + stringlistappend(&list2, list.strings[i]); + } + stringlistfreecontents(&list); + + fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs)); + for(i = 0; i < list2.numstrings; ++i) + { + info = FS_CheckGameDir(list2.strings[i]); + // all this cannot happen any more, but better be safe than sorry + if(!info) + continue; + if(info == fs_checkgamedir_missing) + continue; + if(!*info) + continue; + strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name)); + strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description)); + ++fs_all_gamedirs_count; + } +} + +/* +#ifdef WIN32 +#pragma comment(lib, "shell32.lib") +#include +#endif +*/ + +/* +================ +FS_Init_SelfPack +================ +*/ +void FS_Init_SelfPack (void) +{ + PK3_OpenLibrary (); + fs_mempool = Mem_AllocPool("file management", 0, NULL); + if(com_selffd >= 0) + { + fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true); + if(fs_selfpack) + { + char *buf, *q; + const char *p; + FS_AddSelfPack(); + buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL); + if(buf) + { + const char **new_argv; + int i = 0; + int args_left = 256; + new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2)); + if(com_argc == 0) + { + new_argv[0] = "dummy"; + com_argc = 1; + } + else + { + memcpy((char *)(&new_argv[0]), &com_argv[0], sizeof(*com_argv) * com_argc); + } + p = buf; + while(COM_ParseToken_Console(&p)) + { + size_t sz = strlen(com_token) + 1; // shut up clang + if(i >= args_left) + break; + q = (char *)Mem_Alloc(fs_mempool, sz); + strlcpy(q, com_token, sz); + new_argv[com_argc + i] = q; + ++i; + } + new_argv[i+com_argc] = NULL; + com_argv = new_argv; + com_argc = com_argc + i; + } + Mem_Free(buf); + } + } +} + +static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize) +{ +#if defined(__IPHONEOS__) + if (userdirmode == USERDIRMODE_HOME) + { + // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode + // fs_userdir stores configurations to the Documents folder of the app + strlcpy(userdir, maxlength, "../Documents/"); + return 1; + } + return -1; + +#elif defined(WIN32) + char *homedir; +#if _MSC_VER >= 1400 + size_t homedirlen; +#endif + TCHAR mydocsdir[MAX_PATH + 1]; + wchar_t *savedgamesdirw; + char savedgamesdir[MAX_OSPATH]; + int fd; + char vabuf[1024]; + + userdir[0] = 0; + switch(userdirmode) + { + default: + return -1; + case USERDIRMODE_NOHOME: + strlcpy(userdir, fs_basedir, userdirsize); + break; + case USERDIRMODE_MYGAMES: + if (!shfolder_dll) + Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs); + mydocsdir[0] = 0; + if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK) + { + dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname); + break; + } +#if _MSC_VER >= 1400 + _dupenv_s(&homedir, &homedirlen, "USERPROFILE"); + if(homedir) + { + dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); + free(homedir); + break; + } +#else + homedir = getenv("USERPROFILE"); + if(homedir) + { + dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); + break; + } +#endif + return -1; + case USERDIRMODE_SAVEDGAMES: + if (!shell32_dll) + Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs); + if (!ole32_dll) + Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs); + if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize) + { + savedgamesdir[0] = 0; + qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED); +/* +#ifdef __cplusplus + if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) +#else + if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) +#endif +*/ + if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) + { + memset(savedgamesdir, 0, sizeof(savedgamesdir)); +#if _MSC_VER >= 1400 + wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1); +#else + wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1); +#endif + qCoTaskMemFree(savedgamesdirw); + } + qCoUninitialize(); + if (savedgamesdir[0]) + { + dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname); + break; + } + } + return -1; + } +#else + int fd; + char *homedir; + char vabuf[1024]; + userdir[0] = 0; + switch(userdirmode) + { + default: + return -1; + case USERDIRMODE_NOHOME: + strlcpy(userdir, fs_basedir, userdirsize); + break; + case USERDIRMODE_HOME: + homedir = NULL;//Android + if(homedir) + { + dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); + break; + } + return -1; + case USERDIRMODE_SAVEDGAMES: + homedir = NULL;//Android + if(homedir) + { +#ifdef MACOSX + dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname); +#else + // the XDG say some files would need to go in: + // XDG_CONFIG_HOME (or ~/.config/%s/) + // XDG_DATA_HOME (or ~/.local/share/%s/) + // XDG_CACHE_HOME (or ~/.cache/%s/) + // and also search the following global locations if defined: + // XDG_CONFIG_DIRS (normally /etc/xdg/%s/) + // XDG_DATA_DIRS (normally /usr/share/%s/) + // this would be too complicated... + return -1; +#endif + break; + } + return -1; + } +#endif + + +#ifdef WIN32 + // historical behavior... + if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1")) + return 0; // don't bother checking if the basedir folder is writable, it's annoying... unless it is Quake on Windows where NOHOME is the default preferred and we have to check for an error case +#endif + + // see if we can write to this path (note: won't create path) +#ifdef WIN32 + // no access() here, we must try to open the file for appending + fd = FS_SysOpenFD(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false); + if(fd >= 0) + close(fd); +#else + // on Unix, we don't need to ACTUALLY attempt to open the file + if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0) + fd = 1; + else + fd = 0; +#endif + if(fd >= 0) + { + return 1; // good choice - the path exists and is writable + } + else + { + if (userdirmode == USERDIRMODE_NOHOME) + return -1; // path usually already exists, we lack permissions + else + return 0; // probably good - failed to write but maybe we need to create path + } +} + +/* +================ +FS_Init +================ +*/ +void FS_Init (void) +{ + const char *p; + int i; + + *fs_basedir = 0; + *fs_userdir = 0; + *fs_gamedir = 0; + + // -basedir + // Overrides the system supplied base directory (under GAMENAME) +// COMMANDLINEOPTION: Filesystem: -basedir chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1) + i = COM_CheckParm ("-basedir"); + if (i && i < com_argc-1) + { + strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir)); + i = (int)strlen (fs_basedir); + if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/')) + fs_basedir[i-1] = 0; + } + else + { +// If the base directory is explicitly defined by the compilation process +#ifdef DP_FS_BASEDIR + strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir)); +#elif defined(MACOSX) + // FIXME: is there a better way to find the directory outside the .app, without using Objective-C? + if (strstr(com_argv[0], ".app/")) + { + char *split; + strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir)); + split = strstr(fs_basedir, ".app/"); + if (split) + { + struct stat statresult; + char vabuf[1024]; + // truncate to just after the .app/ + split[5] = 0; + // see if gamedir exists in Resources + if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0) + { + // found gamedir inside Resources, use it + strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir)); + } + else + { + // no gamedir found in Resources, gamedir is probably + // outside the .app, remove .app part of path + while (split > fs_basedir && *split != '/') + split--; + *split = 0; + } + } + } +#endif + } + + // make sure the appending of a path separator won't create an unterminated string + memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2); + // add a path separator to the end of the basedir if it lacks one + if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\') + strlcat(fs_basedir, "/", sizeof(fs_basedir)); + + // Add the personal game directory + if((i = COM_CheckParm("-userdir")) && i < com_argc - 1) + dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]); + else if (COM_CheckParm("-nohome")) + *fs_userdir = 0; // user wants roaming installation, no userdir + else + { + int dirmode; + int highestuserdirmode = USERDIRMODE_COUNT - 1; + int preferreduserdirmode = USERDIRMODE_COUNT - 1; + int userdirstatus[USERDIRMODE_COUNT]; +#ifdef WIN32 + // historical behavior... + if (!strcmp(gamedirname1, "id1")) + preferreduserdirmode = USERDIRMODE_NOHOME; +#endif + // check what limitations the user wants to impose + if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME; + if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES; + if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES; + // gather the status of the possible userdirs + for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++) + { + userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir)); + if (userdirstatus[dirmode] == 1) + Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir); + else if (userdirstatus[dirmode] == 0) + Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir); + else + Con_DPrintf("userdir %i (not applicable)\n", dirmode); + } + // some games may prefer writing to basedir, but if write fails we + // have to search for a real userdir... + if (preferreduserdirmode == 0 && userdirstatus[0] < 1) + preferreduserdirmode = highestuserdirmode; + // check for an existing userdir and continue using it if possible... + for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--) + if (userdirstatus[dirmode] == 1) + break; + // if no existing userdir found, make a new one... + if (dirmode == 0 && preferreduserdirmode > 0) + for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--) + if (userdirstatus[dirmode] >= 0) + break; + // and finally, we picked one... + FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir)); + Con_DPrintf("userdir %i is the winner\n", dirmode); + } + + // if userdir equal to basedir, clear it to avoid confusion later + if (!strcmp(fs_basedir, fs_userdir)) + fs_userdir[0] = 0; + + FS_ListGameDirs(); + + p = FS_CheckGameDir(gamedirname1); + if(!p || p == fs_checkgamedir_missing) + Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1); + + if(gamedirname2) + { + p = FS_CheckGameDir(gamedirname2); + if(!p || p == fs_checkgamedir_missing) + Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2); + } + + // -game + // Adds basedir/gamedir as an override game + // LordHavoc: now supports multiple -game directories + for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++) + { + if (!com_argv[i]) + continue; + if (!strcmp (com_argv[i], "-game") && i < com_argc-1) + { + i++; + p = FS_CheckGameDir(com_argv[i]); + if(!p) + Sys_Error("Nasty -game name rejected: %s", com_argv[i]); + if(p == fs_checkgamedir_missing) + Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]); + // add the gamedir to the list of active gamedirs + strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs])); + fs_numgamedirs++; + } + } + + // generate the searchpath + FS_Rescan(); + + if (Thread_HasThreads()) + fs_mutex = Thread_CreateMutex(); +} + +void FS_Init_Commands(void) +{ + Cvar_RegisterVariable (&scr_screenshot_name); + Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions); + Cvar_RegisterVariable (&cvar_fs_gamedir); + + Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)"); + Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes"); + Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)"); + Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line"); + Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line"); + Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from"); +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown (void) +{ + // close all pack files and such + // (hopefully there aren't any other open files, but they'll be cleaned up + // by the OS anyway) + FS_ClearSearchPath(); + Mem_FreePool (&fs_mempool); + PK3_CloseLibrary (); + +#ifdef WIN32 + Sys_UnloadLibrary (&shfolder_dll); + Sys_UnloadLibrary (&shell32_dll); + Sys_UnloadLibrary (&ole32_dll); +#endif + + if (fs_mutex) + Thread_DestroyMutex(fs_mutex); +} + +int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking) +{ + int handle = -1; + int mod, opt; + unsigned int ind; + qboolean dolock = false; + + // Parse the mode string + switch (mode[0]) + { + case 'r': + mod = O_RDONLY; + opt = 0; + break; + case 'w': + mod = O_WRONLY; + opt = O_CREAT | O_TRUNC; + break; + case 'a': + mod = O_WRONLY; + opt = O_CREAT | O_APPEND; + break; + default: + Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode); + return -1; + } + for (ind = 1; mode[ind] != '\0'; ind++) + { + switch (mode[ind]) + { + case '+': + mod = O_RDWR; + break; + case 'b': + opt |= O_BINARY; + break; + case 'l': + dolock = true; + break; + default: + Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n", + filepath, mode, mode[ind]); + } + } + + if (nonblocking) + opt |= O_NONBLOCK; + + if(COM_CheckParm("-readonly") && mod != O_RDONLY) + return -1; + +#ifdef WIN32 +# if _MSC_VER >= 1400 + _sopen_s(&handle, filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE); +# else + handle = _sopen (filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE); +# endif +#else + handle = open (filepath, mod | opt, 0666); + if(handle >= 0 && dolock) + { + struct flock l; + l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK); + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + if(fcntl(handle, F_SETLK, &l) == -1) + { + close(handle); + handle = -1; + } + } +#endif + + return handle; +} + +/* +==================== +FS_SysOpen + +Internal function used to create a qfile_t and open the relevant non-packed file on disk +==================== +*/ +qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking) +{ + qfile_t* file; + + file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); + file->ungetc = EOF; + file->handle = FS_SysOpenFD(filepath, mode, nonblocking); + if (file->handle < 0) + { + Mem_Free (file); + return NULL; + } + + file->filename = Mem_strdup(fs_mempool, filepath); + + file->real_length = lseek (file->handle, 0, SEEK_END); + + // For files opened in append mode, we start at the end of the file + if (mode[0] == 'a') + file->position = file->real_length; + else + lseek (file->handle, 0, SEEK_SET); + + return file; +} + + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind) +{ + packfile_t *pfile; + int dup_handle; + qfile_t* file; + + pfile = &pack->files[pack_ind]; + + // If we don't have the true offset, get it now + if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS)) + if (!PK3_GetTrueFileOffset (pfile, pack)) + return NULL; + +#ifndef LINK_TO_ZLIB + // No Zlib DLL = no compressed files + if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED)) + { + Con_Printf("WARNING: can't open the compressed file %s\n" + "You need the Zlib DLL to use compressed files\n", + pfile->name); + return NULL; + } +#endif + + // LordHavoc: lseek affects all duplicates of a handle so we do it before + // the dup() call to avoid having to close the dup_handle on error here + if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1) + { + Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n", + pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset)); + return NULL; + } + + dup_handle = dup (pack->handle); + if (dup_handle < 0) + { + Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename); + return NULL; + } + + file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); + memset (file, 0, sizeof (*file)); + file->handle = dup_handle; + file->flags = QFILE_FLAG_PACKED; + file->real_length = pfile->realsize; + file->offset = pfile->offset; + file->position = 0; + file->ungetc = EOF; + + if (pfile->flags & PACKFILE_FLAG_DEFLATED) + { + ztoolkit_t *ztk; + + file->flags |= QFILE_FLAG_DEFLATED; + + // We need some more variables + ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk)); + + ztk->comp_length = pfile->packsize; + + // Initialize zlib stream + ztk->zstream.next_in = ztk->input; + ztk->zstream.avail_in = 0; + + /* From Zlib's "unzip.c": + * + * windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK) + { + Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name); + close(dup_handle); + Mem_Free(file); + return NULL; + } + + ztk->zstream.next_out = file->buff; + ztk->zstream.avail_out = sizeof (file->buff); + + file->ztk = ztk; + } + + return file; +} + +/* +==================== +FS_CheckNastyPath + +Return true if the path should be rejected due to one of the following: +1: path elements that are non-portable +2: path elements that would allow access to files outside the game directory, + or are just not a good idea for a mod to be using. +==================== +*/ +int FS_CheckNastyPath (const char *path, qboolean isgamedir) +{ + // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless + if (!path[0]) + return 2; + + // Windows: don't allow \ in filenames (windows-only), period. + // (on Windows \ is a directory separator, but / is also supported) + if (strstr(path, "\\")) + return 1; // non-portable + + // Mac: don't allow Mac-only filenames - : is a directory separator + // instead of /, but we rely on / working already, so there's no reason to + // support a Mac-only path + // Amiga and Windows: : tries to go to root of drive + if (strstr(path, ":")) + return 1; // non-portable attempt to go to root of drive + + // Amiga: // is parent directory + if (strstr(path, "//")) + return 1; // non-portable attempt to go to parent directory + + // all: don't allow going to parent directory (../ or /../) + if (strstr(path, "..")) + return 2; // attempt to go outside the game directory + + // Windows and UNIXes: don't allow absolute paths + if (path[0] == '/') + return 2; // attempt to go outside the game directory + + // all: don't allow . characters before the last slash (it should only be used in filenames, not path elements), this catches all imaginable cases of ./, ../, .../, etc + if (strchr(path, '.')) + { + if (isgamedir) + { + // gamedir is entirely path elements, so simply forbid . entirely + return 2; + } + if (strchr(path, '.') < strrchr(path, '/')) + return 2; // possible attempt to go outside the game directory + } + + // all: forbid trailing slash on gamedir + if (isgamedir && path[strlen(path)-1] == '/') + return 2; + + // all: forbid leading dot on any filename for any reason + if (strstr(path, "/.")) + return 2; // attempt to go outside the game directory + + // after all these checks we're pretty sure it's a / separated filename + // and won't do much if any harm + return false; +} + + +/* +==================== +FS_FindFile + +Look for a file in the packages and in the filesystem + +Return the searchpath where the file was found (or NULL) +and the file index in the package if relevant +==================== +*/ +static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet) +{ + searchpath_t *search; + pack_t *pak; + + // search through the path, one element at a time + for (search = fs_searchpaths;search;search = search->next) + { + // is the element a pak file? + if (search->pack && !search->pack->vpack) + { + int (*strcmp_funct) (const char* str1, const char* str2); + int left, right, middle; + + pak = search->pack; + strcmp_funct = pak->ignorecase ? strcasecmp : strcmp; + + // Look for the file (binary search) + left = 0; + right = pak->numfiles - 1; + while (left <= right) + { + int diff; + + middle = (left + right) / 2; + diff = strcmp_funct (pak->files[middle].name, name); + + // Found it + if (!diff) + { + if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0) + { + // yes, but the first one is empty so we treat it as not being there + if (!quiet && developer_extra.integer) + Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name); + + if (index != NULL) + *index = -1; + return NULL; + } + + if (!quiet && developer_extra.integer) + Con_DPrintf("FS_FindFile: %s in %s\n", + pak->files[middle].name, pak->filename); + + if (index != NULL) + *index = middle; + return search; + } + + // If we're too far in the list + if (diff > 0) + right = middle - 1; + else + left = middle + 1; + } + } + else + { + char netpath[MAX_OSPATH]; + dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name); + if (FS_SysFileExists (netpath)) + { + if (!quiet && developer_extra.integer) + Con_DPrintf("FS_FindFile: %s\n", netpath); + + if (index != NULL) + *index = -1; + return search; + } + } + } + + if (!quiet && developer_extra.integer) + Con_DPrintf("FS_FindFile: can't find %s\n", name); + + if (index != NULL) + *index = -1; + return NULL; +} + + +/* +=========== +FS_OpenReadFile + +Look for a file in the search paths and open it in read-only mode +=========== +*/ +static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels) +{ + searchpath_t *search; + int pack_ind; + + search = FS_FindFile (filename, &pack_ind, quiet); + + // Not found? + if (search == NULL) + return NULL; + + // Found in the filesystem? + if (pack_ind < 0) + { + // this works with vpacks, so we are fine + char path [MAX_OSPATH]; + dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename); + return FS_SysOpen (path, "rb", nonblocking); + } + + // So, we found it in a package... + + // Is it a PK3 symlink? + // TODO also handle directory symlinks by parsing the whole structure... + // but heck, file symlinks are good enough for now + if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK) + { + if(symlinkLevels <= 0) + { + Con_Printf("symlink: %s: too many levels of symbolic links\n", filename); + return NULL; + } + else + { + char linkbuf[MAX_QPATH]; + fs_offset_t count; + qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind); + const char *mergeslash; + char *mergestart; + + if(!linkfile) + return NULL; + count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1); + FS_Close(linkfile); + if(count < 0) + return NULL; + linkbuf[count] = 0; + + // Now combine the paths... + mergeslash = strrchr(filename, '/'); + mergestart = linkbuf; + if(!mergeslash) + mergeslash = filename; + while(!strncmp(mergestart, "../", 3)) + { + mergestart += 3; + while(mergeslash > filename) + { + --mergeslash; + if(*mergeslash == '/') + break; + } + } + // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended + if(mergeslash == filename) + { + // Either mergeslash == filename, then we just replace the name (done below) + } + else + { + // Or, we append the name after mergeslash; + // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first + int spaceNeeded = mergeslash - filename + 1; + int spaceRemoved = mergestart - linkbuf; + if(count - spaceRemoved + spaceNeeded >= MAX_QPATH) + { + Con_DPrintf("symlink: too long path rejected\n"); + return NULL; + } + memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved); + memcpy(linkbuf, filename, spaceNeeded); + linkbuf[count - spaceRemoved + spaceNeeded] = 0; + mergestart = linkbuf; + } + if (!quiet && developer_loading.integer) + Con_DPrintf("symlink: %s -> %s\n", filename, mergestart); + if(FS_CheckNastyPath (mergestart, false)) + { + Con_DPrintf("symlink: nasty path %s rejected\n", mergestart); + return NULL; + } + return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1); + } + } + + return FS_OpenPackedFile (search->pack, pack_ind); +} + + +/* +============================================================================= + +MAIN PUBLIC FUNCTIONS + +============================================================================= +*/ + +/* +==================== +FS_OpenRealFile + +Open a file in the userpath. The syntax is the same as fopen +Used for savegame scanning in menu, and all file writing. +==================== +*/ +qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet) +{ + char real_path [MAX_OSPATH]; + + if (FS_CheckNastyPath(filepath, false)) + { + Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false"); + return NULL; + } + + dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack + + // If the file is opened in "write", "append", or "read/write" mode, + // create directories up to the file. + if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+')) + FS_CreatePath (real_path); + return FS_SysOpen (real_path, mode, false); +} + + +/* +==================== +FS_OpenVirtualFile + +Open a file. The syntax is the same as fopen +==================== +*/ +qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet) +{ + qfile_t *result = NULL; + if (FS_CheckNastyPath(filepath, false)) + { + Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false"); + return NULL; + } + + if (fs_mutex) Thread_LockMutex(fs_mutex); + result = FS_OpenReadFile (filepath, quiet, false, 16); + if (fs_mutex) Thread_UnlockMutex(fs_mutex); + return result; +} + + +/* +==================== +FS_FileFromData + +Open a file. The syntax is the same as fopen +==================== +*/ +qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet) +{ + qfile_t* file; + file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); + memset (file, 0, sizeof (*file)); + file->flags = QFILE_FLAG_DATA; + file->ungetc = EOF; + file->real_length = size; + file->data = data; + return file; +} + +/* +==================== +FS_Close + +Close a file +==================== +*/ +int FS_Close (qfile_t* file) +{ + if(file->flags & QFILE_FLAG_DATA) + { + Mem_Free(file); + return 0; + } + + if (close (file->handle)) + return EOF; + + if (file->filename) + { + if (file->flags & QFILE_FLAG_REMOVE) + remove(file->filename); + + Mem_Free((void *) file->filename); + } + + if (file->ztk) + { + qz_inflateEnd (&file->ztk->zstream); + Mem_Free (file->ztk); + } + + Mem_Free (file); + return 0; +} + +void FS_RemoveOnClose(qfile_t* file) +{ + file->flags |= QFILE_FLAG_REMOVE; +} + +/* +==================== +FS_Write + +Write "datasize" bytes into a file +==================== +*/ +fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize) +{ + fs_offset_t result; + + // If necessary, seek to the exact file position we're supposed to be + if (file->buff_ind != file->buff_len) + lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR); + + // Purge cached data + FS_Purge (file); + + // Write the buffer and update the position + result = write (file->handle, data, (fs_offset_t)datasize); + file->position = lseek (file->handle, 0, SEEK_CUR); + if (file->real_length < file->position) + file->real_length = file->position; + + if (result < 0) + return 0; + + return result; +} + + +/* +==================== +FS_Read + +Read up to "buffersize" bytes from a file +==================== +*/ +fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize) +{ + fs_offset_t count, done; + + if (buffersize == 0) + return 0; + + // Get rid of the ungetc character + if (file->ungetc != EOF) + { + ((char*)buffer)[0] = file->ungetc; + buffersize--; + file->ungetc = EOF; + done = 1; + } + else + done = 0; + + if(file->flags & QFILE_FLAG_DATA) + { + size_t left = file->real_length - file->position; + if(buffersize > left) + buffersize = left; + memcpy(buffer, file->data + file->position, buffersize); + file->position += buffersize; + return buffersize; + } + + // First, we copy as many bytes as we can from "buff" + if (file->buff_ind < file->buff_len) + { + count = file->buff_len - file->buff_ind; + count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize; + done += count; + memcpy (buffer, &file->buff[file->buff_ind], count); + file->buff_ind += count; + + buffersize -= count; + if (buffersize == 0) + return done; + } + + // NOTE: at this point, the read buffer is always empty + + // If the file isn't compressed + if (! (file->flags & QFILE_FLAG_DEFLATED)) + { + fs_offset_t nb; + + // We must take care to not read after the end of the file + count = file->real_length - file->position; + + // If we have a lot of data to get, put them directly into "buffer" + if (buffersize > sizeof (file->buff) / 2) + { + if (count > (fs_offset_t)buffersize) + count = (fs_offset_t)buffersize; + lseek (file->handle, file->offset + file->position, SEEK_SET); + nb = read (file->handle, &((unsigned char*)buffer)[done], count); + if (nb > 0) + { + done += nb; + file->position += nb; + + // Purge cached data + FS_Purge (file); + } + } + else + { + if (count > (fs_offset_t)sizeof (file->buff)) + count = (fs_offset_t)sizeof (file->buff); + lseek (file->handle, file->offset + file->position, SEEK_SET); + nb = read (file->handle, file->buff, count); + if (nb > 0) + { + file->buff_len = nb; + file->position += nb; + + // Copy the requested data in "buffer" (as much as we can) + count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize; + memcpy (&((unsigned char*)buffer)[done], file->buff, count); + file->buff_ind = count; + done += count; + } + } + + return done; + } + + // If the file is compressed, it's more complicated... + // We cycle through a few operations until we have read enough data + while (buffersize > 0) + { + ztoolkit_t *ztk = file->ztk; + int error; + + // NOTE: at this point, the read buffer is always empty + + // If "input" is also empty, we need to refill it + if (ztk->in_ind == ztk->in_len) + { + // If we are at the end of the file + if (file->position == file->real_length) + return done; + + count = (fs_offset_t)(ztk->comp_length - ztk->in_position); + if (count > (fs_offset_t)sizeof (ztk->input)) + count = (fs_offset_t)sizeof (ztk->input); + lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET); + if (read (file->handle, ztk->input, count) != count) + { + Con_Printf ("FS_Read: unexpected end of file\n"); + break; + } + + ztk->in_ind = 0; + ztk->in_len = count; + ztk->in_position += count; + } + + ztk->zstream.next_in = &ztk->input[ztk->in_ind]; + ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind); + + // Now that we are sure we have compressed data available, we need to determine + // if it's better to inflate it in "file->buff" or directly in "buffer" + + // Inflate the data in "file->buff" + if (buffersize < sizeof (file->buff) / 2) + { + ztk->zstream.next_out = file->buff; + ztk->zstream.avail_out = sizeof (file->buff); + error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + { + Con_Printf ("FS_Read: Can't inflate file\n"); + break; + } + ztk->in_ind = ztk->in_len - ztk->zstream.avail_in; + + file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out; + file->position += file->buff_len; + + // Copy the requested data in "buffer" (as much as we can) + count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize; + memcpy (&((unsigned char*)buffer)[done], file->buff, count); + file->buff_ind = count; + } + + // Else, we inflate directly in "buffer" + else + { + ztk->zstream.next_out = &((unsigned char*)buffer)[done]; + ztk->zstream.avail_out = (unsigned int)buffersize; + error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH); + if (error != Z_OK && error != Z_STREAM_END) + { + Con_Printf ("FS_Read: Can't inflate file\n"); + break; + } + ztk->in_ind = ztk->in_len - ztk->zstream.avail_in; + + // How much data did it inflate? + count = (fs_offset_t)(buffersize - ztk->zstream.avail_out); + file->position += count; + + // Purge cached data + FS_Purge (file); + } + + done += count; + buffersize -= count; + } + + return done; +} + + +/* +==================== +FS_Print + +Print a string into a file +==================== +*/ +int FS_Print (qfile_t* file, const char *msg) +{ + return (int)FS_Write (file, msg, strlen (msg)); +} + +/* +==================== +FS_Printf + +Print a string into a file +==================== +*/ +int FS_Printf(qfile_t* file, const char* format, ...) +{ + int result; + va_list args; + + va_start (args, format); + result = FS_VPrintf (file, format, args); + va_end (args); + + return result; +} + + +/* +==================== +FS_VPrintf + +Print a string into a file +==================== +*/ +int FS_VPrintf (qfile_t* file, const char* format, va_list ap) +{ + int len; + fs_offset_t buff_size = MAX_INPUTLINE; + char *tempbuff; + + for (;;) + { + tempbuff = (char *)Mem_Alloc (tempmempool, buff_size); + len = dpvsnprintf (tempbuff, buff_size, format, ap); + if (len >= 0 && len < buff_size) + break; + Mem_Free (tempbuff); + buff_size *= 2; + } + + len = write (file->handle, tempbuff, len); + Mem_Free (tempbuff); + + return len; +} + + +/* +==================== +FS_Getc + +Get the next character of a file +==================== +*/ +int FS_Getc (qfile_t* file) +{ + unsigned char c; + + if (FS_Read (file, &c, 1) != 1) + return EOF; + + return c; +} + + +/* +==================== +FS_UnGetc + +Put a character back into the read buffer (only supports one character!) +==================== +*/ +int FS_UnGetc (qfile_t* file, unsigned char c) +{ + // If there's already a character waiting to be read + if (file->ungetc != EOF) + return EOF; + + file->ungetc = c; + return c; +} + + +/* +==================== +FS_Seek + +Move the position index in a file +==================== +*/ +int FS_Seek (qfile_t* file, fs_offset_t offset, int whence) +{ + ztoolkit_t *ztk; + unsigned char* buffer; + fs_offset_t buffersize; + + // Compute the file offset + switch (whence) + { + case SEEK_CUR: + offset += file->position - file->buff_len + file->buff_ind; + break; + + case SEEK_SET: + break; + + case SEEK_END: + offset += file->real_length; + break; + + default: + return -1; + } + if (offset < 0 || offset > file->real_length) + return -1; + + if(file->flags & QFILE_FLAG_DATA) + { + file->position = offset; + return 0; + } + + // If we have the data in our read buffer, we don't need to actually seek + if (file->position - file->buff_len <= offset && offset <= file->position) + { + file->buff_ind = offset + file->buff_len - file->position; + return 0; + } + + // Purge cached data + FS_Purge (file); + + // Unpacked or uncompressed files can seek directly + if (! (file->flags & QFILE_FLAG_DEFLATED)) + { + if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1) + return -1; + file->position = offset; + return 0; + } + + // Seeking in compressed files is more a hack than anything else, + // but we need to support it, so here we go. + ztk = file->ztk; + + // If we have to go back in the file, we need to restart from the beginning + if (offset <= file->position) + { + ztk->in_ind = 0; + ztk->in_len = 0; + ztk->in_position = 0; + file->position = 0; + lseek (file->handle, file->offset, SEEK_SET); + + // Reset the Zlib stream + ztk->zstream.next_in = ztk->input; + ztk->zstream.avail_in = 0; + qz_inflateReset (&ztk->zstream); + } + + // We need a big buffer to force inflating into it directly + buffersize = 2 * sizeof (file->buff); + buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize); + + // Skip all data until we reach the requested offset + while (offset > file->position) + { + fs_offset_t diff = offset - file->position; + fs_offset_t count, len; + + count = (diff > buffersize) ? buffersize : diff; + len = FS_Read (file, buffer, count); + if (len != count) + { + Mem_Free (buffer); + return -1; + } + } + + Mem_Free (buffer); + return 0; +} + + +/* +==================== +FS_Tell + +Give the current position in a file +==================== +*/ +fs_offset_t FS_Tell (qfile_t* file) +{ + return file->position - file->buff_len + file->buff_ind; +} + + +/* +==================== +FS_FileSize + +Give the total size of a file +==================== +*/ +fs_offset_t FS_FileSize (qfile_t* file) +{ + return file->real_length; +} + + +/* +==================== +FS_Purge + +Erases any buffered input or output data +==================== +*/ +void FS_Purge (qfile_t* file) +{ + file->buff_len = 0; + file->buff_ind = 0; + file->ungetc = EOF; +} + + +/* +============ +FS_LoadFile + +Filename are relative to the quake directory. +Always appends a 0 byte. +============ +*/ +unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer) +{ + qfile_t *file; + unsigned char *buf = NULL; + fs_offset_t filesize = 0; + + file = FS_OpenVirtualFile(path, quiet); + if (file) + { + filesize = file->real_length; + if(filesize < 0) + { + Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false"); + FS_Close(file); + return NULL; + } + + buf = (unsigned char *)Mem_Alloc (pool, filesize + 1); + buf[filesize] = '\0'; + FS_Read (file, buf, filesize); + FS_Close (file); + if (developer_loadfile.integer) + Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize); + } + + if (filesizepointer) + *filesizepointer = filesize; + return buf; +} + + +/* +============ +FS_WriteFile + +The filename will be prefixed by the current game directory +============ +*/ +qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count) +{ + qfile_t *file; + size_t i; + fs_offset_t lentotal; + + file = FS_OpenRealFile(filename, "wb", false); + if (!file) + { + Con_Printf("FS_WriteFile: failed on %s\n", filename); + return false; + } + + lentotal = 0; + for(i = 0; i < count; ++i) + lentotal += len[i]; + Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal); + for(i = 0; i < count; ++i) + FS_Write (file, data[i], len[i]); + FS_Close (file); + return true; +} + +qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len) +{ + return FS_WriteFileInBlocks(filename, &data, &len, 1); +} + + +/* +============================================================================= + +OTHERS PUBLIC FUNCTIONS + +============================================================================= +*/ + +/* +============ +FS_StripExtension +============ +*/ +void FS_StripExtension (const char *in, char *out, size_t size_out) +{ + char *last = NULL; + char currentchar; + + if (size_out == 0) + return; + + while ((currentchar = *in) && size_out > 1) + { + if (currentchar == '.') + last = out; + else if (currentchar == '/' || currentchar == '\\' || currentchar == ':') + last = NULL; + *out++ = currentchar; + in++; + size_out--; + } + if (last) + *last = 0; + else + *out = 0; +} + + +/* +================== +FS_DefaultExtension +================== +*/ +void FS_DefaultExtension (char *path, const char *extension, size_t size_path) +{ + const char *src; + + // if path doesn't have a .EXT, append extension + // (extension should include the .) + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strlcat (path, extension, size_path); +} + + +/* +================== +FS_FileType + +Look for a file in the packages and in the filesystem +================== +*/ +int FS_FileType (const char *filename) +{ + searchpath_t *search; + char fullpath[MAX_OSPATH]; + + search = FS_FindFile (filename, NULL, true); + if(!search) + return FS_FILETYPE_NONE; + + if(search->pack && !search->pack->vpack) + return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later + + dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename); + return FS_SysFileType(fullpath); +} + + +/* +================== +FS_FileExists + +Look for a file in the packages and in the filesystem +================== +*/ +qboolean FS_FileExists (const char *filename) +{ + return (FS_FindFile (filename, NULL, true) != NULL); +} + + +/* +================== +FS_SysFileExists + +Look for a file in the filesystem only +================== +*/ +int FS_SysFileType (const char *path) +{ +#if WIN32 +// Sajt - some older sdks are missing this define +# ifndef INVALID_FILE_ATTRIBUTES +# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +# endif + + DWORD result = GetFileAttributes(path); + + if(result == INVALID_FILE_ATTRIBUTES) + return FS_FILETYPE_NONE; + + if(result & FILE_ATTRIBUTE_DIRECTORY) + return FS_FILETYPE_DIRECTORY; + + return FS_FILETYPE_FILE; +#else + struct stat buf; + + if (stat (path,&buf) == -1) + return FS_FILETYPE_NONE; + +#ifndef S_ISDIR +#define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR) +#endif + if(S_ISDIR(buf.st_mode)) + return FS_FILETYPE_DIRECTORY; + + return FS_FILETYPE_FILE; +#endif +} + +qboolean FS_SysFileExists (const char *path) +{ + return FS_SysFileType (path) != FS_FILETYPE_NONE; +} + +void FS_mkdir (const char *path) +{ + if(COM_CheckParm("-readonly")) + return; + +#if WIN32 + _mkdir (path); +#else + mkdir (path, 0777); +#endif +} + +/* +=========== +FS_Search + +Allocate and fill a search structure with information on matching filenames. +=========== +*/ +fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet) +{ + fssearch_t *search; + searchpath_t *searchpath; + pack_t *pak; + int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex; + stringlist_t resultlist; + stringlist_t dirlist; + const char *slash, *backslash, *colon, *separator; + char *basepath; + char temp[MAX_OSPATH]; + + for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++) + ; + + if (i > 0) + { + Con_Printf("Don't use punctuation at the beginning of a search pattern!\n"); + return NULL; + } + + stringlistinit(&resultlist); + stringlistinit(&dirlist); + search = NULL; + slash = strrchr(pattern, '/'); + backslash = strrchr(pattern, '\\'); + colon = strrchr(pattern, ':'); + separator = max(slash, backslash); + separator = max(separator, colon); + basepathlength = separator ? (separator + 1 - pattern) : 0; + basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1); + if (basepathlength) + memcpy(basepath, pattern, basepathlength); + basepath[basepathlength] = 0; + + // search through the path, one element at a time + for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next) + { + // is the element a pak file? + if (searchpath->pack && !searchpath->pack->vpack) + { + // look through all the pak file elements + pak = searchpath->pack; + for (i = 0;i < pak->numfiles;i++) + { + strlcpy(temp, pak->files[i].name, sizeof(temp)); + while (temp[0]) + { + if (matchpattern(temp, (char *)pattern, true)) + { + for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) + if (!strcmp(resultlist.strings[resultlistindex], temp)) + break; + if (resultlistindex == resultlist.numstrings) + { + stringlistappend(&resultlist, temp); + if (!quiet && developer_loading.integer) + Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp); + } + } + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = strrchr(temp, '/'); + backslash = strrchr(temp, '\\'); + colon = strrchr(temp, ':'); + separator = temp; + if (separator < slash) + separator = slash; + if (separator < backslash) + separator = backslash; + if (separator < colon) + separator = colon; + *((char *)separator) = 0; + } + } + } + else + { + stringlist_t matchedSet, foundSet; + const char *start = pattern; + + stringlistinit(&matchedSet); + stringlistinit(&foundSet); + // add a first entry to the set + stringlistappend(&matchedSet, ""); + // iterate through pattern's path + while (*start) + { + const char *asterisk, *wildcard, *nextseparator, *prevseparator; + char subpath[MAX_OSPATH]; + char subpattern[MAX_OSPATH]; + + // find the next wildcard + wildcard = strchr(start, '?'); + asterisk = strchr(start, '*'); + if (asterisk && (!wildcard || asterisk < wildcard)) + { + wildcard = asterisk; + } + + if (wildcard) + { + nextseparator = strchr( wildcard, '/' ); + } + else + { + nextseparator = NULL; + } + + if( !nextseparator ) { + nextseparator = start + strlen( start ); + } + + // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string) + // copy everything up except nextseperator + strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1))); + // find the last '/' before the wildcard + prevseparator = strrchr( subpattern, '/' ); + if (!prevseparator) + prevseparator = subpattern; + else + prevseparator++; + // copy everything from start to the previous including the '/' (before the wildcard) + // everything up to start is already included in the path of matchedSet's entries + strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1))); + + // for each entry in matchedSet try to open the subdirectories specified in subpath + for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) { + strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) ); + strlcat( temp, subpath, sizeof(temp) ); + listdirectory( &foundSet, searchpath->filename, temp ); + } + if( dirlistindex == 0 ) { + break; + } + // reset the current result set + stringlistfreecontents( &matchedSet ); + // match against the pattern + for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) { + const char *direntry = foundSet.strings[ dirlistindex ]; + if (matchpattern(direntry, subpattern, true)) { + stringlistappend( &matchedSet, direntry ); + } + } + stringlistfreecontents( &foundSet ); + + start = nextseparator; + } + + for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++) + { + const char *temp = matchedSet.strings[dirlistindex]; + if (matchpattern(temp, (char *)pattern, true)) + { + for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) + if (!strcmp(resultlist.strings[resultlistindex], temp)) + break; + if (resultlistindex == resultlist.numstrings) + { + stringlistappend(&resultlist, temp); + if (!quiet && developer_loading.integer) + Con_Printf("SearchDirFile: %s\n", temp); + } + } + } + stringlistfreecontents( &matchedSet ); + } + } + + if (resultlist.numstrings) + { + stringlistsort(&resultlist, true); + numfiles = resultlist.numstrings; + numchars = 0; + for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) + numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1; + search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *)); + search->filenames = (char **)((char *)search + sizeof(fssearch_t)); + search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *)); + search->numfilenames = (int)numfiles; + numfiles = 0; + numchars = 0; + for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) + { + size_t textlen; + search->filenames[numfiles] = search->filenamesbuffer + numchars; + textlen = strlen(resultlist.strings[resultlistindex]) + 1; + memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen); + numfiles++; + numchars += (int)textlen; + } + } + stringlistfreecontents(&resultlist); + + Mem_Free(basepath); + return search; +} + +void FS_FreeSearch(fssearch_t *search) +{ + Z_Free(search); +} + +extern int con_linewidth; +static int FS_ListDirectory(const char *pattern, int oneperline) +{ + int numfiles; + int numcolumns; + int numlines; + int columnwidth; + int linebufpos; + int i, j, k, l; + const char *name; + char linebuf[MAX_INPUTLINE]; + fssearch_t *search; + search = FS_Search(pattern, true, true); + if (!search) + return 0; + numfiles = search->numfilenames; + if (!oneperline) + { + // FIXME: the names could be added to one column list and then + // gradually shifted into the next column if they fit, and then the + // next to make a compact variable width listing but it's a lot more + // complicated... + // find width for columns + columnwidth = 0; + for (i = 0;i < numfiles;i++) + { + l = (int)strlen(search->filenames[i]); + if (columnwidth < l) + columnwidth = l; + } + // count the spacing character + columnwidth++; + // calculate number of columns + numcolumns = con_linewidth / columnwidth; + // don't bother with the column printing if it's only one column + if (numcolumns >= 2) + { + numlines = (numfiles + numcolumns - 1) / numcolumns; + for (i = 0;i < numlines;i++) + { + linebufpos = 0; + for (k = 0;k < numcolumns;k++) + { + l = i * numcolumns + k; + if (l < numfiles) + { + name = search->filenames[l]; + for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++) + linebuf[linebufpos++] = name[j]; + // space out name unless it's the last on the line + if (k + 1 < numcolumns && l + 1 < numfiles) + for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++) + linebuf[linebufpos++] = ' '; + } + } + linebuf[linebufpos] = 0; + Con_Printf("%s\n", linebuf); + } + } + else + oneperline = true; + } + if (oneperline) + for (i = 0;i < numfiles;i++) + Con_Printf("%s\n", search->filenames[i]); + FS_FreeSearch(search); + return (int)numfiles; +} + +static void FS_ListDirectoryCmd (const char* cmdname, int oneperline) +{ + const char *pattern; + if (Cmd_Argc() >= 3) + { + Con_Printf("usage:\n%s [path/pattern]\n", cmdname); + return; + } + if (Cmd_Argc() == 2) + pattern = Cmd_Argv(1); + else + pattern = "*"; + if (!FS_ListDirectory(pattern, oneperline)) + Con_Print("No files found.\n"); +} + +void FS_Dir_f(void) +{ + FS_ListDirectoryCmd("dir", true); +} + +void FS_Ls_f(void) +{ + FS_ListDirectoryCmd("ls", false); +} + +void FS_Which_f(void) +{ + const char *filename; + int index; + searchpath_t *sp; + if (Cmd_Argc() != 2) + { + Con_Printf("usage:\n%s \n", Cmd_Argv(0)); + return; + } + filename = Cmd_Argv(1); + sp = FS_FindFile(filename, &index, true); + if (!sp) { + Con_Printf("%s isn't anywhere\n", filename); + return; + } + if (sp->pack) + { + if(sp->pack->vpack) + Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname); + else + Con_Printf("%s is in package %s\n", filename, sp->pack->shortname); + } + else + Con_Printf("%s is file %s%s\n", filename, sp->filename, filename); +} + + +const char *FS_WhichPack(const char *filename) +{ + int index; + searchpath_t *sp = FS_FindFile(filename, &index, true); + if(sp && sp->pack) + return sp->pack->shortname; + else + return 0; +} + +/* +==================== +FS_IsRegisteredQuakePack + +Look for a proof of purchase file file in the requested package + +If it is found, this file should NOT be downloaded. +==================== +*/ +qboolean FS_IsRegisteredQuakePack(const char *name) +{ + searchpath_t *search; + pack_t *pak; + + // search through the path, one element at a time + for (search = fs_searchpaths;search;search = search->next) + { + if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name)) + // TODO do we want to support vpacks in here too? + { + int (*strcmp_funct) (const char* str1, const char* str2); + int left, right, middle; + + pak = search->pack; + strcmp_funct = pak->ignorecase ? strcasecmp : strcmp; + + // Look for the file (binary search) + left = 0; + right = pak->numfiles - 1; + while (left <= right) + { + int diff; + + middle = (left + right) / 2; + diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp"); + + // Found it + if (!diff) + return true; + + // If we're too far in the list + if (diff > 0) + right = middle - 1; + else + left = middle + 1; + } + + // we found the requested pack but it is not registered quake + return false; + } + } + + return false; +} + +int FS_CRCFile(const char *filename, size_t *filesizepointer) +{ + int crc = -1; + unsigned char *filedata; + fs_offset_t filesize; + if (filesizepointer) + *filesizepointer = 0; + if (!filename || !*filename) + return crc; + filedata = FS_LoadFile(filename, tempmempool, true, &filesize); + if (filedata) + { + if (filesizepointer) + *filesizepointer = filesize; + crc = CRC_Block(filedata, filesize); + Mem_Free(filedata); + } + return crc; +} + +unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool) +{ + z_stream strm; + unsigned char *out = NULL; + unsigned char *tmp; + + *deflated_size = 0; +#ifndef LINK_TO_ZLIB + if(!zlib_dll) + return NULL; +#endif + + memset(&strm, 0, sizeof(strm)); + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + if(level < 0) + level = Z_DEFAULT_COMPRESSION; + + if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK) + { + Con_Printf("FS_Deflate: deflate init error!\n"); + return NULL; + } + + strm.next_in = (unsigned char*)data; + strm.avail_in = size; + + tmp = (unsigned char *) Mem_Alloc(tempmempool, size); + if(!tmp) + { + Con_Printf("FS_Deflate: not enough memory in tempmempool!\n"); + qz_deflateEnd(&strm); + return NULL; + } + + strm.next_out = tmp; + strm.avail_out = size; + + if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END) + { + Con_Printf("FS_Deflate: deflate failed!\n"); + qz_deflateEnd(&strm); + Mem_Free(tmp); + return NULL; + } + + if(qz_deflateEnd(&strm) != Z_OK) + { + Con_Printf("FS_Deflate: deflateEnd failed\n"); + Mem_Free(tmp); + return NULL; + } + + if(strm.total_out >= size) + { + Con_Printf("FS_Deflate: deflate is useless on this data!\n"); + Mem_Free(tmp); + return NULL; + } + + out = (unsigned char *) Mem_Alloc(mempool, strm.total_out); + if(!out) + { + Con_Printf("FS_Deflate: not enough memory in target mempool!\n"); + Mem_Free(tmp); + return NULL; + } + + if(deflated_size) + *deflated_size = (size_t)strm.total_out; + + memcpy(out, tmp, strm.total_out); + Mem_Free(tmp); + + return out; +} + +static void AssertBufsize(sizebuf_t *buf, int length) +{ + if(buf->cursize + length > buf->maxsize) + { + int oldsize = buf->maxsize; + unsigned char *olddata; + olddata = buf->data; + buf->maxsize += length; + buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize); + if(olddata) + { + memcpy(buf->data, olddata, oldsize); + Mem_Free(olddata); + } + } +} + +unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool) +{ + int ret; + z_stream strm; + unsigned char *out = NULL; + unsigned char tmp[2048]; + unsigned int have; + sizebuf_t outbuf; + + *inflated_size = 0; +#ifndef LINK_TO_ZLIB + if(!zlib_dll) + return NULL; +#endif + + memset(&outbuf, 0, sizeof(outbuf)); + outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp)); + outbuf.maxsize = sizeof(tmp); + + memset(&strm, 0, sizeof(strm)); + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK) + { + Con_Printf("FS_Inflate: inflate init error!\n"); + Mem_Free(outbuf.data); + return NULL; + } + + strm.next_in = (unsigned char*)data; + strm.avail_in = size; + + do + { + strm.next_out = tmp; + strm.avail_out = sizeof(tmp); + ret = qz_inflate(&strm, Z_NO_FLUSH); + // it either returns Z_OK on progress, Z_STREAM_END on end + // or an error code + switch(ret) + { + case Z_STREAM_END: + case Z_OK: + break; + + case Z_STREAM_ERROR: + Con_Print("FS_Inflate: stream error!\n"); + break; + case Z_DATA_ERROR: + Con_Print("FS_Inflate: data error!\n"); + break; + case Z_MEM_ERROR: + Con_Print("FS_Inflate: mem error!\n"); + break; + case Z_BUF_ERROR: + Con_Print("FS_Inflate: buf error!\n"); + break; + default: + Con_Print("FS_Inflate: unknown error!\n"); + break; + + } + if(ret != Z_OK && ret != Z_STREAM_END) + { + Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in); + Mem_Free(outbuf.data); + qz_inflateEnd(&strm); + return NULL; + } + have = sizeof(tmp) - strm.avail_out; + AssertBufsize(&outbuf, max(have, sizeof(tmp))); + SZ_Write(&outbuf, tmp, have); + } while(ret != Z_STREAM_END); + + qz_inflateEnd(&strm); + + out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize); + if(!out) + { + Con_Printf("FS_Inflate: not enough memory in target mempool!\n"); + Mem_Free(outbuf.data); + return NULL; + } + + memcpy(out, outbuf.data, outbuf.cursize); + Mem_Free(outbuf.data); + + if(inflated_size) + *inflated_size = (size_t)outbuf.cursize; + + return out; +} diff --git a/app/jni/fs.h b/app/jni/fs.h new file mode 100644 index 0000000..cd1979a --- /dev/null +++ b/app/jni/fs.h @@ -0,0 +1,144 @@ +/* + DarkPlaces file system + + Copyright (C) 2003-2005 Mathieu Olivier + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + +#ifndef FS_H +#define FS_H + + +// ------ Types ------ // + +typedef struct qfile_s qfile_t; + +#ifdef WIN32 +//typedef long fs_offset_t; // 32bit +typedef __int64 fs_offset_t; ///< 64bit (lots of warnings, and read/write still don't take 64bit on win64) +#else +typedef long long fs_offset_t; +#endif + + + +// ------ Variables ------ // + +extern char fs_gamedir [MAX_OSPATH]; +extern char fs_basedir [MAX_OSPATH]; +extern char fs_userdir [MAX_OSPATH]; + +// list of active game directories (empty if not running a mod) +#define MAX_GAMEDIRS 16 +extern int fs_numgamedirs; +extern char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH]; + + +// ------ Main functions ------ // + +// IMPORTANT: the file path is automatically prefixed by the current game directory for +// each file created by FS_WriteFile, or opened in "write" or "append" mode by FS_OpenRealFile + +qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs); // already_loaded may be NULL if caller does not care +const char *FS_WhichPack(const char *filename); +void FS_CreatePath (char *path); +int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking); // uses absolute path +qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking); // uses absolute path +qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet); +qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet); +qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet); +int FS_Close (qfile_t* file); +void FS_RemoveOnClose(qfile_t* file); +fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize); +fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize); +int FS_Print(qfile_t* file, const char *msg); +int FS_Printf(qfile_t* file, const char* format, ...) DP_FUNC_PRINTF(2); +int FS_VPrintf(qfile_t* file, const char* format, va_list ap); +int FS_Getc (qfile_t* file); +int FS_UnGetc (qfile_t* file, unsigned char c); +int FS_Seek (qfile_t* file, fs_offset_t offset, int whence); +fs_offset_t FS_Tell (qfile_t* file); +fs_offset_t FS_FileSize (qfile_t* file); +void FS_Purge (qfile_t* file); +const char *FS_FileWithoutPath (const char *in); +const char *FS_FileExtension (const char *in); +int FS_CheckNastyPath (const char *path, qboolean isgamedir); + +extern const char *const fs_checkgamedir_missing; // "(missing)" +const char *FS_CheckGameDir(const char *gamedir); // returns NULL if nasty, fs_checkgamedir_missing (exact pointer) if missing + +typedef struct +{ + char name[MAX_OSPATH]; + char description[8192]; +} +gamedir_t; +extern gamedir_t *fs_all_gamedirs; // terminated by entry with empty name +extern int fs_all_gamedirs_count; + +qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing); +qboolean FS_IsRegisteredQuakePack(const char *name); +int FS_CRCFile(const char *filename, size_t *filesizepointer); +void FS_Rescan(void); + +typedef struct fssearch_s +{ + int numfilenames; + char **filenames; + // array of filenames + char *filenamesbuffer; +} +fssearch_t; + +fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet); +void FS_FreeSearch(fssearch_t *search); + +unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer); +qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count); +qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len); + + +// ------ Other functions ------ // + +void FS_StripExtension (const char *in, char *out, size_t size_out); +void FS_DefaultExtension (char *path, const char *extension, size_t size_path); + +#define FS_FILETYPE_NONE 0 +#define FS_FILETYPE_FILE 1 +#define FS_FILETYPE_DIRECTORY 2 +int FS_FileType (const char *filename); // the file can be into a package +int FS_SysFileType (const char *filename); // only look for files outside of packages + +qboolean FS_FileExists (const char *filename); // the file can be into a package +qboolean FS_SysFileExists (const char *filename); // only look for files outside of packages + +void FS_mkdir (const char *path); + +unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool); +unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool); + +qboolean FS_HasZlib(void); + +void FS_Init_SelfPack(void); +void FS_Init(void); +void FS_Shutdown(void); +void FS_Init_Commands(void); + +#endif diff --git a/app/jni/ft2.c b/app/jni/ft2.c new file mode 100644 index 0000000..f55d0e2 --- /dev/null +++ b/app/jni/ft2.c @@ -0,0 +1,1593 @@ +/* FreeType 2 and UTF-8 encoding support for + * DarkPlaces + */ +#include "quakedef.h" + +#include "ft2.h" +#include "ft2_defs.h" +#include "ft2_fontdefs.h" +#include "image.h" + +static int img_fontmap[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // shift+digit line + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // digits + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // caps + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // caps + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // small + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // small + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // specials + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // faces + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* +================================================================================ +CVars introduced with the freetype extension +================================================================================ +*/ + +cvar_t r_font_disable_freetype = {CVAR_SAVE, "r_font_disable_freetype", "1", "disable freetype support for fonts entirely"}; +cvar_t r_font_use_alpha_textures = {CVAR_SAVE, "r_font_use_alpha_textures", "0", "use alpha-textures for font rendering, this should safe memory"}; +cvar_t r_font_size_snapping = {CVAR_SAVE, "r_font_size_snapping", "1", "stick to good looking font sizes whenever possible - bad when the mod doesn't support it!"}; +cvar_t r_font_kerning = {CVAR_SAVE, "r_font_kerning", "1", "Use kerning if available"}; +cvar_t r_font_diskcache = {CVAR_SAVE, "r_font_diskcache", "0", "save font textures to disk for future loading rather than generating them every time"}; +cvar_t r_font_compress = {CVAR_SAVE, "r_font_compress", "0", "use texture compression on font textures to save video memory"}; +cvar_t r_font_nonpoweroftwo = {CVAR_SAVE, "r_font_nonpoweroftwo", "1", "use nonpoweroftwo textures for font (saves memory, potentially slower)"}; +cvar_t developer_font = {CVAR_SAVE, "developer_font", "0", "prints debug messages about fonts"}; + +/* +================================================================================ +Function definitions. Taken from the freetype2 headers. +================================================================================ +*/ + + +FT_EXPORT( FT_Error ) +(*qFT_Init_FreeType)( FT_Library *alibrary ); +FT_EXPORT( FT_Error ) +(*qFT_Done_FreeType)( FT_Library library ); +/* +FT_EXPORT( FT_Error ) +(*qFT_New_Face)( FT_Library library, + const char* filepathname, + FT_Long face_index, + FT_Face *aface ); +*/ +FT_EXPORT( FT_Error ) +(*qFT_New_Memory_Face)( FT_Library library, + const FT_Byte* file_base, + FT_Long file_size, + FT_Long face_index, + FT_Face *aface ); +FT_EXPORT( FT_Error ) +(*qFT_Done_Face)( FT_Face face ); +FT_EXPORT( FT_Error ) +(*qFT_Select_Size)( FT_Face face, + FT_Int strike_index ); +FT_EXPORT( FT_Error ) +(*qFT_Request_Size)( FT_Face face, + FT_Size_Request req ); +FT_EXPORT( FT_Error ) +(*qFT_Set_Char_Size)( FT_Face face, + FT_F26Dot6 char_width, + FT_F26Dot6 char_height, + FT_UInt horz_resolution, + FT_UInt vert_resolution ); +FT_EXPORT( FT_Error ) +(*qFT_Set_Pixel_Sizes)( FT_Face face, + FT_UInt pixel_width, + FT_UInt pixel_height ); +FT_EXPORT( FT_Error ) +(*qFT_Load_Glyph)( FT_Face face, + FT_UInt glyph_index, + FT_Int32 load_flags ); +FT_EXPORT( FT_Error ) +(*qFT_Load_Char)( FT_Face face, + FT_ULong char_code, + FT_Int32 load_flags ); +FT_EXPORT( FT_UInt ) +(*qFT_Get_Char_Index)( FT_Face face, + FT_ULong charcode ); +FT_EXPORT( FT_Error ) +(*qFT_Render_Glyph)( FT_GlyphSlot slot, + FT_Render_Mode render_mode ); +FT_EXPORT( FT_Error ) +(*qFT_Get_Kerning)( FT_Face face, + FT_UInt left_glyph, + FT_UInt right_glyph, + FT_UInt kern_mode, + FT_Vector *akerning ); +FT_EXPORT( FT_Error ) +(*qFT_Attach_Stream)( FT_Face face, + FT_Open_Args* parameters ); +/* +================================================================================ +Support for dynamically loading the FreeType2 library +================================================================================ +*/ + +static dllfunction_t ft2funcs[] = +{ + {"FT_Init_FreeType", (void **) &qFT_Init_FreeType}, + {"FT_Done_FreeType", (void **) &qFT_Done_FreeType}, + //{"FT_New_Face", (void **) &qFT_New_Face}, + {"FT_New_Memory_Face", (void **) &qFT_New_Memory_Face}, + {"FT_Done_Face", (void **) &qFT_Done_Face}, + {"FT_Select_Size", (void **) &qFT_Select_Size}, + {"FT_Request_Size", (void **) &qFT_Request_Size}, + {"FT_Set_Char_Size", (void **) &qFT_Set_Char_Size}, + {"FT_Set_Pixel_Sizes", (void **) &qFT_Set_Pixel_Sizes}, + {"FT_Load_Glyph", (void **) &qFT_Load_Glyph}, + {"FT_Load_Char", (void **) &qFT_Load_Char}, + {"FT_Get_Char_Index", (void **) &qFT_Get_Char_Index}, + {"FT_Render_Glyph", (void **) &qFT_Render_Glyph}, + {"FT_Get_Kerning", (void **) &qFT_Get_Kerning}, + {"FT_Attach_Stream", (void **) &qFT_Attach_Stream}, + {NULL, NULL} +}; + +/// Handle for FreeType2 DLL +static dllhandle_t ft2_dll = NULL; + +/// Memory pool for fonts +static mempool_t *font_mempool= NULL; + +/// FreeType library handle +static FT_Library font_ft2lib = NULL; + +#define POSTPROCESS_MAXRADIUS 8 +typedef struct +{ + unsigned char *buf, *buf2; + int bufsize, bufwidth, bufheight, bufpitch; + float blur, outline, shadowx, shadowy, shadowz; + int padding_t, padding_b, padding_l, padding_r, blurpadding_lt, blurpadding_rb, outlinepadding_t, outlinepadding_b, outlinepadding_l, outlinepadding_r; + unsigned char circlematrix[2*POSTPROCESS_MAXRADIUS+1][2*POSTPROCESS_MAXRADIUS+1]; + unsigned char gausstable[2*POSTPROCESS_MAXRADIUS+1]; +} +font_postprocess_t; +static font_postprocess_t pp; + +typedef struct fontfilecache_s +{ + unsigned char *buf; + fs_offset_t len; + int refcount; + char path[MAX_QPATH]; +} +fontfilecache_t; +#define MAX_FONTFILES 8 +static fontfilecache_t fontfiles[MAX_FONTFILES]; +static const unsigned char *fontfilecache_LoadFile(const char *path, qboolean quiet, fs_offset_t *filesizepointer) +{ + int i; + unsigned char *buf; + + for(i = 0; i < MAX_FONTFILES; ++i) + { + if(fontfiles[i].refcount > 0) + if(!strcmp(path, fontfiles[i].path)) + { + *filesizepointer = fontfiles[i].len; + ++fontfiles[i].refcount; + return fontfiles[i].buf; + } + } + + buf = FS_LoadFile(path, font_mempool, quiet, filesizepointer); + if(buf) + { + for(i = 0; i < MAX_FONTFILES; ++i) + if(fontfiles[i].refcount <= 0) + { + strlcpy(fontfiles[i].path, path, sizeof(fontfiles[i].path)); + fontfiles[i].len = *filesizepointer; + fontfiles[i].buf = buf; + fontfiles[i].refcount = 1; + return buf; + } + } + + return buf; +} +static void fontfilecache_Free(const unsigned char *buf) +{ + int i; + for(i = 0; i < MAX_FONTFILES; ++i) + { + if(fontfiles[i].refcount > 0) + if(fontfiles[i].buf == buf) + { + if(--fontfiles[i].refcount <= 0) + { + Mem_Free(fontfiles[i].buf); + fontfiles[i].buf = NULL; + } + return; + } + } + // if we get here, it used regular allocation + Mem_Free((void *) buf); +} +static void fontfilecache_FreeAll(void) +{ + int i; + for(i = 0; i < MAX_FONTFILES; ++i) + { + if(fontfiles[i].refcount > 0) + Mem_Free(fontfiles[i].buf); + fontfiles[i].buf = NULL; + fontfiles[i].refcount = 0; + } +} + +/* +==================== +Font_CloseLibrary + +Unload the FreeType2 DLL +==================== +*/ +void Font_CloseLibrary (void) +{ + fontfilecache_FreeAll(); + if (font_mempool) + Mem_FreePool(&font_mempool); + if (font_ft2lib && qFT_Done_FreeType) + { + qFT_Done_FreeType(font_ft2lib); + font_ft2lib = NULL; + } + Sys_UnloadLibrary (&ft2_dll); + pp.buf = NULL; +} + +/* +==================== +Font_OpenLibrary + +Try to load the FreeType2 DLL +==================== +*/ +qboolean Font_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libfreetype-6.dll", + "freetype6.dll", +#elif defined(MACOSX) + "libfreetype.6.dylib", + "libfreetype.dylib", +#else + "libfreetype.so.6", + "libfreetype.so", +#endif + NULL + }; + + if (r_font_disable_freetype.integer) + return false; + + // Already loaded? + if (ft2_dll) + return true; + + // Load the DLL + if (!Sys_LoadLibrary (dllnames, &ft2_dll, ft2funcs)) + return false; + return true; +} + +/* +==================== +Font_Init + +Initialize the freetype2 font subsystem +==================== +*/ + +void font_start(void) +{ + if (!Font_OpenLibrary()) + return; + + if (qFT_Init_FreeType(&font_ft2lib)) + { + Con_Print("ERROR: Failed to initialize the FreeType2 library!\n"); + Font_CloseLibrary(); + return; + } + + font_mempool = Mem_AllocPool("FONT", 0, NULL); + if (!font_mempool) + { + Con_Print("ERROR: Failed to allocate FONT memory pool!\n"); + Font_CloseLibrary(); + return; + } +} + +void font_shutdown(void) +{ + int i; + for (i = 0; i < dp_fonts.maxsize; ++i) + { + if (dp_fonts.f[i].ft2) + { + Font_UnloadFont(dp_fonts.f[i].ft2); + dp_fonts.f[i].ft2 = NULL; + } + } + Font_CloseLibrary(); +} + +void font_newmap(void) +{ +} + +void Font_Init(void) +{ + Cvar_RegisterVariable(&r_font_nonpoweroftwo); + Cvar_RegisterVariable(&r_font_disable_freetype); + Cvar_RegisterVariable(&r_font_use_alpha_textures); + Cvar_RegisterVariable(&r_font_size_snapping); + Cvar_RegisterVariable(&r_font_kerning); + Cvar_RegisterVariable(&r_font_diskcache); + Cvar_RegisterVariable(&r_font_compress); + Cvar_RegisterVariable(&developer_font); + + // let's open it at startup already + Font_OpenLibrary(); +} + +/* +================================================================================ +Implementation of a more or less lazy font loading and rendering code. +================================================================================ +*/ + +#include "ft2_fontdefs.h" + +ft2_font_t *Font_Alloc(void) +{ + if (!ft2_dll) + return NULL; + return (ft2_font_t *)Mem_Alloc(font_mempool, sizeof(ft2_font_t)); +} + +static qboolean Font_Attach(ft2_font_t *font, ft2_attachment_t *attachment) +{ + ft2_attachment_t *na; + + font->attachmentcount++; + na = (ft2_attachment_t*)Mem_Alloc(font_mempool, sizeof(font->attachments[0]) * font->attachmentcount); + if (na == NULL) + return false; + if (font->attachments && font->attachmentcount > 1) + { + memcpy(na, font->attachments, sizeof(font->attachments[0]) * (font->attachmentcount - 1)); + Mem_Free(font->attachments); + } + memcpy(na + sizeof(font->attachments[0]) * (font->attachmentcount - 1), attachment, sizeof(*attachment)); + font->attachments = na; + return true; +} + +float Font_VirtualToRealSize(float sz) +{ + int vh; + //int vw; + int si; + float sn; + if(sz < 0) + return sz; + //vw = ((vid.width > 0) ? vid.width : vid_width.value); + vh = ((vid.height > 0) ? vid.height : vid_height.value); + // now try to scale to our actual size: + sn = sz * vh / vid_conheight.value; + si = (int)sn; + if ( sn - (float)si >= 0.5 ) + ++si; + return si; +} + +float Font_SnapTo(float val, float snapwidth) +{ + return floor(val / snapwidth + 0.5f) * snapwidth; +} + +static qboolean Font_LoadFile(const char *name, int _face, ft2_settings_t *settings, ft2_font_t *font); +static qboolean Font_LoadSize(ft2_font_t *font, float size, qboolean check_only); +qboolean Font_LoadFont(const char *name, dp_font_t *dpfnt) +{ + int s, count, i; + ft2_font_t *ft2, *fbfont, *fb; + char vabuf[1024]; + + ft2 = Font_Alloc(); + if (!ft2) + { + dpfnt->ft2 = NULL; + return false; + } + + // check if a fallback font has been specified, if it has been, and the + // font fails to load, use the image font as main font + for (i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + if (dpfnt->fallbacks[i][0]) + break; + } + + if (!Font_LoadFile(name, dpfnt->req_face, &dpfnt->settings, ft2)) + { + if (i >= MAX_FONT_FALLBACKS) + { + dpfnt->ft2 = NULL; + Mem_Free(ft2); + return false; + } + strlcpy(ft2->name, name, sizeof(ft2->name)); + ft2->image_font = true; + ft2->has_kerning = false; + } + else + { + ft2->image_font = false; + } + + // attempt to load fallback fonts: + fbfont = ft2; + for (i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + if (!dpfnt->fallbacks[i][0]) + break; + if (! (fb = Font_Alloc()) ) + { + Con_Printf("Failed to allocate font for fallback %i of font %s\n", i, name); + break; + } + + if (!Font_LoadFile(dpfnt->fallbacks[i], dpfnt->fallback_faces[i], &dpfnt->settings, fb)) + { + if(!FS_FileExists(va(vabuf, sizeof(vabuf), "%s.tga", dpfnt->fallbacks[i]))) + if(!FS_FileExists(va(vabuf, sizeof(vabuf), "%s.png", dpfnt->fallbacks[i]))) + if(!FS_FileExists(va(vabuf, sizeof(vabuf), "%s.jpg", dpfnt->fallbacks[i]))) + if(!FS_FileExists(va(vabuf, sizeof(vabuf), "%s.pcx", dpfnt->fallbacks[i]))) + Con_Printf("Failed to load font %s for fallback %i of font %s\n", dpfnt->fallbacks[i], i, name); + Mem_Free(fb); + continue; + } + count = 0; + for (s = 0; s < MAX_FONT_SIZES && dpfnt->req_sizes[s] >= 0; ++s) + { + if (Font_LoadSize(fb, Font_VirtualToRealSize(dpfnt->req_sizes[s]), true)) + ++count; + } + if (!count) + { + Con_Printf("Failed to allocate font for fallback %i of font %s\n", i, name); + Font_UnloadFont(fb); + Mem_Free(fb); + break; + } + // at least one size of the fallback font loaded successfully + // link it: + fbfont->next = fb; + fbfont = fb; + } + + if (fbfont == ft2 && ft2->image_font) + { + // no fallbacks were loaded successfully: + dpfnt->ft2 = NULL; + Mem_Free(ft2); + return false; + } + + count = 0; + for (s = 0; s < MAX_FONT_SIZES && dpfnt->req_sizes[s] >= 0; ++s) + { + if (Font_LoadSize(ft2, Font_VirtualToRealSize(dpfnt->req_sizes[s]), false)) + ++count; + } + if (!count) + { + // loading failed for every requested size + Font_UnloadFont(ft2); + Mem_Free(ft2); + dpfnt->ft2 = NULL; + return false; + } + + //Con_Printf("%i sizes loaded\n", count); + dpfnt->ft2 = ft2; + return true; +} + +static qboolean Font_LoadFile(const char *name, int _face, ft2_settings_t *settings, ft2_font_t *font) +{ + size_t namelen; + char filename[MAX_QPATH]; + int status; + size_t i; + const unsigned char *data; + fs_offset_t datasize; + + memset(font, 0, sizeof(*font)); + + if (!Font_OpenLibrary()) + { + if (!r_font_disable_freetype.integer) + { + Con_Printf("WARNING: can't open load font %s\n" + "You need the FreeType2 DLL to load font files\n", + name); + } + return false; + } + + font->settings = settings; + + namelen = strlen(name); + + // try load direct file + memcpy(filename, name, namelen+1); + data = fontfilecache_LoadFile(filename, false, &datasize); + // try load .ttf + if (!data) + { + memcpy(filename + namelen, ".ttf", 5); + data = fontfilecache_LoadFile(filename, false, &datasize); + } + // try load .otf + if (!data) + { + memcpy(filename + namelen, ".otf", 5); + data = fontfilecache_LoadFile(filename, false, &datasize); + } + // try load .pfb/afm + if (!data) + { + ft2_attachment_t afm; + + memcpy(filename + namelen, ".pfb", 5); + data = fontfilecache_LoadFile(filename, false, &datasize); + + if (data) + { + memcpy(filename + namelen, ".afm", 5); + afm.data = fontfilecache_LoadFile(filename, false, &afm.size); + + if (afm.data) + Font_Attach(font, &afm); + } + } + if (!data) + { + // FS_LoadFile being not-quiet should print an error :) + return false; + } + Con_DPrintf("Loading font %s face %i...\n", filename, _face); + + status = qFT_New_Memory_Face(font_ft2lib, (FT_Bytes)data, datasize, _face, (FT_Face*)&font->face); + if (status && _face != 0) + { + Con_Printf("Failed to load face %i of %s. Falling back to face 0\n", _face, name); + _face = 0; + status = qFT_New_Memory_Face(font_ft2lib, (FT_Bytes)data, datasize, _face, (FT_Face*)&font->face); + } + font->data = data; + if (status) + { + Con_Printf("ERROR: can't create face for %s\n" + "Error %i\n", // TODO: error strings + name, status); + Font_UnloadFont(font); + return false; + } + + // add the attachments + for (i = 0; i < font->attachmentcount; ++i) + { + FT_Open_Args args; + memset(&args, 0, sizeof(args)); + args.flags = FT_OPEN_MEMORY; + args.memory_base = (const FT_Byte*)font->attachments[i].data; + args.memory_size = font->attachments[i].size; + if (qFT_Attach_Stream((FT_Face)font->face, &args)) + Con_Printf("Failed to add attachment %u to %s\n", (unsigned)i, font->name); + } + + memcpy(font->name, name, namelen+1); + font->image_font = false; + font->has_kerning = !!(((FT_Face)(font->face))->face_flags & FT_FACE_FLAG_KERNING); + return true; +} + +static void Font_Postprocess_Update(ft2_font_t *fnt, int bpp, int w, int h) +{ + int needed, x, y; + float gausstable[2*POSTPROCESS_MAXRADIUS+1]; + qboolean need_gauss = (!pp.buf || pp.blur != fnt->settings->blur || pp.shadowz != fnt->settings->shadowz); + qboolean need_circle = (!pp.buf || pp.outline != fnt->settings->outline || pp.shadowx != fnt->settings->shadowx || pp.shadowy != fnt->settings->shadowy); + pp.blur = fnt->settings->blur; + pp.outline = fnt->settings->outline; + pp.shadowx = fnt->settings->shadowx; + pp.shadowy = fnt->settings->shadowy; + pp.shadowz = fnt->settings->shadowz; + pp.outlinepadding_l = bound(0, ceil(pp.outline - pp.shadowx), POSTPROCESS_MAXRADIUS); + pp.outlinepadding_r = bound(0, ceil(pp.outline + pp.shadowx), POSTPROCESS_MAXRADIUS); + pp.outlinepadding_t = bound(0, ceil(pp.outline - pp.shadowy), POSTPROCESS_MAXRADIUS); + pp.outlinepadding_b = bound(0, ceil(pp.outline + pp.shadowy), POSTPROCESS_MAXRADIUS); + pp.blurpadding_lt = bound(0, ceil(pp.blur - pp.shadowz), POSTPROCESS_MAXRADIUS); + pp.blurpadding_rb = bound(0, ceil(pp.blur + pp.shadowz), POSTPROCESS_MAXRADIUS); + pp.padding_l = pp.blurpadding_lt + pp.outlinepadding_l; + pp.padding_r = pp.blurpadding_rb + pp.outlinepadding_r; + pp.padding_t = pp.blurpadding_lt + pp.outlinepadding_t; + pp.padding_b = pp.blurpadding_rb + pp.outlinepadding_b; + if(need_gauss) + { + float sum = 0; + for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) + gausstable[POSTPROCESS_MAXRADIUS+x] = (pp.blur > 0 ? exp(-(pow(x + pp.shadowz, 2))/(pp.blur*pp.blur * 2)) : (floor(x + pp.shadowz + 0.5) == 0)); + for(x = -pp.blurpadding_rb; x <= pp.blurpadding_lt; ++x) + sum += gausstable[POSTPROCESS_MAXRADIUS+x]; + for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) + pp.gausstable[POSTPROCESS_MAXRADIUS+x] = floor(gausstable[POSTPROCESS_MAXRADIUS+x] / sum * 255 + 0.5); + } + if(need_circle) + { + for(y = -POSTPROCESS_MAXRADIUS; y <= POSTPROCESS_MAXRADIUS; ++y) + for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) + { + float d = pp.outline + 1 - sqrt(pow(x + pp.shadowx, 2) + pow(y + pp.shadowy, 2)); + pp.circlematrix[POSTPROCESS_MAXRADIUS+y][POSTPROCESS_MAXRADIUS+x] = (d >= 1) ? 255 : (d <= 0) ? 0 : floor(d * 255 + 0.5); + } + } + pp.bufwidth = w + pp.padding_l + pp.padding_r; + pp.bufheight = h + pp.padding_t + pp.padding_b; + pp.bufpitch = pp.bufwidth; + needed = pp.bufwidth * pp.bufheight; + if(!pp.buf || pp.bufsize < needed * 2) + { + if(pp.buf) + Mem_Free(pp.buf); + pp.bufsize = needed * 4; + pp.buf = (unsigned char *)Mem_Alloc(font_mempool, pp.bufsize); + pp.buf2 = pp.buf + needed; + } +} + +static void Font_Postprocess(ft2_font_t *fnt, unsigned char *imagedata, int pitch, int bpp, int w, int h, int *pad_l, int *pad_r, int *pad_t, int *pad_b) +{ + int x, y; + + // calculate gauss table + Font_Postprocess_Update(fnt, bpp, w, h); + + if(imagedata) + { + // enlarge buffer + // perform operation, not exceeding the passed padding values, + // but possibly reducing them + *pad_l = min(*pad_l, pp.padding_l); + *pad_r = min(*pad_r, pp.padding_r); + *pad_t = min(*pad_t, pp.padding_t); + *pad_b = min(*pad_b, pp.padding_b); + + // outline the font (RGBA only) + if(bpp == 4 && (pp.outline > 0 || pp.blur > 0 || pp.shadowx != 0 || pp.shadowy != 0 || pp.shadowz != 0)) // we can only do this in BGRA + { + // this is like mplayer subtitle rendering + // bbuffer, bitmap buffer: this is our font + // abuffer, alpha buffer: this is pp.buf + // tmp: this is pp.buf2 + + // create outline buffer + memset(pp.buf, 0, pp.bufwidth * pp.bufheight); + for(y = -*pad_t; y < h + *pad_b; ++y) + for(x = -*pad_l; x < w + *pad_r; ++x) + { + int x1 = max(-x, -pp.outlinepadding_r); + int y1 = max(-y, -pp.outlinepadding_b); + int x2 = min(pp.outlinepadding_l, w-1-x); + int y2 = min(pp.outlinepadding_t, h-1-y); + int mx, my; + int cur = 0; + int highest = 0; + for(my = y1; my <= y2; ++my) + for(mx = x1; mx <= x2; ++mx) + { + cur = pp.circlematrix[POSTPROCESS_MAXRADIUS+my][POSTPROCESS_MAXRADIUS+mx] * (int)imagedata[(x+mx) * bpp + pitch * (y+my) + (bpp - 1)]; + if(cur > highest) + highest = cur; + } + pp.buf[((x + pp.padding_l) + pp.bufpitch * (y + pp.padding_t))] = (highest + 128) / 255; + } + + // blur the outline buffer + if(pp.blur > 0 || pp.shadowz != 0) + { + // horizontal blur + for(y = 0; y < pp.bufheight; ++y) + for(x = 0; x < pp.bufwidth; ++x) + { + int x1 = max(-x, -pp.blurpadding_rb); + int x2 = min(pp.blurpadding_lt, pp.bufwidth-1-x); + int mx; + int blurred = 0; + for(mx = x1; mx <= x2; ++mx) + blurred += pp.gausstable[POSTPROCESS_MAXRADIUS+mx] * (int)pp.buf[(x+mx) + pp.bufpitch * y]; + pp.buf2[x + pp.bufpitch * y] = bound(0, blurred, 65025) / 255; + } + + // vertical blur + for(y = 0; y < pp.bufheight; ++y) + for(x = 0; x < pp.bufwidth; ++x) + { + int y1 = max(-y, -pp.blurpadding_rb); + int y2 = min(pp.blurpadding_lt, pp.bufheight-1-y); + int my; + int blurred = 0; + for(my = y1; my <= y2; ++my) + blurred += pp.gausstable[POSTPROCESS_MAXRADIUS+my] * (int)pp.buf2[x + pp.bufpitch * (y+my)]; + pp.buf[x + pp.bufpitch * y] = bound(0, blurred, 65025) / 255; + } + } + + // paste the outline below the font + for(y = -*pad_t; y < h + *pad_b; ++y) + for(x = -*pad_l; x < w + *pad_r; ++x) + { + unsigned char outlinealpha = pp.buf[(x + pp.padding_l) + pp.bufpitch * (y + pp.padding_t)]; + if(outlinealpha > 0) + { + unsigned char oldalpha = imagedata[x * bpp + pitch * y + (bpp - 1)]; + // a' = 1 - (1 - a1) (1 - a2) + unsigned char newalpha = 255 - ((255 - (int)outlinealpha) * (255 - (int)oldalpha)) / 255; // this is >= oldalpha + // c' = (a2 c2 - a1 a2 c1 + a1 c1) / a' = (a2 c2 + a1 (1 - a2) c1) / a' + unsigned char oldfactor = (255 * (int)oldalpha) / newalpha; + //unsigned char outlinefactor = ((255 - oldalpha) * (int)outlinealpha) / newalpha; + int i; + for(i = 0; i < bpp-1; ++i) + { + unsigned char c = imagedata[x * bpp + pitch * y + i]; + c = (c * (int)oldfactor) / 255 /* + outlinecolor[i] * (int)outlinefactor */; + imagedata[x * bpp + pitch * y + i] = c; + } + imagedata[x * bpp + pitch * y + (bpp - 1)] = newalpha; + } + //imagedata[x * bpp + pitch * y + (bpp - 1)] |= 0x80; + } + } + } + else if(pitch) + { + // perform operation, not exceeding the passed padding values, + // but possibly reducing them + *pad_l = min(*pad_l, pp.padding_l); + *pad_r = min(*pad_r, pp.padding_r); + *pad_t = min(*pad_t, pp.padding_t); + *pad_b = min(*pad_b, pp.padding_b); + } + else + { + // just calculate parameters + *pad_l = pp.padding_l; + *pad_r = pp.padding_r; + *pad_t = pp.padding_t; + *pad_b = pp.padding_b; + } +} + +static float Font_SearchSize(ft2_font_t *font, FT_Face fontface, float size); +static qboolean Font_LoadMap(ft2_font_t *font, ft2_font_map_t *mapstart, Uchar _ch, ft2_font_map_t **outmap); +static qboolean Font_LoadSize(ft2_font_t *font, float size, qboolean check_only) +{ + int map_index; + ft2_font_map_t *fmap, temp; + int gpad_l, gpad_r, gpad_t, gpad_b; + + if (!(size > 0.001f && size < 1000.0f)) + size = 0; + + if (!size) + size = 16; + if (size < 2) // bogus sizes are not allowed - and they screw up our allocations + return false; + + for (map_index = 0; map_index < MAX_FONT_SIZES; ++map_index) + { + if (!font->font_maps[map_index]) + break; + // if a similar size has already been loaded, ignore this one + //abs(font->font_maps[map_index]->size - size) < 4 + if (font->font_maps[map_index]->size == size) + return true; + } + + if (map_index >= MAX_FONT_SIZES) + return false; + + if (check_only) { + FT_Face fontface; + if (font->image_font) + fontface = (FT_Face)font->next->face; + else + fontface = (FT_Face)font->face; + return (Font_SearchSize(font, fontface, size) > 0); + } + + Font_Postprocess(font, NULL, 0, 4, size*2, size*2, &gpad_l, &gpad_r, &gpad_t, &gpad_b); + + memset(&temp, 0, sizeof(temp)); + temp.size = size; + temp.glyphSize = size*2 + max(gpad_l + gpad_r, gpad_t + gpad_b); + if (!(r_font_nonpoweroftwo.integer && vid.support.arb_texture_non_power_of_two)) + temp.glyphSize = CeilPowerOf2(temp.glyphSize); + temp.sfx = (1.0/64.0)/(double)size; + temp.sfy = (1.0/64.0)/(double)size; + temp.intSize = -1; // negative value: LoadMap must search now :) + if (!Font_LoadMap(font, &temp, 0, &fmap)) + { + Con_Printf("ERROR: can't load the first character map for %s\n" + "This is fatal\n", + font->name); + Font_UnloadFont(font); + return false; + } + font->font_maps[map_index] = temp.next; + + fmap->sfx = temp.sfx; + fmap->sfy = temp.sfy; + + // load the default kerning vector: + if (font->has_kerning) + { + Uchar l, r; + FT_Vector kernvec; + for (l = 0; l < 256; ++l) + { + for (r = 0; r < 256; ++r) + { + FT_ULong ul, ur; + ul = qFT_Get_Char_Index((FT_Face)font->face, l); + ur = qFT_Get_Char_Index((FT_Face)font->face, r); + if (qFT_Get_Kerning((FT_Face)font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) + { + fmap->kerning.kerning[l][r][0] = 0; + fmap->kerning.kerning[l][r][1] = 0; + } + else + { + fmap->kerning.kerning[l][r][0] = Font_SnapTo((kernvec.x / 64.0) / fmap->size, 1 / fmap->size); + fmap->kerning.kerning[l][r][1] = Font_SnapTo((kernvec.y / 64.0) / fmap->size, 1 / fmap->size); + } + } + } + } + return true; +} + +int Font_IndexForSize(ft2_font_t *font, float _fsize, float *outw, float *outh) +{ + int match = -1; + float value = 1000000; + float nval; + int matchsize = -10000; + int m; + float fsize_x, fsize_y; + ft2_font_map_t **maps = font->font_maps; + + fsize_x = fsize_y = _fsize * vid.height / vid_conheight.value; + if(outw && *outw) + fsize_x = *outw * vid.width / vid_conwidth.value; + if(outh && *outh) + fsize_y = *outh * vid.height / vid_conheight.value; + + if (fsize_x < 0) + { + if(fsize_y < 0) + fsize_x = fsize_y = 16; + else + fsize_x = fsize_y; + } + else + { + if(fsize_y < 0) + fsize_y = fsize_x; + } + + for (m = 0; m < MAX_FONT_SIZES; ++m) + { + if (!maps[m]) + continue; + // "round up" to the bigger size if two equally-valued matches exist + nval = 0.5 * (fabs(maps[m]->size - fsize_x) + fabs(maps[m]->size - fsize_y)); + if (match == -1 || nval < value || (nval == value && matchsize < maps[m]->size)) + { + value = nval; + match = m; + matchsize = maps[m]->size; + if (value == 0) // there is no better match + break; + } + } + if (value <= r_font_size_snapping.value) + { + // do NOT keep the aspect for perfect rendering + if (outh) *outh = maps[match]->size * vid_conheight.value / vid.height; + if (outw) *outw = maps[match]->size * vid_conwidth.value / vid.width; + } + return match; +} + +ft2_font_map_t *Font_MapForIndex(ft2_font_t *font, int index) +{ + if (index < 0 || index >= MAX_FONT_SIZES) + return NULL; + return font->font_maps[index]; +} + +static qboolean Font_SetSize(ft2_font_t *font, float w, float h) +{ + if (font->currenth == h && + ((!w && (!font->currentw || font->currentw == font->currenth)) || // check if w==h when w is not set + font->currentw == w)) // same size has been requested + { + return true; + } + // sorry, but freetype doesn't seem to care about other sizes + w = (int)w; + h = (int)h; + if (font->image_font) + { + if (qFT_Set_Char_Size((FT_Face)font->next->face, (FT_F26Dot6)(w*64), (FT_F26Dot6)(h*64), 72, 72)) + return false; + } + else + { + if (qFT_Set_Char_Size((FT_Face)font->face, (FT_F26Dot6)(w*64), (FT_F26Dot6)(h*64), 72, 72)) + return false; + } + font->currentw = w; + font->currenth = h; + return true; +} + +qboolean Font_GetKerningForMap(ft2_font_t *font, int map_index, float w, float h, Uchar left, Uchar right, float *outx, float *outy) +{ + ft2_font_map_t *fmap; + if (!font->has_kerning || !r_font_kerning.integer) + return false; + if (map_index < 0 || map_index >= MAX_FONT_SIZES) + return false; + fmap = font->font_maps[map_index]; + if (!fmap) + return false; + if (left < 256 && right < 256) + { + //Con_Printf("%g : %f, %f, %f :: %f\n", (w / (float)fmap->size), w, fmap->size, fmap->intSize, Font_VirtualToRealSize(w)); + // quick-kerning, be aware of the size: scale it + if (outx) *outx = fmap->kerning.kerning[left][right][0];// * (w / (float)fmap->size); + if (outy) *outy = fmap->kerning.kerning[left][right][1];// * (h / (float)fmap->size); + return true; + } + else + { + FT_Vector kernvec; + FT_ULong ul, ur; + + //if (qFT_Set_Pixel_Sizes((FT_Face)font->face, 0, fmap->size)) +#if 0 + if (!Font_SetSize(font, w, h)) + { + // this deserves an error message + Con_Printf("Failed to get kerning for %s\n", font->name); + return false; + } + ul = qFT_Get_Char_Index(font->face, left); + ur = qFT_Get_Char_Index(font->face, right); + if (qFT_Get_Kerning(font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) + { + if (outx) *outx = Font_SnapTo(kernvec.x * fmap->sfx, 1 / fmap->size); + if (outy) *outy = Font_SnapTo(kernvec.y * fmap->sfy, 1 / fmap->size); + return true; + } +#endif + if (!Font_SetSize(font, fmap->intSize, fmap->intSize)) + { + // this deserves an error message + Con_Printf("Failed to get kerning for %s\n", font->name); + return false; + } + ul = qFT_Get_Char_Index((FT_Face)font->face, left); + ur = qFT_Get_Char_Index((FT_Face)font->face, right); + if (qFT_Get_Kerning((FT_Face)font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) + { + if (outx) *outx = Font_SnapTo(kernvec.x * fmap->sfx, 1 / fmap->size);// * (w / (float)fmap->size); + if (outy) *outy = Font_SnapTo(kernvec.y * fmap->sfy, 1 / fmap->size);// * (h / (float)fmap->size); + return true; + } + return false; + } +} + +qboolean Font_GetKerningForSize(ft2_font_t *font, float w, float h, Uchar left, Uchar right, float *outx, float *outy) +{ + return Font_GetKerningForMap(font, Font_IndexForSize(font, h, NULL, NULL), w, h, left, right, outx, outy); +} + +static void UnloadMapRec(ft2_font_map_t *map) +{ + if (map->pic) + { + //Draw_FreePic(map->pic); // FIXME: refcounting needed... + map->pic = NULL; + } + if (map->next) + UnloadMapRec(map->next); + Mem_Free(map); +} + +void Font_UnloadFont(ft2_font_t *font) +{ + int i; + + // unload fallbacks + if(font->next) + Font_UnloadFont(font->next); + + if (font->attachments && font->attachmentcount) + { + for (i = 0; i < (int)font->attachmentcount; ++i) { + if (font->attachments[i].data) + fontfilecache_Free(font->attachments[i].data); + } + Mem_Free(font->attachments); + font->attachmentcount = 0; + font->attachments = NULL; + } + for (i = 0; i < MAX_FONT_SIZES; ++i) + { + if (font->font_maps[i]) + { + UnloadMapRec(font->font_maps[i]); + font->font_maps[i] = NULL; + } + } + if (ft2_dll) + { + if (font->face) + { + qFT_Done_Face((FT_Face)font->face); + font->face = NULL; + } + } + if (font->data) { + fontfilecache_Free(font->data); + font->data = NULL; + } +} + +static float Font_SearchSize(ft2_font_t *font, FT_Face fontface, float size) +{ + float intSize = size; + while (1) + { + if (!Font_SetSize(font, intSize, intSize)) + { + Con_Printf("ERROR: can't set size for font %s: %f ((%f))\n", font->name, size, intSize); + return -1; + } + if ((fontface->size->metrics.height>>6) <= size) + return intSize; + if (intSize < 2) + { + Con_Printf("ERROR: no appropriate size found for font %s: %f\n", font->name, size); + return -1; + } + --intSize; + } +} + +static qboolean Font_LoadMap(ft2_font_t *font, ft2_font_map_t *mapstart, Uchar _ch, ft2_font_map_t **outmap) +{ + char map_identifier[MAX_QPATH]; + unsigned long mapidx = _ch / FONT_CHARS_PER_MAP; + unsigned char *data = NULL; + FT_ULong ch, mapch; + int status; + int tp; + FT_Int32 load_flags; + int gpad_l, gpad_r, gpad_t, gpad_b; + char vabuf[1024]; + + int pitch; + int gR, gC; // glyph position: row and column + + ft2_font_map_t *map, *next; + ft2_font_t *usefont; + + FT_Face fontface; + + int bytesPerPixel = 4; // change the conversion loop too if you change this! + + if (outmap) + *outmap = NULL; + + if (r_font_use_alpha_textures.integer) + bytesPerPixel = 1; + + if (font->image_font) + fontface = (FT_Face)font->next->face; + else + fontface = (FT_Face)font->face; + + switch(font->settings->antialias) + { + case 0: + switch(font->settings->hinting) + { + case 0: + load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT | FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; + break; + case 1: + case 2: + load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; + break; + default: + case 3: + load_flags = FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; + break; + } + break; + default: + case 1: + switch(font->settings->hinting) + { + case 0: + load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT | FT_LOAD_TARGET_NORMAL; + break; + case 1: + load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT; + break; + case 2: + load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL; + break; + default: + case 3: + load_flags = FT_LOAD_TARGET_NORMAL; + break; + } + break; + } + + //status = qFT_Set_Pixel_Sizes((FT_Face)font->face, /*size*/0, mapstart->size); + //if (status) + if (font->image_font && mapstart->intSize < 0) + mapstart->intSize = mapstart->size; + if (mapstart->intSize < 0) + { + /* + mapstart->intSize = mapstart->size; + while (1) + { + if (!Font_SetSize(font, mapstart->intSize, mapstart->intSize)) + { + Con_Printf("ERROR: can't set size for font %s: %f ((%f))\n", font->name, mapstart->size, mapstart->intSize); + return false; + } + if ((fontface->size->metrics.height>>6) <= mapstart->size) + break; + if (mapstart->intSize < 2) + { + Con_Printf("ERROR: no appropriate size found for font %s: %f\n", font->name, mapstart->size); + return false; + } + --mapstart->intSize; + } + */ + if ((mapstart->intSize = Font_SearchSize(font, fontface, mapstart->size)) <= 0) + return false; + Con_DPrintf("Using size: %f for requested size %f\n", mapstart->intSize, mapstart->size); + } + + if (!font->image_font && !Font_SetSize(font, mapstart->intSize, mapstart->intSize)) + { + Con_Printf("ERROR: can't set sizes for font %s: %f\n", font->name, mapstart->size); + return false; + } + + map = (ft2_font_map_t *)Mem_Alloc(font_mempool, sizeof(ft2_font_map_t)); + if (!map) + { + Con_Printf("ERROR: Out of memory when loading fontmap for %s\n", font->name); + return false; + } + + // create a totally unique name for this map, then we will use it to make a unique cachepic_t to avoid redundant textures + dpsnprintf(map_identifier, sizeof(map_identifier), + "%s_cache_%g_%d_%g_%g_%g_%g_%g_%u", + font->name, + (double) mapstart->intSize, + (int) load_flags, + (double) font->settings->blur, + (double) font->settings->outline, + (double) font->settings->shadowx, + (double) font->settings->shadowy, + (double) font->settings->shadowz, + (unsigned) mapidx); + + // create a cachepic_t from the data now, or reuse an existing one + map->pic = Draw_CachePic_Flags(map_identifier, CACHEPICFLAG_QUIET); + if (developer_font.integer) + { + if (map->pic->tex == r_texture_notexture) + Con_Printf("Generating font map %s (size: %.1f MB)\n", map_identifier, mapstart->glyphSize * (256 * 4 / 1048576.0) * mapstart->glyphSize); + else + Con_Printf("Using cached font map %s (size: %.1f MB)\n", map_identifier, mapstart->glyphSize * (256 * 4 / 1048576.0) * mapstart->glyphSize); + } + + Font_Postprocess(font, NULL, 0, bytesPerPixel, mapstart->size*2, mapstart->size*2, &gpad_l, &gpad_r, &gpad_t, &gpad_b); + + // copy over the information + map->size = mapstart->size; + map->intSize = mapstart->intSize; + map->glyphSize = mapstart->glyphSize; + map->sfx = mapstart->sfx; + map->sfy = mapstart->sfy; + + pitch = map->glyphSize * FONT_CHARS_PER_LINE * bytesPerPixel; + if (map->pic->tex == r_texture_notexture) + { + data = (unsigned char *)Mem_Alloc(font_mempool, (FONT_CHAR_LINES * map->glyphSize) * pitch); + if (!data) + { + Con_Printf("ERROR: Failed to allocate memory for font %s size %g\n", font->name, map->size); + Mem_Free(map); + return false; + } + // initialize as white texture with zero alpha + tp = 0; + while (tp < (FONT_CHAR_LINES * map->glyphSize) * pitch) + { + if (bytesPerPixel == 4) + { + data[tp++] = 0xFF; + data[tp++] = 0xFF; + data[tp++] = 0xFF; + } + data[tp++] = 0x00; + } + } + + memset(map->width_of, 0, sizeof(map->width_of)); + + // insert the map + map->start = mapidx * FONT_CHARS_PER_MAP; + next = mapstart; + while(next->next && next->next->start < map->start) + next = next->next; + map->next = next->next; + next->next = map; + + gR = 0; + gC = -1; + for (ch = map->start; + ch < (FT_ULong)map->start + FONT_CHARS_PER_MAP; + ++ch) + { + FT_ULong glyphIndex; + int w, h, x, y; + FT_GlyphSlot glyph; + FT_Bitmap *bmp; + unsigned char *imagedata = NULL, *dst, *src; + glyph_slot_t *mapglyph; + FT_Face face; + int pad_l, pad_r, pad_t, pad_b; + + mapch = ch - map->start; + + if (developer_font.integer) + Con_DPrint("glyphinfo: ------------- GLYPH INFO -----------------\n"); + + ++gC; + if (gC >= FONT_CHARS_PER_LINE) + { + gC -= FONT_CHARS_PER_LINE; + ++gR; + } + + if (data) + { + imagedata = data + gR * pitch * map->glyphSize + gC * map->glyphSize * bytesPerPixel; + imagedata += gpad_t * pitch + gpad_l * bytesPerPixel; + } + //status = qFT_Load_Char(face, ch, FT_LOAD_RENDER); + // we need the glyphIndex + face = (FT_Face)font->face; + usefont = NULL; + if (font->image_font && mapch == ch && img_fontmap[mapch]) + { + map->glyphs[mapch].image = true; + continue; + } + glyphIndex = qFT_Get_Char_Index(face, ch); + if (glyphIndex == 0) + { + // by convention, 0 is the "missing-glyph"-glyph + // try to load from a fallback font + for(usefont = font->next; usefont != NULL; usefont = usefont->next) + { + if (!Font_SetSize(usefont, mapstart->intSize, mapstart->intSize)) + continue; + // try that glyph + face = (FT_Face)usefont->face; + glyphIndex = qFT_Get_Char_Index(face, ch); + if (glyphIndex == 0) + continue; + status = qFT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER | load_flags); + if (status) + continue; + break; + } + if (!usefont) + { + //Con_Printf("failed to load fallback glyph for char %lx from font %s\n", (unsigned long)ch, font->name); + // now we let it use the "missing-glyph"-glyph + face = (FT_Face)font->face; + glyphIndex = 0; + } + } + + if (!usefont) + { + usefont = font; + face = (FT_Face)font->face; + status = qFT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER | load_flags); + if (status) + { + //Con_Printf("failed to load glyph %lu for %s\n", glyphIndex, font->name); + Con_DPrintf("failed to load glyph for char %lx from font %s\n", (unsigned long)ch, font->name); + continue; + } + } + + glyph = face->glyph; + bmp = &glyph->bitmap; + + w = bmp->width; + h = bmp->rows; + + if (w > (map->glyphSize - gpad_l - gpad_r) || h > (map->glyphSize - gpad_t - gpad_b)) { + Con_Printf("WARNING: Glyph %lu is too big in font %s, size %g: %i x %i\n", ch, font->name, map->size, w, h); + if (w > map->glyphSize) + w = map->glyphSize - gpad_l - gpad_r; + if (h > map->glyphSize) + h = map->glyphSize; + } + + if (imagedata) + { + switch (bmp->pixel_mode) + { + case FT_PIXEL_MODE_MONO: + if (developer_font.integer) + Con_DPrint("glyphinfo: Pixel Mode: MONO\n"); + break; + case FT_PIXEL_MODE_GRAY2: + if (developer_font.integer) + Con_DPrint("glyphinfo: Pixel Mode: GRAY2\n"); + break; + case FT_PIXEL_MODE_GRAY4: + if (developer_font.integer) + Con_DPrint("glyphinfo: Pixel Mode: GRAY4\n"); + break; + case FT_PIXEL_MODE_GRAY: + if (developer_font.integer) + Con_DPrint("glyphinfo: Pixel Mode: GRAY\n"); + break; + default: + if (developer_font.integer) + Con_DPrintf("glyphinfo: Pixel Mode: Unknown: %i\n", bmp->pixel_mode); + Mem_Free(data); + Con_Printf("ERROR: Unrecognized pixel mode for font %s size %f: %i\n", font->name, mapstart->size, bmp->pixel_mode); + return false; + } + for (y = 0; y < h; ++y) + { + dst = imagedata + y * pitch; + src = bmp->buffer + y * bmp->pitch; + + switch (bmp->pixel_mode) + { + case FT_PIXEL_MODE_MONO: + dst += bytesPerPixel - 1; // shift to alpha byte + for (x = 0; x < bmp->width; x += 8) + { + unsigned char ch = *src++; + *dst = 255 * !!((ch & 0x80) >> 7); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x40) >> 6); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x20) >> 5); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x10) >> 4); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x08) >> 3); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x04) >> 2); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x02) >> 1); dst += bytesPerPixel; + *dst = 255 * !!((ch & 0x01) >> 0); dst += bytesPerPixel; + } + break; + case FT_PIXEL_MODE_GRAY2: + dst += bytesPerPixel - 1; // shift to alpha byte + for (x = 0; x < bmp->width; x += 4) + { + unsigned char ch = *src++; + *dst = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2; dst += bytesPerPixel; + *dst = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2; dst += bytesPerPixel; + *dst = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2; dst += bytesPerPixel; + *dst = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2; dst += bytesPerPixel; + } + break; + case FT_PIXEL_MODE_GRAY4: + dst += bytesPerPixel - 1; // shift to alpha byte + for (x = 0; x < bmp->width; x += 2) + { + unsigned char ch = *src++; + *dst = ( ((ch & 0xF0) >> 4) * 0x11); dst += bytesPerPixel; + *dst = ( ((ch & 0x0F) ) * 0x11); dst += bytesPerPixel; + } + break; + case FT_PIXEL_MODE_GRAY: + // in this case pitch should equal width + for (tp = 0; tp < bmp->pitch; ++tp) + dst[(bytesPerPixel - 1) + tp*bytesPerPixel] = src[tp]; // copy the grey value into the alpha bytes + + //memcpy((void*)dst, (void*)src, bmp->pitch); + //dst += bmp->pitch; + break; + default: + break; + } + } + + pad_l = gpad_l; + pad_r = gpad_r; + pad_t = gpad_t; + pad_b = gpad_b; + Font_Postprocess(font, imagedata, pitch, bytesPerPixel, w, h, &pad_l, &pad_r, &pad_t, &pad_b); + } + else + { + pad_l = gpad_l; + pad_r = gpad_r; + pad_t = gpad_t; + pad_b = gpad_b; + Font_Postprocess(font, NULL, pitch, bytesPerPixel, w, h, &pad_l, &pad_r, &pad_t, &pad_b); + } + + + // now fill map->glyphs[ch - map->start] + mapglyph = &map->glyphs[mapch]; + + { + // old way + // double advance = (double)glyph->metrics.horiAdvance * map->sfx; + + double bearingX = (glyph->metrics.horiBearingX / 64.0) / map->size; + //double bearingY = (glyph->metrics.horiBearingY >> 6) / map->size; + double advance = (glyph->advance.x / 64.0) / map->size; + //double mWidth = (glyph->metrics.width >> 6) / map->size; + //double mHeight = (glyph->metrics.height >> 6) / map->size; + + mapglyph->txmin = ( (double)(gC * map->glyphSize) + (double)(gpad_l - pad_l) ) / ( (double)(map->glyphSize * FONT_CHARS_PER_LINE) ); + mapglyph->txmax = mapglyph->txmin + (double)(bmp->width + pad_l + pad_r) / ( (double)(map->glyphSize * FONT_CHARS_PER_LINE) ); + mapglyph->tymin = ( (double)(gR * map->glyphSize) + (double)(gpad_r - pad_r) ) / ( (double)(map->glyphSize * FONT_CHAR_LINES) ); + mapglyph->tymax = mapglyph->tymin + (double)(bmp->rows + pad_t + pad_b) / ( (double)(map->glyphSize * FONT_CHAR_LINES) ); + //mapglyph->vxmin = bearingX; + //mapglyph->vxmax = bearingX + mWidth; + mapglyph->vxmin = (glyph->bitmap_left - pad_l) / map->size; + mapglyph->vxmax = mapglyph->vxmin + (bmp->width + pad_l + pad_r) / map->size; // don't ask + //mapglyph->vymin = -bearingY; + //mapglyph->vymax = mHeight - bearingY; + mapglyph->vymin = (-glyph->bitmap_top - pad_t) / map->size; + mapglyph->vymax = mapglyph->vymin + (bmp->rows + pad_t + pad_b) / map->size; + //Con_Printf("dpi = %f %f (%f %d) %d %d\n", bmp->width / (mapglyph->vxmax - mapglyph->vxmin), bmp->rows / (mapglyph->vymax - mapglyph->vymin), map->size, map->glyphSize, (int)fontface->size->metrics.x_ppem, (int)fontface->size->metrics.y_ppem); + //mapglyph->advance_x = advance * usefont->size; + //mapglyph->advance_x = advance; + mapglyph->advance_x = Font_SnapTo(advance, 1 / map->size); + mapglyph->advance_y = 0; + + if (developer_font.integer) + { + Con_DPrintf("glyphinfo: Glyph: %lu at (%i, %i)\n", (unsigned long)ch, gC, gR); + Con_DPrintf("glyphinfo: %f, %f, %lu\n", bearingX, map->sfx, (unsigned long)glyph->metrics.horiBearingX); + if (ch >= 32 && ch <= 128) + Con_DPrintf("glyphinfo: Character: %c\n", (int)ch); + Con_DPrintf("glyphinfo: Vertex info:\n"); + Con_DPrintf("glyphinfo: X: ( %f -- %f )\n", mapglyph->vxmin, mapglyph->vxmax); + Con_DPrintf("glyphinfo: Y: ( %f -- %f )\n", mapglyph->vymin, mapglyph->vymax); + Con_DPrintf("glyphinfo: Texture info:\n"); + Con_DPrintf("glyphinfo: S: ( %f -- %f )\n", mapglyph->txmin, mapglyph->txmax); + Con_DPrintf("glyphinfo: T: ( %f -- %f )\n", mapglyph->tymin, mapglyph->tymax); + Con_DPrintf("glyphinfo: Advance: %f, %f\n", mapglyph->advance_x, mapglyph->advance_y); + } + } + map->glyphs[mapch].image = false; + } + + if (map->pic->tex == r_texture_notexture) + { + int w = map->glyphSize * FONT_CHARS_PER_LINE; + int h = map->glyphSize * FONT_CHAR_LINES; + rtexture_t *tex; + // abuse the Draw_CachePic system to keep track of this texture + tex = R_LoadTexture2D(drawtexturepool, map_identifier, w, h, data, r_font_use_alpha_textures.integer ? TEXTYPE_ALPHA : TEXTYPE_RGBA, TEXF_ALPHA | (r_font_compress.integer > 0 ? TEXF_COMPRESS : 0), -1, NULL); + // if tex is NULL for any reason, the pic->tex will remain set to r_texture_notexture + if (tex) + map->pic->tex = tex; + + if (r_font_diskcache.integer >= 1) + { + // swap to BGRA for tga writing... + int s = w * h; + int x; + int b; + for (x = 0;x < s;x++) + { + b = data[x*4+0]; + data[x*4+0] = data[x*4+2]; + data[x*4+2] = b; + } + Image_WriteTGABGRA(va(vabuf, sizeof(vabuf), "%s.tga", map_identifier), w, h, data); +#ifndef USE_GLES2 + if (r_font_compress.integer && qglGetCompressedTexImageARB && tex) + R_SaveTextureDDSFile(tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", map_identifier), r_texture_dds_save.integer < 2, true); +#endif + } + } + + if(data) + Mem_Free(data); + + if (map->pic->tex == r_texture_notexture) + { + // if the first try isn't successful, keep it with a broken texture + // otherwise we retry to load it every single frame where ft2 rendering is used + // this would be bad... + // only `data' must be freed + Con_Printf("ERROR: Failed to generate texture for font %s size %f map %lu\n", + font->name, mapstart->size, mapidx); + return false; + } + if (outmap) + *outmap = map; + return true; +} + +qboolean Font_LoadMapForIndex(ft2_font_t *font, int map_index, Uchar _ch, ft2_font_map_t **outmap) +{ + if (map_index < 0 || map_index >= MAX_FONT_SIZES) + return false; + // the first map must have been loaded already + if (!font->font_maps[map_index]) + return false; + return Font_LoadMap(font, font->font_maps[map_index], _ch, outmap); +} + +ft2_font_map_t *FontMap_FindForChar(ft2_font_map_t *start, Uchar ch) +{ + while (start && start->start + FONT_CHARS_PER_MAP <= ch) + start = start->next; + if (start && start->start > ch) + return NULL; + return start; +} diff --git a/app/jni/ft2.h b/app/jni/ft2.h new file mode 100644 index 0000000..e8110a7 --- /dev/null +++ b/app/jni/ft2.h @@ -0,0 +1,81 @@ +/* Header for FreeType 2 and UTF-8 encoding support for + * DarkPlaces + */ + +#ifndef DP_FREETYPE2_H__ +#define DP_FREETYPE2_H__ + +//#include + +#include "utf8lib.h" + +/* + * From http://www.unicode.org/Public/UNIDATA/Blocks.txt + * + * E000..F8FF; Private Use Area + * F0000..FFFFF; Supplementary Private Use Area-A + * + * We use: + * Range E000 - E0FF + * Contains the non-FreeType2 version of characters. + */ + +typedef struct ft2_font_map_s ft2_font_map_t; +typedef struct ft2_attachment_s ft2_attachment_t; +#define ft2_oldstyle_map ((ft2_font_map_t*)-1) + +typedef float ft2_kernvec[2]; +typedef struct ft2_kerning_s +{ + ft2_kernvec kerning[256][256]; /* kerning[left char][right char] */ +} ft2_kerning_t; + +typedef struct ft2_font_s +{ + char name[64]; + qboolean has_kerning; + // last requested size loaded using Font_SetSize + float currentw; + float currenth; + float ascend; + float descend; + qboolean image_font; // only fallbacks are freetype fonts + + // TODO: clean this up and do not expose everything. + + const unsigned char *data; // FT2 needs it to stay + //fs_offset_t datasize; + void *face; + + // an unordered array of ordered linked lists of glyph maps for a specific size + ft2_font_map_t *font_maps[MAX_FONT_SIZES]; + int num_sizes; + + // attachments + size_t attachmentcount; + ft2_attachment_t *attachments; + + ft2_settings_t *settings; + + // fallback mechanism + struct ft2_font_s *next; +} ft2_font_t; + +void Font_CloseLibrary(void); +void Font_Init(void); +qboolean Font_OpenLibrary(void); +ft2_font_t* Font_Alloc(void); +void Font_UnloadFont(ft2_font_t *font); +// IndexForSize suggests to change the width and height if a font size is in a reasonable range +// for example, you render at a size of 12.4, and a font of size 12 has been loaded +// in such a case, *outw and *outh are set to 12, which is often a good alternative size +int Font_IndexForSize(ft2_font_t *font, float size, float *outw, float *outh); +ft2_font_map_t *Font_MapForIndex(ft2_font_t *font, int index); +qboolean Font_LoadFont(const char *name, dp_font_t *dpfnt); +qboolean Font_GetKerningForSize(ft2_font_t *font, float w, float h, Uchar left, Uchar right, float *outx, float *outy); +qboolean Font_GetKerningForMap(ft2_font_t *font, int map_index, float w, float h, Uchar left, Uchar right, float *outx, float *outy); +float Font_VirtualToRealSize(float sz); +float Font_SnapTo(float val, float snapwidth); +// since this is used on a font_map_t, let's name it FontMap_* +ft2_font_map_t *FontMap_FindForChar(ft2_font_map_t *start, Uchar ch); +#endif // DP_FREETYPE2_H__ diff --git a/app/jni/ft2_defs.h b/app/jni/ft2_defs.h new file mode 100644 index 0000000..c8d38c6 --- /dev/null +++ b/app/jni/ft2_defs.h @@ -0,0 +1,500 @@ +/* FreeType 2 definitions from the freetype header mostly. + */ + +#ifndef FT2_DEFS_H_H__ +#define FT2_DEFS_H_H__ + +#ifdef _MSC_VER +typedef __int32 FT_Int32; +typedef unsigned __int32 FT_UInt32; +#else +# include +typedef int32_t FT_Int32; +typedef uint32_t FT_UInt32; +#endif + +typedef int FT_Error; + +typedef signed char FT_Char; +typedef unsigned char FT_Byte; +typedef const FT_Byte *FT_Bytes; +typedef char FT_String; +typedef signed short FT_Short; +typedef unsigned short FT_UShort; +typedef signed int FT_Int; +typedef unsigned int FT_UInt; +typedef signed long FT_Long; +typedef signed long FT_Fixed; +typedef unsigned long FT_ULong; +typedef void *FT_Pointer; +typedef size_t FT_Offset; +typedef signed long FT_F26Dot6; + +typedef void *FT_Stream; +typedef void *FT_Module; +typedef void *FT_Library; +typedef struct FT_FaceRec_ *FT_Face; +typedef struct FT_CharMapRec_* FT_CharMap; +typedef struct FT_SizeRec_* FT_Size; +typedef struct FT_Size_InternalRec_* FT_Size_Internal; +typedef struct FT_GlyphSlotRec_* FT_GlyphSlot; +typedef struct FT_SubGlyphRec_* FT_SubGlyph; +typedef struct FT_Slot_InternalRec_* FT_Slot_Internal; + +// Taken from the freetype headers: +typedef signed long FT_Pos; +typedef struct FT_Vector_ +{ + FT_Pos x; + FT_Pos y; +} FT_Vector; + +typedef struct FT_BBox_ +{ + FT_Pos xMin, yMin; + FT_Pos xMax, yMax; +} FT_BBox; + +typedef enum FT_Pixel_Mode_ +{ + FT_PIXEL_MODE_NONE = 0, + FT_PIXEL_MODE_MONO, + FT_PIXEL_MODE_GRAY, + FT_PIXEL_MODE_GRAY2, + FT_PIXEL_MODE_GRAY4, + FT_PIXEL_MODE_LCD, + FT_PIXEL_MODE_LCD_V, + FT_PIXEL_MODE_MAX /* do not remove */ +} FT_Pixel_Mode; +typedef enum FT_Render_Mode_ +{ + FT_RENDER_MODE_NORMAL = 0, + FT_RENDER_MODE_LIGHT, + FT_RENDER_MODE_MONO, + FT_RENDER_MODE_LCD, + FT_RENDER_MODE_LCD_V, + + FT_RENDER_MODE_MAX +} FT_Render_Mode; + +#define ft_pixel_mode_none FT_PIXEL_MODE_NONE +#define ft_pixel_mode_mono FT_PIXEL_MODE_MONO +#define ft_pixel_mode_grays FT_PIXEL_MODE_GRAY +#define ft_pixel_mode_pal2 FT_PIXEL_MODE_GRAY2 +#define ft_pixel_mode_pal4 FT_PIXEL_MODE_GRAY4 + +typedef struct FT_Bitmap_ +{ + int rows; + int width; + int pitch; + unsigned char* buffer; + short num_grays; + char pixel_mode; + char palette_mode; + void* palette; +} FT_Bitmap; + +typedef struct FT_Outline_ +{ + short n_contours; /* number of contours in glyph */ + short n_points; /* number of points in the glyph */ + + FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + short* contours; /* the contour end points */ + + int flags; /* outline masks */ +} FT_Outline; + +#define FT_OUTLINE_NONE 0x0 +#define FT_OUTLINE_OWNER 0x1 +#define FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define FT_OUTLINE_REVERSE_FILL 0x4 +#define FT_OUTLINE_IGNORE_DROPOUTS 0x8 +#define FT_OUTLINE_SMART_DROPOUTS 0x10 +#define FT_OUTLINE_INCLUDE_STUBS 0x20 + +#define FT_OUTLINE_HIGH_PRECISION 0x100 +#define FT_OUTLINE_SINGLE_PASS 0x200 + +#define ft_outline_none FT_OUTLINE_NONE +#define ft_outline_owner FT_OUTLINE_OWNER +#define ft_outline_even_odd_fill FT_OUTLINE_EVEN_ODD_FILL +#define ft_outline_reverse_fill FT_OUTLINE_REVERSE_FILL +#define ft_outline_ignore_dropouts FT_OUTLINE_IGNORE_DROPOUTS +#define ft_outline_high_precision FT_OUTLINE_HIGH_PRECISION +#define ft_outline_single_pass FT_OUTLINE_SINGLE_PASS + +#define FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define FT_CURVE_TAG_ON 1 +#define FT_CURVE_TAG_CONIC 0 +#define FT_CURVE_TAG_CUBIC 2 + +#define FT_CURVE_TAG_TOUCH_X 8 /* reserved for the TrueType hinter */ +#define FT_CURVE_TAG_TOUCH_Y 16 /* reserved for the TrueType hinter */ + +#define FT_CURVE_TAG_TOUCH_BOTH ( FT_CURVE_TAG_TOUCH_X | \ + FT_CURVE_TAG_TOUCH_Y ) + +#define FT_Curve_Tag_On FT_CURVE_TAG_ON +#define FT_Curve_Tag_Conic FT_CURVE_TAG_CONIC +#define FT_Curve_Tag_Cubic FT_CURVE_TAG_CUBIC +#define FT_Curve_Tag_Touch_X FT_CURVE_TAG_TOUCH_X +#define FT_Curve_Tag_Touch_Y FT_CURVE_TAG_TOUCH_Y + +typedef int +(*FT_Outline_MoveToFunc)( const FT_Vector* to, + void* user ); +#define FT_Outline_MoveTo_Func FT_Outline_MoveToFunc + +typedef int +(*FT_Outline_LineToFunc)( const FT_Vector* to, + void* user ); +#define FT_Outline_LineTo_Func FT_Outline_LineToFunc + +typedef int +(*FT_Outline_ConicToFunc)( const FT_Vector* control, + const FT_Vector* to, + void* user ); +#define FT_Outline_ConicTo_Func FT_Outline_ConicToFunc + +typedef int +(*FT_Outline_CubicToFunc)( const FT_Vector* control1, + const FT_Vector* control2, + const FT_Vector* to, + void* user ); +#define FT_Outline_CubicTo_Func FT_Outline_CubicToFunc + +typedef struct FT_Outline_Funcs_ +{ + FT_Outline_MoveToFunc move_to; + FT_Outline_LineToFunc line_to; + FT_Outline_ConicToFunc conic_to; + FT_Outline_CubicToFunc cubic_to; + + int shift; + FT_Pos delta; +} FT_Outline_Funcs; + +#ifndef FT_IMAGE_TAG +#define FT_IMAGE_TAG( value, _x1, _x2, _x3, _x4 ) \ + value = ( ( (unsigned long)_x1 << 24 ) | \ + ( (unsigned long)_x2 << 16 ) | \ + ( (unsigned long)_x3 << 8 ) | \ + (unsigned long)_x4 ) +#endif /* FT_IMAGE_TAG */ + +typedef enum FT_Glyph_Format_ +{ + FT_IMAGE_TAG( FT_GLYPH_FORMAT_NONE, 0, 0, 0, 0 ), + + FT_IMAGE_TAG( FT_GLYPH_FORMAT_COMPOSITE, 'c', 'o', 'm', 'p' ), + FT_IMAGE_TAG( FT_GLYPH_FORMAT_BITMAP, 'b', 'i', 't', 's' ), + FT_IMAGE_TAG( FT_GLYPH_FORMAT_OUTLINE, 'o', 'u', 't', 'l' ), + FT_IMAGE_TAG( FT_GLYPH_FORMAT_PLOTTER, 'p', 'l', 'o', 't' ) +} FT_Glyph_Format; +#define ft_glyph_format_none FT_GLYPH_FORMAT_NONE +#define ft_glyph_format_composite FT_GLYPH_FORMAT_COMPOSITE +#define ft_glyph_format_bitmap FT_GLYPH_FORMAT_BITMAP +#define ft_glyph_format_outline FT_GLYPH_FORMAT_OUTLINE +#define ft_glyph_format_plotter FT_GLYPH_FORMAT_PLOTTER + +typedef struct FT_Glyph_Metrics_ +{ + FT_Pos width; + FT_Pos height; + + FT_Pos horiBearingX; + FT_Pos horiBearingY; + FT_Pos horiAdvance; + + FT_Pos vertBearingX; + FT_Pos vertBearingY; + FT_Pos vertAdvance; +} FT_Glyph_Metrics; + +#define FT_EXPORT( x ) x + +#define FT_OPEN_MEMORY 0x1 +#define FT_OPEN_STREAM 0x2 +#define FT_OPEN_PATHNAME 0x4 +#define FT_OPEN_DRIVER 0x8 +#define FT_OPEN_PARAMS 0x10 + +typedef struct FT_Parameter_ +{ + FT_ULong tag; + FT_Pointer data; +} FT_Parameter; + +typedef struct FT_Open_Args_ +{ + FT_UInt flags; + const FT_Byte* memory_base; + FT_Long memory_size; + FT_String* pathname; + FT_Stream stream; + FT_Module driver; + FT_Int num_params; + FT_Parameter* params; +} FT_Open_Args; +typedef enum FT_Size_Request_Type_ +{ + FT_SIZE_REQUEST_TYPE_NOMINAL, + FT_SIZE_REQUEST_TYPE_REAL_DIM, + FT_SIZE_REQUEST_TYPE_BBOX, + FT_SIZE_REQUEST_TYPE_CELL, + FT_SIZE_REQUEST_TYPE_SCALES, + + FT_SIZE_REQUEST_TYPE_MAX + +} FT_Size_Request_Type; +typedef struct FT_Size_RequestRec_ +{ + FT_Size_Request_Type type; + FT_Long width; + FT_Long height; + FT_UInt horiResolution; + FT_UInt vertResolution; +} FT_Size_RequestRec; +typedef struct FT_Size_RequestRec_ *FT_Size_Request; + +#define FT_LOAD_DEFAULT 0x0 +#define FT_LOAD_NO_SCALE 0x1 +#define FT_LOAD_NO_HINTING 0x2 +#define FT_LOAD_RENDER 0x4 +#define FT_LOAD_NO_BITMAP 0x8 +#define FT_LOAD_VERTICAL_LAYOUT 0x10 +#define FT_LOAD_FORCE_AUTOHINT 0x20 +#define FT_LOAD_CROP_BITMAP 0x40 +#define FT_LOAD_PEDANTIC 0x80 +#define FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH 0x200 +#define FT_LOAD_NO_RECURSE 0x400 +#define FT_LOAD_IGNORE_TRANSFORM 0x800 +#define FT_LOAD_MONOCHROME 0x1000 +#define FT_LOAD_LINEAR_DESIGN 0x2000 +#define FT_LOAD_NO_AUTOHINT 0x8000U + +#define FT_LOAD_TARGET_( x ) ( (FT_Int32)( (x) & 15 ) << 16 ) + +#define FT_LOAD_TARGET_NORMAL FT_LOAD_TARGET_( FT_RENDER_MODE_NORMAL ) +#define FT_LOAD_TARGET_LIGHT FT_LOAD_TARGET_( FT_RENDER_MODE_LIGHT ) +#define FT_LOAD_TARGET_MONO FT_LOAD_TARGET_( FT_RENDER_MODE_MONO ) +#define FT_LOAD_TARGET_LCD FT_LOAD_TARGET_( FT_RENDER_MODE_LCD ) +#define FT_LOAD_TARGET_LCD_V FT_LOAD_TARGET_( FT_RENDER_MODE_LCD_V ) + +#define FT_ENC_TAG( value, a, b, c, d ) \ + value = ( ( (FT_UInt32)(a) << 24 ) | \ + ( (FT_UInt32)(b) << 16 ) | \ + ( (FT_UInt32)(c) << 8 ) | \ + (FT_UInt32)(d) ) + +typedef enum FT_Encoding_ +{ + FT_ENC_TAG( FT_ENCODING_NONE, 0, 0, 0, 0 ), + + FT_ENC_TAG( FT_ENCODING_MS_SYMBOL, 's', 'y', 'm', 'b' ), + FT_ENC_TAG( FT_ENCODING_UNICODE, 'u', 'n', 'i', 'c' ), + + FT_ENC_TAG( FT_ENCODING_SJIS, 's', 'j', 'i', 's' ), + FT_ENC_TAG( FT_ENCODING_GB2312, 'g', 'b', ' ', ' ' ), + FT_ENC_TAG( FT_ENCODING_BIG5, 'b', 'i', 'g', '5' ), + FT_ENC_TAG( FT_ENCODING_WANSUNG, 'w', 'a', 'n', 's' ), + FT_ENC_TAG( FT_ENCODING_JOHAB, 'j', 'o', 'h', 'a' ), + + /* for backwards compatibility */ + FT_ENCODING_MS_SJIS = FT_ENCODING_SJIS, + FT_ENCODING_MS_GB2312 = FT_ENCODING_GB2312, + FT_ENCODING_MS_BIG5 = FT_ENCODING_BIG5, + FT_ENCODING_MS_WANSUNG = FT_ENCODING_WANSUNG, + FT_ENCODING_MS_JOHAB = FT_ENCODING_JOHAB, + + FT_ENC_TAG( FT_ENCODING_ADOBE_STANDARD, 'A', 'D', 'O', 'B' ), + FT_ENC_TAG( FT_ENCODING_ADOBE_EXPERT, 'A', 'D', 'B', 'E' ), + FT_ENC_TAG( FT_ENCODING_ADOBE_CUSTOM, 'A', 'D', 'B', 'C' ), + FT_ENC_TAG( FT_ENCODING_ADOBE_LATIN_1, 'l', 'a', 't', '1' ), + + FT_ENC_TAG( FT_ENCODING_OLD_LATIN_2, 'l', 'a', 't', '2' ), + + FT_ENC_TAG( FT_ENCODING_APPLE_ROMAN, 'a', 'r', 'm', 'n' ) +} FT_Encoding; + +#define ft_encoding_none FT_ENCODING_NONE +#define ft_encoding_unicode FT_ENCODING_UNICODE +#define ft_encoding_symbol FT_ENCODING_MS_SYMBOL +#define ft_encoding_latin_1 FT_ENCODING_ADOBE_LATIN_1 +#define ft_encoding_latin_2 FT_ENCODING_OLD_LATIN_2 +#define ft_encoding_sjis FT_ENCODING_SJIS +#define ft_encoding_gb2312 FT_ENCODING_GB2312 +#define ft_encoding_big5 FT_ENCODING_BIG5 +#define ft_encoding_wansung FT_ENCODING_WANSUNG +#define ft_encoding_johab FT_ENCODING_JOHAB + +#define ft_encoding_adobe_standard FT_ENCODING_ADOBE_STANDARD +#define ft_encoding_adobe_expert FT_ENCODING_ADOBE_EXPERT +#define ft_encoding_adobe_custom FT_ENCODING_ADOBE_CUSTOM +#define ft_encoding_apple_roman FT_ENCODING_APPLE_ROMAN + +typedef struct FT_Bitmap_Size_ +{ + FT_Short height; + FT_Short width; + + FT_Pos size; + + FT_Pos x_ppem; + FT_Pos y_ppem; +} FT_Bitmap_Size; + +typedef struct FT_CharMapRec_ +{ + FT_Face face; + FT_Encoding encoding; + FT_UShort platform_id; + FT_UShort encoding_id; +} FT_CharMapRec; + +typedef void (*FT_Generic_Finalizer)(void* object); +typedef struct FT_Generic_ +{ + void* data; + FT_Generic_Finalizer finalizer; +} FT_Generic; + +typedef struct FT_Size_Metrics_ +{ + FT_UShort x_ppem; /* horizontal pixels per EM */ + FT_UShort y_ppem; /* vertical pixels per EM */ + + FT_Fixed x_scale; /* scaling values used to convert font */ + FT_Fixed y_scale; /* units to 26.6 fractional pixels */ + + FT_Pos ascender; /* ascender in 26.6 frac. pixels */ + FT_Pos descender; /* descender in 26.6 frac. pixels */ + FT_Pos height; /* text height in 26.6 frac. pixels */ + FT_Pos max_advance; /* max horizontal advance, in 26.6 pixels */ +} FT_Size_Metrics; + +typedef struct FT_SizeRec_ +{ + FT_Face face; /* parent face object */ + FT_Generic generic; /* generic pointer for client uses */ + FT_Size_Metrics metrics; /* size metrics */ + FT_Size_Internal internal; +} FT_SizeRec; + +typedef struct FT_FaceRec_ +{ + FT_Long num_faces; + FT_Long face_index; + + FT_Long face_flags; + FT_Long style_flags; + + FT_Long num_glyphs; + + FT_String* family_name; + FT_String* style_name; + + FT_Int num_fixed_sizes; + FT_Bitmap_Size* available_sizes; + + FT_Int num_charmaps; + FT_CharMap* charmaps; + + FT_Generic generic; + + /*# The following member variables (down to `underline_thickness') */ + /*# are only relevant to scalable outlines; cf. @FT_Bitmap_Size */ + /*# for bitmap fonts. */ + FT_BBox bbox; + + FT_UShort units_per_EM; + FT_Short ascender; + FT_Short descender; + FT_Short height; + + FT_Short max_advance_width; + FT_Short max_advance_height; + + FT_Short underline_position; + FT_Short underline_thickness; + + FT_GlyphSlot glyph; + FT_Size size; + FT_CharMap charmap; + + /* ft2 private + FT_Driver driver; + FT_Memory memory; + FT_Stream stream; + + FT_ListRec sizes_list; + + FT_Generic autohint; + void* extensions; + + FT_Face_Internal internal; + */ +} FT_FaceRec; + +typedef struct FT_GlyphSlotRec_ +{ + FT_Library library; + FT_Face face; + FT_GlyphSlot next; + FT_UInt reserved; /* retained for binary compatibility */ + FT_Generic generic; + + FT_Glyph_Metrics metrics; + FT_Fixed linearHoriAdvance; + FT_Fixed linearVertAdvance; + FT_Vector advance; + + FT_Glyph_Format format; + + FT_Bitmap bitmap; + FT_Int bitmap_left; + FT_Int bitmap_top; + + FT_Outline outline; + + FT_UInt num_subglyphs; + FT_SubGlyph subglyphs; + + void* control_data; + long control_len; + + FT_Pos lsb_delta; + FT_Pos rsb_delta; + + void* other; + + FT_Slot_Internal internal; +} FT_GlyphSlotRec; + +#define FT_FACE_FLAG_SCALABLE ( 1L << 0 ) +#define FT_FACE_FLAG_FIXED_SIZES ( 1L << 1 ) +#define FT_FACE_FLAG_FIXED_WIDTH ( 1L << 2 ) +#define FT_FACE_FLAG_SFNT ( 1L << 3 ) +#define FT_FACE_FLAG_HORIZONTAL ( 1L << 4 ) +#define FT_FACE_FLAG_VERTICAL ( 1L << 5 ) +#define FT_FACE_FLAG_KERNING ( 1L << 6 ) +#define FT_FACE_FLAG_FAST_GLYPHS ( 1L << 7 ) +#define FT_FACE_FLAG_MULTIPLE_MASTERS ( 1L << 8 ) +#define FT_FACE_FLAG_GLYPH_NAMES ( 1L << 9 ) +#define FT_FACE_FLAG_EXTERNAL_STREAM ( 1L << 10 ) +#define FT_FACE_FLAG_HINTER ( 1L << 11 ) +#define FT_FACE_FLAG_CID_KEYED ( 1L << 12 ) +#define FT_FACE_FLAG_TRICKY ( 1L << 13 ) + +typedef enum FT_Kerning_Mode_ +{ + FT_KERNING_DEFAULT = 0, + FT_KERNING_UNFITTED, + FT_KERNING_UNSCALED +} FT_Kerning_Mode; + +#endif // FT2_DEFS_H_H__ diff --git a/app/jni/ft2_fontdefs.h b/app/jni/ft2_fontdefs.h new file mode 100644 index 0000000..3f08187 --- /dev/null +++ b/app/jni/ft2_fontdefs.h @@ -0,0 +1,64 @@ +#ifndef FT2_PRIVATE_H__ +#define FT2_PRIVATE_H__ + +// anything should work, but I recommend multiples of 8 +// since the texture size should be a power of 2 +#define FONT_CHARS_PER_LINE 16 +#define FONT_CHAR_LINES 16 +#define FONT_CHARS_PER_MAP (FONT_CHARS_PER_LINE * FONT_CHAR_LINES) + +typedef struct glyph_slot_s +{ + qboolean image; + // we keep the quad coords here only currently + // if you need other info, make Font_LoadMapForIndex fill it into this slot + float txmin; // texture coordinate in [0,1] + float txmax; + float tymin; + float tymax; + float vxmin; + float vxmax; + float vymin; + float vymax; + float advance_x; + float advance_y; +} glyph_slot_t; + +struct ft2_font_map_s +{ + Uchar start; + struct ft2_font_map_s *next; + float size; + // the actual size used in the freetype code + // by convention, the requested size is the height of the font's bounding box. + float intSize; + int glyphSize; + + cachepic_t *pic; + qboolean static_tex; + glyph_slot_t glyphs[FONT_CHARS_PER_MAP]; + + // contains the kerning information for the first 256 characters + // for the other characters, we will lookup the kerning information + ft2_kerning_t kerning; + // safes us the trouble of calculating these over and over again + double sfx, sfy; + + // the width_of for the image-font, pixel-snapped for this size + float width_of[256]; +}; + +struct ft2_attachment_s +{ + const unsigned char *data; + fs_offset_t size; +}; + +//qboolean Font_LoadMapForIndex(ft2_font_t *font, Uchar _ch, ft2_font_map_t **outmap); +qboolean Font_LoadMapForIndex(ft2_font_t *font, int map_index, Uchar _ch, ft2_font_map_t **outmap); + +void font_start(void); +void font_shutdown(void); +void font_newmap(void); + +#endif // FT2_PRIVATE_H__ diff --git a/app/jni/gl_backend.c b/app/jni/gl_backend.c new file mode 100644 index 0000000..939a36c --- /dev/null +++ b/app/jni/gl_backend.c @@ -0,0 +1,4867 @@ + +#include "quakedef.h" +#include "cl_collision.h" +#include "dpsoftrast.h" +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +extern D3DCAPS9 vid_d3d9caps; +#endif + +// on GLES we have to use some proper #define's +#ifndef GL_FRAMEBUFFER +#define GL_FRAMEBUFFER 0x8D40 +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#endif +#ifndef GL_COLOR_ATTACHMENT1 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#endif +#ifndef GL_ARRAY_BUFFER +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#endif +//#ifndef GL_VERTEX_ARRAY +//#define GL_VERTEX_ARRAY 0x8074 +//#define GL_COLOR_ARRAY 0x8076 +//#define GL_TEXTURE_COORD_ARRAY 0x8078 +//#endif +#ifndef GL_TEXTURE0 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#endif + +#ifndef GL_TEXTURE_3D +#define GL_TEXTURE_3D 0x806F +#endif +#ifndef GL_TEXTURE_CUBE_MAP +#define GL_TEXTURE_CUBE_MAP 0x8513 +#endif +//#ifndef GL_MODELVIEW +//#define GL_MODELVIEW 0x1700 +//#endif +//#ifndef GL_PROJECTION +//#define GL_PROJECTION 0x1701 +//#endif +//#ifndef GL_DECAL +//#define GL_DECAL 0x2101 +//#endif +//#ifndef GL_INTERPOLATE +//#define GL_INTERPOLATE 0x8575 +//#endif + + +#define MAX_RENDERTARGETS 4 + +cvar_t gl_mesh_drawrangeelements = {0, "gl_mesh_drawrangeelements", "1", "use glDrawRangeElements function if available instead of glDrawElements (for performance comparisons or bug testing)"}; +cvar_t gl_mesh_testmanualfeeding = {0, "gl_mesh_testmanualfeeding", "0", "use glBegin(GL_TRIANGLES);glTexCoord2f();glVertex3f();glEnd(); primitives instead of glDrawElements (useful to test for driver bugs with glDrawElements)"}; +cvar_t gl_paranoid = {0, "gl_paranoid", "0", "enables OpenGL error checking and other tests"}; +cvar_t gl_printcheckerror = {0, "gl_printcheckerror", "0", "prints all OpenGL error checks, useful to identify location of driver crashes"}; + +cvar_t r_render = {0, "r_render", "1", "enables rendering 3D views (you want this on!)"}; +cvar_t r_renderview = {0, "r_renderview", "1", "enables rendering 3D views (you want this on!)"}; +cvar_t r_waterwarp = {CVAR_SAVE, "r_waterwarp", "1", "warp view while underwater"}; +cvar_t gl_polyblend = {CVAR_SAVE, "gl_polyblend", "1", "tints view while underwater, hurt, etc"}; +cvar_t gl_dither = {CVAR_SAVE, "gl_dither", "1", "enables OpenGL dithering (16bit looks bad with this off)"}; +cvar_t gl_vbo = {CVAR_SAVE, "gl_vbo", "3", "make use of GL_ARB_vertex_buffer_object extension to store static geometry in video memory for faster rendering, 0 disables VBO allocation or use, 1 enables VBOs for vertex and triangle data, 2 only for vertex data, 3 for vertex data and triangle data of simple meshes (ones with only one surface)"}; +cvar_t gl_vbo_dynamicvertex = {CVAR_SAVE, "gl_vbo_dynamicvertex", "0", "make use of GL_ARB_vertex_buffer_object extension when rendering dynamic (animated/procedural) geometry such as text and particles"}; +cvar_t gl_vbo_dynamicindex = {CVAR_SAVE, "gl_vbo_dynamicindex", "0", "make use of GL_ARB_vertex_buffer_object extension when rendering dynamic (animated/procedural) geometry such as text and particles"}; +cvar_t gl_fbo = {CVAR_SAVE, "gl_fbo", "1", "make use of GL_ARB_framebuffer_object extension to enable shadowmaps and other features using pixel formats different from the framebuffer"}; + +cvar_t v_flipped = {0, "v_flipped", "0", "mirror the screen (poor man's left handed mode)"}; +qboolean v_flipped_state = false; + +r_viewport_t gl_viewport; +matrix4x4_t gl_modelmatrix; +matrix4x4_t gl_viewmatrix; +matrix4x4_t gl_modelviewmatrix; +matrix4x4_t gl_projectionmatrix; +matrix4x4_t gl_modelviewprojectionmatrix; +float gl_modelview16f[16]; +float gl_modelviewprojection16f[16]; +qboolean gl_modelmatrixchanged; + +int gl_maxdrawrangeelementsvertices; +int gl_maxdrawrangeelementsindices; + +#ifdef DEBUGGL +int errornumber = 0; + +void GL_PrintError(int errornumber, const char *filename, int linenumber) +{ + switch(errornumber) + { +#ifdef GL_INVALID_ENUM + case GL_INVALID_ENUM: + Con_Printf("GL_INVALID_ENUM at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_INVALID_VALUE + case GL_INVALID_VALUE: + Con_Printf("GL_INVALID_VALUE at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_INVALID_OPERATION + case GL_INVALID_OPERATION: + Con_Printf("GL_INVALID_OPERATION at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_STACK_OVERFLOW + case GL_STACK_OVERFLOW: + Con_Printf("GL_STACK_OVERFLOW at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_STACK_UNDERFLOW + case GL_STACK_UNDERFLOW: + Con_Printf("GL_STACK_UNDERFLOW at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_OUT_OF_MEMORY + case GL_OUT_OF_MEMORY: + Con_Printf("GL_OUT_OF_MEMORY at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_TABLE_TOO_LARGE + case GL_TABLE_TOO_LARGE: + Con_Printf("GL_TABLE_TOO_LARGE at %s:%i\n", filename, linenumber); + break; +#endif +#ifdef GL_INVALID_FRAMEBUFFER_OPERATION + case GL_INVALID_FRAMEBUFFER_OPERATION: + Con_Printf("GL_INVALID_FRAMEBUFFER_OPERATION at %s:%i\n", filename, linenumber); + break; +#endif + default: + Con_Printf("GL UNKNOWN (%i) at %s:%i\n", errornumber, filename, linenumber); + break; + } +} +#endif + +#define BACKENDACTIVECHECK if (!gl_state.active) Sys_Error("GL backend function called when backend is not active"); + +void SCR_ScreenShot_f (void); + +typedef struct gltextureunit_s +{ + int pointer_texcoord_components; + int pointer_texcoord_gltype; + size_t pointer_texcoord_stride; + const void *pointer_texcoord_pointer; + const r_meshbuffer_t *pointer_texcoord_vertexbuffer; + size_t pointer_texcoord_offset; + + rtexture_t *texture; + int t2d, t3d, tcubemap; + int arrayenabled; + int rgbscale, alphascale; + int combine; + int combinergb, combinealpha; + // texmatrixenabled exists only to avoid unnecessary texmatrix compares + int texmatrixenabled; + matrix4x4_t matrix; +} +gltextureunit_t; + +typedef struct gl_state_s +{ + int cullface; + int cullfaceenable; + int blendfunc1; + int blendfunc2; + qboolean blend; + GLboolean depthmask; + int colormask; // stored as bottom 4 bits: r g b a (3 2 1 0 order) + int depthtest; + int depthfunc; + float depthrange[2]; + float polygonoffset[2]; + int alphatest; + int alphafunc; + float alphafuncvalue; + qboolean alphatocoverage; + int scissortest; + unsigned int unit; + unsigned int clientunit; + gltextureunit_t units[MAX_TEXTUREUNITS]; + float color4f[4]; + int lockrange_first; + int lockrange_count; + int vertexbufferobject; + int elementbufferobject; + int uniformbufferobject; + int framebufferobject; + int defaultframebufferobject; // deal with platforms that use a non-zero default fbo + qboolean pointer_color_enabled; + + int pointer_vertex_components; + int pointer_vertex_gltype; + size_t pointer_vertex_stride; + const void *pointer_vertex_pointer; + const r_meshbuffer_t *pointer_vertex_vertexbuffer; + size_t pointer_vertex_offset; + + int pointer_color_components; + int pointer_color_gltype; + size_t pointer_color_stride; + const void *pointer_color_pointer; + const r_meshbuffer_t *pointer_color_vertexbuffer; + size_t pointer_color_offset; + + void *preparevertices_tempdata; + size_t preparevertices_tempdatamaxsize; + r_vertexgeneric_t *preparevertices_vertexgeneric; + r_vertexmesh_t *preparevertices_vertexmesh; + int preparevertices_numvertices; + + qboolean usevbo_staticvertex; + qboolean usevbo_staticindex; + qboolean usevbo_dynamicvertex; + qboolean usevbo_dynamicindex; + + memexpandablearray_t meshbufferarray; + + qboolean active; + +#ifdef SUPPORTD3D +// rtexture_t *d3drt_depthtexture; +// rtexture_t *d3drt_colortextures[MAX_RENDERTARGETS]; + IDirect3DSurface9 *d3drt_depthsurface; + IDirect3DSurface9 *d3drt_colorsurfaces[MAX_RENDERTARGETS]; + IDirect3DSurface9 *d3drt_backbufferdepthsurface; + IDirect3DSurface9 *d3drt_backbuffercolorsurface; + void *d3dvertexbuffer; + void *d3dvertexdata; + size_t d3dvertexsize; +#endif +} +gl_state_t; + +static gl_state_t gl_state; + +void android_kostyl() +{ +gl_state.pointer_vertex_pointer=0; +gl_state.pointer_color_pointer=0; +} + + +/* +note: here's strip order for a terrain row: +0--1--2--3--4 +|\ |\ |\ |\ | +| \| \| \| \| +A--B--C--D--E +clockwise + +A0B, 01B, B1C, 12C, C2D, 23D, D3E, 34E + +*elements++ = i + row; +*elements++ = i; +*elements++ = i + row + 1; +*elements++ = i; +*elements++ = i + 1; +*elements++ = i + row + 1; + + +for (y = 0;y < rows - 1;y++) +{ + for (x = 0;x < columns - 1;x++) + { + i = y * rows + x; + *elements++ = i + columns; + *elements++ = i; + *elements++ = i + columns + 1; + *elements++ = i; + *elements++ = i + 1; + *elements++ = i + columns + 1; + } +} + +alternative: +0--1--2--3--4 +| /| /|\ | /| +|/ |/ | \|/ | +A--B--C--D--E +counterclockwise + +for (y = 0;y < rows - 1;y++) +{ + for (x = 0;x < columns - 1;x++) + { + i = y * rows + x; + *elements++ = i; + *elements++ = i + columns; + *elements++ = i + columns + 1; + *elements++ = i + columns; + *elements++ = i + columns + 1; + *elements++ = i + 1; + } +} +*/ + +int polygonelement3i[(POLYGONELEMENTS_MAXPOINTS-2)*3]; +unsigned short polygonelement3s[(POLYGONELEMENTS_MAXPOINTS-2)*3]; +int quadelement3i[QUADELEMENTS_MAXQUADS*6]; +unsigned short quadelement3s[QUADELEMENTS_MAXQUADS*6]; + +static void GL_VBOStats_f(void) +{ + GL_Mesh_ListVBOs(true); +} + +static void GL_Backend_ResetState(void); + +static void R_Mesh_InitVertexDeclarations(void); +static void R_Mesh_DestroyVertexDeclarations(void); + +static void R_Mesh_SetUseVBO(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + gl_state.usevbo_staticvertex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; + gl_state.usevbo_staticindex = (vid.support.arb_vertex_buffer_object && (gl_vbo.integer == 1 || gl_vbo.integer == 3)) || vid.forcevbo; + gl_state.usevbo_dynamicvertex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer && gl_vbo.integer) || vid.forcevbo; + gl_state.usevbo_dynamicindex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicindex.integer && gl_vbo.integer) || vid.forcevbo; + break; + case RENDERPATH_D3D9: + gl_state.usevbo_staticvertex = gl_state.usevbo_staticindex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; + gl_state.usevbo_dynamicvertex = gl_state.usevbo_dynamicindex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer && gl_vbo_dynamicindex.integer && gl_vbo.integer) || vid.forcevbo; + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + gl_state.usevbo_staticvertex = false; + gl_state.usevbo_staticindex = false; + gl_state.usevbo_dynamicvertex = false; + gl_state.usevbo_dynamicindex = false; + break; + case RENDERPATH_GLES2: + gl_state.usevbo_staticvertex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; + gl_state.usevbo_staticindex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; + gl_state.usevbo_dynamicvertex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer) || vid.forcevbo; + gl_state.usevbo_dynamicindex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicindex.integer) || vid.forcevbo; + break; + } +} + +static void gl_backend_start(void) +{ + memset(&gl_state, 0, sizeof(gl_state)); + + R_Mesh_InitVertexDeclarations(); + + R_Mesh_SetUseVBO(); + Mem_ExpandableArray_NewArray(&gl_state.meshbufferarray, r_main_mempool, sizeof(r_meshbuffer_t), 128); + + Con_DPrintf("OpenGL backend started.\n"); + + CHECKGLERROR + + GL_Backend_ResetState(); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // fetch current fbo here (default fbo is not 0 on some GLES devices) + if (vid.support.ext_framebuffer_object) + qglGetIntegerv(GL_FRAMEBUFFER_BINDING, &gl_state.defaultframebufferobject); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_GetDepthStencilSurface(vid_d3d9dev, &gl_state.d3drt_backbufferdepthsurface); + IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, &gl_state.d3drt_backbuffercolorsurface); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } +} + +static void gl_backend_shutdown(void) +{ + Con_DPrint("OpenGL Backend shutting down\n"); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DSurface9_Release(gl_state.d3drt_backbufferdepthsurface); + IDirect3DSurface9_Release(gl_state.d3drt_backbuffercolorsurface); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + + if (gl_state.preparevertices_tempdata) + Mem_Free(gl_state.preparevertices_tempdata); + + Mem_ExpandableArray_FreeArray(&gl_state.meshbufferarray); + + R_Mesh_DestroyVertexDeclarations(); + + memset(&gl_state, 0, sizeof(gl_state)); +} + +static void gl_backend_newmap(void) +{ +} + +static void gl_backend_devicelost(void) +{ + int i, endindex; + r_meshbuffer_t *buffer; +#ifdef SUPPORTD3D + gl_state.d3dvertexbuffer = NULL; +#endif + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DSurface9_Release(gl_state.d3drt_backbufferdepthsurface); + IDirect3DSurface9_Release(gl_state.d3drt_backbuffercolorsurface); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + endindex = Mem_ExpandableArray_IndexRange(&gl_state.meshbufferarray); + for (i = 0;i < endindex;i++) + { + buffer = (r_meshbuffer_t *) Mem_ExpandableArray_RecordAtIndex(&gl_state.meshbufferarray, i); + if (!buffer || !buffer->isdynamic) + continue; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (buffer->devicebuffer) + { + if (buffer->isindexbuffer) + IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9*)buffer->devicebuffer); + else + IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9*)buffer->devicebuffer); + buffer->devicebuffer = NULL; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + } +} + +static void gl_backend_devicerestored(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_GetDepthStencilSurface(vid_d3d9dev, &gl_state.d3drt_backbufferdepthsurface); + IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, &gl_state.d3drt_backbuffercolorsurface); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } +} + +void gl_backend_init(void) +{ + int i; + + for (i = 0;i < POLYGONELEMENTS_MAXPOINTS - 2;i++) + { + polygonelement3s[i * 3 + 0] = 0; + polygonelement3s[i * 3 + 1] = i + 1; + polygonelement3s[i * 3 + 2] = i + 2; + } + // elements for rendering a series of quads as triangles + for (i = 0;i < QUADELEMENTS_MAXQUADS;i++) + { + quadelement3s[i * 6 + 0] = i * 4; + quadelement3s[i * 6 + 1] = i * 4 + 1; + quadelement3s[i * 6 + 2] = i * 4 + 2; + quadelement3s[i * 6 + 3] = i * 4; + quadelement3s[i * 6 + 4] = i * 4 + 2; + quadelement3s[i * 6 + 5] = i * 4 + 3; + } + + for (i = 0;i < (POLYGONELEMENTS_MAXPOINTS - 2)*3;i++) + polygonelement3i[i] = polygonelement3s[i]; + for (i = 0;i < QUADELEMENTS_MAXQUADS*6;i++) + quadelement3i[i] = quadelement3s[i]; + + Cvar_RegisterVariable(&r_render); + Cvar_RegisterVariable(&r_renderview); + Cvar_RegisterVariable(&r_waterwarp); + Cvar_RegisterVariable(&gl_polyblend); + Cvar_RegisterVariable(&v_flipped); + Cvar_RegisterVariable(&gl_dither); + Cvar_RegisterVariable(&gl_vbo); + Cvar_RegisterVariable(&gl_vbo_dynamicvertex); + Cvar_RegisterVariable(&gl_vbo_dynamicindex); + Cvar_RegisterVariable(&gl_paranoid); + Cvar_RegisterVariable(&gl_printcheckerror); + + Cvar_RegisterVariable(&gl_mesh_drawrangeelements); + Cvar_RegisterVariable(&gl_mesh_testmanualfeeding); + + Cmd_AddCommand("gl_vbostats", GL_VBOStats_f, "prints a list of all buffer objects (vertex data and triangle elements) and total video memory used by them"); + + R_RegisterModule("GL_Backend", gl_backend_start, gl_backend_shutdown, gl_backend_newmap, gl_backend_devicelost, gl_backend_devicerestored); +} + +void GL_SetMirrorState(qboolean state); + +void R_Viewport_TransformToScreen(const r_viewport_t *v, const vec4_t in, vec4_t out) +{ + vec4_t temp; + float iw; + Matrix4x4_Transform4 (&v->viewmatrix, in, temp); + Matrix4x4_Transform4 (&v->projectmatrix, temp, out); + iw = 1.0f / out[3]; + out[0] = v->x + (out[0] * iw + 1.0f) * v->width * 0.5f; + + // for an odd reason, inverting this is wrong for R_Shadow_ScissorForBBox (we then get badly scissored lights) + //out[1] = v->y + v->height - (out[1] * iw + 1.0f) * v->height * 0.5f; + out[1] = v->y + (out[1] * iw + 1.0f) * v->height * 0.5f; + + out[2] = v->z + (out[2] * iw + 1.0f) * v->depth * 0.5f; +} + +void GL_Finish(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglFinish(); + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Finish(); + break; + } +} + +static int bboxedges[12][2] = +{ + // top + {0, 1}, // +X + {0, 2}, // +Y + {1, 3}, // Y, +X + {2, 3}, // X, +Y + // bottom + {4, 5}, // +X + {4, 6}, // +Y + {5, 7}, // Y, +X + {6, 7}, // X, +Y + // verticals + {0, 4}, // +Z + {1, 5}, // X, +Z + {2, 6}, // Y, +Z + {3, 7}, // XY, +Z +}; + +qboolean R_ScissorForBBox(const float *mins, const float *maxs, int *scissor) +{ + int i, ix1, iy1, ix2, iy2; + float x1, y1, x2, y2; + vec4_t v, v2; + float vertex[20][3]; + int j, k; + vec4_t plane4f; + int numvertices; + float corner[8][4]; + float dist[8]; + int sign[8]; + float f; + + scissor[0] = r_refdef.view.viewport.x; + scissor[1] = r_refdef.view.viewport.y; + scissor[2] = r_refdef.view.viewport.width; + scissor[3] = r_refdef.view.viewport.height; + + // if view is inside the box, just say yes it's visible + if (BoxesOverlap(r_refdef.view.origin, r_refdef.view.origin, mins, maxs)) + return false; + + // transform all corners that are infront of the nearclip plane + VectorNegate(r_refdef.view.frustum[4].normal, plane4f); + plane4f[3] = r_refdef.view.frustum[4].dist; + numvertices = 0; + for (i = 0;i < 8;i++) + { + Vector4Set(corner[i], (i & 1) ? maxs[0] : mins[0], (i & 2) ? maxs[1] : mins[1], (i & 4) ? maxs[2] : mins[2], 1); + dist[i] = DotProduct4(corner[i], plane4f); + sign[i] = dist[i] > 0; + if (!sign[i]) + { + VectorCopy(corner[i], vertex[numvertices]); + numvertices++; + } + } + // if some points are behind the nearclip, add clipped edge points to make + // sure that the scissor boundary is complete + if (numvertices > 0 && numvertices < 8) + { + // add clipped edge points + for (i = 0;i < 12;i++) + { + j = bboxedges[i][0]; + k = bboxedges[i][1]; + if (sign[j] != sign[k]) + { + f = dist[j] / (dist[j] - dist[k]); + VectorLerp(corner[j], f, corner[k], vertex[numvertices]); + numvertices++; + } + } + } + + // if we have no points to check, it is behind the view plane + if (!numvertices) + return true; + + // if we have some points to transform, check what screen area is covered + x1 = y1 = x2 = y2 = 0; + v[3] = 1.0f; + //Con_Printf("%i vertices to transform...\n", numvertices); + for (i = 0;i < numvertices;i++) + { + VectorCopy(vertex[i], v); + R_Viewport_TransformToScreen(&r_refdef.view.viewport, v, v2); + //Con_Printf("%.3f %.3f %.3f %.3f transformed to %.3f %.3f %.3f %.3f\n", v[0], v[1], v[2], v[3], v2[0], v2[1], v2[2], v2[3]); + if (i) + { + if (x1 > v2[0]) x1 = v2[0]; + if (x2 < v2[0]) x2 = v2[0]; + if (y1 > v2[1]) y1 = v2[1]; + if (y2 < v2[1]) y2 = v2[1]; + } + else + { + x1 = x2 = v2[0]; + y1 = y2 = v2[1]; + } + } + + // now convert the scissor rectangle to integer screen coordinates + ix1 = (int)(x1 - 1.0f); + //iy1 = vid.height - (int)(y2 - 1.0f); + //iy1 = r_refdef.view.viewport.width + 2 * r_refdef.view.viewport.x - (int)(y2 - 1.0f); + iy1 = (int)(y1 - 1.0f); + ix2 = (int)(x2 + 1.0f); + //iy2 = vid.height - (int)(y1 + 1.0f); + //iy2 = r_refdef.view.viewport.height + 2 * r_refdef.view.viewport.y - (int)(y1 + 1.0f); + iy2 = (int)(y2 + 1.0f); + //Con_Printf("%f %f %f %f\n", x1, y1, x2, y2); + + // clamp it to the screen + if (ix1 < r_refdef.view.viewport.x) ix1 = r_refdef.view.viewport.x; + if (iy1 < r_refdef.view.viewport.y) iy1 = r_refdef.view.viewport.y; + if (ix2 > r_refdef.view.viewport.x + r_refdef.view.viewport.width) ix2 = r_refdef.view.viewport.x + r_refdef.view.viewport.width; + if (iy2 > r_refdef.view.viewport.y + r_refdef.view.viewport.height) iy2 = r_refdef.view.viewport.y + r_refdef.view.viewport.height; + + // if it is inside out, it's not visible + if (ix2 <= ix1 || iy2 <= iy1) + return true; + + // the light area is visible, set up the scissor rectangle + scissor[0] = ix1; + scissor[1] = iy1; + scissor[2] = ix2 - ix1; + scissor[3] = iy2 - iy1; + + // D3D Y coordinate is top to bottom, OpenGL is bottom to top, fix the D3D one + switch(vid.renderpath) + { + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + scissor[1] = vid.height - scissor[1] - scissor[3]; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + } + + return false; +} + + +static void R_Viewport_ApplyNearClipPlaneFloatGL(const r_viewport_t *v, float *m, float normalx, float normaly, float normalz, float dist) +{ + float q[4]; + float d; + float clipPlane[4], v3[3], v4[3]; + float normal[3]; + + // This is inspired by Oblique Depth Projection from http://www.terathon.com/code/oblique.php + + VectorSet(normal, normalx, normaly, normalz); + Matrix4x4_Transform3x3(&v->viewmatrix, normal, clipPlane); + VectorScale(normal, -dist, v3); + Matrix4x4_Transform(&v->viewmatrix, v3, v4); + // FIXME: LordHavoc: I think this can be done more efficiently somehow but I can't remember the technique + clipPlane[3] = -DotProduct(v4, clipPlane); + +#if 0 +{ + // testing code for comparing results + float clipPlane2[4]; + VectorCopy4(clipPlane, clipPlane2); + R_EntityMatrix(&identitymatrix); + VectorSet(q, normal[0], normal[1], normal[2], -dist); + qglClipPlane(GL_CLIP_PLANE0, q); + qglGetClipPlane(GL_CLIP_PLANE0, q); + VectorCopy4(q, clipPlane); +} +#endif + + // Calculate the clip-space corner point opposite the clipping plane + // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and + // transform it into camera space by multiplying it + // by the inverse of the projection matrix + q[0] = ((clipPlane[0] < 0.0f ? -1.0f : clipPlane[0] > 0.0f ? 1.0f : 0.0f) + m[8]) / m[0]; + q[1] = ((clipPlane[1] < 0.0f ? -1.0f : clipPlane[1] > 0.0f ? 1.0f : 0.0f) + m[9]) / m[5]; + q[2] = -1.0f; + q[3] = (1.0f + m[10]) / m[14]; + + // Calculate the scaled plane vector + d = 2.0f / DotProduct4(clipPlane, q); + + // Replace the third row of the projection matrix + m[2] = clipPlane[0] * d; + m[6] = clipPlane[1] * d; + m[10] = clipPlane[2] * d + 1.0f; + m[14] = clipPlane[3] * d; +} + +void R_Viewport_InitOrtho(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float x1, float y1, float x2, float y2, float nearclip, float farclip, const float *nearplane) +{ + float left = x1, right = x2, bottom = y2, top = y1, zNear = nearclip, zFar = farclip; + float m[16]; + memset(v, 0, sizeof(*v)); + v->type = R_VIEWPORTTYPE_ORTHO; + v->cameramatrix = *cameramatrix; + v->x = x; + v->y = y; + v->z = 0; + v->width = width; + v->height = height; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[0] = 2/(right - left); + m[5] = 2/(top - bottom); + m[10] = -2/(zFar - zNear); + m[12] = - (right + left)/(right - left); + m[13] = - (top + bottom)/(top - bottom); + m[14] = - (zFar + zNear)/(zFar - zNear); + m[15] = 1; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + m[10] = -1/(zFar - zNear); + m[14] = -zNear/(zFar-zNear); + break; + } + v->screentodepth[0] = -farclip / (farclip - nearclip); + v->screentodepth[1] = farclip * nearclip / (farclip - nearclip); + + Matrix4x4_Invert_Full(&v->viewmatrix, &v->cameramatrix); + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); + +#if 0 + { + vec4_t test1; + vec4_t test2; + Vector4Set(test1, (x1+x2)*0.5f, (y1+y2)*0.5f, 0.0f, 1.0f); + R_Viewport_TransformToScreen(v, test1, test2); + Con_Printf("%f %f %f -> %f %f %f\n", test1[0], test1[1], test1[2], test2[0], test2[1], test2[2]); + } +#endif +} + +void R_Viewport_InitPerspective(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float nearclip, float farclip, const float *nearplane) +{ + matrix4x4_t tempmatrix, basematrix; + float m[16]; + memset(v, 0, sizeof(*v)); + + v->type = R_VIEWPORTTYPE_PERSPECTIVE; + v->cameramatrix = *cameramatrix; + v->x = x; + v->y = y; + v->z = 0; + v->width = width; + v->height = height; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[0] = 1.0 / frustumx; + m[5] = 1.0 / frustumy; + m[10] = -(farclip + nearclip) / (farclip - nearclip); + m[11] = -1; + m[14] = -2 * nearclip * farclip / (farclip - nearclip); + v->screentodepth[0] = -farclip / (farclip - nearclip); + v->screentodepth[1] = farclip * nearclip / (farclip - nearclip); + + Matrix4x4_Invert_Full(&tempmatrix, &v->cameramatrix); + Matrix4x4_CreateRotate(&basematrix, -90, 1, 0, 0); + Matrix4x4_ConcatRotate(&basematrix, 90, 0, 0, 1); + Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + if(v_flipped.integer) + { + m[0] = -m[0]; + m[4] = -m[4]; + m[8] = -m[8]; + m[12] = -m[12]; + } + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); +} + +void R_Viewport_InitPerspectiveInfinite(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float nearclip, const float *nearplane) +{ + matrix4x4_t tempmatrix, basematrix; + const float nudge = 1.0 - 1.0 / (1<<23); + float m[16]; + memset(v, 0, sizeof(*v)); + + v->type = R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP; + v->cameramatrix = *cameramatrix; + v->x = x; + v->y = y; + v->z = 0; + v->width = width; + v->height = height; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[ 0] = 1.0 / frustumx; + m[ 5] = 1.0 / frustumy; + m[10] = -nudge; + m[11] = -1; + m[14] = -2 * nearclip * nudge; + v->screentodepth[0] = (m[10] + 1) * 0.5 - 1; + v->screentodepth[1] = m[14] * -0.5; + + Matrix4x4_Invert_Full(&tempmatrix, &v->cameramatrix); + Matrix4x4_CreateRotate(&basematrix, -90, 1, 0, 0); + Matrix4x4_ConcatRotate(&basematrix, 90, 0, 0, 1); + Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + if(v_flipped.integer) + { + m[0] = -m[0]; + m[4] = -m[4]; + m[8] = -m[8]; + m[12] = -m[12]; + } + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); +} + +float cubeviewmatrix[6][16] = +{ + // standard cubemap projections + { // +X + 0, 0,-1, 0, + 0,-1, 0, 0, + -1, 0, 0, 0, + 0, 0, 0, 1, + }, + { // -X + 0, 0, 1, 0, + 0,-1, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 1, + }, + { // +Y + 1, 0, 0, 0, + 0, 0,-1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1, + }, + { // -Y + 1, 0, 0, 0, + 0, 0, 1, 0, + 0,-1, 0, 0, + 0, 0, 0, 1, + }, + { // +Z + 1, 0, 0, 0, + 0,-1, 0, 0, + 0, 0,-1, 0, + 0, 0, 0, 1, + }, + { // -Z + -1, 0, 0, 0, + 0,-1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }, +}; +float rectviewmatrix[6][16] = +{ + // sign-preserving cubemap projections + { // +X + 0, 0,-1, 0, + 0, 1, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 1, + }, + { // -X + 0, 0, 1, 0, + 0, 1, 0, 0, + 1, 0, 0, 0, + 0, 0, 0, 1, + }, + { // +Y + 1, 0, 0, 0, + 0, 0,-1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1, + }, + { // -Y + 1, 0, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1, + }, + { // +Z + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0,-1, 0, + 0, 0, 0, 1, + }, + { // -Z + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }, +}; + +void R_Viewport_InitCubeSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, float nearclip, float farclip, const float *nearplane) +{ + matrix4x4_t tempmatrix, basematrix; + float m[16]; + memset(v, 0, sizeof(*v)); + v->type = R_VIEWPORTTYPE_PERSPECTIVECUBESIDE; + v->cameramatrix = *cameramatrix; + v->width = size; + v->height = size; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[0] = m[5] = 1.0f; + m[10] = -(farclip + nearclip) / (farclip - nearclip); + m[11] = -1; + m[14] = -2 * nearclip * farclip / (farclip - nearclip); + + Matrix4x4_FromArrayFloatGL(&basematrix, cubeviewmatrix[side]); + Matrix4x4_Invert_Simple(&tempmatrix, &v->cameramatrix); + Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); +} + +void R_Viewport_InitRectSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, int border, float nearclip, float farclip, const float *nearplane) +{ + matrix4x4_t tempmatrix, basematrix; + float m[16]; + memset(v, 0, sizeof(*v)); + v->type = R_VIEWPORTTYPE_PERSPECTIVECUBESIDE; + v->cameramatrix = *cameramatrix; + v->x = (side & 1) * size; + v->y = (side >> 1) * size; + v->width = size; + v->height = size; + v->depth = 1; + memset(m, 0, sizeof(m)); + m[0] = m[5] = 1.0f * ((float)size - border) / size; + m[10] = -(farclip + nearclip) / (farclip - nearclip); + m[11] = -1; + m[14] = -2 * nearclip * farclip / (farclip - nearclip); + + Matrix4x4_FromArrayFloatGL(&basematrix, rectviewmatrix[side]); + Matrix4x4_Invert_Simple(&tempmatrix, &v->cameramatrix); + Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GL13: + case RENDERPATH_GL11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + m[5] *= -1; + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + + if (nearplane) + R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); + + Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); +} + +void R_SetViewport(const r_viewport_t *v) +{ + float m[16]; + gl_viewport = *v; + + // FIXME: v_flipped_state is evil, this probably breaks somewhere + GL_SetMirrorState(v_flipped.integer && (v->type == R_VIEWPORTTYPE_PERSPECTIVE || v->type == R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP)); + + // copy over the matrices to our state + gl_viewmatrix = v->viewmatrix; + gl_projectionmatrix = v->projectmatrix; + + switch(vid.renderpath) + { + case RENDERPATH_GL13: + case RENDERPATH_GL11: + case RENDERPATH_GLES1: +#ifdef GL_PROJECTION + CHECKGLERROR + qglViewport(v->x, v->y, v->width, v->height);CHECKGLERROR + // Load the projection matrix into OpenGL + qglMatrixMode(GL_PROJECTION);CHECKGLERROR + Matrix4x4_ToArrayFloatGL(&gl_projectionmatrix, m); + qglLoadMatrixf(m);CHECKGLERROR + qglMatrixMode(GL_MODELVIEW);CHECKGLERROR +#endif + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DVIEWPORT9 d3dviewport; + d3dviewport.X = gl_viewport.x; + d3dviewport.Y = gl_viewport.y; + d3dviewport.Width = gl_viewport.width; + d3dviewport.Height = gl_viewport.height; + d3dviewport.MinZ = gl_state.depthrange[0]; + d3dviewport.MaxZ = gl_state.depthrange[1]; + IDirect3DDevice9_SetViewport(vid_d3d9dev, &d3dviewport); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Viewport(v->x, v->y, v->width, v->height); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + CHECKGLERROR + qglViewport(v->x, v->y, v->width, v->height);CHECKGLERROR + break; + } + + // force an update of the derived matrices + gl_modelmatrixchanged = true; + R_EntityMatrix(&gl_modelmatrix); +} + +void R_GetViewport(r_viewport_t *v) +{ + *v = gl_viewport; +} + +static void GL_BindVBO(int bufferobject) +{ + if (gl_state.vertexbufferobject != bufferobject) + { + gl_state.vertexbufferobject = bufferobject; + CHECKGLERROR + qglBindBufferARB(GL_ARRAY_BUFFER, bufferobject);CHECKGLERROR + } +} + +static void GL_BindEBO(int bufferobject) +{ + if (gl_state.elementbufferobject != bufferobject) + { + gl_state.elementbufferobject = bufferobject; + CHECKGLERROR + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, bufferobject);CHECKGLERROR + } +} + +static void GL_BindUBO(int bufferobject) +{ + if (gl_state.uniformbufferobject != bufferobject) + { + gl_state.uniformbufferobject = bufferobject; + CHECKGLERROR + qglBindBufferARB(GL_UNIFORM_BUFFER, bufferobject);CHECKGLERROR + } +} + +static const GLuint drawbuffers[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3}; +int R_Mesh_CreateFramebufferObject(rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (vid.support.arb_framebuffer_object) + { + int temp; + GLuint status; + qglGenFramebuffers(1, (GLuint*)&temp);CHECKGLERROR + R_Mesh_SetRenderTargets(temp, NULL, NULL, NULL, NULL, NULL); + // GL_ARB_framebuffer_object (GL3-class hardware) - depth stencil attachment +#ifdef USE_GLES2 + // FIXME: separate stencil attachment on GLES + if (depthtexture && depthtexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , depthtexture->gltexturetypeenum , depthtexture->texnum , 0);CHECKGLERROR +#else + if (depthtexture && depthtexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, depthtexture->glisdepthstencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT , depthtexture->gltexturetypeenum , depthtexture->texnum , 0);CHECKGLERROR +#endif + if (depthtexture && depthtexture->renderbuffernum ) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, /*depthtexture->glisdepthstencil ? GL_DEPTH_STENCIL_ATTACHMENT : */GL_DEPTH_ATTACHMENT , GL_RENDERBUFFER, depthtexture->renderbuffernum );CHECKGLERROR + if (colortexture && colortexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , colortexture->gltexturetypeenum , colortexture->texnum , 0);CHECKGLERROR + if (colortexture2 && colortexture2->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , colortexture2->gltexturetypeenum, colortexture2->texnum, 0);CHECKGLERROR + if (colortexture3 && colortexture3->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , colortexture3->gltexturetypeenum, colortexture3->texnum, 0);CHECKGLERROR + if (colortexture4 && colortexture4->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3 , colortexture4->gltexturetypeenum, colortexture4->texnum, 0);CHECKGLERROR + if (colortexture && colortexture->renderbuffernum ) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , GL_RENDERBUFFER, colortexture->renderbuffernum );CHECKGLERROR + if (colortexture2 && colortexture2->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , GL_RENDERBUFFER, colortexture2->renderbuffernum);CHECKGLERROR + if (colortexture3 && colortexture3->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , GL_RENDERBUFFER, colortexture3->renderbuffernum);CHECKGLERROR + if (colortexture4 && colortexture4->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3 , GL_RENDERBUFFER, colortexture4->renderbuffernum);CHECKGLERROR + +#ifndef USE_GLES2 + if (colortexture4 && qglDrawBuffersARB) + { + qglDrawBuffersARB(4, drawbuffers);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + } + else if (colortexture3 && qglDrawBuffersARB) + { + qglDrawBuffersARB(3, drawbuffers);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + } + else if (colortexture2 && qglDrawBuffersARB) + { + qglDrawBuffersARB(2, drawbuffers);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + } + else if (colortexture && qglDrawBuffer) + { + qglDrawBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR + qglReadBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR + } + else if (qglDrawBuffer) + { + qglDrawBuffer(GL_NONE);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + } +#endif + status = qglCheckFramebufferStatus(GL_FRAMEBUFFER);CHECKGLERROR + if (status != GL_FRAMEBUFFER_COMPLETE) + { + Con_Printf("R_Mesh_CreateFramebufferObject: glCheckFramebufferStatus returned %i\n", status); + gl_state.framebufferobject = 0; // GL unbinds it for us + qglDeleteFramebuffers(1, (GLuint*)&temp); + temp = 0; + } + return temp; + } + else if (vid.support.ext_framebuffer_object) + { + int temp; + GLuint status; + qglGenFramebuffers(1, (GLuint*)&temp);CHECKGLERROR + R_Mesh_SetRenderTargets(temp, NULL, NULL, NULL, NULL, NULL); + // GL_EXT_framebuffer_object (GL2-class hardware) - no depth stencil attachment, let it break stencil + if (depthtexture && depthtexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , depthtexture->gltexturetypeenum , depthtexture->texnum , 0);CHECKGLERROR + if (depthtexture && depthtexture->renderbuffernum ) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , GL_RENDERBUFFER, depthtexture->renderbuffernum );CHECKGLERROR + if (colortexture && colortexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , colortexture->gltexturetypeenum , colortexture->texnum , 0);CHECKGLERROR + if (colortexture2 && colortexture2->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , colortexture2->gltexturetypeenum, colortexture2->texnum, 0);CHECKGLERROR + if (colortexture3 && colortexture3->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , colortexture3->gltexturetypeenum, colortexture3->texnum, 0);CHECKGLERROR + if (colortexture4 && colortexture4->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3 , colortexture4->gltexturetypeenum, colortexture4->texnum, 0);CHECKGLERROR + if (colortexture && colortexture->renderbuffernum ) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , GL_RENDERBUFFER, colortexture->renderbuffernum );CHECKGLERROR + if (colortexture2 && colortexture2->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , GL_RENDERBUFFER, colortexture2->renderbuffernum);CHECKGLERROR + if (colortexture3 && colortexture3->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , GL_RENDERBUFFER, colortexture3->renderbuffernum);CHECKGLERROR + if (colortexture4 && colortexture4->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3 , GL_RENDERBUFFER, colortexture4->renderbuffernum);CHECKGLERROR + +#ifndef USE_GLES2 + if (colortexture4 && qglDrawBuffersARB) + { + qglDrawBuffersARB(4, drawbuffers);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + } + else if (colortexture3 && qglDrawBuffersARB) + { + qglDrawBuffersARB(3, drawbuffers);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + } + else if (colortexture2 && qglDrawBuffersARB) + { + qglDrawBuffersARB(2, drawbuffers);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + } + else if (colortexture && qglDrawBuffer) + { + qglDrawBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR + qglReadBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR + } + else if (qglDrawBuffer) + { + qglDrawBuffer(GL_NONE);CHECKGLERROR + qglReadBuffer(GL_NONE);CHECKGLERROR + } +#endif + status = qglCheckFramebufferStatus(GL_FRAMEBUFFER);CHECKGLERROR + if (status != GL_FRAMEBUFFER_COMPLETE) + { + Con_Printf("R_Mesh_CreateFramebufferObject: glCheckFramebufferStatus returned %i\n", status); + gl_state.framebufferobject = 0; // GL unbinds it for us + qglDeleteFramebuffers(1, (GLuint*)&temp); + temp = 0; + } + return temp; + } + return 0; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + return 1; + case RENDERPATH_SOFT: + return 1; + } + return 0; +} + +void R_Mesh_DestroyFramebufferObject(int fbo) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (fbo) + { + // GL clears the binding if we delete something bound + if (gl_state.framebufferobject == fbo) + gl_state.framebufferobject = 0; + qglDeleteFramebuffers(1, (GLuint*)&fbo); + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } +} + +#ifdef SUPPORTD3D +void R_Mesh_SetRenderTargetsD3D9(IDirect3DSurface9 *depthsurface, IDirect3DSurface9 *colorsurface0, IDirect3DSurface9 *colorsurface1, IDirect3DSurface9 *colorsurface2, IDirect3DSurface9 *colorsurface3) +{ + gl_state.framebufferobject = depthsurface != gl_state.d3drt_backbufferdepthsurface || colorsurface0 != gl_state.d3drt_backbuffercolorsurface; + if (gl_state.d3drt_depthsurface != depthsurface) + { + gl_state.d3drt_depthsurface = depthsurface; + IDirect3DDevice9_SetDepthStencilSurface(vid_d3d9dev, gl_state.d3drt_depthsurface); + } + if (gl_state.d3drt_colorsurfaces[0] != colorsurface0) + { + gl_state.d3drt_colorsurfaces[0] = colorsurface0; + IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 0, gl_state.d3drt_colorsurfaces[0]); + } + if (gl_state.d3drt_colorsurfaces[1] != colorsurface1) + { + gl_state.d3drt_colorsurfaces[1] = colorsurface1; + IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 1, gl_state.d3drt_colorsurfaces[1]); + } + if (gl_state.d3drt_colorsurfaces[2] != colorsurface2) + { + gl_state.d3drt_colorsurfaces[2] = colorsurface2; + IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 2, gl_state.d3drt_colorsurfaces[2]); + } + if (gl_state.d3drt_colorsurfaces[3] != colorsurface3) + { + gl_state.d3drt_colorsurfaces[3] = colorsurface3; + IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 3, gl_state.d3drt_colorsurfaces[3]); + } +} +#endif + +void R_Mesh_SetRenderTargets(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4) +{ + unsigned int i; + unsigned int j; + rtexture_t *textures[5]; + Vector4Set(textures, colortexture, colortexture2, colortexture3, colortexture4); + textures[4] = depthtexture; + // unbind any matching textures immediately, otherwise D3D will complain about a bound texture being used as a render target + for (j = 0;j < 5;j++) + if (textures[j]) + for (i = 0;i < vid.teximageunits;i++) + if (gl_state.units[i].texture == textures[j]) + R_Mesh_TexBind(i, NULL); + // set up framebuffer object or render targets for the active rendering API + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (gl_state.framebufferobject != fbo) + { + gl_state.framebufferobject = fbo; + qglBindFramebuffer(GL_FRAMEBUFFER, gl_state.framebufferobject ? gl_state.framebufferobject : gl_state.defaultframebufferobject); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + // set up the new render targets, a NULL depthtexture intentionally binds nothing + // TODO: optimize: keep surface pointer around in rtexture_t until texture is freed or lost + if (fbo) + { + IDirect3DSurface9 *surfaces[5]; + for (i = 0;i < 5;i++) + { + surfaces[i] = NULL; + if (textures[i]) + { + if (textures[i]->d3dsurface) + surfaces[i] = (IDirect3DSurface9 *)textures[i]->d3dsurface; + else + IDirect3DTexture9_GetSurfaceLevel((IDirect3DTexture9 *)textures[i]->d3dtexture, 0, &surfaces[i]); + } + } + // set the render targets for real + R_Mesh_SetRenderTargetsD3D9(surfaces[4], surfaces[0], surfaces[1], surfaces[2], surfaces[3]); + // release the texture surface levels (they won't be lost while bound...) + for (i = 0;i < 5;i++) + if (textures[i] && !textures[i]->d3dsurface) + IDirect3DSurface9_Release(surfaces[i]); + } + else + R_Mesh_SetRenderTargetsD3D9(gl_state.d3drt_backbufferdepthsurface, gl_state.d3drt_backbuffercolorsurface, NULL, NULL, NULL); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (fbo) + { + int width, height; + unsigned int *pointers[5]; + memset(pointers, 0, sizeof(pointers)); + for (i = 0;i < 5;i++) + pointers[i] = textures[i] ? (unsigned int *)DPSOFTRAST_Texture_GetPixelPointer(textures[i]->texnum, 0) : NULL; + width = DPSOFTRAST_Texture_GetWidth(textures[0] ? textures[0]->texnum : textures[4]->texnum, 0); + height = DPSOFTRAST_Texture_GetHeight(textures[0] ? textures[0]->texnum : textures[4]->texnum, 0); + DPSOFTRAST_SetRenderTargets(width, height, pointers[4], pointers[0], pointers[1], pointers[2], pointers[3]); + } + else + DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); + break; + } +} + +#ifdef SUPPORTD3D +static int d3dcmpforglfunc(int f) +{ + switch(f) + { + case GL_NEVER: return D3DCMP_NEVER; + case GL_LESS: return D3DCMP_LESS; + case GL_EQUAL: return D3DCMP_EQUAL; + case GL_LEQUAL: return D3DCMP_LESSEQUAL; + case GL_GREATER: return D3DCMP_GREATER; + case GL_NOTEQUAL: return D3DCMP_NOTEQUAL; + case GL_GEQUAL: return D3DCMP_GREATEREQUAL; + case GL_ALWAYS: return D3DCMP_ALWAYS; + default: Con_DPrintf("Unknown GL_DepthFunc\n");return D3DCMP_ALWAYS; + } +} + +static int d3dstencilopforglfunc(int f) +{ + switch(f) + { + case GL_KEEP: return D3DSTENCILOP_KEEP; + case GL_INCR: return D3DSTENCILOP_INCR; // note: GL_INCR is clamped, D3DSTENCILOP_INCR wraps + case GL_DECR: return D3DSTENCILOP_DECR; // note: GL_DECR is clamped, D3DSTENCILOP_DECR wraps + default: Con_DPrintf("Unknown GL_StencilFunc\n");return D3DSTENCILOP_KEEP; + } +} +#endif + +static void GL_Backend_ResetState(void) +{ + unsigned int i; + gl_state.active = true; + gl_state.depthtest = true; + gl_state.alphatest = false; + gl_state.alphafunc = GL_GEQUAL; + gl_state.alphafuncvalue = 0.5f; + gl_state.alphatocoverage = false; + gl_state.blendfunc1 = GL_ONE; + gl_state.blendfunc2 = GL_ZERO; + gl_state.blend = false; + gl_state.depthmask = GL_TRUE; + gl_state.colormask = 15; + gl_state.color4f[0] = gl_state.color4f[1] = gl_state.color4f[2] = gl_state.color4f[3] = 1; + gl_state.lockrange_first = 0; + gl_state.lockrange_count = 0; + gl_state.cullface = GL_FRONT; + gl_state.cullfaceenable = false; + gl_state.polygonoffset[0] = 0; + gl_state.polygonoffset[1] = 0; + gl_state.framebufferobject = 0; + gl_state.depthfunc = GL_LEQUAL; + + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_COLORWRITEENABLE, gl_state.colormask); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_NONE); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZFUNC, d3dcmpforglfunc(gl_state.depthfunc)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZENABLE, gl_state.depthtest); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZWRITEENABLE, gl_state.depthmask); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SLOPESCALEDEPTHBIAS, gl_state.polygonoffset[0]); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DEPTHBIAS, gl_state.polygonoffset[1] * (1.0f / 16777216.0f)); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: +#ifdef GL_ALPHA_TEST + CHECKGLERROR + + qglColorMask(1, 1, 1, 1);CHECKGLERROR + qglAlphaFunc(gl_state.alphafunc, gl_state.alphafuncvalue);CHECKGLERROR + qglDisable(GL_ALPHA_TEST);CHECKGLERROR + qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR + qglDisable(GL_BLEND);CHECKGLERROR + qglCullFace(gl_state.cullface);CHECKGLERROR + qglDisable(GL_CULL_FACE);CHECKGLERROR + qglDepthFunc(GL_LEQUAL);CHECKGLERROR + qglEnable(GL_DEPTH_TEST);CHECKGLERROR + qglDepthMask(gl_state.depthmask);CHECKGLERROR + qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + + if (vid.support.arb_vertex_buffer_object) + { + qglBindBufferARB(GL_ARRAY_BUFFER, 0); + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + if (vid.support.ext_framebuffer_object) + { + //qglBindRenderbuffer(GL_RENDERBUFFER, 0); + qglBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + qglVertexPointer(3, GL_FLOAT, sizeof(float[3]), NULL);CHECKGLERROR + qglEnableClientState(GL_VERTEX_ARRAY);CHECKGLERROR + + qglColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL);CHECKGLERROR + qglDisableClientState(GL_COLOR_ARRAY);CHECKGLERROR + qglColor4f(1, 1, 1, 1);CHECKGLERROR + + if (vid.support.ext_framebuffer_object) + qglBindFramebuffer(GL_FRAMEBUFFER, gl_state.framebufferobject); + + gl_state.unit = MAX_TEXTUREUNITS; + gl_state.clientunit = MAX_TEXTUREUNITS; + for (i = 0;i < vid.texunits;i++) + { + GL_ActiveTexture(i); + GL_ClientActiveTexture(i); + qglDisable(GL_TEXTURE_2D);CHECKGLERROR + qglBindTexture(GL_TEXTURE_2D, 0);CHECKGLERROR + if (vid.support.ext_texture_3d) + { + qglDisable(GL_TEXTURE_3D);CHECKGLERROR + qglBindTexture(GL_TEXTURE_3D, 0);CHECKGLERROR + } + if (vid.support.arb_texture_cube_map) + { + qglDisable(GL_TEXTURE_CUBE_MAP);CHECKGLERROR + qglBindTexture(GL_TEXTURE_CUBE_MAP, 0);CHECKGLERROR + } + GL_BindVBO(0); + qglTexCoordPointer(2, GL_FLOAT, sizeof(float[2]), NULL);CHECKGLERROR + qglDisableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR + qglMatrixMode(GL_TEXTURE);CHECKGLERROR + qglLoadIdentity();CHECKGLERROR + qglMatrixMode(GL_MODELVIEW);CHECKGLERROR + qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);CHECKGLERROR + } + CHECKGLERROR +#endif + break; + case RENDERPATH_SOFT: + DPSOFTRAST_ColorMask(1,1,1,1); + DPSOFTRAST_BlendFunc(gl_state.blendfunc1, gl_state.blendfunc2); + DPSOFTRAST_CullFace(gl_state.cullface); + DPSOFTRAST_DepthFunc(gl_state.depthfunc); + DPSOFTRAST_DepthMask(gl_state.depthmask); + DPSOFTRAST_PolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); + DPSOFTRAST_Viewport(0, 0, vid.width, vid.height); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + CHECKGLERROR + qglColorMask(1, 1, 1, 1);CHECKGLERROR + qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR + qglDisable(GL_BLEND);CHECKGLERROR + qglCullFace(gl_state.cullface);CHECKGLERROR + qglDisable(GL_CULL_FACE);CHECKGLERROR + qglDepthFunc(GL_LEQUAL);CHECKGLERROR + qglEnable(GL_DEPTH_TEST);CHECKGLERROR + qglDepthMask(gl_state.depthmask);CHECKGLERROR + qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + if (vid.support.arb_vertex_buffer_object) + { + qglBindBufferARB(GL_ARRAY_BUFFER, 0); + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0); + } + if (vid.support.ext_framebuffer_object) + qglBindFramebuffer(GL_FRAMEBUFFER, gl_state.defaultframebufferobject); + qglEnableVertexAttribArray(GLSLATTRIB_POSITION); + qglVertexAttribPointer(GLSLATTRIB_POSITION, 3, GL_FLOAT, false, sizeof(float[3]), NULL);CHECKGLERROR + qglDisableVertexAttribArray(GLSLATTRIB_COLOR); + qglVertexAttribPointer(GLSLATTRIB_COLOR, 4, GL_FLOAT, false, sizeof(float[4]), NULL);CHECKGLERROR + qglVertexAttrib4f(GLSLATTRIB_COLOR, 1, 1, 1, 1); + gl_state.unit = MAX_TEXTUREUNITS; + gl_state.clientunit = MAX_TEXTUREUNITS; + for (i = 0;i < vid.teximageunits;i++) + { + GL_ActiveTexture(i); + qglBindTexture(GL_TEXTURE_2D, 0);CHECKGLERROR + if (vid.support.ext_texture_3d) + { + qglBindTexture(GL_TEXTURE_3D, 0);CHECKGLERROR + } + if (vid.support.arb_texture_cube_map) + { + qglBindTexture(GL_TEXTURE_CUBE_MAP, 0);CHECKGLERROR + } + } + for (i = 0;i < vid.texarrayunits;i++) + { + GL_BindVBO(0); + qglVertexAttribPointer(i+GLSLATTRIB_TEXCOORD0, 2, GL_FLOAT, false, sizeof(float[2]), NULL);CHECKGLERROR + qglDisableVertexAttribArray(i+GLSLATTRIB_TEXCOORD0);CHECKGLERROR + } + CHECKGLERROR + break; + } +} + +void GL_ActiveTexture(unsigned int num) +{ + if (gl_state.unit != num) + { + gl_state.unit = num; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (1) + { + CHECKGLERROR + glActiveTexture(GL_TEXTURE0 + gl_state.unit); + CHECKGLERROR + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } + } +} + +void GL_ClientActiveTexture(unsigned int num) +{ + if (gl_state.clientunit != num) + { + gl_state.clientunit = num; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (1) + { + CHECKGLERROR + qglClientActiveTexture(GL_TEXTURE0 + gl_state.clientunit); + CHECKGLERROR + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + break; + } + } +} + +void GL_BlendFunc(int blendfunc1, int blendfunc2) +{ + if (gl_state.blendfunc1 != blendfunc1 || gl_state.blendfunc2 != blendfunc2) + { + qboolean blendenable; + gl_state.blendfunc1 = blendfunc1; + gl_state.blendfunc2 = blendfunc2; + blendenable = (gl_state.blendfunc1 != GL_ONE || gl_state.blendfunc2 != GL_ZERO); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR + if (gl_state.blend != blendenable) + { + gl_state.blend = blendenable; + if (!gl_state.blend) + { + qglDisable(GL_BLEND);CHECKGLERROR + } + else + { + qglEnable(GL_BLEND);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + int i; + int glblendfunc[2]; + D3DBLEND d3dblendfunc[2]; + glblendfunc[0] = gl_state.blendfunc1; + glblendfunc[1] = gl_state.blendfunc2; + for (i = 0;i < 2;i++) + { + switch(glblendfunc[i]) + { + case GL_ZERO: d3dblendfunc[i] = D3DBLEND_ZERO;break; + case GL_ONE: d3dblendfunc[i] = D3DBLEND_ONE;break; + case GL_SRC_COLOR: d3dblendfunc[i] = D3DBLEND_SRCCOLOR;break; + case GL_ONE_MINUS_SRC_COLOR: d3dblendfunc[i] = D3DBLEND_INVSRCCOLOR;break; + case GL_SRC_ALPHA: d3dblendfunc[i] = D3DBLEND_SRCALPHA;break; + case GL_ONE_MINUS_SRC_ALPHA: d3dblendfunc[i] = D3DBLEND_INVSRCALPHA;break; + case GL_DST_ALPHA: d3dblendfunc[i] = D3DBLEND_DESTALPHA;break; + case GL_ONE_MINUS_DST_ALPHA: d3dblendfunc[i] = D3DBLEND_INVDESTALPHA;break; + case GL_DST_COLOR: d3dblendfunc[i] = D3DBLEND_DESTCOLOR;break; + case GL_ONE_MINUS_DST_COLOR: d3dblendfunc[i] = D3DBLEND_INVDESTCOLOR;break; + } + } + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SRCBLEND, d3dblendfunc[0]); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DESTBLEND, d3dblendfunc[1]); + if (gl_state.blend != blendenable) + { + gl_state.blend = blendenable; + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ALPHABLENDENABLE, gl_state.blend); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_BlendFunc(gl_state.blendfunc1, gl_state.blendfunc2); + break; + } + } +} + +void GL_DepthMask(int state) +{ + if (gl_state.depthmask != state) + { + gl_state.depthmask = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglDepthMask(gl_state.depthmask);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZWRITEENABLE, gl_state.depthmask); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DepthMask(gl_state.depthmask); + break; + } + } +} + +void GL_DepthTest(int state) +{ + if (gl_state.depthtest != state) + { + gl_state.depthtest = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (gl_state.depthtest) + { + qglEnable(GL_DEPTH_TEST);CHECKGLERROR + } + else + { + qglDisable(GL_DEPTH_TEST);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZENABLE, gl_state.depthtest); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DepthTest(gl_state.depthtest); + break; + } + } +} + +void GL_DepthFunc(int state) +{ + if (gl_state.depthfunc != state) + { + gl_state.depthfunc = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglDepthFunc(gl_state.depthfunc);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZFUNC, d3dcmpforglfunc(gl_state.depthfunc)); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DepthFunc(gl_state.depthfunc); + break; + } + } +} + +void GL_DepthRange(float nearfrac, float farfrac) +{ + if (gl_state.depthrange[0] != nearfrac || gl_state.depthrange[1] != farfrac) + { + gl_state.depthrange[0] = nearfrac; + gl_state.depthrange[1] = farfrac; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: +#ifdef USE_GLES2 + qglDepthRangef(gl_state.depthrange[0], gl_state.depthrange[1]); +#else + qglDepthRange(gl_state.depthrange[0], gl_state.depthrange[1]); +#endif + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DVIEWPORT9 d3dviewport; + d3dviewport.X = gl_viewport.x; + d3dviewport.Y = gl_viewport.y; + d3dviewport.Width = gl_viewport.width; + d3dviewport.Height = gl_viewport.height; + d3dviewport.MinZ = gl_state.depthrange[0]; + d3dviewport.MaxZ = gl_state.depthrange[1]; + IDirect3DDevice9_SetViewport(vid_d3d9dev, &d3dviewport); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DepthRange(gl_state.depthrange[0], gl_state.depthrange[1]); + break; + } + } +} + +void R_SetStencilSeparate(qboolean enable, int writemask, int frontfail, int frontzfail, int frontzpass, int backfail, int backzfail, int backzpass, int frontcompare, int backcompare, int comparereference, int comparemask) +{ + switch (vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (enable) + { + qglEnable(GL_STENCIL_TEST);CHECKGLERROR + } + else + { + qglDisable(GL_STENCIL_TEST);CHECKGLERROR + } + if (vid.support.ati_separate_stencil) + { + qglStencilMask(writemask);CHECKGLERROR + qglStencilOpSeparate(GL_FRONT, frontfail, frontzfail, frontzpass);CHECKGLERROR + qglStencilOpSeparate(GL_BACK, backfail, backzfail, backzpass);CHECKGLERROR + qglStencilFuncSeparate(GL_FRONT, frontcompare, comparereference, comparereference);CHECKGLERROR + qglStencilFuncSeparate(GL_BACK, backcompare, comparereference, comparereference);CHECKGLERROR + } + else if (vid.support.ext_stencil_two_side) + { +#ifdef GL_STENCIL_TEST_TWO_SIDE_EXT + qglEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);CHECKGLERROR + qglActiveStencilFaceEXT(GL_FRONT);CHECKGLERROR + qglStencilMask(writemask);CHECKGLERROR + qglStencilOp(frontfail, frontzfail, frontzpass);CHECKGLERROR + qglStencilFunc(frontcompare, comparereference, comparemask);CHECKGLERROR + qglActiveStencilFaceEXT(GL_BACK);CHECKGLERROR + qglStencilMask(writemask);CHECKGLERROR + qglStencilOp(backfail, backzfail, backzpass);CHECKGLERROR + qglStencilFunc(backcompare, comparereference, comparemask);CHECKGLERROR +#endif + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_TWOSIDEDSTENCILMODE, true); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILENABLE, enable); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILWRITEMASK, writemask); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFAIL, d3dstencilopforglfunc(frontfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILZFAIL, d3dstencilopforglfunc(frontzfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILPASS, d3dstencilopforglfunc(frontzpass)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFUNC, d3dcmpforglfunc(frontcompare)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILFAIL, d3dstencilopforglfunc(backfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILZFAIL, d3dstencilopforglfunc(backzfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILPASS, d3dstencilopforglfunc(backzpass)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILFUNC, d3dcmpforglfunc(backcompare)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILREF, comparereference); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILMASK, comparemask); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } +} + +void R_SetStencil(qboolean enable, int writemask, int fail, int zfail, int zpass, int compare, int comparereference, int comparemask) +{ + switch (vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (enable) + { + qglEnable(GL_STENCIL_TEST);CHECKGLERROR + } + else + { + qglDisable(GL_STENCIL_TEST);CHECKGLERROR + } + if (vid.support.ext_stencil_two_side) + { +#ifdef GL_STENCIL_TEST_TWO_SIDE_EXT + qglDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);CHECKGLERROR +#endif + } + qglStencilMask(writemask);CHECKGLERROR + qglStencilOp(fail, zfail, zpass);CHECKGLERROR + qglStencilFunc(compare, comparereference, comparemask);CHECKGLERROR + CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (vid.support.ati_separate_stencil) + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_TWOSIDEDSTENCILMODE, true); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILENABLE, enable); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILWRITEMASK, writemask); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFAIL, d3dstencilopforglfunc(fail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILZFAIL, d3dstencilopforglfunc(zfail)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILPASS, d3dstencilopforglfunc(zpass)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFUNC, d3dcmpforglfunc(compare)); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILREF, comparereference); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILMASK, comparemask); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } +} + +void GL_PolygonOffset(float planeoffset, float depthoffset) +{ + if (gl_state.polygonoffset[0] != planeoffset || gl_state.polygonoffset[1] != depthoffset) + { + gl_state.polygonoffset[0] = planeoffset; + gl_state.polygonoffset[1] = depthoffset; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SLOPESCALEDEPTHBIAS, gl_state.polygonoffset[0]); + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DEPTHBIAS, gl_state.polygonoffset[1] * (1.0f / 16777216.0f)); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_PolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); + break; + } + } +} + +void GL_SetMirrorState(qboolean state) +{ + if (v_flipped_state != state) + { + v_flipped_state = state; + if (gl_state.cullface == GL_BACK) + gl_state.cullface = GL_FRONT; + else if (gl_state.cullface == GL_FRONT) + gl_state.cullface = GL_BACK; + else + return; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglCullFace(gl_state.cullface);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, gl_state.cullface == GL_FRONT ? D3DCULL_CCW : D3DCULL_CW); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_CullFace(gl_state.cullface); + break; + } + } +} + +void GL_CullFace(int state) +{ + if(v_flipped_state) + { + if(state == GL_FRONT) + state = GL_BACK; + else if(state == GL_BACK) + state = GL_FRONT; + } + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + + if (state != GL_NONE) + { + if (!gl_state.cullfaceenable) + { + gl_state.cullfaceenable = true; + qglEnable(GL_CULL_FACE);CHECKGLERROR + } + if (gl_state.cullface != state) + { + gl_state.cullface = state; + qglCullFace(gl_state.cullface);CHECKGLERROR + } + } + else + { + if (gl_state.cullfaceenable) + { + gl_state.cullfaceenable = false; + qglDisable(GL_CULL_FACE);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (gl_state.cullface != state) + { + gl_state.cullface = state; + switch(gl_state.cullface) + { + case GL_NONE: + gl_state.cullfaceenable = false; + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_NONE); + break; + case GL_FRONT: + gl_state.cullfaceenable = true; + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_CCW); + break; + case GL_BACK: + gl_state.cullfaceenable = true; + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_CW); + break; + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (gl_state.cullface != state) + { + gl_state.cullface = state; + gl_state.cullfaceenable = state != GL_NONE ? true : false; + DPSOFTRAST_CullFace(gl_state.cullface); + } + break; + } +} + +void GL_AlphaTest(int state) +{ + if (gl_state.alphatest != state) + { + gl_state.alphatest = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: +#ifdef GL_ALPHA_TEST + // only fixed function uses alpha test, other paths use pixel kill capability in shaders + CHECKGLERROR + if (gl_state.alphatest) + { + qglEnable(GL_ALPHA_TEST);CHECKGLERROR + } + else + { + qglDisable(GL_ALPHA_TEST);CHECKGLERROR + } +#endif + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + break; + } + } +} + +void GL_AlphaToCoverage(qboolean state) +{ + if (gl_state.alphatocoverage != state) + { + gl_state.alphatocoverage = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + case RENDERPATH_GL20: +#ifdef GL_SAMPLE_ALPHA_TO_COVERAGE_ARB + // alpha to coverage turns the alpha value of the pixel into 0%, 25%, 50%, 75% or 100% by masking the multisample fragments accordingly + CHECKGLERROR + if (gl_state.alphatocoverage) + { + qglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);CHECKGLERROR +// qglEnable(GL_MULTISAMPLE_ARB);CHECKGLERROR + } + else + { + qglDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);CHECKGLERROR +// qglDisable(GL_MULTISAMPLE_ARB);CHECKGLERROR + } +#endif + break; + } + } +} + +void GL_ColorMask(int r, int g, int b, int a) +{ + // NOTE: this matches D3DCOLORWRITEENABLE_RED, GREEN, BLUE, ALPHA + int state = (r ? 1 : 0) | (g ? 2 : 0) | (b ? 4 : 0) | (a ? 8 : 0); + if (gl_state.colormask != state) + { + gl_state.colormask = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglColorMask((GLboolean)r, (GLboolean)g, (GLboolean)b, (GLboolean)a);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_COLORWRITEENABLE, state); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_ColorMask(r, g, b, a); + break; + } + } +} + +void GL_Color(float cr, float cg, float cb, float ca) +{ + if (gl_state.pointer_color_enabled || gl_state.color4f[0] != cr || gl_state.color4f[1] != cg || gl_state.color4f[2] != cb || gl_state.color4f[3] != ca) + { + gl_state.color4f[0] = cr; + gl_state.color4f[1] = cg; + gl_state.color4f[2] = cb; + gl_state.color4f[3] = ca; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + CHECKGLERROR + qglColor4f(gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]); + CHECKGLERROR + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + // no equivalent in D3D + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Color4f(cr, cg, cb, ca); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + qglVertexAttrib4f(GLSLATTRIB_COLOR, cr, cg, cb, ca); + break; + } + } +} + +void GL_Scissor (int x, int y, int width, int height) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglScissor(x, y,width,height); + CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + RECT d3drect; + d3drect.left = x; + d3drect.top = y; + d3drect.right = x + width; + d3drect.bottom = y + height; + IDirect3DDevice9_SetScissorRect(vid_d3d9dev, &d3drect); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Scissor(x, y, width, height); + break; + } +} + +void GL_ScissorTest(int state) +{ + if (gl_state.scissortest != state) + { + gl_state.scissortest = state; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if(gl_state.scissortest) + qglEnable(GL_SCISSOR_TEST); + else + qglDisable(GL_SCISSOR_TEST); + CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SCISSORTESTENABLE, gl_state.scissortest); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_ScissorTest(gl_state.scissortest); + break; + } + } +} + +void GL_Clear(int mask, const float *colorvalue, float depthvalue, int stencilvalue) +{ + static const float blackcolor[4] = {0, 0, 0, 0}; + // prevent warnings when trying to clear a buffer that does not exist + if (!colorvalue) + colorvalue = blackcolor; + if (!vid.stencil) + { + mask &= ~GL_STENCIL_BUFFER_BIT; + stencilvalue = 0; + } + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + if (mask & GL_COLOR_BUFFER_BIT) + { + qglClearColor(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]);CHECKGLERROR + } + if (mask & GL_DEPTH_BUFFER_BIT) + { +#ifdef USE_GLES2 + qglClearDepthf(depthvalue);CHECKGLERROR +#else + qglClearDepth(depthvalue);CHECKGLERROR +#endif + } + if (mask & GL_STENCIL_BUFFER_BIT) + { + qglClearStencil(stencilvalue);CHECKGLERROR + } + qglClear(mask);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_Clear(vid_d3d9dev, 0, NULL, ((mask & GL_COLOR_BUFFER_BIT) ? D3DCLEAR_TARGET : 0) | ((mask & GL_STENCIL_BUFFER_BIT) ? D3DCLEAR_STENCIL : 0) | ((mask & GL_DEPTH_BUFFER_BIT) ? D3DCLEAR_ZBUFFER : 0), D3DCOLOR_COLORVALUE(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]), depthvalue, stencilvalue); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (mask & GL_COLOR_BUFFER_BIT) + DPSOFTRAST_ClearColor(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]); + if (mask & GL_DEPTH_BUFFER_BIT) + DPSOFTRAST_ClearDepth(depthvalue); + break; + } +} + +void GL_ReadPixelsBGRA(int x, int y, int width, int height, unsigned char *outpixels) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglReadPixels(x, y, width, height, GL_BGRA, GL_UNSIGNED_BYTE, outpixels);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + // LordHavoc: we can't directly download the backbuffer because it may be + // multisampled, and it may not be lockable, so we blit it to a lockable + // surface of the same dimensions (but without multisample) to resolve the + // multisample buffer to a normal image, and then lock that... + IDirect3DSurface9 *stretchsurface = NULL; + if (!FAILED(IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, TRUE, &stretchsurface, NULL))) + { + D3DLOCKED_RECT lockedrect; + if (!FAILED(IDirect3DDevice9_StretchRect(vid_d3d9dev, gl_state.d3drt_backbuffercolorsurface, NULL, stretchsurface, NULL, D3DTEXF_POINT))) + { + if (!FAILED(IDirect3DSurface9_LockRect(stretchsurface, &lockedrect, NULL, D3DLOCK_READONLY))) + { + int line; + unsigned char *row = (unsigned char *)lockedrect.pBits + x * 4 + lockedrect.Pitch * (vid.height - 1 - y); + for (line = 0;line < height;line++, row -= lockedrect.Pitch) + memcpy(outpixels + line * width * 4, row, width * 4); + IDirect3DSurface9_UnlockRect(stretchsurface); + } + } + IDirect3DSurface9_Release(stretchsurface); + } + // code scraps + //IDirect3DSurface9 *syssurface = NULL; + //if (!FAILED(IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, FALSE, &stretchsurface, NULL))) + //if (!FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &syssurface, NULL))) + //IDirect3DDevice9_GetRenderTargetData(vid_d3d9dev, gl_state.d3drt_backbuffercolorsurface, syssurface); + //if (!FAILED(IDirect3DDevice9_GetFrontBufferData(vid_d3d9dev, 0, syssurface))) + //if (!FAILED(IDirect3DSurface9_LockRect(syssurface, &lockedrect, NULL, D3DLOCK_READONLY))) + //IDirect3DSurface9_UnlockRect(syssurface); + //IDirect3DSurface9_Release(syssurface); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_GetPixelsBGRA(x, y, width, height, outpixels); + break; + } +} + +// called at beginning of frame +void R_Mesh_Start(void) +{ + BACKENDACTIVECHECK + R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); + R_Mesh_SetUseVBO(); + if (gl_printcheckerror.integer && !gl_paranoid.integer) + { + Con_Printf("WARNING: gl_printcheckerror is on but gl_paranoid is off, turning it on...\n"); + Cvar_SetValueQuick(&gl_paranoid, 1); + } +} + +static qboolean GL_Backend_CompileShader(int programobject, GLenum shadertypeenum, const char *shadertype, int numstrings, const char **strings) +{ + int shaderobject; + int shadercompiled; + char compilelog[MAX_INPUTLINE]; + shaderobject = qglCreateShader(shadertypeenum);CHECKGLERROR + if (!shaderobject) + return false; + qglShaderSource(shaderobject, numstrings, strings, NULL);CHECKGLERROR + qglCompileShader(shaderobject);CHECKGLERROR + qglGetShaderiv(shaderobject, GL_COMPILE_STATUS, &shadercompiled);CHECKGLERROR + qglGetShaderInfoLog(shaderobject, sizeof(compilelog), NULL, compilelog);CHECKGLERROR + if (compilelog[0] && ((strstr(compilelog, "error") || strstr(compilelog, "ERROR") || strstr(compilelog, "Error")) || ((strstr(compilelog, "WARNING") || strstr(compilelog, "warning") || strstr(compilelog, "Warning")) && developer.integer) || developer_extra.integer)) + { + int i, j, pretextlines = 0; + for (i = 0;i < numstrings - 1;i++) + for (j = 0;strings[i][j];j++) + if (strings[i][j] == '\n') + pretextlines++; + Con_Printf("%s shader compile log:\n%s\n(line offset for any above warnings/errors: %i)\n", shadertype, compilelog, pretextlines); + } + if (!shadercompiled) + { + qglDeleteShader(shaderobject);CHECKGLERROR + return false; + } + qglAttachShader(programobject, shaderobject);CHECKGLERROR + qglDeleteShader(shaderobject);CHECKGLERROR + return true; +} + +unsigned int GL_Backend_CompileProgram(int vertexstrings_count, const char **vertexstrings_list, int geometrystrings_count, const char **geometrystrings_list, int fragmentstrings_count, const char **fragmentstrings_list) +{ + GLint programlinked; + GLuint programobject = 0; + char linklog[MAX_INPUTLINE]; + CHECKGLERROR + + programobject = qglCreateProgram();CHECKGLERROR + if (!programobject) + return 0; + + qglBindAttribLocation(programobject, GLSLATTRIB_POSITION , "Attrib_Position" ); + qglBindAttribLocation(programobject, GLSLATTRIB_COLOR , "Attrib_Color" ); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD0, "Attrib_TexCoord0"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD1, "Attrib_TexCoord1"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD2, "Attrib_TexCoord2"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD3, "Attrib_TexCoord3"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD4, "Attrib_TexCoord4"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD5, "Attrib_TexCoord5"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD6, "Attrib_SkeletalIndex"); + qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD7, "Attrib_SkeletalWeight"); +#ifndef USE_GLES2 + if(vid.support.gl20shaders130) + qglBindFragDataLocation(programobject, 0, "dp_FragColor"); +#endif + + if (vertexstrings_count && !GL_Backend_CompileShader(programobject, GL_VERTEX_SHADER, "vertex", vertexstrings_count, vertexstrings_list)) + goto cleanup; + +#ifdef GL_GEOMETRY_SHADER + if (geometrystrings_count && !GL_Backend_CompileShader(programobject, GL_GEOMETRY_SHADER, "geometry", geometrystrings_count, geometrystrings_list)) + goto cleanup; +#endif + + if (fragmentstrings_count && !GL_Backend_CompileShader(programobject, GL_FRAGMENT_SHADER, "fragment", fragmentstrings_count, fragmentstrings_list)) + goto cleanup; + + qglLinkProgram(programobject);CHECKGLERROR + qglGetProgramiv(programobject, GL_LINK_STATUS, &programlinked);CHECKGLERROR + qglGetProgramInfoLog(programobject, sizeof(linklog), NULL, linklog);CHECKGLERROR + if (linklog[0]) + { + if (strstr(linklog, "error") || strstr(linklog, "ERROR") || strstr(linklog, "Error") || strstr(linklog, "WARNING") || strstr(linklog, "warning") || strstr(linklog, "Warning") || developer_extra.integer) + Con_DPrintf("program link log:\n%s\n", linklog); + // software vertex shader is ok but software fragment shader is WAY + // too slow, fail program if so. + // NOTE: this string might be ATI specific, but that's ok because the + // ATI R300 chip (Radeon 9500-9800/X300) is the most likely to use a + // software fragment shader due to low instruction and dependent + // texture limits. + if (strstr(linklog, "fragment shader will run in software")) + programlinked = false; + } + if (!programlinked) + goto cleanup; + return programobject; +cleanup: + qglDeleteProgram(programobject);CHECKGLERROR + return 0; +} + +void GL_Backend_FreeProgram(unsigned int prog) +{ + CHECKGLERROR + qglDeleteProgram(prog); + CHECKGLERROR +} + +// renders triangles using vertices from the active arrays +int paranoidblah = 0; +void R_Mesh_Draw(int firstvertex, int numvertices, int firsttriangle, int numtriangles, const int *element3i, const r_meshbuffer_t *element3i_indexbuffer, int element3i_bufferoffset, const unsigned short *element3s, const r_meshbuffer_t *element3s_indexbuffer, int element3s_bufferoffset) +{ + unsigned int numelements = numtriangles * 3; + int bufferobject3i; + size_t bufferoffset3i; + int bufferobject3s; + size_t bufferoffset3s; + if (numvertices < 3 || numtriangles < 1) + { + if (numvertices < 0 || numtriangles < 0 || developer_extra.integer) + Con_DPrintf("R_Mesh_Draw(%d, %d, %d, %d, %8p, %8p, %8x, %8p, %8p, %8x);\n", firstvertex, numvertices, firsttriangle, numtriangles, (void *)element3i, (void *)element3i_indexbuffer, (int)element3i_bufferoffset, (void *)element3s, (void *)element3s_indexbuffer, (int)element3s_bufferoffset); + return; + } + + // adjust the pointers for firsttriangle + if (element3i) + element3i += firsttriangle * 3; + if (element3i_indexbuffer) + element3i_bufferoffset += firsttriangle * 3 * sizeof(*element3i); + if (element3s) + element3s += firsttriangle * 3; + if (element3s_indexbuffer) + element3s_bufferoffset += firsttriangle * 3 * sizeof(*element3s); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // check if the user specified to ignore static index buffers + if (!gl_state.usevbo_staticindex || (gl_vbo.integer == 3 && !vid.forcevbo && (element3i_bufferoffset || element3s_bufferoffset))) + { + element3i_indexbuffer = NULL; + element3s_indexbuffer = NULL; + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } + // upload a dynamic index buffer if needed + if (element3s) + { + if (!element3s_indexbuffer && gl_state.usevbo_dynamicindex) + element3s_indexbuffer = R_BufferData_Store(numelements * sizeof(*element3s), (void *)element3s, R_BUFFERDATA_INDEX16, &element3s_bufferoffset); + } + else if (element3i) + { + if (!element3i_indexbuffer && gl_state.usevbo_dynamicindex) + element3i_indexbuffer = R_BufferData_Store(numelements * sizeof(*element3i), (void *)element3i, R_BUFFERDATA_INDEX32, &element3i_bufferoffset); + } + bufferobject3i = element3i_indexbuffer ? element3i_indexbuffer->bufferobject : 0; + bufferoffset3i = element3i_bufferoffset; + bufferobject3s = element3s_indexbuffer ? element3s_indexbuffer->bufferobject : 0; + bufferoffset3s = element3s_bufferoffset; + r_refdef.stats[r_stat_draws]++; + r_refdef.stats[r_stat_draws_vertices] += numvertices; + r_refdef.stats[r_stat_draws_elements] += numelements; + if (gl_paranoid.integer) + { + unsigned int i; + // LordHavoc: disabled this - it needs to be updated to handle components and gltype and stride in each array +#if 0 + unsigned int j, size; + const int *p; + // note: there's no validation done here on buffer objects because it + // is somewhat difficult to get at the data, and gl_paranoid can be + // used without buffer objects if the need arises + // (the data could be gotten using glMapBuffer but it would be very + // slow due to uncachable video memory reads) + if (!qglIsEnabled(GL_VERTEX_ARRAY)) + Con_Print("R_Mesh_Draw: vertex array not enabled\n"); + CHECKGLERROR + if (gl_state.pointer_vertex_pointer) + for (j = 0, size = numvertices * 3, p = (int *)((float *)gl_state.pointer_vertex + firstvertex * 3);j < size;j++, p++) + paranoidblah += *p; + if (gl_state.pointer_color_enabled) + { + if (!qglIsEnabled(GL_COLOR_ARRAY)) + Con_Print("R_Mesh_Draw: color array set but not enabled\n"); + CHECKGLERROR + if (gl_state.pointer_color && gl_state.pointer_color_enabled) + for (j = 0, size = numvertices * 4, p = (int *)((float *)gl_state.pointer_color + firstvertex * 4);j < size;j++, p++) + paranoidblah += *p; + } + for (i = 0;i < vid.texarrayunits;i++) + { + if (gl_state.units[i].arrayenabled) + { + GL_ClientActiveTexture(i); + if (!qglIsEnabled(GL_TEXTURE_COORD_ARRAY)) + Con_Print("R_Mesh_Draw: texcoord array set but not enabled\n"); + CHECKGLERROR + if (gl_state.units[i].pointer_texcoord && gl_state.units[i].arrayenabled) + for (j = 0, size = numvertices * gl_state.units[i].arraycomponents, p = (int *)((float *)gl_state.units[i].pointer_texcoord + firstvertex * gl_state.units[i].arraycomponents);j < size;j++, p++) + paranoidblah += *p; + } + } +#endif + if (element3i) + { + for (i = 0;i < (unsigned int) numtriangles * 3;i++) + { + if (element3i[i] < firstvertex || element3i[i] >= firstvertex + numvertices) + { + Con_Printf("R_Mesh_Draw: invalid vertex index %i (outside range %i - %i) in element3i array\n", element3i[i], firstvertex, firstvertex + numvertices); + return; + } + } + } + if (element3s) + { + for (i = 0;i < (unsigned int) numtriangles * 3;i++) + { + if (element3s[i] < firstvertex || element3s[i] >= firstvertex + numvertices) + { + Con_Printf("R_Mesh_Draw: invalid vertex index %i (outside range %i - %i) in element3s array\n", element3s[i], firstvertex, firstvertex + numvertices); + return; + } + } + } + } + if (r_render.integer || r_refdef.draw2dstage) + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + CHECKGLERROR + if (gl_mesh_testmanualfeeding.integer) + { +#ifndef USE_GLES2 + unsigned int i, j, element; + const GLfloat *p; + qglBegin(GL_TRIANGLES); + if(vid.renderpath == RENDERPATH_GL20) + { + for (i = 0;i < (unsigned int) numtriangles * 3;i++) + { + if (element3i) + element = element3i[i]; + else if (element3s) + element = element3s[i]; + else + element = firstvertex + i; + for (j = 0;j < vid.texarrayunits;j++) + { + if (gl_state.units[j].pointer_texcoord_pointer && gl_state.units[j].arrayenabled) + { + if (gl_state.units[j].pointer_texcoord_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (gl_state.units[j].pointer_texcoord_components == 4) + qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1], p[2], p[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1], p[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1]); + else + qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, p[0]); + } + else if (gl_state.units[j].pointer_texcoord_gltype == (int)(GL_SHORT | 0x80000000)) + { + const GLshort *s = (const GLshort *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (gl_state.units[j].pointer_texcoord_components == 4) + qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1], s[2], s[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1], s[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, s[0]); + } + else if (gl_state.units[j].pointer_texcoord_gltype == GL_BYTE) + { + const GLbyte *sb = (const GLbyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (gl_state.units[j].pointer_texcoord_components == 4) + qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 127.0f), sb[1] * (1.0f / 127.0f), sb[2] * (1.0f / 127.0f), sb[3] * (1.0f / 127.0f)); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 127.0f), sb[1] * (1.0f / 127.0f), sb[2] * (1.0f / 127.0f)); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 127.0f), sb[1] * (1.0f / 127.0f)); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 127.0f)); + } + else if (gl_state.units[j].pointer_texcoord_gltype == GL_UNSIGNED_BYTE) + { + const GLubyte *sb = (const GLubyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (gl_state.units[j].pointer_texcoord_components == 4) + qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 255.0f), sb[1] * (1.0f / 255.0f), sb[2] * (1.0f / 255.0f), sb[3] * (1.0f / 255.0f)); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 255.0f), sb[1] * (1.0f / 255.0f), sb[2] * (1.0f / 255.0f)); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 255.0f), sb[1] * (1.0f / 255.0f)); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 255.0f)); + } + else if (gl_state.units[j].pointer_texcoord_gltype == (int)(GL_UNSIGNED_BYTE | 0x80000000)) + { + const GLubyte *sb = (const GLubyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (gl_state.units[j].pointer_texcoord_components == 4) + qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1], sb[2], sb[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1], sb[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, sb[0]); + } + } + } + if (gl_state.pointer_color_pointer && gl_state.pointer_color_enabled && gl_state.pointer_color_components == 4) + { + if (gl_state.pointer_color_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); + qglVertexAttrib4f(GLSLATTRIB_COLOR, p[0], p[1], p[2], p[3]); + } + else if (gl_state.pointer_color_gltype == GL_UNSIGNED_BYTE) + { + const GLubyte *ub = (const GLubyte *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); + qglVertexAttrib4Nub(GLSLATTRIB_COLOR, ub[0], ub[1], ub[2], ub[3]); + } + } + if (gl_state.pointer_vertex_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.pointer_vertex_pointer + element * gl_state.pointer_vertex_stride); + if (gl_state.pointer_vertex_components == 4) + qglVertexAttrib4f(GLSLATTRIB_POSITION, p[0], p[1], p[2], p[3]); + else if (gl_state.pointer_vertex_components == 3) + qglVertexAttrib3f(GLSLATTRIB_POSITION, p[0], p[1], p[2]); + else + qglVertexAttrib2f(GLSLATTRIB_POSITION, p[0], p[1]); + } + } + } + else + { + for (i = 0;i < (unsigned int) numtriangles * 3;i++) + { + if (element3i) + element = element3i[i]; + else if (element3s) + element = element3s[i]; + else + element = firstvertex + i; + for (j = 0;j < vid.texarrayunits;j++) + { + if (gl_state.units[j].pointer_texcoord_pointer && gl_state.units[j].arrayenabled) + { + if (gl_state.units[j].pointer_texcoord_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (vid.texarrayunits > 1) + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglMultiTexCoord4f(GL_TEXTURE0 + j, p[0], p[1], p[2], p[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglMultiTexCoord3f(GL_TEXTURE0 + j, p[0], p[1], p[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglMultiTexCoord2f(GL_TEXTURE0 + j, p[0], p[1]); + else + qglMultiTexCoord1f(GL_TEXTURE0 + j, p[0]); + } + else + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglTexCoord4f(p[0], p[1], p[2], p[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglTexCoord3f(p[0], p[1], p[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglTexCoord2f(p[0], p[1]); + else + qglTexCoord1f(p[0]); + } + } + else if (gl_state.units[j].pointer_texcoord_gltype == GL_SHORT) + { + const GLshort *s = (const GLshort *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (vid.texarrayunits > 1) + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglMultiTexCoord4f(GL_TEXTURE0 + j, s[0], s[1], s[2], s[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglMultiTexCoord3f(GL_TEXTURE0 + j, s[0], s[1], s[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglMultiTexCoord2f(GL_TEXTURE0 + j, s[0], s[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglMultiTexCoord1f(GL_TEXTURE0 + j, s[0]); + } + else + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglTexCoord4f(s[0], s[1], s[2], s[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglTexCoord3f(s[0], s[1], s[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglTexCoord2f(s[0], s[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglTexCoord1f(s[0]); + } + } + else if (gl_state.units[j].pointer_texcoord_gltype == GL_BYTE) + { + const GLbyte *sb = (const GLbyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); + if (vid.texarrayunits > 1) + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglMultiTexCoord4f(GL_TEXTURE0 + j, sb[0], sb[1], sb[2], sb[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglMultiTexCoord3f(GL_TEXTURE0 + j, sb[0], sb[1], sb[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglMultiTexCoord2f(GL_TEXTURE0 + j, sb[0], sb[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglMultiTexCoord1f(GL_TEXTURE0 + j, sb[0]); + } + else + { + if (gl_state.units[j].pointer_texcoord_components == 4) + qglTexCoord4f(sb[0], sb[1], sb[2], sb[3]); + else if (gl_state.units[j].pointer_texcoord_components == 3) + qglTexCoord3f(sb[0], sb[1], sb[2]); + else if (gl_state.units[j].pointer_texcoord_components == 2) + qglTexCoord2f(sb[0], sb[1]); + else if (gl_state.units[j].pointer_texcoord_components == 1) + qglTexCoord1f(sb[0]); + } + } + } + } + if (gl_state.pointer_color_pointer && gl_state.pointer_color_enabled && gl_state.pointer_color_components == 4) + { + if (gl_state.pointer_color_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); + qglColor4f(p[0], p[1], p[2], p[3]); + } + else if (gl_state.pointer_color_gltype == GL_UNSIGNED_BYTE) + { + const GLubyte *ub = (const GLubyte *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); + qglColor4ub(ub[0], ub[1], ub[2], ub[3]); + } + } + if (gl_state.pointer_vertex_gltype == GL_FLOAT) + { + p = (const GLfloat *)((const unsigned char *)gl_state.pointer_vertex_pointer + element * gl_state.pointer_vertex_stride); + if (gl_state.pointer_vertex_components == 4) + qglVertex4f(p[0], p[1], p[2], p[3]); + else if (gl_state.pointer_vertex_components == 3) + qglVertex3f(p[0], p[1], p[2]); + else + qglVertex2f(p[0], p[1]); + } + } + } + qglEnd(); + CHECKGLERROR +#endif + } + else if (bufferobject3s) + { + GL_BindEBO(bufferobject3s); +#ifndef USE_GLES2 + if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) + { + qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_SHORT, (void *)bufferoffset3s); + CHECKGLERROR + } + else +#endif + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, (void *)bufferoffset3s); + CHECKGLERROR + } + } + else if (bufferobject3i) + { + GL_BindEBO(bufferobject3i); +#ifndef USE_GLES2 + if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) + { + qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_INT, (void *)bufferoffset3i); + CHECKGLERROR + } + else +#endif + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, (void *)bufferoffset3i); + CHECKGLERROR + } + } + else if (element3s) + { + GL_BindEBO(0); +#ifndef USE_GLES2 + if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) + { + qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_SHORT, element3s); + CHECKGLERROR + } + else +#endif + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, element3s); + CHECKGLERROR + } + } + else if (element3i) + { + GL_BindEBO(0); +#ifndef USE_GLES2 + if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) + { + qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_INT, element3i); + CHECKGLERROR + } + else +#endif + { + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, element3i); + CHECKGLERROR + } + } + else + { + qglDrawArrays(GL_TRIANGLES, firstvertex, numvertices); + CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (gl_state.d3dvertexbuffer && ((element3s && element3s_indexbuffer) || (element3i && element3i_indexbuffer))) + { + if (element3s_indexbuffer) + { + IDirect3DDevice9_SetIndices(vid_d3d9dev, (IDirect3DIndexBuffer9 *)element3s_indexbuffer->devicebuffer); + IDirect3DDevice9_DrawIndexedPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, 0, firstvertex, numvertices, element3s_bufferoffset>>1, numtriangles); + } + else if (element3i_indexbuffer) + { + IDirect3DDevice9_SetIndices(vid_d3d9dev, (IDirect3DIndexBuffer9 *)element3i_indexbuffer->devicebuffer); + IDirect3DDevice9_DrawIndexedPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, 0, firstvertex, numvertices, element3i_bufferoffset>>2, numtriangles); + } + else + IDirect3DDevice9_DrawPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices); + } + else + { + if (element3s) + IDirect3DDevice9_DrawIndexedPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices, numtriangles, element3s, D3DFMT_INDEX16, gl_state.d3dvertexdata, gl_state.d3dvertexsize); + else if (element3i) + IDirect3DDevice9_DrawIndexedPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices, numtriangles, element3i, D3DFMT_INDEX32, gl_state.d3dvertexdata, gl_state.d3dvertexsize); + else + IDirect3DDevice9_DrawPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, numvertices, (void *)gl_state.d3dvertexdata, gl_state.d3dvertexsize); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_DrawTriangles(firstvertex, numvertices, numtriangles, element3i, element3s); + break; + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // GLES does not have glDrawRangeElements so this is a bit shorter than the GL20 path + if (bufferobject3s) + { + GL_BindEBO(bufferobject3s); + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, (void *)bufferoffset3s); + CHECKGLERROR + } + else if (bufferobject3i) + { + GL_BindEBO(bufferobject3i); + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, (void *)bufferoffset3i); + CHECKGLERROR + } + else if (element3s) + { + GL_BindEBO(0); + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, element3s); + CHECKGLERROR + } + else if (element3i) + { + static int enableandroidhack=0;//Tegra 3 doesn't list uint extension: using the most precise & dirty way of detection + GL_BindEBO(0); + if (!enableandroidhack) + { + CHECKGLERROR + qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, element3i); + if (glGetError()!=GL_NO_ERROR) + enableandroidhack=1; + } + if (enableandroidhack) + { + unsigned short tmpconv[numelements]; + int i; + for (i=0;ibufferobject = 0; + buffer->devicebuffer = NULL; + buffer->size = size; + buffer->isindexbuffer = isindexbuffer; + buffer->isuniformbuffer = isuniformbuffer; + buffer->isdynamic = isdynamic; + buffer->isindex16 = isindex16; + strlcpy(buffer->name, name, sizeof(buffer->name)); + R_Mesh_UpdateMeshBuffer(buffer, data, size, false, 0); + return buffer; +} + +void R_Mesh_UpdateMeshBuffer(r_meshbuffer_t *buffer, const void *data, size_t size, qboolean subdata, size_t offset) +{ + if (!buffer) + return; + if (buffer->isindexbuffer) + { + r_refdef.stats[r_stat_indexbufferuploadcount]++; + r_refdef.stats[r_stat_indexbufferuploadsize] += size; + } + else + { + r_refdef.stats[r_stat_vertexbufferuploadcount]++; + r_refdef.stats[r_stat_vertexbufferuploadsize] += size; + } + if (!subdata) + buffer->size = size; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (!buffer->bufferobject) + qglGenBuffersARB(1, (GLuint *)&buffer->bufferobject); + if (buffer->isuniformbuffer) + GL_BindUBO(buffer->bufferobject); + else if (buffer->isindexbuffer) + GL_BindEBO(buffer->bufferobject); + else + GL_BindVBO(buffer->bufferobject); + if (subdata) + qglBufferSubDataARB(buffer->isuniformbuffer ? GL_UNIFORM_BUFFER : (buffer->isindexbuffer ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER), offset, size, data); + else + qglBufferDataARB(buffer->isuniformbuffer ? GL_UNIFORM_BUFFER : (buffer->isindexbuffer ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER), size, data, buffer->isdynamic ? GL_STREAM_DRAW : GL_STATIC_DRAW); + if (buffer->isuniformbuffer) + GL_BindUBO(0); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + int result; + void *datapointer = NULL; + if (buffer->isindexbuffer) + { + IDirect3DIndexBuffer9 *d3d9indexbuffer = (IDirect3DIndexBuffer9 *)buffer->devicebuffer; + if (offset+size > buffer->size || !buffer->devicebuffer) + { + if (buffer->devicebuffer) + IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9*)buffer->devicebuffer); + buffer->devicebuffer = NULL; + if (FAILED(result = IDirect3DDevice9_CreateIndexBuffer(vid_d3d9dev, offset+size, buffer->isdynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : 0, buffer->isindex16 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, buffer->isdynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, &d3d9indexbuffer, NULL))) + Sys_Error("IDirect3DDevice9_CreateIndexBuffer(%p, %d, %x, %x, %x, %p, NULL) returned %x\n", vid_d3d9dev, (int)size, buffer->isdynamic ? (int)D3DUSAGE_DYNAMIC : 0, buffer->isindex16 ? (int)D3DFMT_INDEX16 : (int)D3DFMT_INDEX32, buffer->isdynamic ? (int)D3DPOOL_DEFAULT : (int)D3DPOOL_MANAGED, &d3d9indexbuffer, (int)result); + buffer->devicebuffer = (void *)d3d9indexbuffer; + buffer->size = offset+size; + } + if (!FAILED(IDirect3DIndexBuffer9_Lock(d3d9indexbuffer, (unsigned int)offset, (unsigned int)size, &datapointer, buffer->isdynamic ? D3DLOCK_DISCARD : 0))) + { + if (data) + memcpy(datapointer, data, size); + else + memset(datapointer, 0, size); + IDirect3DIndexBuffer9_Unlock(d3d9indexbuffer); + } + } + else + { + IDirect3DVertexBuffer9 *d3d9vertexbuffer = (IDirect3DVertexBuffer9 *)buffer->devicebuffer; + if (offset+size > buffer->size || !buffer->devicebuffer) + { + if (buffer->devicebuffer) + IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9*)buffer->devicebuffer); + buffer->devicebuffer = NULL; + if (FAILED(result = IDirect3DDevice9_CreateVertexBuffer(vid_d3d9dev, offset+size, buffer->isdynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : 0, 0, buffer->isdynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, &d3d9vertexbuffer, NULL))) + Sys_Error("IDirect3DDevice9_CreateVertexBuffer(%p, %d, %x, %x, %x, %p, NULL) returned %x\n", vid_d3d9dev, (int)size, buffer->isdynamic ? (int)D3DUSAGE_DYNAMIC : 0, 0, buffer->isdynamic ? (int)D3DPOOL_DEFAULT : (int)D3DPOOL_MANAGED, &d3d9vertexbuffer, (int)result); + buffer->devicebuffer = (void *)d3d9vertexbuffer; + buffer->size = offset+size; + } + if (!FAILED(IDirect3DVertexBuffer9_Lock(d3d9vertexbuffer, (unsigned int)offset, (unsigned int)size, &datapointer, buffer->isdynamic ? D3DLOCK_DISCARD : 0))) + { + if (data) + memcpy(datapointer, data, size); + else + memset(datapointer, 0, size); + IDirect3DVertexBuffer9_Unlock(d3d9vertexbuffer); + } + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_DestroyMeshBuffer(r_meshbuffer_t *buffer) +{ + if (!buffer) + return; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // GL clears the binding if we delete something bound + if (gl_state.uniformbufferobject == buffer->bufferobject) + gl_state.uniformbufferobject = 0; + if (gl_state.vertexbufferobject == buffer->bufferobject) + gl_state.vertexbufferobject = 0; + if (gl_state.elementbufferobject == buffer->bufferobject) + gl_state.elementbufferobject = 0; + qglDeleteBuffersARB(1, (GLuint *)&buffer->bufferobject); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (gl_state.d3dvertexbuffer == (void *)buffer) + gl_state.d3dvertexbuffer = NULL; + if (buffer->devicebuffer) + { + if (buffer->isindexbuffer) + IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9 *)buffer->devicebuffer); + else + IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9 *)buffer->devicebuffer); + buffer->devicebuffer = NULL; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + Mem_ExpandableArray_FreeRecord(&gl_state.meshbufferarray, (void *)buffer); +} + +static const char *buffertypename[R_BUFFERDATA_COUNT] = {"vertex", "index16", "index32", "uniform"}; +void GL_Mesh_ListVBOs(qboolean printeach) +{ + int i, endindex; + int type; + int isdynamic; + int index16count, index16mem; + int index32count, index32mem; + int vertexcount, vertexmem; + int uniformcount, uniformmem; + int totalcount, totalmem; + size_t bufferstat[R_BUFFERDATA_COUNT][2][2]; + r_meshbuffer_t *buffer; + memset(bufferstat, 0, sizeof(bufferstat)); + endindex = Mem_ExpandableArray_IndexRange(&gl_state.meshbufferarray); + for (i = 0;i < endindex;i++) + { + buffer = (r_meshbuffer_t *) Mem_ExpandableArray_RecordAtIndex(&gl_state.meshbufferarray, i); + if (!buffer) + continue; + if (buffer->isuniformbuffer) + type = R_BUFFERDATA_UNIFORM; + else if (buffer->isindexbuffer && buffer->isindex16) + type = R_BUFFERDATA_INDEX16; + else if (buffer->isindexbuffer) + type = R_BUFFERDATA_INDEX32; + else + type = R_BUFFERDATA_VERTEX; + isdynamic = buffer->isdynamic; + bufferstat[type][isdynamic][0]++; + bufferstat[type][isdynamic][1] += buffer->size; + if (printeach) + Con_Printf("buffer #%i %s = %i bytes (%s %s)\n", i, buffer->name, (int)buffer->size, isdynamic ? "dynamic" : "static", buffertypename[type]); + } + index16count = (int)(bufferstat[R_BUFFERDATA_INDEX16][0][0] + bufferstat[R_BUFFERDATA_INDEX16][1][0]); + index16mem = (int)(bufferstat[R_BUFFERDATA_INDEX16][0][1] + bufferstat[R_BUFFERDATA_INDEX16][1][1]); + index32count = (int)(bufferstat[R_BUFFERDATA_INDEX32][0][0] + bufferstat[R_BUFFERDATA_INDEX32][1][0]); + index32mem = (int)(bufferstat[R_BUFFERDATA_INDEX32][0][1] + bufferstat[R_BUFFERDATA_INDEX32][1][1]); + vertexcount = (int)(bufferstat[R_BUFFERDATA_VERTEX ][0][0] + bufferstat[R_BUFFERDATA_VERTEX ][1][0]); + vertexmem = (int)(bufferstat[R_BUFFERDATA_VERTEX ][0][1] + bufferstat[R_BUFFERDATA_VERTEX ][1][1]); + uniformcount = (int)(bufferstat[R_BUFFERDATA_UNIFORM][0][0] + bufferstat[R_BUFFERDATA_UNIFORM][1][0]); + uniformmem = (int)(bufferstat[R_BUFFERDATA_UNIFORM][0][1] + bufferstat[R_BUFFERDATA_UNIFORM][1][1]); + totalcount = index16count + index32count + vertexcount + uniformcount; + totalmem = index16mem + index32mem + vertexmem + uniformmem; + Con_Printf("%i 16bit indexbuffers totalling %i bytes (%.3f MB)\n%i 32bit indexbuffers totalling %i bytes (%.3f MB)\n%i vertexbuffers totalling %i bytes (%.3f MB)\n%i uniformbuffers totalling %i bytes (%.3f MB)\ncombined %i buffers totalling %i bytes (%.3fMB)\n", index16count, index16mem, index16mem / 10248576.0, index32count, index32mem, index32mem / 10248576.0, vertexcount, vertexmem, vertexmem / 10248576.0, uniformcount, uniformmem, uniformmem / 10248576.0, totalcount, totalmem, totalmem / 10248576.0); +} + + + +void R_Mesh_VertexPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (gl_state.pointer_vertex_components != components || gl_state.pointer_vertex_gltype != gltype || gl_state.pointer_vertex_stride != stride || gl_state.pointer_vertex_pointer != pointer || gl_state.pointer_vertex_vertexbuffer != vertexbuffer || gl_state.pointer_vertex_offset != bufferoffset) + { + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + gl_state.pointer_vertex_components = components; + gl_state.pointer_vertex_gltype = gltype; + gl_state.pointer_vertex_stride = stride; + gl_state.pointer_vertex_pointer = pointer; + gl_state.pointer_vertex_vertexbuffer = vertexbuffer; + gl_state.pointer_vertex_offset = bufferoffset; + CHECKGLERROR + GL_BindVBO(bufferobject); + qglVertexPointer(components, gltype, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (gl_state.pointer_vertex_components != components || gl_state.pointer_vertex_gltype != gltype || gl_state.pointer_vertex_stride != stride || gl_state.pointer_vertex_pointer != pointer || gl_state.pointer_vertex_vertexbuffer != vertexbuffer || gl_state.pointer_vertex_offset != bufferoffset) + { + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + gl_state.pointer_vertex_components = components; + gl_state.pointer_vertex_gltype = gltype; + gl_state.pointer_vertex_stride = stride; + gl_state.pointer_vertex_pointer = pointer; + gl_state.pointer_vertex_vertexbuffer = vertexbuffer; + gl_state.pointer_vertex_offset = bufferoffset; + CHECKGLERROR + GL_BindVBO(bufferobject); + // LordHavoc: special flag added to gltype for unnormalized types + qglVertexAttribPointer(GLSLATTRIB_POSITION, components, gltype & ~0x80000000, (gltype & 0x80000000) == 0, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_ColorPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) +{ + // note: vertexbuffer may be non-NULL even if pointer is NULL, so check + // the pointer only. + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: +#ifdef GL_MODELVIEW + CHECKGLERROR + if (pointer) + { + // caller wants color array enabled + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + if (!gl_state.pointer_color_enabled) + { + gl_state.pointer_color_enabled = true; + CHECKGLERROR + qglEnableClientState(GL_COLOR_ARRAY);CHECKGLERROR + } + if (gl_state.pointer_color_components != components || gl_state.pointer_color_gltype != gltype || gl_state.pointer_color_stride != stride || gl_state.pointer_color_pointer != pointer || gl_state.pointer_color_vertexbuffer != vertexbuffer || gl_state.pointer_color_offset != bufferoffset) + { + gl_state.pointer_color_components = components; + gl_state.pointer_color_gltype = gltype; + gl_state.pointer_color_stride = stride; + gl_state.pointer_color_pointer = pointer; + gl_state.pointer_color_vertexbuffer = vertexbuffer; + gl_state.pointer_color_offset = bufferoffset; + CHECKGLERROR + GL_BindVBO(bufferobject); + qglColorPointer(components, gltype, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + } + else + { + // caller wants color array disabled + if (gl_state.pointer_color_enabled) + { + gl_state.pointer_color_enabled = false; + CHECKGLERROR + qglDisableClientState(GL_COLOR_ARRAY);CHECKGLERROR + // when color array is on the glColor gets trashed, set it again + qglColor4f(gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]);CHECKGLERROR + } + } +#endif + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + CHECKGLERROR + if (pointer) + { + // caller wants color array enabled + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + if (!gl_state.pointer_color_enabled) + { + gl_state.pointer_color_enabled = true; + CHECKGLERROR + qglEnableVertexAttribArray(GLSLATTRIB_COLOR);CHECKGLERROR + } + if (gl_state.pointer_color_components != components || gl_state.pointer_color_gltype != gltype || gl_state.pointer_color_stride != stride || gl_state.pointer_color_pointer != pointer || gl_state.pointer_color_vertexbuffer != vertexbuffer || gl_state.pointer_color_offset != bufferoffset) + { + gl_state.pointer_color_components = components; + gl_state.pointer_color_gltype = gltype; + gl_state.pointer_color_stride = stride; + gl_state.pointer_color_pointer = pointer; + gl_state.pointer_color_vertexbuffer = vertexbuffer; + gl_state.pointer_color_offset = bufferoffset; + CHECKGLERROR + GL_BindVBO(bufferobject); + // LordHavoc: special flag added to gltype for unnormalized types + qglVertexAttribPointer(GLSLATTRIB_COLOR, components, gltype & ~0x80000000, (gltype & 0x80000000) == 0, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + } + else + { + // caller wants color array disabled + if (gl_state.pointer_color_enabled) + { + gl_state.pointer_color_enabled = false; + CHECKGLERROR + qglDisableVertexAttribArray(GLSLATTRIB_COLOR);CHECKGLERROR + // when color array is on the glColor gets trashed, set it again + qglVertexAttrib4f(GLSLATTRIB_COLOR, gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_TexCoordPointer(unsigned int unitnum, int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + // update array settings + // note: there is no need to check bufferobject here because all cases + // that involve a valid bufferobject also supply a texcoord array + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: +#ifdef GL_MODELVIEW + CHECKGLERROR + if (pointer) + { + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + // texture array unit is enabled, enable the array + if (!unit->arrayenabled) + { + unit->arrayenabled = true; + GL_ClientActiveTexture(unitnum); + qglEnableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR + } + // texcoord array + if (unit->pointer_texcoord_components != components || unit->pointer_texcoord_gltype != gltype || unit->pointer_texcoord_stride != stride || unit->pointer_texcoord_pointer != pointer || unit->pointer_texcoord_vertexbuffer != vertexbuffer || unit->pointer_texcoord_offset != bufferoffset) + { + unit->pointer_texcoord_components = components; + unit->pointer_texcoord_gltype = gltype; + unit->pointer_texcoord_stride = stride; + unit->pointer_texcoord_pointer = pointer; + unit->pointer_texcoord_vertexbuffer = vertexbuffer; + unit->pointer_texcoord_offset = bufferoffset; + GL_ClientActiveTexture(unitnum); + GL_BindVBO(bufferobject); + qglTexCoordPointer(components, gltype, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + } + else + { + // texture array unit is disabled, disable the array + if (unit->arrayenabled) + { + unit->arrayenabled = false; + GL_ClientActiveTexture(unitnum); + qglDisableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR + } + } +#endif + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + CHECKGLERROR + if (pointer) + { + int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; + // texture array unit is enabled, enable the array + if (!unit->arrayenabled) + { + unit->arrayenabled = true; + qglEnableVertexAttribArray(unitnum+GLSLATTRIB_TEXCOORD0);CHECKGLERROR + } + // texcoord array + if (unit->pointer_texcoord_components != components || unit->pointer_texcoord_gltype != gltype || unit->pointer_texcoord_stride != stride || unit->pointer_texcoord_pointer != pointer || unit->pointer_texcoord_vertexbuffer != vertexbuffer || unit->pointer_texcoord_offset != bufferoffset) + { + unit->pointer_texcoord_components = components; + unit->pointer_texcoord_gltype = gltype; + unit->pointer_texcoord_stride = stride; + unit->pointer_texcoord_pointer = pointer; + unit->pointer_texcoord_vertexbuffer = vertexbuffer; + unit->pointer_texcoord_offset = bufferoffset; + GL_BindVBO(bufferobject); + // LordHavoc: special flag added to gltype for unnormalized types + qglVertexAttribPointer(unitnum+GLSLATTRIB_TEXCOORD0, components, gltype & ~0x80000000, (gltype & 0x80000000) == 0, stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR + } + } + else + { + // texture array unit is disabled, disable the array + if (unit->arrayenabled) + { + unit->arrayenabled = false; + qglDisableVertexAttribArray(unitnum+GLSLATTRIB_TEXCOORD0);CHECKGLERROR + } + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } +} + +int R_Mesh_TexBound(unsigned int unitnum, int id) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + if (unitnum >= vid.teximageunits) + return 0; + if (id == GL_TEXTURE_2D) + return unit->t2d; + if (id == GL_TEXTURE_3D) + return unit->t3d; + if (id == GL_TEXTURE_CUBE_MAP) + return unit->tcubemap; + return 0; +} + +void R_Mesh_CopyToTexture(rtexture_t *tex, int tx, int ty, int sx, int sy, int width, int height) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + R_Mesh_TexBind(0, tex); + GL_ActiveTexture(0);CHECKGLERROR + qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, tx, ty, sx, sy, width, height);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + IDirect3DSurface9 *currentsurface = NULL; + IDirect3DSurface9 *texturesurface = NULL; + RECT sourcerect; + RECT destrect; + sourcerect.left = sx; + sourcerect.top = sy; + sourcerect.right = sx + width; + sourcerect.bottom = sy + height; + destrect.left = tx; + destrect.top = ty; + destrect.right = tx + width; + destrect.bottom = ty + height; + if (!FAILED(IDirect3DTexture9_GetSurfaceLevel(((IDirect3DTexture9 *)tex->d3dtexture), 0, &texturesurface))) + { + if (!FAILED(IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, ¤tsurface))) + { + IDirect3DDevice9_StretchRect(vid_d3d9dev, currentsurface, &sourcerect, texturesurface, &destrect, D3DTEXF_NONE); + IDirect3DSurface9_Release(currentsurface); + } + IDirect3DSurface9_Release(texturesurface); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_CopyRectangleToTexture(tex->texnum, 0, tx, ty, sx, sy, width, height); + break; + } +} + +#ifdef SUPPORTD3D +int d3drswrap[16] = {D3DRS_WRAP0, D3DRS_WRAP1, D3DRS_WRAP2, D3DRS_WRAP3, D3DRS_WRAP4, D3DRS_WRAP5, D3DRS_WRAP6, D3DRS_WRAP7, D3DRS_WRAP8, D3DRS_WRAP9, D3DRS_WRAP10, D3DRS_WRAP11, D3DRS_WRAP12, D3DRS_WRAP13, D3DRS_WRAP14, D3DRS_WRAP15}; +#endif + +void R_Mesh_ClearBindingsForTexture(int texnum) +{ + gltextureunit_t *unit; + unsigned int unitnum; + // this doesn't really unbind the texture, but it does prevent a mistaken "do nothing" behavior on the next time this same texnum is bound on the same unit as the same type (this mainly affects r_shadow_bouncegrid because 3D textures are so rarely used) + for (unitnum = 0;unitnum < vid.teximageunits;unitnum++) + { + unit = gl_state.units + unitnum; + if (unit->t2d == texnum) + unit->t2d = -1; + if (unit->t3d == texnum) + unit->t3d = -1; + if (unit->tcubemap == texnum) + unit->tcubemap = -1; + } +} + +void R_Mesh_TexBind(unsigned int unitnum, rtexture_t *tex) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + int tex2d, tex3d, texcubemap, texnum; + if (unitnum >= vid.teximageunits) + return; +// if (unit->texture == tex) +// return; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (!tex) + { + tex = r_texture_white; + // not initialized enough yet... + if (!tex) + return; + } + unit->texture = tex; + texnum = R_GetTexture(tex); + switch(tex->gltexturetypeenum) + { + case GL_TEXTURE_2D: if (unit->t2d != texnum) {GL_ActiveTexture(unitnum);unit->t2d = texnum;qglBindTexture(GL_TEXTURE_2D, unit->t2d);CHECKGLERROR}break; + case GL_TEXTURE_3D: if (unit->t3d != texnum) {GL_ActiveTexture(unitnum);unit->t3d = texnum;qglBindTexture(GL_TEXTURE_3D, unit->t3d);CHECKGLERROR}break; + case GL_TEXTURE_CUBE_MAP: if (unit->tcubemap != texnum) {GL_ActiveTexture(unitnum);unit->tcubemap = texnum;qglBindTexture(GL_TEXTURE_CUBE_MAP, unit->tcubemap);CHECKGLERROR}break; + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + unit->texture = tex; + tex2d = 0; + tex3d = 0; + texcubemap = 0; + if (tex) + { + texnum = R_GetTexture(tex); + switch(tex->gltexturetypeenum) + { + case GL_TEXTURE_2D: + tex2d = texnum; + break; + case GL_TEXTURE_3D: + tex3d = texnum; + break; + case GL_TEXTURE_CUBE_MAP: + texcubemap = texnum; + break; + } + } + // update 2d texture binding + if (unit->t2d != tex2d) + { + GL_ActiveTexture(unitnum); + if (tex2d) + { + if (unit->t2d == 0) + { + qglEnable(GL_TEXTURE_2D);CHECKGLERROR + } + } + else + { + if (unit->t2d) + { + qglDisable(GL_TEXTURE_2D);CHECKGLERROR + } + } + unit->t2d = tex2d; + qglBindTexture(GL_TEXTURE_2D, unit->t2d);CHECKGLERROR + } + // update 3d texture binding + if (unit->t3d != tex3d) + { + GL_ActiveTexture(unitnum); + if (tex3d) + { + if (unit->t3d == 0) + { + qglEnable(GL_TEXTURE_3D);CHECKGLERROR + } + } + else + { + if (unit->t3d) + { + qglDisable(GL_TEXTURE_3D);CHECKGLERROR + } + } + unit->t3d = tex3d; + qglBindTexture(GL_TEXTURE_3D, unit->t3d);CHECKGLERROR + } + // update cubemap texture binding + if (unit->tcubemap != texcubemap) + { + GL_ActiveTexture(unitnum); + if (texcubemap) + { + if (unit->tcubemap == 0) + { + qglEnable(GL_TEXTURE_CUBE_MAP);CHECKGLERROR + } + } + else + { + if (unit->tcubemap) + { + qglDisable(GL_TEXTURE_CUBE_MAP);CHECKGLERROR + } + } + unit->tcubemap = texcubemap; + qglBindTexture(GL_TEXTURE_CUBE_MAP, unit->tcubemap);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + extern cvar_t gl_texture_anisotropy; + if (!tex) + { + tex = r_texture_white; + // not initialized enough yet... + if (!tex) + return; + } + // upload texture if needed + R_GetTexture(tex); + if (unit->texture == tex) + return; + unit->texture = tex; + IDirect3DDevice9_SetTexture(vid_d3d9dev, unitnum, (IDirect3DBaseTexture9*)tex->d3dtexture); + //IDirect3DDevice9_SetRenderState(vid_d3d9dev, d3drswrap[unitnum], (tex->flags & TEXF_CLAMP) ? (D3DWRAPCOORD_0 | D3DWRAPCOORD_1 | D3DWRAPCOORD_2) : 0); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSU, tex->d3daddressu); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSV, tex->d3daddressv); + if (tex->d3daddressw) + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSW, tex->d3daddressw); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAGFILTER, tex->d3dmagfilter); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MINFILTER, tex->d3dminfilter); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MIPFILTER, tex->d3dmipfilter); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MIPMAPLODBIAS, tex->d3dmipmaplodbias); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAXMIPLEVEL, tex->d3dmaxmiplevelfilter); + IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAXANISOTROPY, gl_texture_anisotropy.integer); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (!tex) + { + tex = r_texture_white; + // not initialized enough yet... + if (!tex) + return; + } + texnum = R_GetTexture(tex); + if (unit->texture == tex) + return; + unit->texture = tex; + DPSOFTRAST_SetTexture(unitnum, texnum); + break; + } +} + +void R_Mesh_TexMatrix(unsigned int unitnum, const matrix4x4_t *matrix) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: +#ifdef GL_MODELVIEW + if (matrix && matrix->m[3][3]) + { + // texmatrix specified, check if it is different + if (!unit->texmatrixenabled || memcmp(&unit->matrix, matrix, sizeof(matrix4x4_t))) + { + float glmatrix[16]; + unit->texmatrixenabled = true; + unit->matrix = *matrix; + CHECKGLERROR + Matrix4x4_ToArrayFloatGL(&unit->matrix, glmatrix); + GL_ActiveTexture(unitnum); + qglMatrixMode(GL_TEXTURE);CHECKGLERROR + qglLoadMatrixf(glmatrix);CHECKGLERROR + qglMatrixMode(GL_MODELVIEW);CHECKGLERROR + } + } + else + { + // no texmatrix specified, revert to identity + if (unit->texmatrixenabled) + { + unit->texmatrixenabled = false; + unit->matrix = identitymatrix; + CHECKGLERROR + GL_ActiveTexture(unitnum); + qglMatrixMode(GL_TEXTURE);CHECKGLERROR + qglLoadIdentity();CHECKGLERROR + qglMatrixMode(GL_MODELVIEW);CHECKGLERROR + } + } +#endif + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_TexCombine(unsigned int unitnum, int combinergb, int combinealpha, int rgbscale, int alphascale) +{ + gltextureunit_t *unit = gl_state.units + unitnum; + CHECKGLERROR + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + // do nothing + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: +#ifdef GL_TEXTURE_ENV + // GL_ARB_texture_env_combine + if (!combinergb) + combinergb = GL_MODULATE; + if (!combinealpha) + combinealpha = GL_MODULATE; + if (!rgbscale) + rgbscale = 1; + if (!alphascale) + alphascale = 1; + if (combinergb != combinealpha || rgbscale != 1 || alphascale != 1) + { + if (combinergb == GL_DECAL) + combinergb = GL_INTERPOLATE; + if (unit->combine != GL_COMBINE) + { + unit->combine = GL_COMBINE; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);CHECKGLERROR + qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE);CHECKGLERROR // for GL_INTERPOLATE mode + } + if (unit->combinergb != combinergb) + { + unit->combinergb = combinergb; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, unit->combinergb);CHECKGLERROR + } + if (unit->combinealpha != combinealpha) + { + unit->combinealpha = combinealpha; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, unit->combinealpha);CHECKGLERROR + } + if (unit->rgbscale != rgbscale) + { + unit->rgbscale = rgbscale; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE, unit->rgbscale);CHECKGLERROR + } + if (unit->alphascale != alphascale) + { + unit->alphascale = alphascale; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, unit->alphascale);CHECKGLERROR + } + } + else + { + if (unit->combine != combinergb) + { + unit->combine = combinergb; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, unit->combine);CHECKGLERROR + } + } +#endif + break; + case RENDERPATH_GL11: + // normal GL texenv +#ifdef GL_TEXTURE_ENV + if (!combinergb) + combinergb = GL_MODULATE; + if (unit->combine != combinergb) + { + unit->combine = combinergb; + GL_ActiveTexture(unitnum); + qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, unit->combine);CHECKGLERROR + } +#endif + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + break; + } +} + +void R_Mesh_ResetTextureState(void) +{ + unsigned int unitnum; + + BACKENDACTIVECHECK + + for (unitnum = 0;unitnum < vid.teximageunits;unitnum++) + R_Mesh_TexBind(unitnum, NULL); + for (unitnum = 0;unitnum < vid.texarrayunits;unitnum++) + R_Mesh_TexCoordPointer(unitnum, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + for (unitnum = 0;unitnum < vid.texunits;unitnum++) + { + R_Mesh_TexCombine(unitnum, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexMatrix(unitnum, NULL); + } + break; + } +} + + + +#ifdef SUPPORTD3D +//#define r_vertex3f_d3d9fvf (D3DFVF_XYZ) +//#define r_vertexgeneric_d3d9fvf (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1) +//#define r_vertexmesh_d3d9fvf (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX5 | D3DFVF_TEXCOORDSIZE1(3) | D3DFVF_TEXCOORDSIZE2(3) | D3DFVF_TEXCOORDSIZE3(3)) + +D3DVERTEXELEMENT9 r_vertex3f_d3d9elements[] = +{ + {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, + D3DDECL_END() +}; + +D3DVERTEXELEMENT9 r_vertexgeneric_d3d9elements[] = +{ + {0, (int)((size_t)&((r_vertexgeneric_t *)0)->vertex3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, + {0, (int)((size_t)&((r_vertexgeneric_t *)0)->color4f ), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, + {0, (int)((size_t)&((r_vertexgeneric_t *)0)->texcoord2f), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, + D3DDECL_END() +}; + +D3DVERTEXELEMENT9 r_vertexmesh_d3d9elements[] = +{ + {0, (int)((size_t)&((r_vertexmesh_t *)0)->vertex3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->color4f ), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->texcoordtexture2f ), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->svector3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->tvector3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->normal3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->texcoordlightmap2f), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->skeletalindex4ub ), D3DDECLTYPE_UBYTE4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 6}, + {0, (int)((size_t)&((r_vertexmesh_t *)0)->skeletalweight4ub ), D3DDECLTYPE_UBYTE4N, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 7}, + D3DDECL_END() +}; + +IDirect3DVertexDeclaration9 *r_vertex3f_d3d9decl; +IDirect3DVertexDeclaration9 *r_vertexgeneric_d3d9decl; +IDirect3DVertexDeclaration9 *r_vertexmesh_d3d9decl; +#endif + +static void R_Mesh_InitVertexDeclarations(void) +{ +#ifdef SUPPORTD3D + r_vertex3f_d3d9decl = NULL; + r_vertexgeneric_d3d9decl = NULL; + r_vertexmesh_d3d9decl = NULL; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GL13: + case RENDERPATH_GL11: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertex3f_d3d9elements, &r_vertex3f_d3d9decl); + IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertexgeneric_d3d9elements, &r_vertexgeneric_d3d9decl); + IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertexmesh_d3d9elements, &r_vertexmesh_d3d9decl); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } +#endif +} + +static void R_Mesh_DestroyVertexDeclarations(void) +{ +#ifdef SUPPORTD3D + if (r_vertex3f_d3d9decl) + IDirect3DVertexDeclaration9_Release(r_vertex3f_d3d9decl); + r_vertex3f_d3d9decl = NULL; + if (r_vertexgeneric_d3d9decl) + IDirect3DVertexDeclaration9_Release(r_vertexgeneric_d3d9decl); + r_vertexgeneric_d3d9decl = NULL; + if (r_vertexmesh_d3d9decl) + IDirect3DVertexDeclaration9_Release(r_vertexmesh_d3d9decl); + r_vertexmesh_d3d9decl = NULL; +#endif +} + +void R_Mesh_PrepareVertices_Vertex3f(int numvertices, const float *vertex3f, const r_meshbuffer_t *vertexbuffer, int bufferoffset) +{ + // upload temporary vertexbuffer for this rendering + if (!gl_state.usevbo_staticvertex) + vertexbuffer = NULL; + if (!vertexbuffer && gl_state.usevbo_dynamicvertex) + vertexbuffer = R_BufferData_Store(numvertices * sizeof(float[3]), (void *)vertex3f, R_BUFFERDATA_VERTEX, &bufferoffset); + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (vertexbuffer) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, bufferoffset); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + } + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (vertexbuffer) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, bufferoffset); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + break; + case RENDERPATH_GL11: + if (vertexbuffer) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, bufferoffset); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertex3f_d3d9decl); + if (vertexbuffer) + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, bufferoffset, sizeof(float[3])); + else + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); + gl_state.d3dvertexbuffer = (void *)vertexbuffer; + gl_state.d3dvertexdata = (void *)vertex3f; + gl_state.d3dvertexsize = sizeof(float[3]); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); + DPSOFTRAST_SetColorPointer(NULL, 0); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), NULL); + break; + } +} + + + +r_vertexgeneric_t *R_Mesh_PrepareVertices_Generic_Lock(int numvertices) +{ + size_t size; + size = sizeof(r_vertexgeneric_t) * numvertices; + if (gl_state.preparevertices_tempdatamaxsize < size) + { + gl_state.preparevertices_tempdatamaxsize = size; + gl_state.preparevertices_tempdata = Mem_Realloc(r_main_mempool, gl_state.preparevertices_tempdata, gl_state.preparevertices_tempdatamaxsize); + } + gl_state.preparevertices_vertexgeneric = (r_vertexgeneric_t *)gl_state.preparevertices_tempdata; + gl_state.preparevertices_numvertices = numvertices; + return gl_state.preparevertices_vertexgeneric; +} + +qboolean R_Mesh_PrepareVertices_Generic_Unlock(void) +{ + R_Mesh_PrepareVertices_Generic(gl_state.preparevertices_numvertices, gl_state.preparevertices_vertexgeneric, NULL, 0); + gl_state.preparevertices_vertexgeneric = NULL; + gl_state.preparevertices_numvertices = 0; + return true; +} + +void R_Mesh_PrepareVertices_Generic_Arrays(int numvertices, const float *vertex3f, const float *color4f, const float *texcoord2f) +{ + int i; + r_vertexgeneric_t *vertex; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (gl_state.usevbo_dynamicvertex) + { + r_meshbuffer_t *buffer_vertex3f = NULL; + r_meshbuffer_t *buffer_color4f = NULL; + r_meshbuffer_t *buffer_texcoord2f = NULL; + int bufferoffset_vertex3f = 0; + int bufferoffset_color4f = 0; + int bufferoffset_texcoord2f = 0; + buffer_color4f = R_BufferData_Store(numvertices * sizeof(float[4]), color4f , R_BUFFERDATA_VERTEX, &bufferoffset_color4f ); + buffer_vertex3f = R_BufferData_Store(numvertices * sizeof(float[3]), vertex3f , R_BUFFERDATA_VERTEX, &bufferoffset_vertex3f ); + buffer_texcoord2f = R_BufferData_Store(numvertices * sizeof(float[2]), texcoord2f, R_BUFFERDATA_VERTEX, &bufferoffset_texcoord2f); + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(float[3]) , vertex3f , buffer_vertex3f , bufferoffset_vertex3f ); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(float[4]) , color4f , buffer_color4f , bufferoffset_color4f ); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(float[2]) , texcoord2f , buffer_texcoord2f , bufferoffset_texcoord2f ); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(float[3]) , NULL , NULL , 0 ); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(float[3]) , NULL , NULL , 0 ); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(float[3]) , NULL , NULL , 0 ); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(float[2]) , NULL , NULL , 0 ); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT , sizeof(float[2]) , NULL , NULL , 0 ); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL , NULL , 0 ); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL , NULL , 0 ); + } + else if (!vid.useinterleavedarrays) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoord2f, NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + return; + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (!vid.useinterleavedarrays) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoord2f, NULL, 0); + if (vid.texunits >= 2) + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + if (vid.texunits >= 3) + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + return; + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); + DPSOFTRAST_SetColorPointer(color4f, sizeof(float[4])); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), texcoord2f); + DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(float[2]), NULL); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), NULL); + return; + } + + // no quick path for this case, convert to vertex structs + vertex = R_Mesh_PrepareVertices_Generic_Lock(numvertices); + for (i = 0;i < numvertices;i++) + VectorCopy(vertex3f + 3*i, vertex[i].vertex3f); + if (color4f) + { + for (i = 0;i < numvertices;i++) + Vector4Copy(color4f + 4*i, vertex[i].color4f); + } + else + { + for (i = 0;i < numvertices;i++) + Vector4Copy(gl_state.color4f, vertex[i].color4f); + } + if (texcoord2f) + for (i = 0;i < numvertices;i++) + Vector2Copy(texcoord2f + 2*i, vertex[i].texcoord2f); + R_Mesh_PrepareVertices_Generic_Unlock(); + R_Mesh_PrepareVertices_Generic(numvertices, vertex, NULL, 0); +} + +void R_Mesh_PrepareVertices_Generic(int numvertices, const r_vertexgeneric_t *vertex, const r_meshbuffer_t *vertexbuffer, int bufferoffset) +{ + // upload temporary vertexbuffer for this rendering + if (!gl_state.usevbo_staticvertex) + vertexbuffer = NULL; + if (!vertexbuffer && gl_state.usevbo_dynamicvertex) + vertexbuffer = R_BufferData_Store(numvertices * sizeof(*vertex), (void *)vertex, R_BUFFERDATA_VERTEX, &bufferoffset); + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + } + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + } + break; + case RENDERPATH_GL11: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertexgeneric_d3d9decl); + if (vertexbuffer) + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, bufferoffset, sizeof(*vertex)); + else + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); + gl_state.d3dvertexbuffer = (void *)vertexbuffer; + gl_state.d3dvertexdata = (void *)vertex; + gl_state.d3dvertexsize = sizeof(*vertex); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex->vertex3f, sizeof(*vertex)); + DPSOFTRAST_SetColorPointer(vertex->color4f, sizeof(*vertex)); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(*vertex), vertex->texcoord2f); + DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(*vertex), NULL); + DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(*vertex), NULL); + DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(*vertex), NULL); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(*vertex), NULL); + break; + } +} + + + +r_vertexmesh_t *R_Mesh_PrepareVertices_Mesh_Lock(int numvertices) +{ + size_t size; + size = sizeof(r_vertexmesh_t) * numvertices; + if (gl_state.preparevertices_tempdatamaxsize < size) + { + gl_state.preparevertices_tempdatamaxsize = size; + gl_state.preparevertices_tempdata = Mem_Realloc(r_main_mempool, gl_state.preparevertices_tempdata, gl_state.preparevertices_tempdatamaxsize); + } + gl_state.preparevertices_vertexmesh = (r_vertexmesh_t *)gl_state.preparevertices_tempdata; + gl_state.preparevertices_numvertices = numvertices; + return gl_state.preparevertices_vertexmesh; +} + +qboolean R_Mesh_PrepareVertices_Mesh_Unlock(void) +{ + R_Mesh_PrepareVertices_Mesh(gl_state.preparevertices_numvertices, gl_state.preparevertices_vertexmesh, NULL, 0); + gl_state.preparevertices_vertexmesh = NULL; + gl_state.preparevertices_numvertices = 0; + return true; +} + +void R_Mesh_PrepareVertices_Mesh_Arrays(int numvertices, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *color4f, const float *texcoordtexture2f, const float *texcoordlightmap2f) +{ + int i; + r_vertexmesh_t *vertex; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (gl_state.usevbo_dynamicvertex) + { + r_meshbuffer_t *buffer_vertex3f = NULL; + r_meshbuffer_t *buffer_color4f = NULL; + r_meshbuffer_t *buffer_texcoordtexture2f = NULL; + r_meshbuffer_t *buffer_svector3f = NULL; + r_meshbuffer_t *buffer_tvector3f = NULL; + r_meshbuffer_t *buffer_normal3f = NULL; + r_meshbuffer_t *buffer_texcoordlightmap2f = NULL; + int bufferoffset_vertex3f = 0; + int bufferoffset_color4f = 0; + int bufferoffset_texcoordtexture2f = 0; + int bufferoffset_svector3f = 0; + int bufferoffset_tvector3f = 0; + int bufferoffset_normal3f = 0; + int bufferoffset_texcoordlightmap2f = 0; + buffer_color4f = R_BufferData_Store(numvertices * sizeof(float[4]), color4f , R_BUFFERDATA_VERTEX, &bufferoffset_color4f ); + buffer_vertex3f = R_BufferData_Store(numvertices * sizeof(float[3]), vertex3f , R_BUFFERDATA_VERTEX, &bufferoffset_vertex3f ); + buffer_svector3f = R_BufferData_Store(numvertices * sizeof(float[3]), svector3f , R_BUFFERDATA_VERTEX, &bufferoffset_svector3f ); + buffer_tvector3f = R_BufferData_Store(numvertices * sizeof(float[3]), tvector3f , R_BUFFERDATA_VERTEX, &bufferoffset_tvector3f ); + buffer_normal3f = R_BufferData_Store(numvertices * sizeof(float[3]), normal3f , R_BUFFERDATA_VERTEX, &bufferoffset_normal3f ); + buffer_texcoordtexture2f = R_BufferData_Store(numvertices * sizeof(float[2]), texcoordtexture2f , R_BUFFERDATA_VERTEX, &bufferoffset_texcoordtexture2f ); + buffer_texcoordlightmap2f = R_BufferData_Store(numvertices * sizeof(float[2]), texcoordlightmap2f, R_BUFFERDATA_VERTEX, &bufferoffset_texcoordlightmap2f); + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(float[3]) , vertex3f , buffer_vertex3f , bufferoffset_vertex3f ); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(float[4]) , color4f , buffer_color4f , bufferoffset_color4f ); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(float[2]) , texcoordtexture2f , buffer_texcoordtexture2f , bufferoffset_texcoordtexture2f ); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(float[3]) , svector3f , buffer_svector3f , bufferoffset_svector3f ); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(float[3]) , tvector3f , buffer_tvector3f , bufferoffset_tvector3f ); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(float[3]) , normal3f , buffer_normal3f , bufferoffset_normal3f ); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(float[2]) , texcoordlightmap2f, buffer_texcoordlightmap2f, bufferoffset_texcoordlightmap2f); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT , sizeof(float[2]) , NULL , NULL , 0 ); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL , NULL , 0 ); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL , NULL , 0 ); + } + else if (!vid.useinterleavedarrays) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoordtexture2f, NULL, 0); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), svector3f, NULL, 0); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), tvector3f, NULL, 0); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT, sizeof(float[3]), normal3f, NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), texcoordlightmap2f, NULL, 0); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); + return; + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (!vid.useinterleavedarrays) + { + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoordtexture2f, NULL, 0); + if (vid.texunits >= 2) + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), texcoordlightmap2f, NULL, 0); + if (vid.texunits >= 3) + R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + return; + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); + DPSOFTRAST_SetColorPointer(color4f, sizeof(float[4])); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), texcoordtexture2f); + DPSOFTRAST_SetTexCoordPointer(1, 3, sizeof(float[3]), svector3f); + DPSOFTRAST_SetTexCoordPointer(2, 3, sizeof(float[3]), tvector3f); + DPSOFTRAST_SetTexCoordPointer(3, 3, sizeof(float[3]), normal3f); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), texcoordlightmap2f); + return; + } + + vertex = R_Mesh_PrepareVertices_Mesh_Lock(numvertices); + for (i = 0;i < numvertices;i++) + VectorCopy(vertex3f + 3*i, vertex[i].vertex3f); + if (svector3f) + for (i = 0;i < numvertices;i++) + VectorCopy(svector3f + 3*i, vertex[i].svector3f); + if (tvector3f) + for (i = 0;i < numvertices;i++) + VectorCopy(tvector3f + 3*i, vertex[i].tvector3f); + if (normal3f) + for (i = 0;i < numvertices;i++) + VectorCopy(normal3f + 3*i, vertex[i].normal3f); + if (color4f) + { + for (i = 0;i < numvertices;i++) + Vector4Copy(color4f + 4*i, vertex[i].color4f); + } + else + { + for (i = 0;i < numvertices;i++) + Vector4Copy(gl_state.color4f, vertex[i].color4f); + } + if (texcoordtexture2f) + for (i = 0;i < numvertices;i++) + Vector2Copy(texcoordtexture2f + 2*i, vertex[i].texcoordtexture2f); + if (texcoordlightmap2f) + for (i = 0;i < numvertices;i++) + Vector2Copy(texcoordlightmap2f + 2*i, vertex[i].texcoordlightmap2f); + R_Mesh_PrepareVertices_Mesh_Unlock(); + R_Mesh_PrepareVertices_Mesh(numvertices, vertex, NULL, 0); +} + +void R_Mesh_PrepareVertices_Mesh(int numvertices, const r_vertexmesh_t *vertex, const r_meshbuffer_t *vertexbuffer, int bufferoffset) +{ + // upload temporary vertexbuffer for this rendering + if (!gl_state.usevbo_staticvertex) + vertexbuffer = NULL; + if (!vertexbuffer && gl_state.usevbo_dynamicvertex) + vertexbuffer = R_BufferData_Store(numvertices * sizeof(*vertex), (void *)vertex, R_BUFFERDATA_VERTEX, &bufferoffset); + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(*vertex), vertex->svector3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->svector3f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(*vertex), vertex->tvector3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->tvector3f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(*vertex), vertex->normal3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->normal3f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordlightmap2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT , sizeof(*vertex), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE | 0x80000000, sizeof(*vertex), vertex->skeletalindex4ub , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->skeletalindex4ub - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(*vertex), vertex->skeletalweight4ub , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->skeletalweight4ub - (unsigned char *)vertex)); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(*vertex), vertex->svector3f , NULL, 0); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(*vertex), vertex->tvector3f , NULL, 0); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(*vertex), vertex->normal3f , NULL, 0); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, NULL, 0); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT , sizeof(*vertex), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE | 0x80000000, sizeof(*vertex), vertex->skeletalindex4ub , NULL, 0); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(*vertex), vertex->skeletalweight4ub , NULL, 0); + } + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordlightmap2f - (unsigned char *)vertex)); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, NULL, 0); + } + break; + case RENDERPATH_GL11: + if (vertexbuffer) + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); + } + else + { + R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); + R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertexmesh_d3d9decl); + if (vertexbuffer) + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, bufferoffset, sizeof(*vertex)); + else + IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); + gl_state.d3dvertexbuffer = (void *)vertexbuffer; + gl_state.d3dvertexdata = (void *)vertex; + gl_state.d3dvertexsize = sizeof(*vertex); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_SetVertexPointer(vertex->vertex3f, sizeof(*vertex)); + DPSOFTRAST_SetColorPointer(vertex->color4f, sizeof(*vertex)); + DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(*vertex), vertex->texcoordtexture2f); + DPSOFTRAST_SetTexCoordPointer(1, 3, sizeof(*vertex), vertex->svector3f); + DPSOFTRAST_SetTexCoordPointer(2, 3, sizeof(*vertex), vertex->tvector3f); + DPSOFTRAST_SetTexCoordPointer(3, 3, sizeof(*vertex), vertex->normal3f); + DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(*vertex), vertex->texcoordlightmap2f); + break; + } +} + +void GL_BlendEquationSubtract(qboolean negated) +{ + if(negated) + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglBlendEquationEXT(GL_FUNC_REVERSE_SUBTRACT); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_BLENDOP, D3DBLENDOP_SUBTRACT); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_BlendSubtract(true); + break; + } + } + else + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglBlendEquationEXT(GL_FUNC_ADD); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_BLENDOP, D3DBLENDOP_ADD); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_BlendSubtract(false); + break; + } + } +} diff --git a/app/jni/gl_backend.h b/app/jni/gl_backend.h new file mode 100644 index 0000000..04d23db --- /dev/null +++ b/app/jni/gl_backend.h @@ -0,0 +1,127 @@ + +#ifndef GL_BACKEND_H +#define GL_BACKEND_H + +extern r_viewport_t gl_viewport; +extern matrix4x4_t gl_modelmatrix; +extern matrix4x4_t gl_viewmatrix; +extern matrix4x4_t gl_modelviewmatrix; +extern matrix4x4_t gl_projectionmatrix; +extern matrix4x4_t gl_modelviewprojectionmatrix; +extern float gl_modelview16f[16]; +extern float gl_modelviewprojection16f[16]; +extern qboolean gl_modelmatrixchanged; + +extern cvar_t gl_vbo_dynamicvertex; +extern cvar_t gl_vbo_dynamicindex; + +#define POLYGONELEMENTS_MAXPOINTS 258 +extern int polygonelement3i[(POLYGONELEMENTS_MAXPOINTS-2)*3]; +extern unsigned short polygonelement3s[(POLYGONELEMENTS_MAXPOINTS-2)*3]; +#define QUADELEMENTS_MAXQUADS 128 +extern int quadelement3i[QUADELEMENTS_MAXQUADS*6]; +extern unsigned short quadelement3s[QUADELEMENTS_MAXQUADS*6]; + +void R_Viewport_TransformToScreen(const r_viewport_t *v, const vec4_t in, vec4_t out); +qboolean R_ScissorForBBox(const float *mins, const float *maxs, int *scissor); +void R_Viewport_InitOrtho(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float x1, float y1, float x2, float y2, float zNear, float zFar, const float *nearplane); +void R_Viewport_InitPerspective(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float zNear, float zFar, const float *nearplane); +void R_Viewport_InitPerspectiveInfinite(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float zNear, const float *nearplane); +void R_Viewport_InitCubeSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, float nearclip, float farclip, const float *nearplane); +void R_Viewport_InitRectSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, int border, float nearclip, float farclip, const float *nearplane); +void R_SetViewport(const r_viewport_t *v); +void R_GetViewport(r_viewport_t *v); +void GL_Finish(void); + +void GL_BlendFunc(int blendfunc1, int blendfunc2); +void GL_BlendEquationSubtract(qboolean negated); +void GL_DepthMask(int state); +void GL_DepthTest(int state); +void GL_DepthFunc(int state); +void GL_DepthRange(float nearfrac, float farfrac); +void R_SetStencilSeparate(qboolean enable, int writemask, int frontfail, int frontzfail, int frontzpass, int backfail, int backzfail, int backzpass, int frontcompare, int backcompare, int comparereference, int comparemask); +void R_SetStencil(qboolean enable, int writemask, int fail, int zfail, int zpass, int compare, int comparereference, int comparemask); +void GL_PolygonOffset(float planeoffset, float depthoffset); +void GL_CullFace(int state); +void GL_AlphaTest(int state); +void GL_AlphaToCoverage(qboolean state); +void GL_ColorMask(int r, int g, int b, int a); +void GL_Color(float cr, float cg, float cb, float ca); +void GL_ActiveTexture(unsigned int num); +void GL_ClientActiveTexture(unsigned int num); +void GL_Scissor(int x, int y, int width, int height); +void GL_ScissorTest(int state); +void GL_Clear(int mask, const float *colorvalue, float depthvalue, int stencilvalue); +void GL_ReadPixelsBGRA(int x, int y, int width, int height, unsigned char *outpixels); +int R_Mesh_CreateFramebufferObject(rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4); +void R_Mesh_DestroyFramebufferObject(int fbo); +void R_Mesh_SetRenderTargets(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4); + +unsigned int GL_Backend_CompileProgram(int vertexstrings_count, const char **vertexstrings_list, int geometrystrings_count, const char **geometrystrings_list, int fragmentstrings_count, const char **fragmentstrings_list); +void GL_Backend_FreeProgram(unsigned int prog); + +extern cvar_t gl_paranoid; +extern cvar_t gl_printcheckerror; + +// adds console variables and registers the render module (only call from GL_Init) +void gl_backend_init(void); + +// starts mesh rendering for the frame +void R_Mesh_Start(void); + +// ends mesh rendering for the frame +// (only valid after R_Mesh_Start) +void R_Mesh_Finish(void); + + +// vertex buffer and index buffer creation/updating/freeing +r_meshbuffer_t *R_Mesh_CreateMeshBuffer(const void *data, size_t size, const char *name, qboolean isindexbuffer, qboolean isuniformbuffer, qboolean isdynamic, qboolean isindex16); +void R_Mesh_UpdateMeshBuffer(r_meshbuffer_t *buffer, const void *data, size_t size, qboolean subdata, size_t offset); +void R_Mesh_DestroyMeshBuffer(r_meshbuffer_t *buffer); +void GL_Mesh_ListVBOs(qboolean printeach); + +void R_Mesh_PrepareVertices_Vertex3f(int numvertices, const float *vertex3f, const r_meshbuffer_t *buffer, int bufferoffset); + +r_vertexgeneric_t *R_Mesh_PrepareVertices_Generic_Lock(int numvertices); +qboolean R_Mesh_PrepareVertices_Generic_Unlock(void); +void R_Mesh_PrepareVertices_Generic_Arrays(int numvertices, const float *vertex3f, const float *color4f, const float *texcoord2f); +void R_Mesh_PrepareVertices_Generic(int numvertices, const r_vertexgeneric_t *vertex, const r_meshbuffer_t *vertexbuffer, int bufferoffset); + +r_vertexmesh_t *R_Mesh_PrepareVertices_Mesh_Lock(int numvertices); +qboolean R_Mesh_PrepareVertices_Mesh_Unlock(void); // if this returns false, you need to prepare the mesh again! +void R_Mesh_PrepareVertices_Mesh_Arrays(int numvertices, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *color4f, const float *texcoordtexture2f, const float *texcoordlightmap2f); +void R_Mesh_PrepareVertices_Mesh(int numvertices, const r_vertexmesh_t *vertex, const r_meshbuffer_t *buffer, int bufferoffset); + +// sets up the requested vertex transform matrix +void R_EntityMatrix(const matrix4x4_t *matrix); +// sets the vertex array pointer +void R_Mesh_VertexPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); +// sets the color array pointer (GL_Color only works when this is NULL) +void R_Mesh_ColorPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); +// sets the texcoord array pointer for an array unit, if GL_UNSIGNED_BYTE | 0x80000000 is specified it will be an unnormalized type (integer values) +void R_Mesh_TexCoordPointer(unsigned int unitnum, int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); +// returns current texture bound to this identifier +int R_Mesh_TexBound(unsigned int unitnum, int id); +// copies a section of the framebuffer to a 2D texture +void R_Mesh_CopyToTexture(rtexture_t *tex, int tx, int ty, int sx, int sy, int width, int height); +// bind a given texture to a given image unit +void R_Mesh_TexBind(unsigned int unitnum, rtexture_t *tex); +// sets the texcoord matrix for a texenv unit, can be NULL or blank (will use identity) +void R_Mesh_TexMatrix(unsigned int unitnum, const matrix4x4_t *matrix); +// sets the combine state for a texenv unit +void R_Mesh_TexCombine(unsigned int unitnum, int combinergb, int combinealpha, int rgbscale, int alphascale); +// set up a blank texture state (unbinds all textures, texcoord pointers, and resets combine settings) +void R_Mesh_ResetTextureState(void); +// before a texture is freed, make sure there are no references to it +void R_Mesh_ClearBindingsForTexture(int texnum); + +// renders a mesh +void R_Mesh_Draw(int firstvertex, int numvertices, int firsttriangle, int numtriangles, const int *element3i, const r_meshbuffer_t *element3i_indexbuffer, int element3i_bufferoffset, const unsigned short *element3s, const r_meshbuffer_t *element3s_indexbuffer, int element3s_bufferoffset); + +// saves a section of the rendered frame to a .tga or .jpg file +qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean png, qboolean gammacorrect, qboolean keep_alpha); +// used by R_Envmap_f and internally in backend, clears the frame +void R_ClearScreen(qboolean fogcolor); + +#endif + diff --git a/app/jni/gl_draw.c b/app/jni/gl_draw.c new file mode 100644 index 0000000..88431d9 --- /dev/null +++ b/app/jni/gl_draw.c @@ -0,0 +1,2268 @@ +/* +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 "image.h" +#include "wad.h" + +#include "cl_video.h" +#include "cl_dyntexture.h" + +#include "ft2.h" +#include "ft2_fontdefs.h" + +dp_fonts_t dp_fonts; +static mempool_t *fonts_mempool = NULL; + +cvar_t r_textshadow = {CVAR_SAVE, "r_textshadow", "0", "draws a shadow on all text to improve readability (note: value controls offset, 1 = 1 pixel, 1.5 = 1.5 pixels, etc)"}; +cvar_t r_textbrightness = {CVAR_SAVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"}; +cvar_t r_textcontrast = {CVAR_SAVE, "r_textcontrast", "1", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"}; + +cvar_t r_font_postprocess_blur = {CVAR_SAVE, "r_font_postprocess_blur", "0", "font blur amount"}; +cvar_t r_font_postprocess_outline = {CVAR_SAVE, "r_font_postprocess_outline", "0", "font outline amount"}; +cvar_t r_font_postprocess_shadow_x = {CVAR_SAVE, "r_font_postprocess_shadow_x", "0", "font shadow X shift amount, applied during outlining"}; +cvar_t r_font_postprocess_shadow_y = {CVAR_SAVE, "r_font_postprocess_shadow_y", "0", "font shadow Y shift amount, applied during outlining"}; +cvar_t r_font_postprocess_shadow_z = {CVAR_SAVE, "r_font_postprocess_shadow_z", "0", "font shadow Z shift amount, applied during blurring"}; +cvar_t r_font_hinting = {CVAR_SAVE, "r_font_hinting", "3", "0 = no hinting, 1 = light autohinting, 2 = full autohinting, 3 = full hinting"}; +cvar_t r_font_antialias = {CVAR_SAVE, "r_font_antialias", "1", "0 = monochrome, 1 = grey" /* , 2 = rgb, 3 = bgr" */}; +cvar_t r_nearest_2d = {CVAR_SAVE, "r_nearest_2d", "0", "use nearest filtering on all 2d textures (including conchars)"}; +cvar_t r_nearest_conchars = {CVAR_SAVE, "r_nearest_conchars", "0", "use nearest filtering on conchars texture"}; + +extern cvar_t v_glslgamma; + +//============================================================================= +/* Support Routines */ + +#define FONT_FILESIZE 13468 +static cachepic_t *cachepichash[CACHEPICHASHSIZE]; +static cachepic_t cachepics[MAX_CACHED_PICS]; +static int numcachepics; + +rtexturepool_t *drawtexturepool; + +static const unsigned char concharimage[FONT_FILESIZE] = +{ +#include "lhfont.h" +}; + +static rtexture_t *draw_generateconchars(void) +{ + int i; + unsigned char *data; + double random; + rtexture_t *tex; + + data = LoadTGA_BGRA (concharimage, FONT_FILESIZE, NULL); +// Gold numbers + for (i = 0;i < 8192;i++) + { + random = lhrandom (0.0,1.0); + data[i*4+3] = data[i*4+0]; + data[i*4+2] = 83 + (unsigned char)(random * 64); + data[i*4+1] = 71 + (unsigned char)(random * 32); + data[i*4+0] = 23 + (unsigned char)(random * 16); + } +// White chars + for (i = 8192;i < 32768;i++) + { + random = lhrandom (0.0,1.0); + data[i*4+3] = data[i*4+0]; + data[i*4+2] = 95 + (unsigned char)(random * 64); + data[i*4+1] = 95 + (unsigned char)(random * 64); + data[i*4+0] = 95 + (unsigned char)(random * 64); + } +// Gold numbers + for (i = 32768;i < 40960;i++) + { + random = lhrandom (0.0,1.0); + data[i*4+3] = data[i*4+0]; + data[i*4+2] = 83 + (unsigned char)(random * 64); + data[i*4+1] = 71 + (unsigned char)(random * 32); + data[i*4+0] = 23 + (unsigned char)(random * 16); + } +// Red chars + for (i = 40960;i < 65536;i++) + { + random = lhrandom (0.0,1.0); + data[i*4+3] = data[i*4+0]; + data[i*4+2] = 96 + (unsigned char)(random * 64); + data[i*4+1] = 43 + (unsigned char)(random * 32); + data[i*4+0] = 27 + (unsigned char)(random * 32); + } + +#if 0 + Image_WriteTGABGRA ("gfx/generated_conchars.tga", 256, 256, data); +#endif + + tex = R_LoadTexture2D(drawtexturepool, "conchars", 256, 256, data, TEXTYPE_BGRA, TEXF_ALPHA | (r_nearest_conchars.integer ? TEXF_FORCENEAREST : 0), -1, NULL); + Mem_Free(data); + return tex; +} + +static rtexture_t *draw_generateditherpattern(void) +{ + int x, y; + unsigned char pixels[8][8]; + for (y = 0;y < 8;y++) + for (x = 0;x < 8;x++) + pixels[y][x] = ((x^y) & 4) ? 254 : 0; + return R_LoadTexture2D(drawtexturepool, "ditherpattern", 8, 8, pixels[0], TEXTYPE_PALETTE, TEXF_FORCENEAREST, -1, palette_bgra_transparent); +} + +typedef struct embeddedpic_s +{ + const char *name; + int width; + int height; + const char *pixels; +} +embeddedpic_t; + +static const embeddedpic_t embeddedpics[] = +{ + { + "gfx/prydoncursor001", 16, 16, + "477777774......." + "77.....6........" + "7.....6........." + "7....6.........." + "7.....6........." + "7..6...6........" + "7.6.6...6......." + "76...6...6......" + "4.....6.6......." + ".......6........" + "................" + "................" + "................" + "................" + "................" + "................" + }, + { + "ui/mousepointer", 16, 16, + "477777774......." + "77.....6........" + "7.....6........." + "7....6.........." + "7.....6........." + "7..6...6........" + "7.6.6...6......." + "76...6...6......" + "4.....6.6......." + ".......6........" + "................" + "................" + "................" + "................" + "................" + "................" + }, + { + "gfx/crosshair1", 16, 16, + "................" + "................" + "................" + "...33......33..." + "...355....553..." + "....577..775...." + ".....77..77....." + "................" + "................" + ".....77..77....." + "....577..775...." + "...355....553..." + "...33......33..." + "................" + "................" + "................" + }, + { + "gfx/crosshair2", 16, 16, + "................" + "................" + "................" + "...3........3..." + "....5......5...." + ".....7....7....." + "......7..7......" + "................" + "................" + "......7..7......" + ".....7....7....." + "....5......5...." + "...3........3..." + "................" + "................" + "................" + }, + { + "gfx/crosshair3", 16, 16, + "................" + ".......77......." + ".......77......." + "................" + "................" + ".......44......." + ".......44......." + ".77..44..44..77." + ".77..44..44..77." + ".......44......." + ".......44......." + "................" + "................" + ".......77......." + ".......77......." + "................" + }, + { + "gfx/crosshair4", 16, 16, + "................" + "................" + "................" + "................" + "................" + "................" + "................" + "................" + "........7777777." + "........752....." + "........72......" + "........7......." + "........7......." + "........7......." + "........7......." + "................" + }, + { + "gfx/crosshair5", 8, 8, + "........" + "........" + "....7..." + "........" + "..7.7.7." + "........" + "....7..." + "........" + }, + { + "gfx/crosshair6", 2, 2, + "77" + "77" + }, + { + "gfx/crosshair7", 16, 16, + "................" + ".3............3." + "..5...2332...5.." + "...7.3....3.7..." + "....7......7...." + "...3.7....7.3..." + "..2...7..7...2.." + "..3..........3.." + "..3..........3.." + "..2...7..7...2.." + "...3.7....7.3..." + "....7......7...." + "...7.3....3.7..." + "..5...2332...5.." + ".3............3." + "................" + }, + {NULL, 0, 0, NULL} +}; + +static rtexture_t *draw_generatepic(const char *name, qboolean quiet) +{ + const embeddedpic_t *p; + for (p = embeddedpics;p->name;p++) + if (!strcmp(name, p->name)) + return R_LoadTexture2D(drawtexturepool, p->name, p->width, p->height, (const unsigned char *)p->pixels, TEXTYPE_PALETTE, TEXF_ALPHA, -1, palette_bgra_embeddedpic); + if (!strcmp(name, "gfx/conchars")) + return draw_generateconchars(); + if (!strcmp(name, "gfx/colorcontrol/ditherpattern")) + return draw_generateditherpattern(); + if (!quiet) + Con_DPrintf("Draw_CachePic: failed to load %s\n", name); + return r_texture_notexture; +} + +int draw_frame = 1; + +/* +================ +Draw_CachePic +================ +*/ +// FIXME: move this to client somehow +cachepic_t *Draw_CachePic_Flags(const char *path, unsigned int cachepicflags) +{ + int crc, hashkey; + unsigned char *pixels = NULL; + cachepic_t *pic; + fs_offset_t lmpsize; + unsigned char *lmpdata; + char lmpname[MAX_QPATH]; + int texflags; + int j; + qboolean ddshasalpha; + float ddsavgcolor[4]; + qboolean loaded = false; + char vabuf[1024]; + + texflags = TEXF_ALPHA; + if (!(cachepicflags & CACHEPICFLAG_NOCLAMP)) + texflags |= TEXF_CLAMP; + if (cachepicflags & CACHEPICFLAG_MIPMAP) + texflags |= TEXF_MIPMAP; + if (!(cachepicflags & CACHEPICFLAG_NOCOMPRESSION) && gl_texturecompression_2d.integer && gl_texturecompression.integer) + texflags |= TEXF_COMPRESS; + if ((cachepicflags & CACHEPICFLAG_NEAREST) || r_nearest_2d.integer) + texflags |= TEXF_FORCENEAREST; + + // check whether the picture has already been cached + crc = CRC_Block((unsigned char *)path, strlen(path)); + hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; + for (pic = cachepichash[hashkey];pic;pic = pic->chain) + { + if (!strcmp (path, pic->name)) + { + // if it was created (or replaced) by Draw_NewPic, just return it + if(pic->flags & CACHEPICFLAG_NEWPIC) + return pic; + if (!((pic->texflags ^ texflags) & ~(TEXF_COMPRESS | TEXF_MIPMAP))) // ignore TEXF_COMPRESS when comparing, because fallback pics remove the flag, and ignore TEXF_MIPMAP because QC specifies that + { + if(!(cachepicflags & CACHEPICFLAG_NOTPERSISTENT)) + { + if(pic->tex) + pic->autoload = false; // persist it + else + goto reload; // load it below, and then persist + } + return pic; + } + } + } + + if (numcachepics == MAX_CACHED_PICS) + { + Con_Printf ("Draw_CachePic: numcachepics == MAX_CACHED_PICS\n"); + // FIXME: support NULL in callers? + return cachepics; // return the first one + } + pic = cachepics + (numcachepics++); + memset(pic, 0, sizeof(*pic)); + strlcpy (pic->name, path, sizeof(pic->name)); + // link into list + pic->chain = cachepichash[hashkey]; + cachepichash[hashkey] = pic; + +reload: + // TODO why does this crash? + if(pic->allow_free_tex && pic->tex) + R_PurgeTexture(pic->tex); + + // check whether it is an dynamic texture (if so, we can directly use its texture handler) + pic->flags = cachepicflags; + pic->tex = CL_GetDynTexture( path ); + // if so, set the width/height, too + if( pic->tex ) { + pic->allow_free_tex = false; + pic->width = R_TextureWidth(pic->tex); + pic->height = R_TextureHeight(pic->tex); + // we're done now (early-out) + return pic; + } + + pic->allow_free_tex = true; + + pic->hasalpha = true; // assume alpha unless we know it has none + pic->texflags = texflags; + pic->autoload = (cachepicflags & CACHEPICFLAG_NOTPERSISTENT); + pic->lastusedframe = draw_frame; + + // load a high quality image from disk if possible + if (!loaded && r_texture_dds_load.integer != 0 && (pic->tex = R_LoadTextureDDSFile(drawtexturepool, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), vid.sRGB2D, pic->texflags, &ddshasalpha, ddsavgcolor, 0, false))) + { + // note this loads even if autoload is true, otherwise we can't get the width/height + loaded = true; + pic->hasalpha = ddshasalpha; + pic->width = R_TextureWidth(pic->tex); + pic->height = R_TextureHeight(pic->tex); + } + if (!loaded && ((pixels = loadimagepixelsbgra(pic->name, false, true, false, NULL)) || (!strncmp(pic->name, "gfx/", 4) && (pixels = loadimagepixelsbgra(pic->name+4, false, true, false, NULL))))) + { + loaded = true; + pic->hasalpha = false; + if (pic->texflags & TEXF_ALPHA) + { + for (j = 3;j < image_width * image_height * 4;j += 4) + { + if (pixels[j] < 255) + { + pic->hasalpha = true; + break; + } + } + } + + pic->width = image_width; + pic->height = image_height; + if (!pic->autoload) + { + pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, image_width, image_height, pixels, vid.sRGB2D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, pic->texflags & (pic->hasalpha ? ~0 : ~TEXF_ALPHA), -1, NULL); +#ifndef USE_GLES2 + if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) + R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); +#endif + } + } + if (!loaded) + { + pic->autoload = false; + // never compress the fallback images + pic->texflags &= ~TEXF_COMPRESS; + } + + // now read the low quality version (wad or lmp file), and take the pic + // size from that even if we don't upload the texture, this way the pics + // show up the right size in the menu even if they were replaced with + // higher or lower resolution versions + dpsnprintf(lmpname, sizeof(lmpname), "%s.lmp", pic->name); + if ((!strncmp(pic->name, "gfx/", 4) || (gamemode == GAME_BLOODOMNICIDE && !strncmp(pic->name, "locale/", 6))) && (lmpdata = FS_LoadFile(lmpname, tempmempool, false, &lmpsize))) + { + if (developer_loading.integer) + Con_Printf("loading lump \"%s\"\n", pic->name); + + if (lmpsize >= 9) + { + pic->width = lmpdata[0] + lmpdata[1] * 256 + lmpdata[2] * 65536 + lmpdata[3] * 16777216; + pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216; + // if no high quality replacement image was found, upload the original low quality texture + if (!loaded) + { + loaded = true; + pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, pic->width, pic->height, lmpdata + 8, vid.sRGB2D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_transparent); + } + } + Mem_Free(lmpdata); + } + else if ((lmpdata = W_GetLumpName (pic->name + 4))) + { + if (developer_loading.integer) + Con_Printf("loading gfx.wad lump \"%s\"\n", pic->name + 4); + + if (!strcmp(pic->name, "gfx/conchars")) + { + // conchars is a raw image and with color 0 as transparent instead of 255 + pic->width = 128; + pic->height = 128; + // if no high quality replacement image was found, upload the original low quality texture + if (!loaded) + { + loaded = true; + pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, 128, 128, lmpdata, vid.sRGB2D != 0 ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_font); + } + } + else + { + pic->width = lmpdata[0] + lmpdata[1] * 256 + lmpdata[2] * 65536 + lmpdata[3] * 16777216; + pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216; + // if no high quality replacement image was found, upload the original low quality texture + if (!loaded) + { + loaded = true; + pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, pic->width, pic->height, lmpdata + 8, vid.sRGB2D != 0 ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_transparent); + } + } + } + + if (pixels) + { + Mem_Free(pixels); + pixels = NULL; + } + if (!loaded) + { + // if it's not found on disk, generate an image + pic->tex = draw_generatepic(pic->name, (cachepicflags & CACHEPICFLAG_QUIET) != 0); + pic->width = R_TextureWidth(pic->tex); + pic->height = R_TextureHeight(pic->tex); + pic->allow_free_tex = (pic->tex != r_texture_notexture); + } + + return pic; +} + +cachepic_t *Draw_CachePic (const char *path) +{ + return Draw_CachePic_Flags (path, 0); // default to persistent! +} + +rtexture_t *Draw_GetPicTexture(cachepic_t *pic) +{ + char vabuf[1024]; + if (pic->autoload && !pic->tex) + { + if (pic->tex == NULL && r_texture_dds_load.integer != 0) + { + qboolean ddshasalpha; + float ddsavgcolor[4]; + pic->tex = R_LoadTextureDDSFile(drawtexturepool, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), vid.sRGB2D, pic->texflags, &ddshasalpha, ddsavgcolor, 0, false); + } + if (pic->tex == NULL) + { + pic->tex = loadtextureimage(drawtexturepool, pic->name, false, pic->texflags, true, vid.sRGB2D); +#ifndef USE_GLES2 + if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) + R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); +#endif + } + if (pic->tex == NULL && !strncmp(pic->name, "gfx/", 4)) + { + pic->tex = loadtextureimage(drawtexturepool, pic->name+4, false, pic->texflags, true, vid.sRGB2D); +#ifndef USE_GLES2 + if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) + R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); +#endif + } + if (pic->tex == NULL) + pic->tex = draw_generatepic(pic->name, true); + } + pic->lastusedframe = draw_frame; + return pic->tex; +} + +void Draw_Frame(void) +{ + int i; + cachepic_t *pic; + static double nextpurgetime; + if (nextpurgetime > realtime) + return; + nextpurgetime = realtime + 0.05; + for (i = 0, pic = cachepics;i < numcachepics;i++, pic++) + { + if (pic->autoload && pic->tex && pic->lastusedframe < draw_frame) + { + R_FreeTexture(pic->tex); + pic->tex = NULL; + } + } + draw_frame++; +} + +cachepic_t *Draw_NewPic(const char *picname, int width, int height, int alpha, unsigned char *pixels_bgra) +{ + int crc, hashkey; + cachepic_t *pic; + + crc = CRC_Block((unsigned char *)picname, strlen(picname)); + hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; + for (pic = cachepichash[hashkey];pic;pic = pic->chain) + if (!strcmp (picname, pic->name)) + break; + + if (pic) + { + if (pic->flags == CACHEPICFLAG_NEWPIC && pic->tex && pic->width == width && pic->height == height) + { + R_UpdateTexture(pic->tex, pixels_bgra, 0, 0, 0, width, height, 1); + return pic; + } + } + else + { + if (numcachepics == MAX_CACHED_PICS) + { + Con_Printf ("Draw_NewPic: numcachepics == MAX_CACHED_PICS\n"); + // FIXME: support NULL in callers? + return cachepics; // return the first one + } + pic = cachepics + (numcachepics++); + memset(pic, 0, sizeof(*pic)); + strlcpy (pic->name, picname, sizeof(pic->name)); + // link into list + pic->chain = cachepichash[hashkey]; + cachepichash[hashkey] = pic; + } + + pic->flags = CACHEPICFLAG_NEWPIC; // disable texflags checks in Draw_CachePic + pic->width = width; + pic->height = height; + if (pic->allow_free_tex && pic->tex) + R_FreeTexture(pic->tex); + pic->tex = R_LoadTexture2D(drawtexturepool, picname, width, height, pixels_bgra, TEXTYPE_BGRA, (alpha ? TEXF_ALPHA : 0), -1, NULL); + return pic; +} + +void Draw_FreePic(const char *picname) +{ + int crc; + int hashkey; + cachepic_t *pic; + // this doesn't really free the pic, but does free it's texture + crc = CRC_Block((unsigned char *)picname, strlen(picname)); + hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; + for (pic = cachepichash[hashkey];pic;pic = pic->chain) + { + if (!strcmp (picname, pic->name) && pic->tex) + { + R_FreeTexture(pic->tex); + pic->tex = NULL; + pic->width = 0; + pic->height = 0; + return; + } + } +} + +static float snap_to_pixel_x(float x, float roundUpAt); +extern int con_linewidth; // to force rewrapping +void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset) +{ + int i, ch; + float maxwidth; + char widthfile[MAX_QPATH]; + char *widthbuf; + fs_offset_t widthbufsize; + + if(override || !fnt->texpath[0]) + { + strlcpy(fnt->texpath, name, sizeof(fnt->texpath)); + // load the cvars when the font is FIRST loader + fnt->settings.scale = scale; + fnt->settings.voffset = voffset; + fnt->settings.antialias = r_font_antialias.integer; + fnt->settings.hinting = r_font_hinting.integer; + fnt->settings.outline = r_font_postprocess_outline.value; + fnt->settings.blur = r_font_postprocess_blur.value; + fnt->settings.shadowx = r_font_postprocess_shadow_x.value; + fnt->settings.shadowy = r_font_postprocess_shadow_y.value; + fnt->settings.shadowz = r_font_postprocess_shadow_z.value; + } + // fix bad scale + if (fnt->settings.scale <= 0) + fnt->settings.scale = 1; + + if(drawtexturepool == NULL) + return; // before gl_draw_start, so will be loaded later + + if(fnt->ft2) + { + // clear freetype font + Font_UnloadFont(fnt->ft2); + Mem_Free(fnt->ft2); + fnt->ft2 = NULL; + } + + if(fnt->req_face != -1) + { + if(!Font_LoadFont(fnt->texpath, fnt)) + Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath); + } + + fnt->tex = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex; + if(fnt->tex == r_texture_notexture) + { + for (i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + if (!fnt->fallbacks[i][0]) + break; + fnt->tex = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex; + if(fnt->tex != r_texture_notexture) + break; + } + if(fnt->tex == r_texture_notexture) + { + fnt->tex = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex; + strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile)); + } + else + dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]); + } + else + dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath); + + // unspecified width == 1 (base width) + for(ch = 0; ch < 256; ++ch) + fnt->width_of[ch] = 1; + + // FIXME load "name.width", if it fails, fill all with 1 + if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize))) + { + float extraspacing = 0; + const char *p = widthbuf; + + ch = 0; + while(ch < 256) + { + if(!COM_ParseToken_Simple(&p, false, false, true)) + return; + + switch(*com_token) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case '.': + fnt->width_of[ch] = atof(com_token) + extraspacing; + ch++; + break; + default: + if(!strcmp(com_token, "extraspacing")) + { + if(!COM_ParseToken_Simple(&p, false, false, true)) + return; + extraspacing = atof(com_token); + } + else if(!strcmp(com_token, "scale")) + { + if(!COM_ParseToken_Simple(&p, false, false, true)) + return; + fnt->settings.scale = atof(com_token); + } + else + { + Con_Printf("Warning: skipped unknown font property %s\n", com_token); + if(!COM_ParseToken_Simple(&p, false, false, true)) + return; + } + break; + } + } + + Mem_Free(widthbuf); + } + + if(fnt->ft2) + { + for (i = 0; i < MAX_FONT_SIZES; ++i) + { + ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i); + if (!map) + break; + for(ch = 0; ch < 256; ++ch) + map->width_of[ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size); + } + } + + maxwidth = fnt->width_of[0]; + for(i = 1; i < 256; ++i) + maxwidth = max(maxwidth, fnt->width_of[i]); + fnt->maxwidth = maxwidth; + + // fix up maxwidth for overlap + fnt->maxwidth *= fnt->settings.scale; + + if(fnt == FONT_CONSOLE) + con_linewidth = -1; // rewrap console in next frame +} + +extern cvar_t developer_font; +dp_font_t *FindFont(const char *title, qboolean allocate_new) +{ + int i, oldsize; + + // find font + for(i = 0; i < dp_fonts.maxsize; ++i) + if(!strcmp(dp_fonts.f[i].title, title)) + return &dp_fonts.f[i]; + // if not found - try allocate + if (allocate_new) + { + // find any font with empty title + for(i = 0; i < dp_fonts.maxsize; ++i) + { + if(!strcmp(dp_fonts.f[i].title, "")) + { + strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title)); + return &dp_fonts.f[i]; + } + } + // if no any 'free' fonts - expand buffer + oldsize = dp_fonts.maxsize; + dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND; + if (developer_font.integer) + Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize); + dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize); + // relink ft2 structures + for(i = 0; i < oldsize; ++i) + if (dp_fonts.f[i].ft2) + dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings; + // register a font in first expanded slot + strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title)); + return &dp_fonts.f[oldsize]; + } + return NULL; +} + +static float snap_to_pixel_x(float x, float roundUpAt) +{ + float pixelpos = x * vid.width / vid_conwidth.value; + int snap = (int) pixelpos; + if (pixelpos - snap >= roundUpAt) ++snap; + return ((float)snap * vid_conwidth.value / vid.width); + /* + x = (int)(x * vid.width / vid_conwidth.value); + x = (x * vid_conwidth.value / vid.width); + return x; + */ +} + +static float snap_to_pixel_y(float y, float roundUpAt) +{ + float pixelpos = y * vid.height / vid_conheight.value; + int snap = (int) pixelpos; + if (pixelpos - snap > roundUpAt) ++snap; + return ((float)snap * vid_conheight.value / vid.height); + /* + y = (int)(y * vid.height / vid_conheight.value); + y = (y * vid_conheight.value / vid.height); + return y; + */ +} + +static void LoadFont_f(void) +{ + dp_font_t *f; + int i, sizes; + const char *filelist, *c, *cm; + float sz, scale, voffset; + char mainfont[MAX_QPATH]; + + if(Cmd_Argc() < 2) + { + Con_Printf("Available font commands:\n"); + for(i = 0; i < dp_fonts.maxsize; ++i) + if (dp_fonts.f[i].title[0]) + Con_Printf(" loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title); + Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n" + "can specify multiple fonts and faces\n" + "Like this: gfx/vera-sans:2,gfx/fallback:1\n" + "to load face 2 of the font gfx/vera-sans and use face 1\n" + "of gfx/fallback as fallback font.\n" + "You can also specify a list of font sizes to load, like this:\n" + "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n" + "In many cases, 8 12 16 24 32 should be a good choice.\n" + "custom switches:\n" + " scale x : scale all characters by this amount when rendering (doesnt change line height)\n" + " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n" + ); + return; + } + f = FindFont(Cmd_Argv(1), true); + if(f == NULL) + { + Con_Printf("font function not found\n"); + return; + } + + if(Cmd_Argc() < 3) + filelist = "gfx/conchars"; + else + filelist = Cmd_Argv(2); + + memset(f->fallbacks, 0, sizeof(f->fallbacks)); + memset(f->fallback_faces, 0, sizeof(f->fallback_faces)); + + // first font is handled "normally" + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->req_face = atoi(c+1); + else + { + f->req_face = 0; + c = cm; + } + + if(!c || (c - filelist) > MAX_QPATH) + strlcpy(mainfont, filelist, sizeof(mainfont)); + else + { + memcpy(mainfont, filelist, c - filelist); + mainfont[c - filelist] = 0; + } + + for(i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + c = strchr(filelist, ','); + if(!c) + break; + filelist = c + 1; + if(!*filelist) + break; + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->fallback_faces[i] = atoi(c+1); + else + { + f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index + c = cm; + } + if(!c || (c-filelist) > MAX_QPATH) + { + strlcpy(f->fallbacks[i], filelist, sizeof(mainfont)); + } + else + { + memcpy(f->fallbacks[i], filelist, c - filelist); + f->fallbacks[i][c - filelist] = 0; + } + } + + // for now: by default load only one size: the default size + f->req_sizes[0] = 0; + for(i = 1; i < MAX_FONT_SIZES; ++i) + f->req_sizes[i] = -1; + + scale = 1; + voffset = 0; + if(Cmd_Argc() >= 4) + { + for(sizes = 0, i = 3; i < Cmd_Argc(); ++i) + { + // special switches + if (!strcmp(Cmd_Argv(i), "scale")) + { + i++; + if (i < Cmd_Argc()) + scale = atof(Cmd_Argv(i)); + continue; + } + if (!strcmp(Cmd_Argv(i), "voffset")) + { + i++; + if (i < Cmd_Argc()) + voffset = atof(Cmd_Argv(i)); + continue; + } + + if (sizes == -1) + continue; // no slot for other sizes + + // parse one of sizes + sz = atof(Cmd_Argv(i)); + if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes + { + // search for duplicated sizes + int j; + for (j=0; jreq_sizes[j] == sz) + break; + if (j != sizes) + continue; // sz already in req_sizes, don't add it again + + if (sizes == MAX_FONT_SIZES) + { + Con_Printf("Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES); + sizes = -1; + continue; + } + f->req_sizes[sizes] = sz; + sizes++; + } + } + } + + LoadFont(true, mainfont, f, scale, voffset); +} + +/* +=============== +Draw_Init +=============== +*/ +static void gl_draw_start(void) +{ + int i; + char vabuf[1024]; + drawtexturepool = R_AllocTexturePool(); + + numcachepics = 0; + memset(cachepichash, 0, sizeof(cachepichash)); + + font_start(); + + // load default font textures + for(i = 0; i < dp_fonts.maxsize; ++i) + if (dp_fonts.f[i].title[0]) + LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0); + + // draw the loading screen so people have something to see in the newly opened window + SCR_UpdateLoadingScreen(true, true); +} + +static void gl_draw_shutdown(void) +{ + font_shutdown(); + + R_FreeTexturePool(&drawtexturepool); + + numcachepics = 0; + memset(cachepichash, 0, sizeof(cachepichash)); +} + +static void gl_draw_newmap(void) +{ + font_newmap(); +} + +void GL_Draw_Init (void) +{ + int i, j; + + Cvar_RegisterVariable(&r_font_postprocess_blur); + Cvar_RegisterVariable(&r_font_postprocess_outline); + Cvar_RegisterVariable(&r_font_postprocess_shadow_x); + Cvar_RegisterVariable(&r_font_postprocess_shadow_y); + Cvar_RegisterVariable(&r_font_postprocess_shadow_z); + Cvar_RegisterVariable(&r_font_hinting); + Cvar_RegisterVariable(&r_font_antialias); + Cvar_RegisterVariable(&r_textshadow); + Cvar_RegisterVariable(&r_textbrightness); + Cvar_RegisterVariable(&r_textcontrast); + Cvar_RegisterVariable(&r_nearest_2d); + Cvar_RegisterVariable(&r_nearest_conchars); + + // allocate fonts storage + fonts_mempool = Mem_AllocPool("FONTS", 0, NULL); + dp_fonts.maxsize = MAX_FONTS; + dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize); + memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize); + + // assign starting font names + strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title)); + strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath)); + strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title)); + strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title)); + strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title)); + strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title)); + strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title)); + strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title)); + strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title)); + for(i = 0, j = 0; i < MAX_USERFONTS; ++i) + if(!FONT_USER(i)->title[0]) + dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++); + + Cmd_AddCommand ("loadfont",LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions"); + R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL); +} + +static void _DrawQ_Setup(void) // see R_ResetViewRendering2D +{ + if (r_refdef.draw2dstage == 1) + return; + r_refdef.draw2dstage = 1; + + R_ResetViewRendering2D_Common(0, NULL, NULL, vid_conwidth.integer, vid_conheight.integer); +} + +qboolean r_draw2d_force = false; +static void _DrawQ_SetupAndProcessDrawFlag(int flags, cachepic_t *pic, float alpha) +{ + _DrawQ_Setup(); + if(!r_draw2d.integer && !r_draw2d_force) + return; + DrawQ_ProcessDrawFlag(flags, (alpha < 1) || (pic && pic->hasalpha)); +} +void DrawQ_ProcessDrawFlag(int flags, qboolean alpha) +{ + if(flags == DRAWFLAG_ADDITIVE) + { + GL_DepthMask(false); + GL_BlendFunc(alpha ? GL_SRC_ALPHA : GL_ONE, GL_ONE); + } + else if(flags == DRAWFLAG_MODULATE) + { + GL_DepthMask(false); + GL_BlendFunc(GL_DST_COLOR, GL_ZERO); + } + else if(flags == DRAWFLAG_2XMODULATE) + { + GL_DepthMask(false); + GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + } + else if(flags == DRAWFLAG_SCREEN) + { + GL_DepthMask(false); + GL_BlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE); + } + else if(alpha) + { + GL_DepthMask(false); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + else + { + GL_DepthMask(true); + GL_BlendFunc(GL_ONE, GL_ZERO); + } +} + +void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags) +{ + float floats[36]; + + _DrawQ_SetupAndProcessDrawFlag(flags, pic, alpha); + if(!r_draw2d.integer && !r_draw2d_force) + return; + +// R_Mesh_ResetTextureState(); + floats[12] = 0.0f;floats[13] = 0.0f; + floats[14] = 1.0f;floats[15] = 0.0f; + floats[16] = 1.0f;floats[17] = 1.0f; + floats[18] = 0.0f;floats[19] = 1.0f; + floats[20] = floats[24] = floats[28] = floats[32] = red; + floats[21] = floats[25] = floats[29] = floats[33] = green; + floats[22] = floats[26] = floats[30] = floats[34] = blue; + floats[23] = floats[27] = floats[31] = floats[35] = alpha; + if (pic) + { + if (width == 0) + width = pic->width; + if (height == 0) + height = pic->height; + R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); + +#if 0 + // AK07: lets be texel correct on the corners + { + float horz_offset = 0.5f / pic->width; + float vert_offset = 0.5f / pic->height; + + floats[12] = 0.0f + horz_offset;floats[13] = 0.0f + vert_offset; + floats[14] = 1.0f - horz_offset;floats[15] = 0.0f + vert_offset; + floats[16] = 1.0f - horz_offset;floats[17] = 1.0f - vert_offset; + floats[18] = 0.0f + horz_offset;floats[19] = 1.0f - vert_offset; + } +#endif + } + else + R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); + + floats[2] = floats[5] = floats[8] = floats[11] = 0; + floats[0] = floats[9] = x; + floats[1] = floats[4] = y; + floats[3] = floats[6] = x + width; + floats[7] = floats[10] = y + height; + + R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags) +{ + float floats[36]; + float af = DEG2RAD(-angle); // forward + float ar = DEG2RAD(-angle + 90); // right + float sinaf = sin(af); + float cosaf = cos(af); + float sinar = sin(ar); + float cosar = cos(ar); + + _DrawQ_SetupAndProcessDrawFlag(flags, pic, alpha); + if(!r_draw2d.integer && !r_draw2d_force) + return; + +// R_Mesh_ResetTextureState(); + if (pic) + { + if (width == 0) + width = pic->width; + if (height == 0) + height = pic->height; + R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); + } + else + R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); + + floats[2] = floats[5] = floats[8] = floats[11] = 0; + +// top left + floats[0] = x - cosaf*org_x - cosar*org_y; + floats[1] = y - sinaf*org_x - sinar*org_y; + +// top right + floats[3] = x + cosaf*(width-org_x) - cosar*org_y; + floats[4] = y + sinaf*(width-org_x) - sinar*org_y; + +// bottom right + floats[6] = x + cosaf*(width-org_x) + cosar*(height-org_y); + floats[7] = y + sinaf*(width-org_x) + sinar*(height-org_y); + +// bottom left + floats[9] = x - cosaf*org_x + cosar*(height-org_y); + floats[10] = y - sinaf*org_x + sinar*(height-org_y); + + floats[12] = 0.0f;floats[13] = 0.0f; + floats[14] = 1.0f;floats[15] = 0.0f; + floats[16] = 1.0f;floats[17] = 1.0f; + floats[18] = 0.0f;floats[19] = 1.0f; + floats[20] = floats[24] = floats[28] = floats[32] = red; + floats[21] = floats[25] = floats[29] = floats[33] = green; + floats[22] = floats[26] = floats[30] = floats[34] = blue; + floats[23] = floats[27] = floats[31] = floats[35] = alpha; + + R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags) +{ + float floats[36]; + + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, alpha); + if(!r_draw2d.integer && !r_draw2d_force) + return; + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); + + floats[2] = floats[5] = floats[8] = floats[11] = 0; + floats[0] = floats[9] = x; + floats[1] = floats[4] = y; + floats[3] = floats[6] = x + width; + floats[7] = floats[10] = y + height; + floats[12] = 0.0f;floats[13] = 0.0f; + floats[14] = 1.0f;floats[15] = 0.0f; + floats[16] = 1.0f;floats[17] = 1.0f; + floats[18] = 0.0f;floats[19] = 1.0f; + floats[20] = floats[24] = floats[28] = floats[32] = red; + floats[21] = floats[25] = floats[29] = floats[33] = green; + floats[22] = floats[26] = floats[30] = floats[34] = blue; + floats[23] = floats[27] = floats[31] = floats[35] = alpha; + + R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +/// color tag printing +static const vec4_t string_colors[] = +{ + // Quake3 colors + // LordHavoc: why on earth is cyan before magenta in Quake3? + // LordHavoc: note: Doom3 uses white for [0] and [7] + {0.0, 0.0, 0.0, 1.0}, // black + {1.0, 0.0, 0.0, 1.0}, // red + {0.0, 1.0, 0.0, 1.0}, // green + {1.0, 1.0, 0.0, 1.0}, // yellow + {0.0, 0.0, 1.0, 1.0}, // blue + {0.0, 1.0, 1.0, 1.0}, // cyan + {1.0, 0.0, 1.0, 1.0}, // magenta + {1.0, 1.0, 1.0, 1.0}, // white + // [515]'s BX_COLOREDTEXT extension + {1.0, 1.0, 1.0, 0.5}, // half transparent + {0.5, 0.5, 0.5, 1.0} // half brightness + // Black's color table + //{1.0, 1.0, 1.0, 1.0}, + //{1.0, 0.0, 0.0, 1.0}, + //{0.0, 1.0, 0.0, 1.0}, + //{0.0, 0.0, 1.0, 1.0}, + //{1.0, 1.0, 0.0, 1.0}, + //{0.0, 1.0, 1.0, 1.0}, + //{1.0, 0.0, 1.0, 1.0}, + //{0.1, 0.1, 0.1, 1.0} +}; + +#define STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t)) + +static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qboolean shadow) +{ + float C = r_textcontrast.value; + float B = r_textbrightness.value; + if (colorindex & 0x10000) // that bit means RGB color + { + color[0] = ((colorindex >> 12) & 0xf) / 15.0; + color[1] = ((colorindex >> 8) & 0xf) / 15.0; + color[2] = ((colorindex >> 4) & 0xf) / 15.0; + color[3] = (colorindex & 0xf) / 15.0; + } + else + Vector4Copy(string_colors[colorindex], color); + Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a); + if (shadow) + { + float shadowalpha = (color[0]+color[1]+color[2]) * 0.8; + Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1)); + } +} + +// NOTE: this function always draws exactly one character if maxwidth <= 0 +float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) +{ + const char *text_start = text; + int colorindex = STRING_COLOR_DEFAULT; + size_t i; + float x = 0; + Uchar ch, mapch, nextch; + Uchar prevch = 0; // used for kerning + int tempcolorindex; + float kx; + int map_index = 0; + size_t bytes_left; + ft2_font_map_t *fontmap = NULL; + ft2_font_map_t *map = NULL; + //ft2_font_map_t *prevmap = NULL; + ft2_font_t *ft2 = fnt->ft2; + // float ftbase_x; + qboolean snap = true; + qboolean least_one = false; + float dw; // display w + //float dh; // display h + const float *width_of; + + if (!h) h = w; + if (!h) { + w = h = 1; + snap = false; + } + // do this in the end + w *= fnt->settings.scale; + h *= fnt->settings.scale; + + // find the most fitting size: + if (ft2 != NULL) + { + if (snap) + map_index = Font_IndexForSize(ft2, h, &w, &h); + else + map_index = Font_IndexForSize(ft2, h, NULL, NULL); + fontmap = Font_MapForIndex(ft2, map_index); + } + + dw = w * sw; + //dh = h * sh; + + if (*maxlen < 1) + *maxlen = 1<<30; + + if (!outcolor || *outcolor == -1) + colorindex = STRING_COLOR_DEFAULT; + else + colorindex = *outcolor; + + // maxwidth /= fnt->scale; // w and h are multiplied by it already + // ftbase_x = snap_to_pixel_x(0); + + if(maxwidth <= 0) + { + least_one = true; + maxwidth = -maxwidth; + } + + //if (snap) + // x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway + + if (fontmap) + width_of = fontmap->width_of; + else + width_of = fnt->width_of; + + i = 0; + while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text) + { + size_t i0 = i; + nextch = ch = u8_getnchar(text, &text, bytes_left); + i = text - text_start; + if (!ch) + break; + if (ch == ' ' && !fontmap) + { + if(!least_one || i0) // never skip the first character + if(x + width_of[(int) ' '] * dw > maxwidth) + { + i = i0; + break; // oops, can't draw this + } + x += width_of[(int) ' '] * dw; + continue; + } + // i points to the char after ^ + if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen) + { + ch = *text; // colors are ascii, so no u8_ needed + if (ch <= '9' && ch >= '0') // ^[0-9] found + { + colorindex = ch - '0'; + ++text; + ++i; + continue; + } + // i points to the char after ^... + // i+3 points to 3 in ^x123 + // i+3 == *maxlen would mean that char is missing + else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found + { + // building colorindex... + ch = tolower(text[1]); + tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000 + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12; + else tempcolorindex = 0; + if (tempcolorindex) + { + ch = tolower(text[2]); + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8; + else tempcolorindex = 0; + if (tempcolorindex) + { + ch = tolower(text[3]); + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4; + else tempcolorindex = 0; + if (tempcolorindex) + { + colorindex = tempcolorindex | 0xf; + // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa) + i+=4; + text += 4; + continue; + } + } + } + } + else if (ch == STRING_COLOR_TAG) // ^^ found, ignore the first ^ and go to print the second + { + i++; + text++; + } + i--; + } + ch = nextch; + + if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF)) + { + if (ch > 0xE000) + ch -= 0xE000; + if (ch > 0xFF) + continue; + if (fontmap) + map = ft2_oldstyle_map; + prevch = 0; + if(!least_one || i0) // never skip the first character + if(x + width_of[ch] * dw > maxwidth) + { + i = i0; + break; // oops, can't draw this + } + x += width_of[ch] * dw; + } else { + if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP) + { + map = FontMap_FindForChar(fontmap, ch); + if (!map) + { + if (!Font_LoadMapForIndex(ft2, map_index, ch, &map)) + break; + if (!map) + break; + } + } + mapch = ch - map->start; + if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL)) + x += kx * dw; + x += map->glyphs[mapch].advance_x * dw; + //prevmap = map; + prevch = ch; + } + } + + *maxlen = i; + + if (outcolor) + *outcolor = colorindex; + + return x; +} + +float DrawQ_Color[4]; +float DrawQ_String_Scale(float startx, float starty, const char *text, size_t maxlen, float w, float h, float sw, float sh, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + int shadow, colorindex = STRING_COLOR_DEFAULT; + size_t i; + float x = startx, y, s, t, u, v, thisw; + float *av, *at, *ac; + int batchcount; + static float vertex3f[QUADELEMENTS_MAXQUADS*4*3]; + static float texcoord2f[QUADELEMENTS_MAXQUADS*4*2]; + static float color4f[QUADELEMENTS_MAXQUADS*4*4]; + Uchar ch, mapch, nextch; + Uchar prevch = 0; // used for kerning + int tempcolorindex; + int map_index = 0; + //ft2_font_map_t *prevmap = NULL; // the previous map + ft2_font_map_t *map = NULL; // the currently used map + ft2_font_map_t *fontmap = NULL; // the font map for the size + float ftbase_y; + const char *text_start = text; + float kx, ky; + ft2_font_t *ft2 = fnt->ft2; + qboolean snap = true; + float pix_x, pix_y; + size_t bytes_left; + float dw, dh; + const float *width_of; + + int tw, th; + tw = R_TextureWidth(fnt->tex); + th = R_TextureHeight(fnt->tex); + + if (!h) h = w; + if (!h) { + h = w = 1; + snap = false; + } + + starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset + w *= fnt->settings.scale; + h *= fnt->settings.scale; + + if (ft2 != NULL) + { + if (snap) + map_index = Font_IndexForSize(ft2, h, &w, &h); + else + map_index = Font_IndexForSize(ft2, h, NULL, NULL); + fontmap = Font_MapForIndex(ft2, map_index); + } + + dw = w * sw; + dh = h * sh; + + // draw the font at its baseline when using freetype + //ftbase_x = 0; + ftbase_y = dh * (4.5/6.0); + + if (maxlen < 1) + maxlen = 1<<30; + + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, 0); + if(!r_draw2d.integer && !r_draw2d_force) + return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000); + +// R_Mesh_ResetTextureState(); + if (!fontmap) + R_Mesh_TexBind(0, fnt->tex); + R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); + + ac = color4f; + at = texcoord2f; + av = vertex3f; + batchcount = 0; + + //ftbase_x = snap_to_pixel_x(ftbase_x); + if(snap) + { + startx = snap_to_pixel_x(startx, 0.4); + starty = snap_to_pixel_y(starty, 0.4); + ftbase_y = snap_to_pixel_y(ftbase_y, 0.3); + } + + pix_x = vid.width / vid_conwidth.value; + pix_y = vid.height / vid_conheight.value; + + if (fontmap) + width_of = fontmap->width_of; + else + width_of = fnt->width_of; + + for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--) + { + prevch = 0; + text = text_start; + + if (!outcolor || *outcolor == -1) + colorindex = STRING_COLOR_DEFAULT; + else + colorindex = *outcolor; + + DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); + + x = startx; + y = starty; + /* + if (shadow) + { + x += r_textshadow.value * vid.width / vid_conwidth.value; + y += r_textshadow.value * vid.height / vid_conheight.value; + } + */ + while (((bytes_left = maxlen - (text - text_start)) > 0) && *text) + { + nextch = ch = u8_getnchar(text, &text, bytes_left); + i = text - text_start; + if (!ch) + break; + if (ch == ' ' && !fontmap) + { + x += width_of[(int) ' '] * dw; + continue; + } + if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen) + { + ch = *text; // colors are ascii, so no u8_ needed + if (ch <= '9' && ch >= '0') // ^[0-9] found + { + colorindex = ch - '0'; + DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); + ++text; + ++i; + continue; + } + else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found + { + // building colorindex... + ch = tolower(text[1]); + tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000 + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12; + else tempcolorindex = 0; + if (tempcolorindex) + { + ch = tolower(text[2]); + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8; + else tempcolorindex = 0; + if (tempcolorindex) + { + ch = tolower(text[3]); + if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4; + else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4; + else tempcolorindex = 0; + if (tempcolorindex) + { + colorindex = tempcolorindex | 0xf; + // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa) + //Con_Printf("^1colorindex:^7 %x\n", colorindex); + DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); + i+=4; + text+=4; + continue; + } + } + } + } + else if (ch == STRING_COLOR_TAG) + { + i++; + text++; + } + i--; + } + // get the backup + ch = nextch; + // using a value of -1 for the oldstyle map because NULL means uninitialized... + // this way we don't need to rebind fnt->tex for every old-style character + // E000..E0FF: emulate old-font characters (to still have smileys and such available) + if (shadow) + { + x += 1.0/pix_x * r_textshadow.value; + y += 1.0/pix_y * r_textshadow.value; + } + if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF)) + { + if (ch >= 0xE000) + ch -= 0xE000; + if (ch > 0xFF) + goto out; + if (fontmap) + { + if (map != ft2_oldstyle_map) + { + if (batchcount) + { + // switching from freetype to non-freetype rendering + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); + map = ft2_oldstyle_map; + } + } + prevch = 0; + //num = (unsigned char) text[i]; + //thisw = fnt->width_of[num]; + thisw = fnt->width_of[ch]; + // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering + if (r_nearest_conchars.integer) + { + s = (ch & 15)*0.0625f; + t = (ch >> 4)*0.0625f; + u = 0.0625f * thisw; + v = 0.0625f; + } + else + { + s = (ch & 15)*0.0625f + (0.5f / tw); + t = (ch >> 4)*0.0625f + (0.5f / th); + u = 0.0625f * thisw - (1.0f / tw); + v = 0.0625f - (1.0f / th); + } + ac[ 0] = DrawQ_Color[0];ac[ 1] = DrawQ_Color[1];ac[ 2] = DrawQ_Color[2];ac[ 3] = DrawQ_Color[3]; + ac[ 4] = DrawQ_Color[0];ac[ 5] = DrawQ_Color[1];ac[ 6] = DrawQ_Color[2];ac[ 7] = DrawQ_Color[3]; + ac[ 8] = DrawQ_Color[0];ac[ 9] = DrawQ_Color[1];ac[10] = DrawQ_Color[2];ac[11] = DrawQ_Color[3]; + ac[12] = DrawQ_Color[0];ac[13] = DrawQ_Color[1];ac[14] = DrawQ_Color[2];ac[15] = DrawQ_Color[3]; + at[ 0] = s ; at[ 1] = t ; + at[ 2] = s+u ; at[ 3] = t ; + at[ 4] = s+u ; at[ 5] = t+v ; + at[ 6] = s ; at[ 7] = t+v ; + av[ 0] = x ; av[ 1] = y ; av[ 2] = 10; + av[ 3] = x+dw*thisw ; av[ 4] = y ; av[ 5] = 10; + av[ 6] = x+dw*thisw ; av[ 7] = y+dh ; av[ 8] = 10; + av[ 9] = x ; av[10] = y+dh ; av[11] = 10; + ac += 16; + at += 8; + av += 12; + batchcount++; + if (batchcount >= QUADELEMENTS_MAXQUADS) + { + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + x += width_of[ch] * dw; + } else { + if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP) + { + // new charmap - need to render + if (batchcount) + { + // we need a different character map, render what we currently have: + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + // find the new map + map = FontMap_FindForChar(fontmap, ch); + if (!map) + { + if (!Font_LoadMapForIndex(ft2, map_index, ch, &map)) + { + shadow = -1; + break; + } + if (!map) + { + // this shouldn't happen + shadow = -1; + break; + } + } + R_SetupShader_Generic(map->pic->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); + } + + mapch = ch - map->start; + thisw = map->glyphs[mapch].advance_x; + + //x += ftbase_x; + y += ftbase_y; + if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky)) + { + x += kx * dw; + y += ky * dh; + } + else + kx = ky = 0; + ac[ 0] = DrawQ_Color[0]; ac[ 1] = DrawQ_Color[1]; ac[ 2] = DrawQ_Color[2]; ac[ 3] = DrawQ_Color[3]; + ac[ 4] = DrawQ_Color[0]; ac[ 5] = DrawQ_Color[1]; ac[ 6] = DrawQ_Color[2]; ac[ 7] = DrawQ_Color[3]; + ac[ 8] = DrawQ_Color[0]; ac[ 9] = DrawQ_Color[1]; ac[10] = DrawQ_Color[2]; ac[11] = DrawQ_Color[3]; + ac[12] = DrawQ_Color[0]; ac[13] = DrawQ_Color[1]; ac[14] = DrawQ_Color[2]; ac[15] = DrawQ_Color[3]; + at[0] = map->glyphs[mapch].txmin; at[1] = map->glyphs[mapch].tymin; + at[2] = map->glyphs[mapch].txmax; at[3] = map->glyphs[mapch].tymin; + at[4] = map->glyphs[mapch].txmax; at[5] = map->glyphs[mapch].tymax; + at[6] = map->glyphs[mapch].txmin; at[7] = map->glyphs[mapch].tymax; + av[ 0] = x + dw * map->glyphs[mapch].vxmin; av[ 1] = y + dh * map->glyphs[mapch].vymin; av[ 2] = 10; + av[ 3] = x + dw * map->glyphs[mapch].vxmax; av[ 4] = y + dh * map->glyphs[mapch].vymin; av[ 5] = 10; + av[ 6] = x + dw * map->glyphs[mapch].vxmax; av[ 7] = y + dh * map->glyphs[mapch].vymax; av[ 8] = 10; + av[ 9] = x + dw * map->glyphs[mapch].vxmin; av[10] = y + dh * map->glyphs[mapch].vymax; av[11] = 10; + //x -= ftbase_x; + y -= ftbase_y; + + x += thisw * dw; + ac += 16; + at += 8; + av += 12; + batchcount++; + if (batchcount >= QUADELEMENTS_MAXQUADS) + { + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + + //prevmap = map; + prevch = ch; + } +out: + if (shadow) + { + x -= 1.0/pix_x * r_textshadow.value; + y -= 1.0/pix_y * r_textshadow.value; + } + } + } + if (batchcount > 0) + { + R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); + } + + if (outcolor) + *outcolor = colorindex; + + // note: this relies on the proper text (not shadow) being drawn last + return x; +} + +float DrawQ_String(float startx, float starty, const char *text, size_t maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt); +} + +float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) +{ + return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth); +} + +float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000); +} + +float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth) +{ + return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth); +} + +#if 0 +// not used +// no ^xrgb management +static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qboolean ignorecolorcodes, int *outcolor) +{ + int color, numchars = 0; + char *outputend2c = output2c + maxoutchars - 2; + if (!outcolor || *outcolor == -1) + color = STRING_COLOR_DEFAULT; + else + color = *outcolor; + if (!maxreadchars) + maxreadchars = 1<<30; + textend = text + maxreadchars; + while (text != textend && *text) + { + if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend) + { + if (text[1] == STRING_COLOR_TAG) + text++; + else if (text[1] >= '0' && text[1] <= '9') + { + color = text[1] - '0'; + text += 2; + continue; + } + } + if (output2c >= outputend2c) + break; + *output2c++ = *text++; + *output2c++ = color; + numchars++; + } + output2c[0] = output2c[1] = 0; + if (outcolor) + *outcolor = color; + return numchars; +} +#endif + +void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags) +{ + float floats[36]; + + _DrawQ_SetupAndProcessDrawFlag(flags, pic, a1*a2*a3*a4); + if(!r_draw2d.integer && !r_draw2d_force) + return; + +// R_Mesh_ResetTextureState(); + if (pic) + { + if (width == 0) + width = pic->width; + if (height == 0) + height = pic->height; + R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & (DRAWFLAGS_BLEND | DRAWFLAG_NOGAMMA)) ? false : true, true, false); + } + else + R_SetupShader_Generic_NoTexture((flags & (DRAWFLAGS_BLEND | DRAWFLAG_NOGAMMA)) ? false : true, true); + + floats[2] = floats[5] = floats[8] = floats[11] = 0; + floats[0] = floats[9] = x; + floats[1] = floats[4] = y; + floats[3] = floats[6] = x + width; + floats[7] = floats[10] = y + height; + floats[12] = s1;floats[13] = t1; + floats[14] = s2;floats[15] = t2; + floats[16] = s4;floats[17] = t4; + floats[18] = s3;floats[19] = t3; + floats[20] = r1;floats[21] = g1;floats[22] = b1;floats[23] = a1; + floats[24] = r2;floats[25] = g2;floats[26] = b2;floats[27] = a2; + floats[28] = r4;floats[29] = g4;floats[30] = b4;floats[31] = a4; + floats[32] = r3;floats[33] = g3;floats[34] = b3;floats[35] = a3; + + R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +void DrawQ_Mesh (drawqueuemesh_t *mesh, int flags, qboolean hasalpha) +{ + _DrawQ_Setup(); + if(!r_draw2d.integer && !r_draw2d_force) + return; + DrawQ_ProcessDrawFlag(flags, hasalpha); + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(mesh->texture, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); + + R_Mesh_PrepareVertices_Generic_Arrays(mesh->num_vertices, mesh->data_vertex3f, mesh->data_color4f, mesh->data_texcoord2f); + R_Mesh_Draw(0, mesh->num_vertices, 0, mesh->num_triangles, mesh->data_element3i, NULL, 0, mesh->data_element3s, NULL, 0); +} + +void DrawQ_LineLoop (drawqueuemesh_t *mesh, int flags) +{ + int num; + + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, 1); + if(!r_draw2d.integer && !r_draw2d_force) + return; + + GL_Color(1,1,1,1); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: +#ifndef USE_GLES2 + CHECKGLERROR + qglBegin(GL_LINE_LOOP); + for (num = 0;num < mesh->num_vertices;num++) + { + if (mesh->data_color4f) + GL_Color(mesh->data_color4f[num*4+0], mesh->data_color4f[num*4+1], mesh->data_color4f[num*4+2], mesh->data_color4f[num*4+3]); + qglVertex2f(mesh->data_vertex3f[num*3+0], mesh->data_vertex3f[num*3+1]); + } + qglEnd(); + CHECKGLERROR +#endif + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + return; + } +} + +//[515]: this is old, delete +void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags) +{ + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, alpha); + if(!r_draw2d.integer && !r_draw2d_force) + return; + + R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: +#ifndef USE_GLES2 + CHECKGLERROR + + //qglLineWidth(width);CHECKGLERROR + + GL_Color(r,g,b,alpha); + CHECKGLERROR + qglBegin(GL_LINES); + qglVertex2f(x1, y1); + qglVertex2f(x2, y2); + qglEnd(); + CHECKGLERROR +#endif + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + return; + } +} + +void DrawQ_Lines (float width, int numlines, int flags, qboolean hasalpha) +{ + _DrawQ_SetupAndProcessDrawFlag(flags, NULL, hasalpha ? 0.5f : 1.0f); + + if(!r_draw2d.integer && !r_draw2d_force) + return; + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + CHECKGLERROR + + R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); + + //qglLineWidth(width);CHECKGLERROR + + CHECKGLERROR + qglDrawArrays(GL_LINES, 0, numlines*2); + CHECKGLERROR + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + return; + } +} + +void DrawQ_SetClipArea(float x, float y, float width, float height) +{ + int ix, iy, iw, ih; + _DrawQ_Setup(); + + // We have to convert the con coords into real coords + // OGL uses top to bottom + ix = (int)(0.5 + x * ((float)vid.width / vid_conwidth.integer)); + iy = (int)(0.5 + y * ((float) vid.height / vid_conheight.integer)); + iw = (int)(0.5 + (x+width) * ((float)vid.width / vid_conwidth.integer)) - ix; + ih = (int)(0.5 + (y+height) * ((float) vid.height / vid_conheight.integer)) - iy; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_SOFT: + GL_Scissor(ix, vid.height - iy - ih, iw, ih); + break; + case RENDERPATH_D3D9: + GL_Scissor(ix, iy, iw, ih); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + + GL_ScissorTest(true); +} + +void DrawQ_ResetClipArea(void) +{ + _DrawQ_Setup(); + GL_ScissorTest(false); +} + +void DrawQ_Finish(void) +{ + r_refdef.draw2dstage = 0; +} + +void DrawQ_RecalcView(void) +{ + if(r_refdef.draw2dstage) + r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again +} + +static float blendvertex3f[9] = {-5000, -5000, 10, 10000, -5000, 10, -5000, 10000, 10}; +void R_DrawGamma(void) +{ + float c[4]; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_GLES2: + if (vid_usinghwgamma || v_glslgamma.integer) + return; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + if (vid_usinghwgamma) + return; + break; + case RENDERPATH_GLES1: + case RENDERPATH_SOFT: + return; + } + // all the blends ignore depth +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic_NoTexture(true, true); + GL_DepthMask(true); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(false); + + // interpretation of brightness and contrast: + // color range := brightness .. (brightness + contrast) + // i.e. "c *= contrast; c += brightness" + // plausible values for brightness thus range from -contrast to 1 + + // apply pre-brightness (subtractive brightness, for where contrast was >= 1) + if (vid.support.ext_blend_subtract) + { + if (v_color_enable.integer) + { + c[0] = -v_color_black_r.value / v_color_white_r.value; + c[1] = -v_color_black_g.value / v_color_white_g.value; + c[2] = -v_color_black_b.value / v_color_white_b.value; + } + else + c[0] = c[1] = c[2] = -v_brightness.value / v_contrast.value; + if (c[0] >= 0.01f || c[1] >= 0.01f || c[2] >= 0.01f) + { + // need SUBTRACTIVE blending to do this! + GL_BlendEquationSubtract(true); + GL_BlendFunc(GL_ONE, GL_ONE); + GL_Color(c[0], c[1], c[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + GL_BlendEquationSubtract(false); + } + } + + // apply contrast + if (v_color_enable.integer) + { + c[0] = v_color_white_r.value; + c[1] = v_color_white_g.value; + c[2] = v_color_white_b.value; + } + else + c[0] = c[1] = c[2] = v_contrast.value; + if (c[0] >= 1.003f || c[1] >= 1.003f || c[2] >= 1.003f) + { + GL_BlendFunc(GL_DST_COLOR, GL_ONE); + while (c[0] >= 1.003f || c[1] >= 1.003f || c[2] >= 1.003f) + { + float cc[4]; + cc[0] = bound(0, c[0] - 1, 1); + cc[1] = bound(0, c[1] - 1, 1); + cc[2] = bound(0, c[2] - 1, 1); + GL_Color(cc[0], cc[1], cc[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + c[0] /= 1 + cc[0]; + c[1] /= 1 + cc[1]; + c[2] /= 1 + cc[2]; + } + } + if (c[0] <= 0.997f || c[1] <= 0.997f || c[2] <= 0.997f) + { + GL_BlendFunc(GL_DST_COLOR, GL_ZERO); + GL_Color(c[0], c[1], c[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } + + // apply post-brightness (additive brightness, for where contrast was <= 1) + if (v_color_enable.integer) + { + c[0] = v_color_black_r.value; + c[1] = v_color_black_g.value; + c[2] = v_color_black_b.value; + } + else + c[0] = c[1] = c[2] = v_brightness.value; + if (c[0] >= 0.01f || c[1] >= 0.01f || c[2] >= 0.01f) + { + GL_BlendFunc(GL_ONE, GL_ONE); + GL_Color(c[0], c[1], c[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } +} + diff --git a/app/jni/gl_rmain.c b/app/jni/gl_rmain.c new file mode 100644 index 0000000..5526949 --- /dev/null +++ b/app/jni/gl_rmain.c @@ -0,0 +1,12691 @@ +/* +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. + +*/ +// r_main.c + +#include "quakedef.h" +#include "cl_dyntexture.h" +#include "r_shadow.h" +#include "polygon.h" +#include "image.h" +#include "ft2.h" +#include "csprogs.h" +#include "cl_video.h" +#include "dpsoftrast.h" + +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +#endif + +mempool_t *r_main_mempool; +rtexturepool_t *r_main_texturepool; + +static int r_textureframe = 0; ///< used only by R_GetCurrentTexture + +static qboolean r_loadnormalmap; +static qboolean r_loadgloss; +qboolean r_loadfog; +static qboolean r_loaddds; +static qboolean r_savedds; +static qboolean r_gpuskeletal; + +cvar_t r_worldscale = {CVAR_SAVE, "r_worldscale", "40.0", "World scale multiplier (default is 40)"}; + +float GetStereoSeparation() +{ + return r_worldscale.value * 0.065; +} + + +//Define the stereo side we are drawing +int r_stereo_side; + +// +// screen size info +// +r_refdef_t r_refdef; + +cvar_t r_motionblur = {CVAR_SAVE, "r_motionblur", "0", "screen motionblur - value represents intensity, somewhere around 0.5 recommended - NOTE: bad performance on multi-gpu!"}; +cvar_t r_damageblur = {CVAR_SAVE, "r_damageblur", "0", "screen motionblur based on damage - value represents intensity, somewhere around 0.5 recommended - NOTE: bad performance on multi-gpu!"}; +cvar_t r_motionblur_averaging = {CVAR_SAVE, "r_motionblur_averaging", "0.1", "sliding average reaction time for velocity (higher = slower adaption to change)"}; +cvar_t r_motionblur_randomize = {CVAR_SAVE, "r_motionblur_randomize", "0.1", "randomizing coefficient to workaround ghosting"}; +cvar_t r_motionblur_minblur = {CVAR_SAVE, "r_motionblur_minblur", "0.5", "factor of blur to apply at all times (always have this amount of blur no matter what the other factors are)"}; +cvar_t r_motionblur_maxblur = {CVAR_SAVE, "r_motionblur_maxblur", "0.9", "maxmimum amount of blur"}; +cvar_t r_motionblur_velocityfactor = {CVAR_SAVE, "r_motionblur_velocityfactor", "1", "factoring in of player velocity to the blur equation - the faster the player moves around the map, the more blur they get"}; +cvar_t r_motionblur_velocityfactor_minspeed = {CVAR_SAVE, "r_motionblur_velocityfactor_minspeed", "400", "lower value of velocity when it starts to factor into blur equation"}; +cvar_t r_motionblur_velocityfactor_maxspeed = {CVAR_SAVE, "r_motionblur_velocityfactor_maxspeed", "800", "upper value of velocity when it reaches the peak factor into blur equation"}; +cvar_t r_motionblur_mousefactor = {CVAR_SAVE, "r_motionblur_mousefactor", "2", "factoring in of mouse acceleration to the blur equation - the faster the player turns their mouse, the more blur they get"}; +cvar_t r_motionblur_mousefactor_minspeed = {CVAR_SAVE, "r_motionblur_mousefactor_minspeed", "0", "lower value of mouse acceleration when it starts to factor into blur equation"}; +cvar_t r_motionblur_mousefactor_maxspeed = {CVAR_SAVE, "r_motionblur_mousefactor_maxspeed", "50", "upper value of mouse acceleration when it reaches the peak factor into blur equation"}; + +// TODO do we want a r_equalize_entities cvar that works on all ents, or would that be a cheat? +cvar_t r_equalize_entities_fullbright = {CVAR_SAVE, "r_equalize_entities_fullbright", "0", "render fullbright entities by equalizing their lightness, not by not rendering light"}; +cvar_t r_equalize_entities_minambient = {CVAR_SAVE, "r_equalize_entities_minambient", "0.5", "light equalizing: ensure at least this ambient/diffuse ratio"}; +cvar_t r_equalize_entities_by = {CVAR_SAVE, "r_equalize_entities_by", "0.7", "light equalizing: exponent of dynamics compression (0 = no compression, 1 = full compression)"}; +cvar_t r_equalize_entities_to = {CVAR_SAVE, "r_equalize_entities_to", "0.8", "light equalizing: target light level"}; + +cvar_t r_depthfirst = {CVAR_SAVE, "r_depthfirst", "0", "renders a depth-only version of the scene before normal rendering begins to eliminate overdraw, values: 0 = off, 1 = world depth, 2 = world and model depth"}; +cvar_t r_useinfinitefarclip = {CVAR_SAVE, "r_useinfinitefarclip", "1", "enables use of a special kind of projection matrix that has an extremely large farclip"}; +cvar_t r_farclip_base = {0, "r_farclip_base", "65536", "farclip (furthest visible distance) for rendering when r_useinfinitefarclip is 0"}; +cvar_t r_farclip_world = {0, "r_farclip_world", "2", "adds map size to farclip multiplied by this value"}; +cvar_t r_nearclip = {0, "r_nearclip", "1", "distance from camera of nearclip plane" }; +cvar_t r_deformvertexes = {0, "r_deformvertexes", "1", "allows use of deformvertexes in shader files (can be turned off to check performance impact)"}; +cvar_t r_transparent = {0, "r_transparent", "1", "allows use of transparent surfaces (can be turned off to check performance impact)"}; +cvar_t r_transparent_alphatocoverage = {0, "r_transparent_alphatocoverage", "1", "enables GL_ALPHA_TO_COVERAGE antialiasing technique on alphablend and alphatest surfaces when using vid_samples 2 or higher"}; +cvar_t r_transparent_sortsurfacesbynearest = {0, "r_transparent_sortsurfacesbynearest", "1", "sort entity and world surfaces by nearest point on bounding box instead of using the center of the bounding box, usually reduces sorting artifacts"}; +cvar_t r_transparent_useplanardistance = {0, "r_transparent_useplanardistance", "0", "sort transparent meshes by distance from view plane rather than spherical distance to the chosen point"}; +cvar_t r_showoverdraw = {0, "r_showoverdraw", "0", "shows overlapping geometry"}; +cvar_t r_showbboxes = {0, "r_showbboxes", "0", "shows bounding boxes of server entities, value controls opacity scaling (1 = 10%, 10 = 100%)"}; +cvar_t r_showsurfaces = {0, "r_showsurfaces", "0", "1 shows surfaces as different colors, or a value of 2 shows triangle draw order (for analyzing whether meshes are optimized for vertex cache)"}; +cvar_t r_showtris = {0, "r_showtris", "0", "shows triangle outlines, value controls brightness (can be above 1)"}; +cvar_t r_shownormals = {0, "r_shownormals", "0", "shows per-vertex surface normals and tangent vectors for bumpmapped lighting"}; +cvar_t r_showlighting = {0, "r_showlighting", "0", "shows areas lit by lights, useful for finding out why some areas of a map render slowly (bright orange = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful"}; +cvar_t r_showshadowvolumes = {0, "r_showshadowvolumes", "0", "shows areas shadowed by lights, useful for finding out why some areas of a map render slowly (bright blue = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful"}; +cvar_t r_showcollisionbrushes = {0, "r_showcollisionbrushes", "0", "draws collision brushes in quake3 maps (mode 1), mode 2 disables rendering of world (trippy!)"}; +cvar_t r_showcollisionbrushes_polygonfactor = {0, "r_showcollisionbrushes_polygonfactor", "-1", "expands outward the brush polygons a little bit, used to make collision brushes appear infront of walls"}; +cvar_t r_showcollisionbrushes_polygonoffset = {0, "r_showcollisionbrushes_polygonoffset", "0", "nudges brush polygon depth in hardware depth units, used to make collision brushes appear infront of walls"}; +cvar_t r_showdisabledepthtest = {0, "r_showdisabledepthtest", "0", "disables depth testing on r_show* cvars, allowing you to see what hidden geometry the graphics card is processing"}; +cvar_t r_drawportals = {0, "r_drawportals", "0", "shows portals (separating polygons) in world interior in quake1 maps"}; +cvar_t r_drawentities = {0, "r_drawentities","1", "draw entities (doors, players, projectiles, etc)"}; +cvar_t r_draw2d = {0, "r_draw2d","1", "draw 2D stuff (dangerous to turn off)"}; +cvar_t r_drawworld = {0, "r_drawworld","1", "draw world (most static stuff)"}; +cvar_t r_drawviewmodel = {0, "r_drawviewmodel","1", "draw your weapon model"}; +cvar_t r_drawexteriormodel = {0, "r_drawexteriormodel","1", "draw your player model (e.g. in chase cam, reflections)"}; +cvar_t r_cullentities_trace = {0, "r_cullentities_trace", "1", "probabistically cull invisible entities"}; +cvar_t r_cullentities_trace_samples = {0, "r_cullentities_trace_samples", "2", "number of samples to test for entity culling (in addition to center sample)"}; +cvar_t r_cullentities_trace_tempentitysamples = {0, "r_cullentities_trace_tempentitysamples", "-1", "number of samples to test for entity culling of temp entities (including all CSQC entities), -1 disables trace culling on these entities to prevent flicker (pvs still applies)"}; +cvar_t r_cullentities_trace_enlarge = {0, "r_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; +cvar_t r_cullentities_trace_delay = {0, "r_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; +cvar_t r_sortentities = {0, "r_sortentities", "0", "sort entities before drawing (might be faster)"}; +cvar_t r_speeds = {0, "r_speeds","0", "displays rendering statistics and per-subsystem timings"}; +cvar_t r_fullbright = {0, "r_fullbright","0", "makes map very bright and renders faster"}; + +cvar_t r_fakelight = {0, "r_fakelight","0", "render 'fake' lighting instead of real lightmaps"}; +cvar_t r_fakelight_intensity = {0, "r_fakelight_intensity","0.75", "fakelight intensity modifier"}; +#define FAKELIGHT_ENABLED (r_fakelight.integer >= 2 || (r_fakelight.integer && r_refdef.scene.worldmodel && !r_refdef.scene.worldmodel->lit)) + +cvar_t r_wateralpha = {CVAR_SAVE, "r_wateralpha","1", "opacity of water polygons"}; +cvar_t r_dynamic = {CVAR_SAVE, "r_dynamic","1", "enables dynamic lights (rocket glow and such)"}; +cvar_t r_fullbrights = {CVAR_SAVE, "r_fullbrights", "1", "enables glowing pixels in quake textures (changes need r_restart to take effect)"}; +cvar_t r_shadows = {CVAR_SAVE, "r_shadows", "0", "casts fake stencil shadows from models onto the world (rtlights are unaffected by this); when set to 2, always cast the shadows in the direction set by r_shadows_throwdirection, otherwise use the model lighting."}; +cvar_t r_shadows_darken = {CVAR_SAVE, "r_shadows_darken", "0.5", "how much shadowed areas will be darkened"}; +cvar_t r_shadows_throwdistance = {CVAR_SAVE, "r_shadows_throwdistance", "500", "how far to cast shadows from models"}; +cvar_t r_shadows_throwdirection = {CVAR_SAVE, "r_shadows_throwdirection", "0 0 -1", "override throwing direction for r_shadows 2"}; +cvar_t r_shadows_drawafterrtlighting = {CVAR_SAVE, "r_shadows_drawafterrtlighting", "0", "draw fake shadows AFTER realtime lightning is drawn. May be useful for simulating fast sunlight on large outdoor maps with only one noshadow rtlight. The price is less realistic appearance of dynamic light shadows."}; +cvar_t r_shadows_castfrombmodels = {CVAR_SAVE, "r_shadows_castfrombmodels", "0", "do cast shadows from bmodels"}; +cvar_t r_shadows_focus = {CVAR_SAVE, "r_shadows_focus", "0 0 0", "offset the shadowed area focus"}; +cvar_t r_shadows_shadowmapscale = {CVAR_SAVE, "r_shadows_shadowmapscale", "1", "increases shadowmap quality (multiply global shadowmap precision) for fake shadows. Needs shadowmapping ON."}; +cvar_t r_shadows_shadowmapbias = {CVAR_SAVE, "r_shadows_shadowmapbias", "-1", "sets shadowmap bias for fake shadows. -1 sets the value of r_shadow_shadowmapping_bias. Needs shadowmapping ON."}; +cvar_t r_q1bsp_skymasking = {0, "r_q1bsp_skymasking", "1", "allows sky polygons in quake1 maps to obscure other geometry"}; +cvar_t r_polygonoffset_submodel_factor = {0, "r_polygonoffset_submodel_factor", "0", "biases depth values of world submodels such as doors, to prevent z-fighting artifacts in Quake maps"}; +cvar_t r_polygonoffset_submodel_offset = {0, "r_polygonoffset_submodel_offset", "0", "biases depth values of world submodels such as doors, to prevent z-fighting artifacts in Quake maps"};//changed default 14 to 0 to fix Tegra Z-buffer depth +cvar_t r_polygonoffset_decals_factor = {0, "r_polygonoffset_decals_factor", "0", "biases depth values of decals to prevent z-fighting artifacts"}; +cvar_t r_polygonoffset_decals_offset = {0, "r_polygonoffset_decals_offset", "-14", "biases depth values of decals to prevent z-fighting artifacts"}; +cvar_t r_fog_exp2 = {0, "r_fog_exp2", "0", "uses GL_EXP2 fog (as in Nehahra) rather than realistic GL_EXP fog"}; +cvar_t r_fog_clear = {0, "r_fog_clear", "1", "clears renderbuffer with fog color before render starts"}; +cvar_t r_drawfog = {CVAR_SAVE, "r_drawfog", "1", "allows one to disable fog rendering"}; +cvar_t r_transparentdepthmasking = {CVAR_SAVE, "r_transparentdepthmasking", "0", "enables depth writes on transparent meshes whose materially is normally opaque, this prevents seeing the inside of a transparent mesh"}; +cvar_t r_transparent_sortmindist = {CVAR_SAVE, "r_transparent_sortmindist", "1", "lower distance limit for transparent sorting"}; +cvar_t r_transparent_sortmaxdist = {CVAR_SAVE, "r_transparent_sortmaxdist", "32768", "upper distance limit for transparent sorting"}; +cvar_t r_transparent_sortarraysize = {CVAR_SAVE, "r_transparent_sortarraysize", "4096", "number of distance-sorting layers"}; +cvar_t r_celshading = {CVAR_SAVE, "r_celshading", "0", "cartoon-style light shading (OpenGL 2.x only)"}; // FIXME remove OpenGL 2.x only once implemented for DX9 +cvar_t r_celoutlines = {CVAR_SAVE, "r_celoutlines", "0", "cartoon-style outlines (requires r_shadow_deferred; OpenGL 2.x only)"}; // FIXME remove OpenGL 2.x only once implemented for DX9 + +cvar_t gl_fogenable = {0, "gl_fogenable", "0", "nehahra fog enable (for Nehahra compatibility only)"}; +cvar_t gl_fogdensity = {0, "gl_fogdensity", "0.25", "nehahra fog density (recommend values below 0.1) (for Nehahra compatibility only)"}; +cvar_t gl_fogred = {0, "gl_fogred","0.3", "nehahra fog color red value (for Nehahra compatibility only)"}; +cvar_t gl_foggreen = {0, "gl_foggreen","0.3", "nehahra fog color green value (for Nehahra compatibility only)"}; +cvar_t gl_fogblue = {0, "gl_fogblue","0.3", "nehahra fog color blue value (for Nehahra compatibility only)"}; +cvar_t gl_fogstart = {0, "gl_fogstart", "0", "nehahra fog start distance (for Nehahra compatibility only)"}; +cvar_t gl_fogend = {0, "gl_fogend","0", "nehahra fog end distance (for Nehahra compatibility only)"}; +cvar_t gl_skyclip = {0, "gl_skyclip", "4608", "nehahra farclip distance - the real fog end (for Nehahra compatibility only)"}; + +cvar_t r_texture_dds_load = {CVAR_SAVE, "r_texture_dds_load", "0", "load compressed dds/filename.dds texture instead of filename.tga, if the file exists (requires driver support)"}; +cvar_t r_texture_dds_save = {CVAR_SAVE, "r_texture_dds_save", "0", "save compressed dds/filename.dds texture when filename.tga is loaded, so that it can be loaded instead next time"}; + +cvar_t r_textureunits = {0, "r_textureunits", "32", "number of texture units to use in GL 1.1 and GL 1.3 rendering paths"}; +static cvar_t gl_combine = {CVAR_READONLY, "gl_combine", "1", "indicates whether the OpenGL 1.3 rendering path is active"}; +static cvar_t r_glsl = {CVAR_READONLY, "r_glsl", "1", "indicates whether the OpenGL 2.0 rendering path is active"}; + +cvar_t r_usedepthtextures = {CVAR_SAVE, "r_usedepthtextures", "1", "use depth texture instead of depth renderbuffer where possible, uses less video memory but may render slower (or faster) depending on hardware"}; +cvar_t r_viewfbo = {CVAR_SAVE, "r_viewfbo", "0", "enables use of an 8bit (1) or 16bit (2) or 32bit (3) per component float framebuffer render, which may be at a different resolution than the video mode"}; +cvar_t r_viewscale = {CVAR_SAVE, "r_viewscale", "1", "scaling factor for resolution of the fbo rendering method, must be > 0, can be above 1 for a costly antialiasing behavior, typical values are 0.5 for 1/4th as many pixels rendered, or 1 for normal rendering"}; +cvar_t r_viewscale_fpsscaling = {CVAR_SAVE, "r_viewscale_fpsscaling", "0", "change resolution based on framerate"}; +cvar_t r_viewscale_fpsscaling_min = {CVAR_SAVE, "r_viewscale_fpsscaling_min", "0.0625", "worst acceptable quality"}; +cvar_t r_viewscale_fpsscaling_multiply = {CVAR_SAVE, "r_viewscale_fpsscaling_multiply", "5", "adjust quality up or down by the frametime difference from 1.0/target, multiplied by this factor"}; +cvar_t r_viewscale_fpsscaling_stepsize = {CVAR_SAVE, "r_viewscale_fpsscaling_stepsize", "0.01", "smallest adjustment to hit the target framerate (this value prevents minute oscillations)"}; +cvar_t r_viewscale_fpsscaling_stepmax = {CVAR_SAVE, "r_viewscale_fpsscaling_stepmax", "1.00", "largest adjustment to hit the target framerate (this value prevents wild overshooting of the estimate)"}; +cvar_t r_viewscale_fpsscaling_target = {CVAR_SAVE, "r_viewscale_fpsscaling_target", "70", "desired framerate"}; + +cvar_t r_glsl_skeletal = {CVAR_SAVE, "r_glsl_skeletal", "1", "render skeletal models faster using a gpu-skinning technique"}; +cvar_t r_glsl_deluxemapping = {CVAR_SAVE, "r_glsl_deluxemapping", "1", "use per pixel lighting on deluxemap-compiled q3bsp maps (or a value of 2 forces deluxemap shading even without deluxemaps)"}; +cvar_t r_glsl_offsetmapping = {CVAR_SAVE, "r_glsl_offsetmapping", "0", "offset mapping effect (also known as parallax mapping or virtual displacement mapping)"}; +cvar_t r_glsl_offsetmapping_steps = {CVAR_SAVE, "r_glsl_offsetmapping_steps", "2", "offset mapping steps (note: too high values may be not supported by your GPU)"}; +cvar_t r_glsl_offsetmapping_reliefmapping = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping", "0", "relief mapping effect (higher quality)"}; +cvar_t r_glsl_offsetmapping_reliefmapping_steps = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping_steps", "10", "relief mapping steps (note: too high values may be not supported by your GPU)"}; +cvar_t r_glsl_offsetmapping_reliefmapping_refinesteps = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping_refinesteps", "5", "relief mapping refine steps (these are a binary search executed as the last step as given by r_glsl_offsetmapping_reliefmapping_steps)"}; +cvar_t r_glsl_offsetmapping_scale = {CVAR_SAVE, "r_glsl_offsetmapping_scale", "0.04", "how deep the offset mapping effect is"}; +cvar_t r_glsl_offsetmapping_lod = {CVAR_SAVE, "r_glsl_offsetmapping_lod", "0", "apply distance-based level-of-detail correction to number of offsetmappig steps, effectively making it render faster on large open-area maps"}; +cvar_t r_glsl_offsetmapping_lod_distance = {CVAR_SAVE, "r_glsl_offsetmapping_lod_distance", "32", "first LOD level distance, second level (-50% steps) is 2x of this, third (33%) - 3x etc."}; +cvar_t r_glsl_postprocess = {CVAR_SAVE, "r_glsl_postprocess", "0", "use a GLSL postprocessing shader"}; +cvar_t r_glsl_postprocess_uservec1 = {CVAR_SAVE, "r_glsl_postprocess_uservec1", "0 0 0 0", "a 4-component vector to pass as uservec1 to the postprocessing shader (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec2 = {CVAR_SAVE, "r_glsl_postprocess_uservec2", "0 0 0 0", "a 4-component vector to pass as uservec2 to the postprocessing shader (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec3 = {CVAR_SAVE, "r_glsl_postprocess_uservec3", "0 0 0 0", "a 4-component vector to pass as uservec3 to the postprocessing shader (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec4 = {CVAR_SAVE, "r_glsl_postprocess_uservec4", "0 0 0 0", "a 4-component vector to pass as uservec4 to the postprocessing shader (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec1_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec1_enable", "1", "enables postprocessing uservec1 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec2_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec2_enable", "1", "enables postprocessing uservec2 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec3_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec3_enable", "1", "enables postprocessing uservec3 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; +cvar_t r_glsl_postprocess_uservec4_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec4_enable", "1", "enables postprocessing uservec4 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; + +cvar_t r_water = {CVAR_SAVE, "r_water", "0", "whether to use reflections and refraction on water surfaces (note: r_wateralpha must be set below 1)"}; +cvar_t r_water_clippingplanebias = {CVAR_SAVE, "r_water_clippingplanebias", "1", "a rather technical setting which avoids black pixels around water edges"}; +cvar_t r_water_resolutionmultiplier = {CVAR_SAVE, "r_water_resolutionmultiplier", "0.5", "multiplier for screen resolution when rendering refracted/reflected scenes, 1 is full quality, lower values are faster"}; +cvar_t r_water_refractdistort = {CVAR_SAVE, "r_water_refractdistort", "0.01", "how much water refractions shimmer"}; +cvar_t r_water_reflectdistort = {CVAR_SAVE, "r_water_reflectdistort", "0.01", "how much water reflections shimmer"}; +cvar_t r_water_scissormode = {0, "r_water_scissormode", "3", "scissor (1) or cull (2) or both (3) water renders"}; +cvar_t r_water_lowquality = {0, "r_water_lowquality", "0", "special option to accelerate water rendering, 1 disables shadows and particles, 2 disables all dynamic lights"}; +cvar_t r_water_hideplayer = {CVAR_SAVE, "r_water_hideplayer", "0", "if set to 1 then player will be hidden in refraction views, if set to 2 then player will also be hidden in reflection views, player is always visible in camera views"}; +cvar_t r_water_fbo = {CVAR_SAVE, "r_water_fbo", "1", "enables use of render to texture for water effects, otherwise copy to texture is used (slower)"}; + +cvar_t r_lerpsprites = {CVAR_SAVE, "r_lerpsprites", "0", "enables animation smoothing on sprites"}; +cvar_t r_lerpmodels = {CVAR_SAVE, "r_lerpmodels", "1", "enables animation smoothing on models"}; +cvar_t r_lerplightstyles = {CVAR_SAVE, "r_lerplightstyles", "0", "enable animation smoothing on flickering lights"}; +cvar_t r_waterscroll = {CVAR_SAVE, "r_waterscroll", "1", "makes water scroll around, value controls how much"}; + +cvar_t r_bloom = {CVAR_SAVE, "r_bloom", "0", "enables bloom effect (makes bright pixels affect neighboring pixels)"}; +cvar_t r_bloom_colorscale = {CVAR_SAVE, "r_bloom_colorscale", "1", "how bright the glow is"}; + +cvar_t r_bloom_brighten = {CVAR_SAVE, "r_bloom_brighten", "2", "how bright the glow is, after subtract/power"}; +cvar_t r_bloom_blur = {CVAR_SAVE, "r_bloom_blur", "4", "how large the glow is"}; +cvar_t r_bloom_resolution = {CVAR_SAVE, "r_bloom_resolution", "320", "what resolution to perform the bloom effect at (independent of screen resolution)"}; +cvar_t r_bloom_colorexponent = {CVAR_SAVE, "r_bloom_colorexponent", "1", "how exaggerated the glow is"}; +cvar_t r_bloom_colorsubtract = {CVAR_SAVE, "r_bloom_colorsubtract", "0.125", "reduces bloom colors by a certain amount"}; +cvar_t r_bloom_scenebrightness = {CVAR_SAVE, "r_bloom_scenebrightness", "1", "global rendering brightness when bloom is enabled"}; + +cvar_t r_hdr_scenebrightness = {CVAR_SAVE, "r_hdr_scenebrightness", "1", "global rendering brightness"}; +cvar_t r_hdr_glowintensity = {CVAR_SAVE, "r_hdr_glowintensity", "1", "how bright light emitting textures should appear"}; +cvar_t r_hdr_irisadaptation = {CVAR_SAVE, "r_hdr_irisadaptation", "0", "adjust scene brightness according to light intensity at player location"}; +cvar_t r_hdr_irisadaptation_multiplier = {CVAR_SAVE, "r_hdr_irisadaptation_multiplier", "2", "brightness at which value will be 1.0"}; +cvar_t r_hdr_irisadaptation_minvalue = {CVAR_SAVE, "r_hdr_irisadaptation_minvalue", "0.5", "minimum value that can result from multiplier / brightness"}; +cvar_t r_hdr_irisadaptation_maxvalue = {CVAR_SAVE, "r_hdr_irisadaptation_maxvalue", "4", "maximum value that can result from multiplier / brightness"}; +cvar_t r_hdr_irisadaptation_value = {0, "r_hdr_irisadaptation_value", "1", "current value as scenebrightness multiplier, changes continuously when irisadaptation is active"}; +cvar_t r_hdr_irisadaptation_fade_up = {CVAR_SAVE, "r_hdr_irisadaptation_fade_up", "0.1", "fade rate at which value adjusts to darkness"}; +cvar_t r_hdr_irisadaptation_fade_down = {CVAR_SAVE, "r_hdr_irisadaptation_fade_down", "0.5", "fade rate at which value adjusts to brightness"}; +cvar_t r_hdr_irisadaptation_radius = {CVAR_SAVE, "r_hdr_irisadaptation_radius", "15", "lighting within this many units of the eye is averaged"}; + +cvar_t r_smoothnormals_areaweighting = {0, "r_smoothnormals_areaweighting", "1", "uses significantly faster (and supposedly higher quality) area-weighted vertex normals and tangent vectors rather than summing normalized triangle normals and tangents"}; + +cvar_t developer_texturelogging = {0, "developer_texturelogging", "0", "produces a textures.log file containing names of skins and map textures the engine tried to load"}; + +cvar_t gl_lightmaps = {0, "gl_lightmaps", "0", "draws only lightmaps, no texture (for level designers), a value of 2 keeps normalmap shading"}; + +cvar_t r_test = {0, "r_test", "0", "internal development use only, leave it alone (usually does nothing anyway)"}; + +cvar_t r_batch_multidraw = {CVAR_SAVE, "r_batch_multidraw", "1", "issue multiple glDrawElements calls when rendering a batch of surfaces with the same texture (otherwise the index data is copied to make it one draw)"}; +cvar_t r_batch_multidraw_mintriangles = {CVAR_SAVE, "r_batch_multidraw_mintriangles", "0", "minimum number of triangles to activate multidraw path (copying small groups of triangles may be faster)"}; +cvar_t r_batch_debugdynamicvertexpath = {CVAR_SAVE, "r_batch_debugdynamicvertexpath", "0", "force the dynamic batching code path for debugging purposes"}; +cvar_t r_batch_dynamicbuffer = {CVAR_SAVE, "r_batch_dynamicbuffer", "0", "use vertex/index buffers for drawing dynamic and copytriangles batches"}; + +cvar_t r_glsl_saturation = {CVAR_SAVE, "r_glsl_saturation", "1", "saturation multiplier (only working in glsl!)"}; +cvar_t r_glsl_saturation_redcompensate = {CVAR_SAVE, "r_glsl_saturation_redcompensate", "0", "a 'vampire sight' addition to desaturation effect, does compensation for red color, r_glsl_restart is required"}; + +cvar_t r_glsl_vertextextureblend_usebothalphas = {CVAR_SAVE, "r_glsl_vertextextureblend_usebothalphas", "0", "use both alpha layers on vertex blended surfaces, each alpha layer sets amount of 'blend leak' on another layer, requires mod_q3shader_force_terrain_alphaflag on."}; + +cvar_t r_framedatasize = {CVAR_SAVE, "r_framedatasize", "0.5", "size of renderer data cache used during one frame (for skeletal animation caching, light processing, etc)"}; +cvar_t r_buffermegs[R_BUFFERDATA_COUNT] = +{ + {CVAR_SAVE, "r_buffermegs_vertex", "4", "vertex buffer size for one frame"}, + {CVAR_SAVE, "r_buffermegs_index16", "1", "index buffer size for one frame (16bit indices)"}, + {CVAR_SAVE, "r_buffermegs_index32", "1", "index buffer size for one frame (32bit indices)"}, + {CVAR_SAVE, "r_buffermegs_uniform", "0.25", "uniform buffer size for one frame"}, +}; + +extern cvar_t v_glslgamma; +extern cvar_t v_glslgamma_2d; + +extern qboolean v_flipped_state; + +r_framebufferstate_t r_fb; + +/// shadow volume bsp struct with automatically growing nodes buffer +svbsp_t r_svbsp; + +int r_uniformbufferalignment = 32; // dynamically updated to match GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT + +rtexture_t *r_texture_blanknormalmap; +rtexture_t *r_texture_white; +rtexture_t *r_texture_grey128; +rtexture_t *r_texture_black; +rtexture_t *r_texture_notexture; +rtexture_t *r_texture_whitecube; +rtexture_t *r_texture_normalizationcube; +rtexture_t *r_texture_fogattenuation; +rtexture_t *r_texture_fogheighttexture; +rtexture_t *r_texture_gammaramps; +unsigned int r_texture_gammaramps_serial; +//rtexture_t *r_texture_fogintensity; +rtexture_t *r_texture_reflectcube; + +// TODO: hash lookups? +typedef struct cubemapinfo_s +{ + char basename[64]; + rtexture_t *texture; +} +cubemapinfo_t; + +int r_texture_numcubemaps; +cubemapinfo_t *r_texture_cubemaps[MAX_CUBEMAPS]; + +unsigned int r_queries[MAX_OCCLUSION_QUERIES]; +unsigned int r_numqueries; +unsigned int r_maxqueries; + +typedef struct r_qwskincache_s +{ + char name[MAX_QPATH]; + skinframe_t *skinframe; +} +r_qwskincache_t; + +static r_qwskincache_t *r_qwskincache; +static int r_qwskincache_size; + +/// vertex coordinates for a quad that covers the screen exactly +extern const float r_screenvertex3f[12]; +extern const float r_d3dscreenvertex3f[12]; +const float r_screenvertex3f[12] = +{ + 0, 0, 0, + 1, 0, 0, + 1, 1, 0, + 0, 1, 0 +}; +const float r_d3dscreenvertex3f[12] = +{ + 0, 1, 0, + 1, 1, 0, + 1, 0, 0, + 0, 0, 0 +}; + +void R_ModulateColors(float *in, float *out, int verts, float r, float g, float b) +{ + int i; + for (i = 0;i < verts;i++) + { + out[0] = in[0] * r; + out[1] = in[1] * g; + out[2] = in[2] * b; + out[3] = in[3]; + in += 4; + out += 4; + } +} + +void R_FillColors(float *out, int verts, float r, float g, float b, float a) +{ + int i; + for (i = 0;i < verts;i++) + { + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = a; + out += 4; + } +} + +// FIXME: move this to client? +void FOG_clear(void) +{ + if (gamemode == GAME_NEHAHRA) + { + Cvar_Set("gl_fogenable", "0"); + Cvar_Set("gl_fogdensity", "0.2"); + Cvar_Set("gl_fogred", "0.3"); + Cvar_Set("gl_foggreen", "0.3"); + Cvar_Set("gl_fogblue", "0.3"); + } + r_refdef.fog_density = 0; + r_refdef.fog_red = 0; + r_refdef.fog_green = 0; + r_refdef.fog_blue = 0; + r_refdef.fog_alpha = 1; + r_refdef.fog_start = 0; + r_refdef.fog_end = 16384; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; + memset(r_refdef.fog_height_texturename, 0, sizeof(r_refdef.fog_height_texturename)); +} + +static void R_BuildBlankTextures(void) +{ + unsigned char data[4]; + data[2] = 128; // normal X + data[1] = 128; // normal Y + data[0] = 255; // normal Z + data[3] = 255; // height + r_texture_blanknormalmap = R_LoadTexture2D(r_main_texturepool, "blankbump", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); + data[0] = 255; + data[1] = 255; + data[2] = 255; + data[3] = 255; + r_texture_white = R_LoadTexture2D(r_main_texturepool, "blankwhite", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); + data[0] = 128; + data[1] = 128; + data[2] = 128; + data[3] = 255; + r_texture_grey128 = R_LoadTexture2D(r_main_texturepool, "blankgrey128", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 255; + r_texture_black = R_LoadTexture2D(r_main_texturepool, "blankblack", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); +} + +static void R_BuildNoTexture(void) +{ + int x, y; + unsigned char pix[16][16][4]; + // this makes a light grey/dark grey checkerboard texture + for (y = 0;y < 16;y++) + { + for (x = 0;x < 16;x++) + { + if ((y < 8) ^ (x < 8)) + { + pix[y][x][0] = 128; + pix[y][x][1] = 128; + pix[y][x][2] = 128; + pix[y][x][3] = 255; + } + else + { + pix[y][x][0] = 64; + pix[y][x][1] = 64; + pix[y][x][2] = 64; + pix[y][x][3] = 255; + } + } + } + r_texture_notexture = R_LoadTexture2D(r_main_texturepool, "notexture", 16, 16, &pix[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_PERSISTENT, -1, NULL); +} + +static void R_BuildWhiteCube(void) +{ + unsigned char data[6*1*1*4]; + memset(data, 255, sizeof(data)); + r_texture_whitecube = R_LoadTextureCubeMap(r_main_texturepool, "whitecube", 1, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); +} + +static void R_BuildNormalizationCube(void) +{ + int x, y, side; + vec3_t v; + vec_t s, t, intensity; +#define NORMSIZE 64 + unsigned char *data; + data = (unsigned char *)Mem_Alloc(tempmempool, 6*NORMSIZE*NORMSIZE*4); + for (side = 0;side < 6;side++) + { + for (y = 0;y < NORMSIZE;y++) + { + for (x = 0;x < NORMSIZE;x++) + { + s = (x + 0.5f) * (2.0f / NORMSIZE) - 1.0f; + t = (y + 0.5f) * (2.0f / NORMSIZE) - 1.0f; + switch(side) + { + default: + case 0: + v[0] = 1; + v[1] = -t; + v[2] = -s; + break; + case 1: + v[0] = -1; + v[1] = -t; + v[2] = s; + break; + case 2: + v[0] = s; + v[1] = 1; + v[2] = t; + break; + case 3: + v[0] = s; + v[1] = -1; + v[2] = -t; + break; + case 4: + v[0] = s; + v[1] = -t; + v[2] = 1; + break; + case 5: + v[0] = -s; + v[1] = -t; + v[2] = -1; + break; + } + intensity = 127.0f / sqrt(DotProduct(v, v)); + data[((side*64+y)*64+x)*4+2] = (unsigned char)(128.0f + intensity * v[0]); + data[((side*64+y)*64+x)*4+1] = (unsigned char)(128.0f + intensity * v[1]); + data[((side*64+y)*64+x)*4+0] = (unsigned char)(128.0f + intensity * v[2]); + data[((side*64+y)*64+x)*4+3] = 255; + } + } + } + r_texture_normalizationcube = R_LoadTextureCubeMap(r_main_texturepool, "normalcube", NORMSIZE, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); + Mem_Free(data); +} + +static void R_BuildFogTexture(void) +{ + int x, b; +#define FOGWIDTH 256 + unsigned char data1[FOGWIDTH][4]; + //unsigned char data2[FOGWIDTH][4]; + double d, r, alpha; + + r_refdef.fogmasktable_start = r_refdef.fog_start; + r_refdef.fogmasktable_alpha = r_refdef.fog_alpha; + r_refdef.fogmasktable_range = r_refdef.fogrange; + r_refdef.fogmasktable_density = r_refdef.fog_density; + + r = r_refdef.fogmasktable_range / FOGMASKTABLEWIDTH; + for (x = 0;x < FOGMASKTABLEWIDTH;x++) + { + d = (x * r - r_refdef.fogmasktable_start); + if(developer_extra.integer) + Con_DPrintf("%f ", d); + d = max(0, d); + if (r_fog_exp2.integer) + alpha = exp(-r_refdef.fogmasktable_density * r_refdef.fogmasktable_density * 0.0001 * d * d); + else + alpha = exp(-r_refdef.fogmasktable_density * 0.004 * d); + if(developer_extra.integer) + Con_DPrintf(" : %f ", alpha); + alpha = 1 - (1 - alpha) * r_refdef.fogmasktable_alpha; + if(developer_extra.integer) + Con_DPrintf(" = %f\n", alpha); + r_refdef.fogmasktable[x] = bound(0, alpha, 1); + } + + for (x = 0;x < FOGWIDTH;x++) + { + b = (int)(r_refdef.fogmasktable[x * (FOGMASKTABLEWIDTH - 1) / (FOGWIDTH - 1)] * 255); + data1[x][0] = b; + data1[x][1] = b; + data1[x][2] = b; + data1[x][3] = 255; + //data2[x][0] = 255 - b; + //data2[x][1] = 255 - b; + //data2[x][2] = 255 - b; + //data2[x][3] = 255; + } + if (r_texture_fogattenuation) + { + R_UpdateTexture(r_texture_fogattenuation, &data1[0][0], 0, 0, 0, FOGWIDTH, 1, 1); + //R_UpdateTexture(r_texture_fogattenuation, &data2[0][0], 0, 0, 0, FOGWIDTH, 1, 1); + } + else + { + r_texture_fogattenuation = R_LoadTexture2D(r_main_texturepool, "fogattenuation", FOGWIDTH, 1, &data1[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); + //r_texture_fogintensity = R_LoadTexture2D(r_main_texturepool, "fogintensity", FOGWIDTH, 1, &data2[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP, NULL); + } +} + +static void R_BuildFogHeightTexture(void) +{ + unsigned char *inpixels; + int size; + int x; + int y; + int j; + float c[4]; + float f; + inpixels = NULL; + strlcpy(r_refdef.fogheighttexturename, r_refdef.fog_height_texturename, sizeof(r_refdef.fogheighttexturename)); + if (r_refdef.fogheighttexturename[0]) + inpixels = loadimagepixelsbgra(r_refdef.fogheighttexturename, true, false, false, NULL); + if (!inpixels) + { + r_refdef.fog_height_tablesize = 0; + if (r_texture_fogheighttexture) + R_FreeTexture(r_texture_fogheighttexture); + r_texture_fogheighttexture = NULL; + if (r_refdef.fog_height_table2d) + Mem_Free(r_refdef.fog_height_table2d); + r_refdef.fog_height_table2d = NULL; + if (r_refdef.fog_height_table1d) + Mem_Free(r_refdef.fog_height_table1d); + r_refdef.fog_height_table1d = NULL; + return; + } + size = image_width; + r_refdef.fog_height_tablesize = size; + r_refdef.fog_height_table1d = (unsigned char *)Mem_Alloc(r_main_mempool, size * 4); + r_refdef.fog_height_table2d = (unsigned char *)Mem_Alloc(r_main_mempool, size * size * 4); + memcpy(r_refdef.fog_height_table1d, inpixels, size * 4); + Mem_Free(inpixels); + // LordHavoc: now the magic - what is that table2d for? it is a cooked + // average fog color table accounting for every fog layer between a point + // and the camera. (Note: attenuation is handled separately!) + for (y = 0;y < size;y++) + { + for (x = 0;x < size;x++) + { + Vector4Clear(c); + f = 0; + if (x < y) + { + for (j = x;j <= y;j++) + { + Vector4Add(c, r_refdef.fog_height_table1d + j*4, c); + f++; + } + } + else + { + for (j = x;j >= y;j--) + { + Vector4Add(c, r_refdef.fog_height_table1d + j*4, c); + f++; + } + } + f = 1.0f / f; + r_refdef.fog_height_table2d[(y*size+x)*4+0] = (unsigned char)(c[0] * f); + r_refdef.fog_height_table2d[(y*size+x)*4+1] = (unsigned char)(c[1] * f); + r_refdef.fog_height_table2d[(y*size+x)*4+2] = (unsigned char)(c[2] * f); + r_refdef.fog_height_table2d[(y*size+x)*4+3] = (unsigned char)(c[3] * f); + } + } + r_texture_fogheighttexture = R_LoadTexture2D(r_main_texturepool, "fogheighttable", size, size, r_refdef.fog_height_table2d, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_CLAMP, -1, NULL); +} + +//======================================================================================================================================================= + +static const char *builtinshaderstrings[] = +{ +#include "shader_glsl.h" +0 +}; + +const char *builtinhlslshaderstrings[] = +{ +#include "shader_hlsl.h" +0 +}; + +char *glslshaderstring = NULL; +char *hlslshaderstring = NULL; + +//======================================================================================================================================================= + +typedef struct shaderpermutationinfo_s +{ + const char *pretext; + const char *name; +} +shaderpermutationinfo_t; + +typedef struct shadermodeinfo_s +{ + const char *filename; + const char *pretext; + const char *name; +} +shadermodeinfo_t; + +// NOTE: MUST MATCH ORDER OF SHADERPERMUTATION_* DEFINES! +shaderpermutationinfo_t shaderpermutationinfo[SHADERPERMUTATION_COUNT] = +{ + {"#define USEDIFFUSE\n", " diffuse"}, + {"#define USEVERTEXTEXTUREBLEND\n", " vertextextureblend"}, + {"#define USEVIEWTINT\n", " viewtint"}, + {"#define USECOLORMAPPING\n", " colormapping"}, + {"#define USESATURATION\n", " saturation"}, + {"#define USEFOGINSIDE\n", " foginside"}, + {"#define USEFOGOUTSIDE\n", " fogoutside"}, + {"#define USEFOGHEIGHTTEXTURE\n", " fogheighttexture"}, + {"#define USEFOGALPHAHACK\n", " fogalphahack"}, + {"#define USEGAMMARAMPS\n", " gammaramps"}, + {"#define USECUBEFILTER\n", " cubefilter"}, + {"#define USEGLOW\n", " glow"}, + {"#define USEBLOOM\n", " bloom"}, + {"#define USESPECULAR\n", " specular"}, + {"#define USEPOSTPROCESSING\n", " postprocessing"}, + {"#define USEREFLECTION\n", " reflection"}, + {"#define USEOFFSETMAPPING\n", " offsetmapping"}, + {"#define USEOFFSETMAPPING_RELIEFMAPPING\n", " reliefmapping"}, + {"#define USESHADOWMAP2D\n", " shadowmap2d"}, + {"#define USESHADOWMAPVSDCT\n", " shadowmapvsdct"}, // TODO make this a static parm + {"#define USESHADOWMAPORTHO\n", " shadowmaportho"}, + {"#define USEDEFERREDLIGHTMAP\n", " deferredlightmap"}, + {"#define USEALPHAKILL\n", " alphakill"}, + {"#define USEREFLECTCUBE\n", " reflectcube"}, + {"#define USENORMALMAPSCROLLBLEND\n", " normalmapscrollblend"}, + {"#define USEBOUNCEGRID\n", " bouncegrid"}, + {"#define USEBOUNCEGRIDDIRECTIONAL\n", " bouncegriddirectional"}, // TODO make this a static parm + {"#define USETRIPPY\n", " trippy"}, + {"#define USEDEPTHRGB\n", " depthrgb"}, + {"#define USEALPHAGENVERTEX\n", " alphagenvertex"}, + {"#define USESKELETAL\n", " skeletal"} +}; + +// NOTE: MUST MATCH ORDER OF SHADERMODE_* ENUMS! +shadermodeinfo_t glslshadermodeinfo[SHADERMODE_COUNT] = +{ + {"glsl/default.glsl", "#define MODE_GENERIC\n", " generic"}, + {"glsl/default.glsl", "#define MODE_POSTPROCESS\n", " postprocess"}, + {"glsl/default.glsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"}, + {"glsl/default.glsl", "#define MODE_FLATCOLOR\n", " flatcolor"}, + {"glsl/default.glsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"}, + {"glsl/default.glsl", "#define MODE_LIGHTMAP\n", " lightmap"}, + {"glsl/default.glsl", "#define MODE_FAKELIGHT\n", " fakelight"}, + {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"}, + {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"}, + {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP\n", " lightdirectionmap_forced_lightmap"}, + {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR\n", " lightdirectionmap_forced_vertexcolor"}, + {"glsl/default.glsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"}, + {"glsl/default.glsl", "#define MODE_LIGHTSOURCE\n", " lightsource"}, + {"glsl/default.glsl", "#define MODE_REFRACTION\n", " refraction"}, + {"glsl/default.glsl", "#define MODE_WATER\n", " water"}, + {"glsl/default.glsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"}, + {"glsl/default.glsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"}, +}; + +shadermodeinfo_t hlslshadermodeinfo[SHADERMODE_COUNT] = +{ + {"hlsl/default.hlsl", "#define MODE_GENERIC\n", " generic"}, + {"hlsl/default.hlsl", "#define MODE_POSTPROCESS\n", " postprocess"}, + {"hlsl/default.hlsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"}, + {"hlsl/default.hlsl", "#define MODE_FLATCOLOR\n", " flatcolor"}, + {"hlsl/default.hlsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"}, + {"hlsl/default.hlsl", "#define MODE_LIGHTMAP\n", " lightmap"}, + {"hlsl/default.hlsl", "#define MODE_FAKELIGHT\n", " fakelight"}, + {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"}, + {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"}, + {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP\n", " lightdirectionmap_forced_lightmap"}, + {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR\n", " lightdirectionmap_forced_vertexcolor"}, + {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"}, + {"hlsl/default.hlsl", "#define MODE_LIGHTSOURCE\n", " lightsource"}, + {"hlsl/default.hlsl", "#define MODE_REFRACTION\n", " refraction"}, + {"hlsl/default.hlsl", "#define MODE_WATER\n", " water"}, + {"hlsl/default.hlsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"}, + {"hlsl/default.hlsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"}, +}; + +struct r_glsl_permutation_s; +typedef struct r_glsl_permutation_s +{ + /// hash lookup data + struct r_glsl_permutation_s *hashnext; + unsigned int mode; + unsigned int permutation; + + /// indicates if we have tried compiling this permutation already + qboolean compiled; + /// 0 if compilation failed + int program; + // texture units assigned to each detected uniform + int tex_Texture_First; + int tex_Texture_Second; + int tex_Texture_GammaRamps; + int tex_Texture_Normal; + int tex_Texture_Color; + int tex_Texture_Gloss; + int tex_Texture_Glow; + int tex_Texture_SecondaryNormal; + int tex_Texture_SecondaryColor; + int tex_Texture_SecondaryGloss; + int tex_Texture_SecondaryGlow; + int tex_Texture_Pants; + int tex_Texture_Shirt; + int tex_Texture_FogHeightTexture; + int tex_Texture_FogMask; + int tex_Texture_Lightmap; + int tex_Texture_Deluxemap; + int tex_Texture_Attenuation; + int tex_Texture_Cube; + int tex_Texture_Refraction; + int tex_Texture_Reflection; + int tex_Texture_ShadowMap2D; + int tex_Texture_CubeProjection; + int tex_Texture_ScreenNormalMap; + int tex_Texture_ScreenDiffuse; + int tex_Texture_ScreenSpecular; + int tex_Texture_ReflectMask; + int tex_Texture_ReflectCube; + int tex_Texture_BounceGrid; + /// locations of detected uniforms in program object, or -1 if not found + int loc_Texture_First; + int loc_Texture_Second; + int loc_Texture_GammaRamps; + int loc_Texture_Normal; + int loc_Texture_Color; + int loc_Texture_Gloss; + int loc_Texture_Glow; + int loc_Texture_SecondaryNormal; + int loc_Texture_SecondaryColor; + int loc_Texture_SecondaryGloss; + int loc_Texture_SecondaryGlow; + int loc_Texture_Pants; + int loc_Texture_Shirt; + int loc_Texture_FogHeightTexture; + int loc_Texture_FogMask; + int loc_Texture_Lightmap; + int loc_Texture_Deluxemap; + int loc_Texture_Attenuation; + int loc_Texture_Cube; + int loc_Texture_Refraction; + int loc_Texture_Reflection; + int loc_Texture_ShadowMap2D; + int loc_Texture_CubeProjection; + int loc_Texture_ScreenNormalMap; + int loc_Texture_ScreenDiffuse; + int loc_Texture_ScreenSpecular; + int loc_Texture_ReflectMask; + int loc_Texture_ReflectCube; + int loc_Texture_BounceGrid; + int loc_Alpha; + int loc_BloomBlur_Parameters; + int loc_ClientTime; + int loc_Color_Ambient; + int loc_Color_Diffuse; + int loc_Color_Specular; + int loc_Color_Glow; + int loc_Color_Pants; + int loc_Color_Shirt; + int loc_DeferredColor_Ambient; + int loc_DeferredColor_Diffuse; + int loc_DeferredColor_Specular; + int loc_DeferredMod_Diffuse; + int loc_DeferredMod_Specular; + int loc_DistortScaleRefractReflect; + int loc_EyePosition; + int loc_FogColor; + int loc_FogHeightFade; + int loc_FogPlane; + int loc_FogPlaneViewDist; + int loc_FogRangeRecip; + int loc_LightColor; + int loc_LightDir; + int loc_LightPosition; + int loc_OffsetMapping_ScaleSteps; + int loc_OffsetMapping_LodDistance; + int loc_OffsetMapping_Bias; + int loc_PixelSize; + int loc_ReflectColor; + int loc_ReflectFactor; + int loc_ReflectOffset; + int loc_RefractColor; + int loc_Saturation; + int loc_ScreenCenterRefractReflect; + int loc_ScreenScaleRefractReflect; + int loc_ScreenToDepth; + int loc_ShadowMap_Parameters; + int loc_ShadowMap_TextureScale; + int loc_SpecularPower; + int loc_Skeletal_Transform12; + int loc_UserVec1; + int loc_UserVec2; + int loc_UserVec3; + int loc_UserVec4; + int loc_ViewTintColor; + int loc_ViewToLight; + int loc_ModelToLight; + int loc_TexMatrix; + int loc_BackgroundTexMatrix; + int loc_ModelViewProjectionMatrix; + int loc_ModelViewMatrix; + int loc_PixelToScreenTexCoord; + int loc_ModelToReflectCube; + int loc_ShadowMapMatrix; + int loc_BloomColorSubtract; + int loc_NormalmapScrollBlend; + int loc_BounceGridMatrix; + int loc_BounceGridIntensity; + /// uniform block bindings + int ubibind_Skeletal_Transform12_UniformBlock; + /// uniform block indices + int ubiloc_Skeletal_Transform12_UniformBlock; +} +r_glsl_permutation_t; + +#define SHADERPERMUTATION_HASHSIZE 256 + + +// non-degradable "lightweight" shader parameters to keep the permutations simpler +// these can NOT degrade! only use for simple stuff +enum +{ + SHADERSTATICPARM_SATURATION_REDCOMPENSATE = 0, ///< red compensation filter for saturation + SHADERSTATICPARM_EXACTSPECULARMATH = 1, ///< (lightsource or deluxemapping) use exact reflection map for specular effects, as opposed to the usual OpenGL approximation + SHADERSTATICPARM_POSTPROCESS_USERVEC1 = 2, ///< postprocess uservec1 is enabled + SHADERSTATICPARM_POSTPROCESS_USERVEC2 = 3, ///< postprocess uservec2 is enabled + SHADERSTATICPARM_POSTPROCESS_USERVEC3 = 4, ///< postprocess uservec3 is enabled + SHADERSTATICPARM_POSTPROCESS_USERVEC4 = 5, ///< postprocess uservec4 is enabled + SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS = 6, // use both alpha layers while blending materials, allows more advanced microblending + SHADERSTATICPARM_OFFSETMAPPING_USELOD = 7, ///< LOD for offsetmapping + SHADERSTATICPARM_SHADOWMAPPCF_1 = 8, ///< PCF 1 + SHADERSTATICPARM_SHADOWMAPPCF_2 = 9, ///< PCF 2 + SHADERSTATICPARM_SHADOWSAMPLER = 10, ///< sampler + SHADERSTATICPARM_CELSHADING = 11, ///< celshading (alternative diffuse and specular math) + SHADERSTATICPARM_CELOUTLINES = 12, ///< celoutline (depth buffer analysis to produce outlines) +}; +#define SHADERSTATICPARMS_COUNT 13 + +static const char *shaderstaticparmstrings_list[SHADERSTATICPARMS_COUNT]; +static int shaderstaticparms_count = 0; + +static unsigned int r_compileshader_staticparms[(SHADERSTATICPARMS_COUNT + 0x1F) >> 5] = {0}; +#define R_COMPILESHADER_STATICPARM_ENABLE(p) r_compileshader_staticparms[(p) >> 5] |= (1 << ((p) & 0x1F)) + +extern qboolean r_shadow_shadowmapsampler; +extern int r_shadow_shadowmappcf; +qboolean R_CompileShader_CheckStaticParms(void) +{ + static int r_compileshader_staticparms_save[1]; + memcpy(r_compileshader_staticparms_save, r_compileshader_staticparms, sizeof(r_compileshader_staticparms)); + memset(r_compileshader_staticparms, 0, sizeof(r_compileshader_staticparms)); + + // detect all + if (r_glsl_saturation_redcompensate.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SATURATION_REDCOMPENSATE); + if (r_glsl_vertextextureblend_usebothalphas.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS); + if (r_shadow_glossexact.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_EXACTSPECULARMATH); + if (r_glsl_postprocess.integer) + { + if (r_glsl_postprocess_uservec1_enable.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC1); + if (r_glsl_postprocess_uservec2_enable.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC2); + if (r_glsl_postprocess_uservec3_enable.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC3); + if (r_glsl_postprocess_uservec4_enable.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC4); + } + if (r_glsl_offsetmapping_lod.integer && r_glsl_offsetmapping_lod_distance.integer > 0) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_OFFSETMAPPING_USELOD); + + if (r_shadow_shadowmapsampler) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SHADOWSAMPLER); + if (r_shadow_shadowmappcf > 1) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SHADOWMAPPCF_2); + else if (r_shadow_shadowmappcf) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SHADOWMAPPCF_1); + if (r_celshading.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_CELSHADING); + if (r_celoutlines.integer) + R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_CELOUTLINES); + + return memcmp(r_compileshader_staticparms, r_compileshader_staticparms_save, sizeof(r_compileshader_staticparms)) != 0; +} + +#define R_COMPILESHADER_STATICPARM_EMIT(p, n) \ + if(r_compileshader_staticparms[(p) >> 5] & (1 << ((p) & 0x1F))) \ + shaderstaticparmstrings_list[shaderstaticparms_count++] = "#define " n "\n"; \ + else \ + shaderstaticparmstrings_list[shaderstaticparms_count++] = "\n" +static void R_CompileShader_AddStaticParms(unsigned int mode, unsigned int permutation) +{ + shaderstaticparms_count = 0; + + // emit all + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SATURATION_REDCOMPENSATE, "SATURATION_REDCOMPENSATE"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_EXACTSPECULARMATH, "USEEXACTSPECULARMATH"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC1, "USERVEC1"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC2, "USERVEC2"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC3, "USERVEC3"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC4, "USERVEC4"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS, "USEBOTHALPHAS"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_OFFSETMAPPING_USELOD, "USEOFFSETMAPPING_LOD"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SHADOWMAPPCF_1, "USESHADOWMAPPCF 1"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SHADOWMAPPCF_2, "USESHADOWMAPPCF 2"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SHADOWSAMPLER, "USESHADOWSAMPLER"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_CELSHADING, "USECELSHADING"); + R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_CELOUTLINES, "USECELOUTLINES"); +} + +/// information about each possible shader permutation +r_glsl_permutation_t *r_glsl_permutationhash[SHADERMODE_COUNT][SHADERPERMUTATION_HASHSIZE]; +/// currently selected permutation +r_glsl_permutation_t *r_glsl_permutation; +/// storage for permutations linked in the hash table +memexpandablearray_t r_glsl_permutationarray; + +static r_glsl_permutation_t *R_GLSL_FindPermutation(unsigned int mode, unsigned int permutation) +{ + //unsigned int hashdepth = 0; + unsigned int hashindex = (permutation * 0x1021) & (SHADERPERMUTATION_HASHSIZE - 1); + r_glsl_permutation_t *p; + for (p = r_glsl_permutationhash[mode][hashindex];p;p = p->hashnext) + { + if (p->mode == mode && p->permutation == permutation) + { + //if (hashdepth > 10) + // Con_Printf("R_GLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); + return p; + } + //hashdepth++; + } + p = (r_glsl_permutation_t*)Mem_ExpandableArray_AllocRecord(&r_glsl_permutationarray); + p->mode = mode; + p->permutation = permutation; + p->hashnext = r_glsl_permutationhash[mode][hashindex]; + r_glsl_permutationhash[mode][hashindex] = p; + //if (hashdepth > 10) + // Con_Printf("R_GLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); + return p; +} + +static char *R_ShaderStrCat(const char **strings) +{ + char *string, *s; + const char **p = strings; + const char *t; + size_t len = 0; + for (p = strings;(t = *p);p++) + len += strlen(t); + len++; + s = string = (char *)Mem_Alloc(r_main_mempool, len); + len = 0; + for (p = strings;(t = *p);p++) + { + len = strlen(t); + memcpy(s, t, len); + s += len; + } + *s = 0; + return string; +} + +static char *R_GetShaderText(const char *filename, qboolean printfromdisknotice, qboolean builtinonly) +{ + char *shaderstring; + if (!filename || !filename[0]) + return NULL; + // LordHavoc: note that FS_LoadFile appends a 0 byte to make it a valid string, so does R_ShaderStrCat + if (!strcmp(filename, "glsl/default.glsl")) + { + if (builtinonly) + return R_ShaderStrCat(builtinshaderstrings); + if (!glslshaderstring) + { + glslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); + if (glslshaderstring) + Con_DPrintf("Loading shaders from file %s...\n", filename); + else + glslshaderstring = R_ShaderStrCat(builtinshaderstrings); + } + shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(glslshaderstring) + 1); + memcpy(shaderstring, glslshaderstring, strlen(glslshaderstring) + 1); + return shaderstring; + } + if (!strcmp(filename, "hlsl/default.hlsl")) + { + if (builtinonly) + return R_ShaderStrCat(builtinhlslshaderstrings); + if (!hlslshaderstring) + { + hlslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); + if (hlslshaderstring) + Con_DPrintf("Loading shaders from file %s...\n", filename); + else + hlslshaderstring = R_ShaderStrCat(builtinhlslshaderstrings); + } + shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(hlslshaderstring) + 1); + memcpy(shaderstring, hlslshaderstring, strlen(hlslshaderstring) + 1); + return shaderstring; + } + // we don't have builtin strings for any other files + if (builtinonly) + return NULL; + shaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); + if (shaderstring) + { + if (printfromdisknotice) + Con_DPrintf("from disk %s... ", filename); + return shaderstring; + } + return shaderstring; +} + +static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode, unsigned int permutation) +{ + int i; + int ubibind; + int sampler; + shadermodeinfo_t *modeinfo = glslshadermodeinfo + mode; + char *sourcestring; + char permutationname[256]; + int vertstrings_count = 0; + int geomstrings_count = 0; + int fragstrings_count = 0; + const char *vertstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; + const char *geomstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; + const char *fragstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; + + if (p->compiled) + return; + p->compiled = true; + p->program = 0; + + permutationname[0] = 0; + sourcestring = R_GetShaderText(modeinfo->filename, true, false); + + strlcat(permutationname, modeinfo->filename, sizeof(permutationname)); + + // we need 140 for r_glsl_skeletal (GL_ARB_uniform_buffer_object) + if(vid.support.glshaderversion >= 140) + { + vertstrings_list[vertstrings_count++] = "#version 140\n"; + geomstrings_list[geomstrings_count++] = "#version 140\n"; + fragstrings_list[fragstrings_count++] = "#version 140\n"; + vertstrings_list[vertstrings_count++] = "#define GLSL140\n"; + geomstrings_list[geomstrings_count++] = "#define GLSL140\n"; + fragstrings_list[fragstrings_count++] = "#define GLSL140\n"; + } + // if we can do #version 130, we should (this improves quality of offset/reliefmapping thanks to textureGrad) + else if(vid.support.glshaderversion >= 130) + { + vertstrings_list[vertstrings_count++] = "#version 130\n"; + geomstrings_list[geomstrings_count++] = "#version 130\n"; + fragstrings_list[fragstrings_count++] = "#version 130\n"; + vertstrings_list[vertstrings_count++] = "#define GLSL130\n"; + geomstrings_list[geomstrings_count++] = "#define GLSL130\n"; + fragstrings_list[fragstrings_count++] = "#define GLSL130\n"; + } + + // the first pretext is which type of shader to compile as + // (later these will all be bound together as a program object) + vertstrings_list[vertstrings_count++] = "#define VERTEX_SHADER\n"; + geomstrings_list[geomstrings_count++] = "#define GEOMETRY_SHADER\n"; + fragstrings_list[fragstrings_count++] = "#define FRAGMENT_SHADER\n"; + + // the second pretext is the mode (for example a light source) + vertstrings_list[vertstrings_count++] = modeinfo->pretext; + geomstrings_list[geomstrings_count++] = modeinfo->pretext; + fragstrings_list[fragstrings_count++] = modeinfo->pretext; + strlcat(permutationname, modeinfo->name, sizeof(permutationname)); + + // now add all the permutation pretexts + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + { + if (permutation & (1<program = GL_Backend_CompileProgram(vertstrings_count, vertstrings_list, geomstrings_count, geomstrings_list, fragstrings_count, fragstrings_list); + if (p->program) + { + CHECKGLERROR + qglUseProgram(p->program);CHECKGLERROR + // look up all the uniform variable names we care about, so we don't + // have to look them up every time we set them + + p->loc_Texture_First = qglGetUniformLocation(p->program, "Texture_First"); + p->loc_Texture_Second = qglGetUniformLocation(p->program, "Texture_Second"); + p->loc_Texture_GammaRamps = qglGetUniformLocation(p->program, "Texture_GammaRamps"); + p->loc_Texture_Normal = qglGetUniformLocation(p->program, "Texture_Normal"); + p->loc_Texture_Color = qglGetUniformLocation(p->program, "Texture_Color"); + p->loc_Texture_Gloss = qglGetUniformLocation(p->program, "Texture_Gloss"); + p->loc_Texture_Glow = qglGetUniformLocation(p->program, "Texture_Glow"); + p->loc_Texture_SecondaryNormal = qglGetUniformLocation(p->program, "Texture_SecondaryNormal"); + p->loc_Texture_SecondaryColor = qglGetUniformLocation(p->program, "Texture_SecondaryColor"); + p->loc_Texture_SecondaryGloss = qglGetUniformLocation(p->program, "Texture_SecondaryGloss"); + p->loc_Texture_SecondaryGlow = qglGetUniformLocation(p->program, "Texture_SecondaryGlow"); + p->loc_Texture_Pants = qglGetUniformLocation(p->program, "Texture_Pants"); + p->loc_Texture_Shirt = qglGetUniformLocation(p->program, "Texture_Shirt"); + p->loc_Texture_FogHeightTexture = qglGetUniformLocation(p->program, "Texture_FogHeightTexture"); + p->loc_Texture_FogMask = qglGetUniformLocation(p->program, "Texture_FogMask"); + p->loc_Texture_Lightmap = qglGetUniformLocation(p->program, "Texture_Lightmap"); + p->loc_Texture_Deluxemap = qglGetUniformLocation(p->program, "Texture_Deluxemap"); + p->loc_Texture_Attenuation = qglGetUniformLocation(p->program, "Texture_Attenuation"); + p->loc_Texture_Cube = qglGetUniformLocation(p->program, "Texture_Cube"); + p->loc_Texture_Refraction = qglGetUniformLocation(p->program, "Texture_Refraction"); + p->loc_Texture_Reflection = qglGetUniformLocation(p->program, "Texture_Reflection"); + p->loc_Texture_ShadowMap2D = qglGetUniformLocation(p->program, "Texture_ShadowMap2D"); + p->loc_Texture_CubeProjection = qglGetUniformLocation(p->program, "Texture_CubeProjection"); + p->loc_Texture_ScreenNormalMap = qglGetUniformLocation(p->program, "Texture_ScreenNormalMap"); + p->loc_Texture_ScreenDiffuse = qglGetUniformLocation(p->program, "Texture_ScreenDiffuse"); + p->loc_Texture_ScreenSpecular = qglGetUniformLocation(p->program, "Texture_ScreenSpecular"); + p->loc_Texture_ReflectMask = qglGetUniformLocation(p->program, "Texture_ReflectMask"); + p->loc_Texture_ReflectCube = qglGetUniformLocation(p->program, "Texture_ReflectCube"); + p->loc_Texture_BounceGrid = qglGetUniformLocation(p->program, "Texture_BounceGrid"); + p->loc_Alpha = qglGetUniformLocation(p->program, "Alpha"); + p->loc_BloomBlur_Parameters = qglGetUniformLocation(p->program, "BloomBlur_Parameters"); + p->loc_ClientTime = qglGetUniformLocation(p->program, "ClientTime"); + p->loc_Color_Ambient = qglGetUniformLocation(p->program, "Color_Ambient"); + p->loc_Color_Diffuse = qglGetUniformLocation(p->program, "Color_Diffuse"); + p->loc_Color_Specular = qglGetUniformLocation(p->program, "Color_Specular"); + p->loc_Color_Glow = qglGetUniformLocation(p->program, "Color_Glow"); + p->loc_Color_Pants = qglGetUniformLocation(p->program, "Color_Pants"); + p->loc_Color_Shirt = qglGetUniformLocation(p->program, "Color_Shirt"); + p->loc_DeferredColor_Ambient = qglGetUniformLocation(p->program, "DeferredColor_Ambient"); + p->loc_DeferredColor_Diffuse = qglGetUniformLocation(p->program, "DeferredColor_Diffuse"); + p->loc_DeferredColor_Specular = qglGetUniformLocation(p->program, "DeferredColor_Specular"); + p->loc_DeferredMod_Diffuse = qglGetUniformLocation(p->program, "DeferredMod_Diffuse"); + p->loc_DeferredMod_Specular = qglGetUniformLocation(p->program, "DeferredMod_Specular"); + p->loc_DistortScaleRefractReflect = qglGetUniformLocation(p->program, "DistortScaleRefractReflect"); + p->loc_EyePosition = qglGetUniformLocation(p->program, "EyePosition"); + p->loc_FogColor = qglGetUniformLocation(p->program, "FogColor"); + p->loc_FogHeightFade = qglGetUniformLocation(p->program, "FogHeightFade"); + p->loc_FogPlane = qglGetUniformLocation(p->program, "FogPlane"); + p->loc_FogPlaneViewDist = qglGetUniformLocation(p->program, "FogPlaneViewDist"); + p->loc_FogRangeRecip = qglGetUniformLocation(p->program, "FogRangeRecip"); + p->loc_LightColor = qglGetUniformLocation(p->program, "LightColor"); + p->loc_LightDir = qglGetUniformLocation(p->program, "LightDir"); + p->loc_LightPosition = qglGetUniformLocation(p->program, "LightPosition"); + p->loc_OffsetMapping_ScaleSteps = qglGetUniformLocation(p->program, "OffsetMapping_ScaleSteps"); + p->loc_OffsetMapping_LodDistance = qglGetUniformLocation(p->program, "OffsetMapping_LodDistance"); + p->loc_OffsetMapping_Bias = qglGetUniformLocation(p->program, "OffsetMapping_Bias"); + p->loc_PixelSize = qglGetUniformLocation(p->program, "PixelSize"); + p->loc_ReflectColor = qglGetUniformLocation(p->program, "ReflectColor"); + p->loc_ReflectFactor = qglGetUniformLocation(p->program, "ReflectFactor"); + p->loc_ReflectOffset = qglGetUniformLocation(p->program, "ReflectOffset"); + p->loc_RefractColor = qglGetUniformLocation(p->program, "RefractColor"); + p->loc_Saturation = qglGetUniformLocation(p->program, "Saturation"); + p->loc_ScreenCenterRefractReflect = qglGetUniformLocation(p->program, "ScreenCenterRefractReflect"); + p->loc_ScreenScaleRefractReflect = qglGetUniformLocation(p->program, "ScreenScaleRefractReflect"); + p->loc_ScreenToDepth = qglGetUniformLocation(p->program, "ScreenToDepth"); + p->loc_ShadowMap_Parameters = qglGetUniformLocation(p->program, "ShadowMap_Parameters"); + p->loc_ShadowMap_TextureScale = qglGetUniformLocation(p->program, "ShadowMap_TextureScale"); + p->loc_SpecularPower = qglGetUniformLocation(p->program, "SpecularPower"); + p->loc_UserVec1 = qglGetUniformLocation(p->program, "UserVec1"); + p->loc_UserVec2 = qglGetUniformLocation(p->program, "UserVec2"); + p->loc_UserVec3 = qglGetUniformLocation(p->program, "UserVec3"); + p->loc_UserVec4 = qglGetUniformLocation(p->program, "UserVec4"); + p->loc_ViewTintColor = qglGetUniformLocation(p->program, "ViewTintColor"); + p->loc_ViewToLight = qglGetUniformLocation(p->program, "ViewToLight"); + p->loc_ModelToLight = qglGetUniformLocation(p->program, "ModelToLight"); + p->loc_TexMatrix = qglGetUniformLocation(p->program, "TexMatrix"); + p->loc_BackgroundTexMatrix = qglGetUniformLocation(p->program, "BackgroundTexMatrix"); + p->loc_ModelViewMatrix = qglGetUniformLocation(p->program, "ModelViewMatrix"); + p->loc_ModelViewProjectionMatrix = qglGetUniformLocation(p->program, "ModelViewProjectionMatrix"); + p->loc_PixelToScreenTexCoord = qglGetUniformLocation(p->program, "PixelToScreenTexCoord"); + p->loc_ModelToReflectCube = qglGetUniformLocation(p->program, "ModelToReflectCube"); + p->loc_ShadowMapMatrix = qglGetUniformLocation(p->program, "ShadowMapMatrix"); + p->loc_BloomColorSubtract = qglGetUniformLocation(p->program, "BloomColorSubtract"); + p->loc_NormalmapScrollBlend = qglGetUniformLocation(p->program, "NormalmapScrollBlend"); + p->loc_BounceGridMatrix = qglGetUniformLocation(p->program, "BounceGridMatrix"); + p->loc_BounceGridIntensity = qglGetUniformLocation(p->program, "BounceGridIntensity"); + // initialize the samplers to refer to the texture units we use + p->tex_Texture_First = -1; + p->tex_Texture_Second = -1; + p->tex_Texture_GammaRamps = -1; + p->tex_Texture_Normal = -1; + p->tex_Texture_Color = -1; + p->tex_Texture_Gloss = -1; + p->tex_Texture_Glow = -1; + p->tex_Texture_SecondaryNormal = -1; + p->tex_Texture_SecondaryColor = -1; + p->tex_Texture_SecondaryGloss = -1; + p->tex_Texture_SecondaryGlow = -1; + p->tex_Texture_Pants = -1; + p->tex_Texture_Shirt = -1; + p->tex_Texture_FogHeightTexture = -1; + p->tex_Texture_FogMask = -1; + p->tex_Texture_Lightmap = -1; + p->tex_Texture_Deluxemap = -1; + p->tex_Texture_Attenuation = -1; + p->tex_Texture_Cube = -1; + p->tex_Texture_Refraction = -1; + p->tex_Texture_Reflection = -1; + p->tex_Texture_ShadowMap2D = -1; + p->tex_Texture_CubeProjection = -1; + p->tex_Texture_ScreenNormalMap = -1; + p->tex_Texture_ScreenDiffuse = -1; + p->tex_Texture_ScreenSpecular = -1; + p->tex_Texture_ReflectMask = -1; + p->tex_Texture_ReflectCube = -1; + p->tex_Texture_BounceGrid = -1; + // bind the texture samplers in use + sampler = 0; + if (p->loc_Texture_First >= 0) {p->tex_Texture_First = sampler;qglUniform1i(p->loc_Texture_First , sampler);sampler++;} + if (p->loc_Texture_Second >= 0) {p->tex_Texture_Second = sampler;qglUniform1i(p->loc_Texture_Second , sampler);sampler++;} + if (p->loc_Texture_GammaRamps >= 0) {p->tex_Texture_GammaRamps = sampler;qglUniform1i(p->loc_Texture_GammaRamps , sampler);sampler++;} + if (p->loc_Texture_Normal >= 0) {p->tex_Texture_Normal = sampler;qglUniform1i(p->loc_Texture_Normal , sampler);sampler++;} + if (p->loc_Texture_Color >= 0) {p->tex_Texture_Color = sampler;qglUniform1i(p->loc_Texture_Color , sampler);sampler++;} + if (p->loc_Texture_Gloss >= 0) {p->tex_Texture_Gloss = sampler;qglUniform1i(p->loc_Texture_Gloss , sampler);sampler++;} + if (p->loc_Texture_Glow >= 0) {p->tex_Texture_Glow = sampler;qglUniform1i(p->loc_Texture_Glow , sampler);sampler++;} + if (p->loc_Texture_SecondaryNormal >= 0) {p->tex_Texture_SecondaryNormal = sampler;qglUniform1i(p->loc_Texture_SecondaryNormal , sampler);sampler++;} + if (p->loc_Texture_SecondaryColor >= 0) {p->tex_Texture_SecondaryColor = sampler;qglUniform1i(p->loc_Texture_SecondaryColor , sampler);sampler++;} + if (p->loc_Texture_SecondaryGloss >= 0) {p->tex_Texture_SecondaryGloss = sampler;qglUniform1i(p->loc_Texture_SecondaryGloss , sampler);sampler++;} + if (p->loc_Texture_SecondaryGlow >= 0) {p->tex_Texture_SecondaryGlow = sampler;qglUniform1i(p->loc_Texture_SecondaryGlow , sampler);sampler++;} + if (p->loc_Texture_Pants >= 0) {p->tex_Texture_Pants = sampler;qglUniform1i(p->loc_Texture_Pants , sampler);sampler++;} + if (p->loc_Texture_Shirt >= 0) {p->tex_Texture_Shirt = sampler;qglUniform1i(p->loc_Texture_Shirt , sampler);sampler++;} + if (p->loc_Texture_FogHeightTexture>= 0) {p->tex_Texture_FogHeightTexture = sampler;qglUniform1i(p->loc_Texture_FogHeightTexture, sampler);sampler++;} + if (p->loc_Texture_FogMask >= 0) {p->tex_Texture_FogMask = sampler;qglUniform1i(p->loc_Texture_FogMask , sampler);sampler++;} + if (p->loc_Texture_Lightmap >= 0) {p->tex_Texture_Lightmap = sampler;qglUniform1i(p->loc_Texture_Lightmap , sampler);sampler++;} + if (p->loc_Texture_Deluxemap >= 0) {p->tex_Texture_Deluxemap = sampler;qglUniform1i(p->loc_Texture_Deluxemap , sampler);sampler++;} + if (p->loc_Texture_Attenuation >= 0) {p->tex_Texture_Attenuation = sampler;qglUniform1i(p->loc_Texture_Attenuation , sampler);sampler++;} + if (p->loc_Texture_Cube >= 0) {p->tex_Texture_Cube = sampler;qglUniform1i(p->loc_Texture_Cube , sampler);sampler++;} + if (p->loc_Texture_Refraction >= 0) {p->tex_Texture_Refraction = sampler;qglUniform1i(p->loc_Texture_Refraction , sampler);sampler++;} + if (p->loc_Texture_Reflection >= 0) {p->tex_Texture_Reflection = sampler;qglUniform1i(p->loc_Texture_Reflection , sampler);sampler++;} + if (p->loc_Texture_ShadowMap2D >= 0) {p->tex_Texture_ShadowMap2D = sampler;qglUniform1i(p->loc_Texture_ShadowMap2D , sampler);sampler++;} + if (p->loc_Texture_CubeProjection >= 0) {p->tex_Texture_CubeProjection = sampler;qglUniform1i(p->loc_Texture_CubeProjection , sampler);sampler++;} + if (p->loc_Texture_ScreenNormalMap >= 0) {p->tex_Texture_ScreenNormalMap = sampler;qglUniform1i(p->loc_Texture_ScreenNormalMap , sampler);sampler++;} + if (p->loc_Texture_ScreenDiffuse >= 0) {p->tex_Texture_ScreenDiffuse = sampler;qglUniform1i(p->loc_Texture_ScreenDiffuse , sampler);sampler++;} + if (p->loc_Texture_ScreenSpecular >= 0) {p->tex_Texture_ScreenSpecular = sampler;qglUniform1i(p->loc_Texture_ScreenSpecular , sampler);sampler++;} + if (p->loc_Texture_ReflectMask >= 0) {p->tex_Texture_ReflectMask = sampler;qglUniform1i(p->loc_Texture_ReflectMask , sampler);sampler++;} + if (p->loc_Texture_ReflectCube >= 0) {p->tex_Texture_ReflectCube = sampler;qglUniform1i(p->loc_Texture_ReflectCube , sampler);sampler++;} + if (p->loc_Texture_BounceGrid >= 0) {p->tex_Texture_BounceGrid = sampler;qglUniform1i(p->loc_Texture_BounceGrid , sampler);sampler++;} + // get the uniform block indices so we can bind them + if (vid.support.arb_uniform_buffer_object) + p->ubiloc_Skeletal_Transform12_UniformBlock = qglGetUniformBlockIndex(p->program, "Skeletal_Transform12_UniformBlock"); + else + p->ubiloc_Skeletal_Transform12_UniformBlock = -1; + // clear the uniform block bindings + p->ubibind_Skeletal_Transform12_UniformBlock = -1; + // bind the uniform blocks in use + ubibind = 0; + if (p->ubiloc_Skeletal_Transform12_UniformBlock >= 0) {p->ubibind_Skeletal_Transform12_UniformBlock = ubibind;qglUniformBlockBinding(p->program, p->ubiloc_Skeletal_Transform12_UniformBlock, ubibind);ubibind++;} + // we're done compiling and setting up the shader, at least until it is used + CHECKGLERROR + Con_DPrintf("^5GLSL shader %s compiled (%i textures).\n", permutationname, sampler); + } + else + Con_Printf("^1GLSL shader %s failed! some features may not work properly.\n", permutationname); + + // free the strings + if (sourcestring) + Mem_Free(sourcestring); +} + +static void R_SetupShader_SetPermutationGLSL(unsigned int mode, unsigned int permutation) +{ + r_glsl_permutation_t *perm = R_GLSL_FindPermutation(mode, permutation); + if (r_glsl_permutation != perm) + { + r_glsl_permutation = perm; + if (!r_glsl_permutation->program) + { + if (!r_glsl_permutation->compiled) + R_GLSL_CompilePermutation(perm, mode, permutation); + if (!r_glsl_permutation->program) + { + // remove features until we find a valid permutation + int i; + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + { + // reduce i more quickly whenever it would not remove any bits + int j = 1<<(SHADERPERMUTATION_COUNT-1-i); + if (!(permutation & j)) + continue; + permutation -= j; + r_glsl_permutation = R_GLSL_FindPermutation(mode, permutation); + if (!r_glsl_permutation->compiled) + R_GLSL_CompilePermutation(perm, mode, permutation); + if (r_glsl_permutation->program) + break; + } + if (i >= SHADERPERMUTATION_COUNT) + { + //Con_Printf("Could not find a working OpenGL 2.0 shader for permutation %s %s\n", shadermodeinfo[mode].filename, shadermodeinfo[mode].pretext); + r_glsl_permutation = R_GLSL_FindPermutation(mode, permutation); + qglUseProgram(0);CHECKGLERROR + return; // no bit left to clear, entire mode is broken + } + } + } + CHECKGLERROR + qglUseProgram(r_glsl_permutation->program);CHECKGLERROR + } + if (r_glsl_permutation->loc_ModelViewProjectionMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewProjectionMatrix, 1, false, gl_modelviewprojection16f); + if (r_glsl_permutation->loc_ModelViewMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewMatrix, 1, false, gl_modelview16f); + if (r_glsl_permutation->loc_ClientTime >= 0) qglUniform1f(r_glsl_permutation->loc_ClientTime, cl.time); + CHECKGLERROR +} + +#ifdef SUPPORTD3D + +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +extern D3DCAPS9 vid_d3d9caps; +#endif + +struct r_hlsl_permutation_s; +typedef struct r_hlsl_permutation_s +{ + /// hash lookup data + struct r_hlsl_permutation_s *hashnext; + unsigned int mode; + unsigned int permutation; + + /// indicates if we have tried compiling this permutation already + qboolean compiled; + /// NULL if compilation failed + IDirect3DVertexShader9 *vertexshader; + IDirect3DPixelShader9 *pixelshader; +} +r_hlsl_permutation_t; + +typedef enum D3DVSREGISTER_e +{ + D3DVSREGISTER_TexMatrix = 0, // float4x4 + D3DVSREGISTER_BackgroundTexMatrix = 4, // float4x4 + D3DVSREGISTER_ModelViewProjectionMatrix = 8, // float4x4 + D3DVSREGISTER_ModelViewMatrix = 12, // float4x4 + D3DVSREGISTER_ShadowMapMatrix = 16, // float4x4 + D3DVSREGISTER_ModelToLight = 20, // float4x4 + D3DVSREGISTER_EyePosition = 24, + D3DVSREGISTER_FogPlane = 25, + D3DVSREGISTER_LightDir = 26, + D3DVSREGISTER_LightPosition = 27, +} +D3DVSREGISTER_t; + +typedef enum D3DPSREGISTER_e +{ + D3DPSREGISTER_Alpha = 0, + D3DPSREGISTER_BloomBlur_Parameters = 1, + D3DPSREGISTER_ClientTime = 2, + D3DPSREGISTER_Color_Ambient = 3, + D3DPSREGISTER_Color_Diffuse = 4, + D3DPSREGISTER_Color_Specular = 5, + D3DPSREGISTER_Color_Glow = 6, + D3DPSREGISTER_Color_Pants = 7, + D3DPSREGISTER_Color_Shirt = 8, + D3DPSREGISTER_DeferredColor_Ambient = 9, + D3DPSREGISTER_DeferredColor_Diffuse = 10, + D3DPSREGISTER_DeferredColor_Specular = 11, + D3DPSREGISTER_DeferredMod_Diffuse = 12, + D3DPSREGISTER_DeferredMod_Specular = 13, + D3DPSREGISTER_DistortScaleRefractReflect = 14, + D3DPSREGISTER_EyePosition = 15, // unused + D3DPSREGISTER_FogColor = 16, + D3DPSREGISTER_FogHeightFade = 17, + D3DPSREGISTER_FogPlane = 18, + D3DPSREGISTER_FogPlaneViewDist = 19, + D3DPSREGISTER_FogRangeRecip = 20, + D3DPSREGISTER_LightColor = 21, + D3DPSREGISTER_LightDir = 22, // unused + D3DPSREGISTER_LightPosition = 23, + D3DPSREGISTER_OffsetMapping_ScaleSteps = 24, + D3DPSREGISTER_PixelSize = 25, + D3DPSREGISTER_ReflectColor = 26, + D3DPSREGISTER_ReflectFactor = 27, + D3DPSREGISTER_ReflectOffset = 28, + D3DPSREGISTER_RefractColor = 29, + D3DPSREGISTER_Saturation = 30, + D3DPSREGISTER_ScreenCenterRefractReflect = 31, + D3DPSREGISTER_ScreenScaleRefractReflect = 32, + D3DPSREGISTER_ScreenToDepth = 33, + D3DPSREGISTER_ShadowMap_Parameters = 34, + D3DPSREGISTER_ShadowMap_TextureScale = 35, + D3DPSREGISTER_SpecularPower = 36, + D3DPSREGISTER_UserVec1 = 37, + D3DPSREGISTER_UserVec2 = 38, + D3DPSREGISTER_UserVec3 = 39, + D3DPSREGISTER_UserVec4 = 40, + D3DPSREGISTER_ViewTintColor = 41, + D3DPSREGISTER_PixelToScreenTexCoord = 42, + D3DPSREGISTER_BloomColorSubtract = 43, + D3DPSREGISTER_ViewToLight = 44, // float4x4 + D3DPSREGISTER_ModelToReflectCube = 48, // float4x4 + D3DPSREGISTER_NormalmapScrollBlend = 52, + D3DPSREGISTER_OffsetMapping_LodDistance = 53, + D3DPSREGISTER_OffsetMapping_Bias = 54, + // next at 54 +} +D3DPSREGISTER_t; + +/// information about each possible shader permutation +r_hlsl_permutation_t *r_hlsl_permutationhash[SHADERMODE_COUNT][SHADERPERMUTATION_HASHSIZE]; +/// currently selected permutation +r_hlsl_permutation_t *r_hlsl_permutation; +/// storage for permutations linked in the hash table +memexpandablearray_t r_hlsl_permutationarray; + +static r_hlsl_permutation_t *R_HLSL_FindPermutation(unsigned int mode, unsigned int permutation) +{ + //unsigned int hashdepth = 0; + unsigned int hashindex = (permutation * 0x1021) & (SHADERPERMUTATION_HASHSIZE - 1); + r_hlsl_permutation_t *p; + for (p = r_hlsl_permutationhash[mode][hashindex];p;p = p->hashnext) + { + if (p->mode == mode && p->permutation == permutation) + { + //if (hashdepth > 10) + // Con_Printf("R_HLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); + return p; + } + //hashdepth++; + } + p = (r_hlsl_permutation_t*)Mem_ExpandableArray_AllocRecord(&r_hlsl_permutationarray); + p->mode = mode; + p->permutation = permutation; + p->hashnext = r_hlsl_permutationhash[mode][hashindex]; + r_hlsl_permutationhash[mode][hashindex] = p; + //if (hashdepth > 10) + // Con_Printf("R_HLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); + return p; +} + +#include +//#include +//#include + +static void R_HLSL_CacheShader(r_hlsl_permutation_t *p, const char *cachename, const char *vertstring, const char *fragstring) +{ + DWORD *vsbin = NULL; + DWORD *psbin = NULL; + fs_offset_t vsbinsize; + fs_offset_t psbinsize; +// IDirect3DVertexShader9 *vs = NULL; +// IDirect3DPixelShader9 *ps = NULL; + ID3DXBuffer *vslog = NULL; + ID3DXBuffer *vsbuffer = NULL; + ID3DXConstantTable *vsconstanttable = NULL; + ID3DXBuffer *pslog = NULL; + ID3DXBuffer *psbuffer = NULL; + ID3DXConstantTable *psconstanttable = NULL; + int vsresult = 0; + int psresult = 0; + char temp[MAX_INPUTLINE]; + const char *vsversion = "vs_3_0", *psversion = "ps_3_0"; + char vabuf[1024]; + qboolean debugshader = gl_paranoid.integer != 0; + if (p->permutation & SHADERPERMUTATION_OFFSETMAPPING) {vsversion = "vs_3_0";psversion = "ps_3_0";} + if (p->permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) {vsversion = "vs_3_0";psversion = "ps_3_0";} + if (!debugshader) + { + vsbin = (DWORD *)FS_LoadFile(va(vabuf, sizeof(vabuf), "%s.vsbin", cachename), r_main_mempool, true, &vsbinsize); + psbin = (DWORD *)FS_LoadFile(va(vabuf, sizeof(vabuf), "%s.psbin", cachename), r_main_mempool, true, &psbinsize); + } + if ((!vsbin && vertstring) || (!psbin && fragstring)) + { + const char* dllnames_d3dx9 [] = + { + "d3dx9_43.dll", + "d3dx9_42.dll", + "d3dx9_41.dll", + "d3dx9_40.dll", + "d3dx9_39.dll", + "d3dx9_38.dll", + "d3dx9_37.dll", + "d3dx9_36.dll", + "d3dx9_35.dll", + "d3dx9_34.dll", + "d3dx9_33.dll", + "d3dx9_32.dll", + "d3dx9_31.dll", + "d3dx9_30.dll", + "d3dx9_29.dll", + "d3dx9_28.dll", + "d3dx9_27.dll", + "d3dx9_26.dll", + "d3dx9_25.dll", + "d3dx9_24.dll", + NULL + }; + dllhandle_t d3dx9_dll = NULL; + HRESULT (WINAPI *qD3DXCompileShaderFromFileA)(LPCSTR pSrcFile, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPCSTR pFunctionName, LPCSTR pProfile, DWORD Flags, LPD3DXBUFFER* ppShader, LPD3DXBUFFER* ppErrorMsgs, LPD3DXCONSTANTTABLE* ppConstantTable); + HRESULT (WINAPI *qD3DXPreprocessShader)(LPCSTR pSrcData, UINT SrcDataSize, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPD3DXBUFFER* ppShaderText, LPD3DXBUFFER* ppErrorMsgs); + HRESULT (WINAPI *qD3DXCompileShader)(LPCSTR pSrcData, UINT SrcDataLen, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPCSTR pFunctionName, LPCSTR pProfile, DWORD Flags, LPD3DXBUFFER* ppShader, LPD3DXBUFFER* ppErrorMsgs, LPD3DXCONSTANTTABLE* ppConstantTable); + dllfunction_t d3dx9_dllfuncs[] = + { + {"D3DXCompileShaderFromFileA", (void **) &qD3DXCompileShaderFromFileA}, + {"D3DXPreprocessShader", (void **) &qD3DXPreprocessShader}, + {"D3DXCompileShader", (void **) &qD3DXCompileShader}, + {NULL, NULL} + }; + // LordHavoc: the June 2010 SDK lacks these macros to make ID3DXBuffer usable in C, and to make it work in both C and C++ the macros are needed... +#ifndef ID3DXBuffer_GetBufferPointer +#if !defined(__cplusplus) || defined(CINTERFACE) +#define ID3DXBuffer_GetBufferPointer(p) (p)->lpVtbl->GetBufferPointer(p) +#define ID3DXBuffer_GetBufferSize(p) (p)->lpVtbl->GetBufferSize(p) +#define ID3DXBuffer_Release(p) (p)->lpVtbl->Release(p) +#else +#define ID3DXBuffer_GetBufferPointer(p) (p)->GetBufferPointer() +#define ID3DXBuffer_GetBufferSize(p) (p)->GetBufferSize() +#define ID3DXBuffer_Release(p) (p)->Release() +#endif +#endif + if (Sys_LoadLibrary(dllnames_d3dx9, &d3dx9_dll, d3dx9_dllfuncs)) + { + DWORD shaderflags = 0; + if (debugshader) + shaderflags = D3DXSHADER_DEBUG | D3DXSHADER_SKIPOPTIMIZATION; + vsbin = (DWORD *)Mem_Realloc(tempmempool, vsbin, 0); + psbin = (DWORD *)Mem_Realloc(tempmempool, psbin, 0); + if (vertstring && vertstring[0]) + { + if (debugshader) + { + FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_vs.fx", cachename), vertstring, strlen(vertstring)); + vsresult = qD3DXCompileShaderFromFileA(va(vabuf, sizeof(vabuf), "%s/%s_vs.fx", fs_gamedir, cachename), NULL, NULL, "main", vsversion, shaderflags, &vsbuffer, &vslog, &vsconstanttable); + } + else + vsresult = qD3DXCompileShader(vertstring, strlen(vertstring), NULL, NULL, "main", vsversion, shaderflags, &vsbuffer, &vslog, &vsconstanttable); + if (vsbuffer) + { + vsbinsize = ID3DXBuffer_GetBufferSize(vsbuffer); + vsbin = (DWORD *)Mem_Alloc(tempmempool, vsbinsize); + memcpy(vsbin, ID3DXBuffer_GetBufferPointer(vsbuffer), vsbinsize); + ID3DXBuffer_Release(vsbuffer); + } + if (vslog) + { + strlcpy(temp, (const char *)ID3DXBuffer_GetBufferPointer(vslog), min(sizeof(temp), ID3DXBuffer_GetBufferSize(vslog))); + Con_DPrintf("HLSL vertex shader compile output for %s follows:\n%s\n", cachename, temp); + ID3DXBuffer_Release(vslog); + } + } + if (fragstring && fragstring[0]) + { + if (debugshader) + { + FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_ps.fx", cachename), fragstring, strlen(fragstring)); + psresult = qD3DXCompileShaderFromFileA(va(vabuf, sizeof(vabuf), "%s/%s_ps.fx", fs_gamedir, cachename), NULL, NULL, "main", psversion, shaderflags, &psbuffer, &pslog, &psconstanttable); + } + else + psresult = qD3DXCompileShader(fragstring, strlen(fragstring), NULL, NULL, "main", psversion, shaderflags, &psbuffer, &pslog, &psconstanttable); + if (psbuffer) + { + psbinsize = ID3DXBuffer_GetBufferSize(psbuffer); + psbin = (DWORD *)Mem_Alloc(tempmempool, psbinsize); + memcpy(psbin, ID3DXBuffer_GetBufferPointer(psbuffer), psbinsize); + ID3DXBuffer_Release(psbuffer); + } + if (pslog) + { + strlcpy(temp, (const char *)ID3DXBuffer_GetBufferPointer(pslog), min(sizeof(temp), ID3DXBuffer_GetBufferSize(pslog))); + Con_DPrintf("HLSL pixel shader compile output for %s follows:\n%s\n", cachename, temp); + ID3DXBuffer_Release(pslog); + } + } + Sys_UnloadLibrary(&d3dx9_dll); + } + else + Con_DPrintf("Unable to compile shader - D3DXCompileShader function not found\n"); + } + if (vsbin && psbin) + { + vsresult = IDirect3DDevice9_CreateVertexShader(vid_d3d9dev, vsbin, &p->vertexshader); + if (FAILED(vsresult)) + Con_DPrintf("HLSL CreateVertexShader failed for %s (hresult = %8x)\n", cachename, vsresult); + psresult = IDirect3DDevice9_CreatePixelShader(vid_d3d9dev, psbin, &p->pixelshader); + if (FAILED(psresult)) + Con_DPrintf("HLSL CreatePixelShader failed for %s (hresult = %8x)\n", cachename, psresult); + } + // free the shader data + vsbin = (DWORD *)Mem_Realloc(tempmempool, vsbin, 0); + psbin = (DWORD *)Mem_Realloc(tempmempool, psbin, 0); +} + +static void R_HLSL_CompilePermutation(r_hlsl_permutation_t *p, unsigned int mode, unsigned int permutation) +{ + int i; + shadermodeinfo_t *modeinfo = hlslshadermodeinfo + mode; + int vertstring_length = 0; + int geomstring_length = 0; + int fragstring_length = 0; + char *t; + char *sourcestring; + char *vertstring, *geomstring, *fragstring; + char permutationname[256]; + char cachename[256]; + int vertstrings_count = 0; + int geomstrings_count = 0; + int fragstrings_count = 0; + const char *vertstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; + const char *geomstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; + const char *fragstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; + + if (p->compiled) + return; + p->compiled = true; + p->vertexshader = NULL; + p->pixelshader = NULL; + + permutationname[0] = 0; + cachename[0] = 0; + sourcestring = R_GetShaderText(modeinfo->filename, true, false); + + strlcat(permutationname, modeinfo->filename, sizeof(permutationname)); + strlcat(cachename, "hlsl/", sizeof(cachename)); + + // define HLSL so that the shader can tell apart the HLSL compiler and the Cg compiler + vertstrings_count = 0; + geomstrings_count = 0; + fragstrings_count = 0; + vertstrings_list[vertstrings_count++] = "#define HLSL\n"; + geomstrings_list[geomstrings_count++] = "#define HLSL\n"; + fragstrings_list[fragstrings_count++] = "#define HLSL\n"; + + // the first pretext is which type of shader to compile as + // (later these will all be bound together as a program object) + vertstrings_list[vertstrings_count++] = "#define VERTEX_SHADER\n"; + geomstrings_list[geomstrings_count++] = "#define GEOMETRY_SHADER\n"; + fragstrings_list[fragstrings_count++] = "#define FRAGMENT_SHADER\n"; + + // the second pretext is the mode (for example a light source) + vertstrings_list[vertstrings_count++] = modeinfo->pretext; + geomstrings_list[geomstrings_count++] = modeinfo->pretext; + fragstrings_list[fragstrings_count++] = modeinfo->pretext; + strlcat(permutationname, modeinfo->name, sizeof(permutationname)); + strlcat(cachename, modeinfo->name, sizeof(cachename)); + + // now add all the permutation pretexts + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + { + if (permutation & (1<vertexshader || !vertstring[0]) && (p->pixelshader || !fragstring[0])) + Con_DPrintf("^5HLSL shader %s compiled.\n", permutationname); + else + Con_Printf("^1HLSL shader %s failed! some features may not work properly.\n", permutationname); + + // free the strings + if (vertstring) + Mem_Free(vertstring); + if (geomstring) + Mem_Free(geomstring); + if (fragstring) + Mem_Free(fragstring); + if (sourcestring) + Mem_Free(sourcestring); +} + +static inline void hlslVSSetParameter16f(D3DVSREGISTER_t r, const float *a) {IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, a, 4);} +static inline void hlslVSSetParameter4fv(D3DVSREGISTER_t r, const float *a) {IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, a, 1);} +static inline void hlslVSSetParameter4f(D3DVSREGISTER_t r, float x, float y, float z, float w) {float temp[4];Vector4Set(temp, x, y, z, w);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslVSSetParameter3f(D3DVSREGISTER_t r, float x, float y, float z) {float temp[4];Vector4Set(temp, x, y, z, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslVSSetParameter2f(D3DVSREGISTER_t r, float x, float y) {float temp[4];Vector4Set(temp, x, y, 0, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslVSSetParameter1f(D3DVSREGISTER_t r, float x) {float temp[4];Vector4Set(temp, x, 0, 0, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} + +static inline void hlslPSSetParameter16f(D3DPSREGISTER_t r, const float *a) {IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, a, 4);} +static inline void hlslPSSetParameter4fv(D3DPSREGISTER_t r, const float *a) {IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, a, 1);} +static inline void hlslPSSetParameter4f(D3DPSREGISTER_t r, float x, float y, float z, float w) {float temp[4];Vector4Set(temp, x, y, z, w);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslPSSetParameter3f(D3DPSREGISTER_t r, float x, float y, float z) {float temp[4];Vector4Set(temp, x, y, z, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslPSSetParameter2f(D3DPSREGISTER_t r, float x, float y) {float temp[4];Vector4Set(temp, x, y, 0, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} +static inline void hlslPSSetParameter1f(D3DPSREGISTER_t r, float x) {float temp[4];Vector4Set(temp, x, 0, 0, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} + +void R_SetupShader_SetPermutationHLSL(unsigned int mode, unsigned int permutation) +{ + r_hlsl_permutation_t *perm = R_HLSL_FindPermutation(mode, permutation); + if (r_hlsl_permutation != perm) + { + r_hlsl_permutation = perm; + if (!r_hlsl_permutation->vertexshader && !r_hlsl_permutation->pixelshader) + { + if (!r_hlsl_permutation->compiled) + R_HLSL_CompilePermutation(perm, mode, permutation); + if (!r_hlsl_permutation->vertexshader && !r_hlsl_permutation->pixelshader) + { + // remove features until we find a valid permutation + int i; + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + { + // reduce i more quickly whenever it would not remove any bits + int j = 1<<(SHADERPERMUTATION_COUNT-1-i); + if (!(permutation & j)) + continue; + permutation -= j; + r_hlsl_permutation = R_HLSL_FindPermutation(mode, permutation); + if (!r_hlsl_permutation->compiled) + R_HLSL_CompilePermutation(perm, mode, permutation); + if (r_hlsl_permutation->vertexshader || r_hlsl_permutation->pixelshader) + break; + } + if (i >= SHADERPERMUTATION_COUNT) + { + //Con_Printf("Could not find a working HLSL shader for permutation %s %s\n", shadermodeinfo[mode].filename, shadermodeinfo[mode].pretext); + r_hlsl_permutation = R_HLSL_FindPermutation(mode, permutation); + return; // no bit left to clear, entire mode is broken + } + } + } + IDirect3DDevice9_SetVertexShader(vid_d3d9dev, r_hlsl_permutation->vertexshader); + IDirect3DDevice9_SetPixelShader(vid_d3d9dev, r_hlsl_permutation->pixelshader); + } + hlslVSSetParameter16f(D3DVSREGISTER_ModelViewProjectionMatrix, gl_modelviewprojection16f); + hlslVSSetParameter16f(D3DVSREGISTER_ModelViewMatrix, gl_modelview16f); + hlslPSSetParameter1f(D3DPSREGISTER_ClientTime, cl.time); +} +#endif + +static void R_SetupShader_SetPermutationSoft(unsigned int mode, unsigned int permutation) +{ + DPSOFTRAST_SetShader(mode, permutation, r_shadow_glossexact.integer); + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, 1, false, gl_modelviewprojection16f); + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewMatrixM1, 1, false, gl_modelview16f); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ClientTime, cl.time); +} + +void R_GLSL_Restart_f(void) +{ + unsigned int i, limit; + if (glslshaderstring) + Mem_Free(glslshaderstring); + glslshaderstring = NULL; + if (hlslshaderstring) + Mem_Free(hlslshaderstring); + hlslshaderstring = NULL; + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + r_hlsl_permutation_t *p; + r_hlsl_permutation = NULL; + limit = Mem_ExpandableArray_IndexRange(&r_hlsl_permutationarray); + for (i = 0;i < limit;i++) + { + if ((p = (r_hlsl_permutation_t*)Mem_ExpandableArray_RecordAtIndex(&r_hlsl_permutationarray, i))) + { + if (p->vertexshader) + IDirect3DVertexShader9_Release(p->vertexshader); + if (p->pixelshader) + IDirect3DPixelShader9_Release(p->pixelshader); + Mem_ExpandableArray_FreeRecord(&r_hlsl_permutationarray, (void*)p); + } + } + memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + { + r_glsl_permutation_t *p; + r_glsl_permutation = NULL; + limit = Mem_ExpandableArray_IndexRange(&r_glsl_permutationarray); + for (i = 0;i < limit;i++) + { + if ((p = (r_glsl_permutation_t*)Mem_ExpandableArray_RecordAtIndex(&r_glsl_permutationarray, i))) + { + GL_Backend_FreeProgram(p->program); + Mem_ExpandableArray_FreeRecord(&r_glsl_permutationarray, (void*)p); + } + } + memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + case RENDERPATH_SOFT: + break; + } +} + +static void R_GLSL_DumpShader_f(void) +{ + int i, language, mode, dupe; + char *text; + shadermodeinfo_t *modeinfo; + qfile_t *file; + + for (language = 0;language < 2;language++) + { + modeinfo = (language == 0 ? glslshadermodeinfo : hlslshadermodeinfo); + for (mode = 0;mode < SHADERMODE_COUNT;mode++) + { + // don't dump the same file multiple times (most or all shaders come from the same file) + for (dupe = mode - 1;dupe >= 0;dupe--) + if (!strcmp(modeinfo[mode].filename, modeinfo[dupe].filename)) + break; + if (dupe >= 0) + continue; + text = R_GetShaderText(modeinfo[mode].filename, false, true); + if (!text) + continue; + file = FS_OpenRealFile(modeinfo[mode].filename, "w", false); + if (file) + { + FS_Print(file, "/* The engine may define the following macros:\n"); + FS_Print(file, "#define VERTEX_SHADER\n#define GEOMETRY_SHADER\n#define FRAGMENT_SHADER\n"); + for (i = 0;i < SHADERMODE_COUNT;i++) + FS_Print(file, modeinfo[i].pretext); + for (i = 0;i < SHADERPERMUTATION_COUNT;i++) + FS_Print(file, shaderpermutationinfo[i].pretext); + FS_Print(file, "*/\n"); + FS_Print(file, text); + FS_Close(file); + Con_Printf("%s written\n", modeinfo[mode].filename); + } + else + Con_Printf("failed to write to %s\n", modeinfo[mode].filename); + Mem_Free(text); + } + } +} + +void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemode, int rgbscale, qboolean usegamma, qboolean notrippy, qboolean suppresstexalpha) +{ + unsigned int permutation = 0; + if (r_trippy.integer && !notrippy) + permutation |= SHADERPERMUTATION_TRIPPY; + permutation |= SHADERPERMUTATION_VIEWTINT; + if (first) + permutation |= SHADERPERMUTATION_DIFFUSE; + if (second) + permutation |= SHADERPERMUTATION_SPECULAR; + if (texturemode == GL_MODULATE) + permutation |= SHADERPERMUTATION_COLORMAPPING; + else if (texturemode == GL_ADD) + permutation |= SHADERPERMUTATION_GLOW; + else if (texturemode == GL_DECAL) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + if (usegamma && v_glslgamma.integer && v_glslgamma_2d.integer && !vid.sRGB2D && r_texture_gammaramps && !vid_gammatables_trivial) + permutation |= SHADERPERMUTATION_GAMMARAMPS; + if (suppresstexalpha) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + if (!second) + texturemode = GL_MODULATE; + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + switch (vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + R_SetupShader_SetPermutationHLSL(SHADERMODE_GENERIC, permutation); + R_Mesh_TexBind(GL20TU_FIRST , first ); + R_Mesh_TexBind(GL20TU_SECOND, second); + if (permutation & SHADERPERMUTATION_GAMMARAMPS) + R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_SetupShader_SetPermutationGLSL(SHADERMODE_GENERIC, permutation); + if (r_glsl_permutation->tex_Texture_First >= 0) + R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , first ); + if (r_glsl_permutation->tex_Texture_Second >= 0) + R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second, second); + if (r_glsl_permutation->tex_Texture_GammaRamps >= 0) + R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + R_Mesh_TexBind(0, first ); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexMatrix(0, NULL); + R_Mesh_TexBind(1, second); + if (second) + { + R_Mesh_TexCombine(1, texturemode, texturemode, rgbscale, 1); + R_Mesh_TexMatrix(1, NULL); + } + break; + case RENDERPATH_GL11: + R_Mesh_TexBind(0, first ); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexMatrix(0, NULL); + break; + case RENDERPATH_SOFT: + R_SetupShader_SetPermutationSoft(SHADERMODE_GENERIC, permutation); + R_Mesh_TexBind(GL20TU_FIRST , first ); + R_Mesh_TexBind(GL20TU_SECOND, second); + break; + } +} + +void R_SetupShader_Generic_NoTexture(qboolean usegamma, qboolean notrippy) +{ + R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, usegamma, notrippy, false); +} + +void R_SetupShader_DepthOrShadow(qboolean notrippy, qboolean depthrgb, qboolean skeletal) +{ + unsigned int permutation = 0; + if (r_trippy.integer && !notrippy) + permutation |= SHADERPERMUTATION_TRIPPY; + if (depthrgb) + permutation |= SHADERPERMUTATION_DEPTHRGB; + if (skeletal) + permutation |= SHADERPERMUTATION_SKELETAL; + + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + switch (vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + R_SetupShader_SetPermutationHLSL(SHADERMODE_DEPTH_OR_SHADOW, permutation); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_SetupShader_SetPermutationGLSL(SHADERMODE_DEPTH_OR_SHADOW, permutation); + if (r_glsl_permutation->ubiloc_Skeletal_Transform12_UniformBlock >= 0 && rsurface.batchskeletaltransform3x4buffer) qglBindBufferRange(GL_UNIFORM_BUFFER, r_glsl_permutation->ubibind_Skeletal_Transform12_UniformBlock, rsurface.batchskeletaltransform3x4buffer->bufferobject, rsurface.batchskeletaltransform3x4offset, rsurface.batchskeletaltransform3x4size); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + R_Mesh_TexBind(0, 0); + R_Mesh_TexBind(1, 0); + break; + case RENDERPATH_GL11: + R_Mesh_TexBind(0, 0); + break; + case RENDERPATH_SOFT: + R_SetupShader_SetPermutationSoft(SHADERMODE_DEPTH_OR_SHADOW, permutation); + break; + } +} + +extern qboolean r_shadow_usingdeferredprepass; +extern rtexture_t *r_shadow_attenuationgradienttexture; +extern rtexture_t *r_shadow_attenuation2dtexture; +extern rtexture_t *r_shadow_attenuation3dtexture; +extern qboolean r_shadow_usingshadowmap2d; +extern qboolean r_shadow_usingshadowmaportho; +extern float r_shadow_shadowmap_texturescale[2]; +extern float r_shadow_shadowmap_parameters[4]; +extern qboolean r_shadow_shadowmapvsdct; +extern rtexture_t *r_shadow_shadowmap2ddepthbuffer; +extern rtexture_t *r_shadow_shadowmap2ddepthtexture; +extern rtexture_t *r_shadow_shadowmapvsdcttexture; +extern matrix4x4_t r_shadow_shadowmapmatrix; +extern int r_shadow_shadowmaplod; // changes for each light based on distance +extern int r_shadow_prepass_width; +extern int r_shadow_prepass_height; +extern rtexture_t *r_shadow_prepassgeometrydepthbuffer; +extern rtexture_t *r_shadow_prepassgeometrynormalmaptexture; +extern rtexture_t *r_shadow_prepasslightingdiffusetexture; +extern rtexture_t *r_shadow_prepasslightingspeculartexture; + +#define BLENDFUNC_ALLOWS_COLORMOD 1 +#define BLENDFUNC_ALLOWS_FOG 2 +#define BLENDFUNC_ALLOWS_FOG_HACK0 4 +#define BLENDFUNC_ALLOWS_FOG_HACKALPHA 8 +#define BLENDFUNC_ALLOWS_ANYFOG (BLENDFUNC_ALLOWS_FOG | BLENDFUNC_ALLOWS_FOG_HACK0 | BLENDFUNC_ALLOWS_FOG_HACKALPHA) +static int R_BlendFuncFlags(int src, int dst) +{ + int r = 0; + + // a blendfunc allows colormod if: + // a) it can never keep the destination pixel invariant, or + // b) it can keep the destination pixel invariant, and still can do so if colormodded + // this is to prevent unintended side effects from colormod + + // a blendfunc allows fog if: + // blend(fog(src), fog(dst)) == fog(blend(src, dst)) + // this is to prevent unintended side effects from fog + + // these checks are the output of fogeval.pl + + r |= BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_DST_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_DST_ALPHA && dst == GL_ONE_MINUS_DST_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_DST_COLOR && dst == GL_ONE_MINUS_SRC_ALPHA) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_DST_COLOR && dst == GL_ONE_MINUS_SRC_COLOR) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_DST_COLOR && dst == GL_SRC_ALPHA) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_DST_COLOR && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_DST_COLOR && dst == GL_ZERO) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_ONE && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_ONE && dst == GL_ONE_MINUS_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG_HACKALPHA; + if(src == GL_ONE && dst == GL_ZERO) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ONE_MINUS_DST_ALPHA && dst == GL_DST_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ONE_MINUS_DST_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_ONE_MINUS_DST_COLOR && dst == GL_SRC_COLOR) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_ONE_MINUS_SRC_COLOR && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + if(src == GL_SRC_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; + if(src == GL_SRC_ALPHA && dst == GL_ONE_MINUS_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ZERO && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG; + if(src == GL_ZERO && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; + + return r; +} + +void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting, float ambientscale, float diffusescale, float specularscale, rsurfacepass_t rsurfacepass, int texturenumsurfaces, const msurface_t **texturesurfacelist, void *surfacewaterplane, qboolean notrippy) +{ + // select a permutation of the lighting shader appropriate to this + // combination of texture, entity, light source, and fogging, only use the + // minimum features necessary to avoid wasting rendering time in the + // fragment shader on features that are not being used + unsigned int permutation = 0; + unsigned int mode = 0; + int blendfuncflags; + static float dummy_colormod[3] = {1, 1, 1}; + float *colormod = rsurface.colormod; + float m16f[16]; + matrix4x4_t tempmatrix; + r_waterstate_waterplane_t *waterplane = (r_waterstate_waterplane_t *)surfacewaterplane; + if (r_trippy.integer && !notrippy) + permutation |= SHADERPERMUTATION_TRIPPY; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + permutation |= SHADERPERMUTATION_ALPHAKILL; + if (rsurface.texture->r_water_waterscroll[0] && rsurface.texture->r_water_waterscroll[1]) + permutation |= SHADERPERMUTATION_NORMALMAPSCROLLBLEND; // todo: make generic + if (rsurfacepass == RSURFPASS_BACKGROUND) + { + // distorted background + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERSHADER) + { + mode = SHADERMODE_WATER; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) + permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; + if((r_wateralpha.value < 1) && (rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERALPHA)) + { + // this is the right thing to do for wateralpha + GL_BlendFunc(GL_ONE, GL_ZERO); + blendfuncflags = R_BlendFuncFlags(GL_ONE, GL_ZERO); + } + else + { + // this is the right thing to do for entity alpha + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFRACTION) + { + mode = SHADERMODE_REFRACTION; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) + permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + else + { + mode = SHADERMODE_GENERIC; + permutation |= SHADERPERMUTATION_DIFFUSE | SHADERPERMUTATION_ALPHAKILL; + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + } + else if (rsurfacepass == RSURFPASS_DEFERREDGEOMETRY) + { + if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + // normalmap (deferred prepass), may use alpha test on diffuse + mode = SHADERMODE_DEFERREDGEOMETRY; + GL_BlendFunc(GL_ONE, GL_ZERO); + blendfuncflags = R_BlendFuncFlags(GL_ONE, GL_ZERO); + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + } + else if (rsurfacepass == RSURFPASS_RTLIGHT) + { + if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) + permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; + // light source + mode = SHADERMODE_LIGHTSOURCE; + if (rsurface.rtlight->currentcubemap != r_texture_whitecube) + permutation |= SHADERPERMUTATION_CUBEFILTER; + if (diffusescale > 0) + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmap2d) + { + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + if(r_shadow_shadowmapvsdct) + permutation |= SHADERPERMUTATION_SHADOWMAPVSDCT; + + if (r_shadow_shadowmap2ddepthbuffer) + permutation |= SHADERPERMUTATION_DEPTHRGB; + } + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE); + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) + { + if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) + permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; + // unshaded geometry (fullbright or ambient model lighting) + mode = SHADERMODE_FLATCOLOR; + ambientscale = diffusescale = specularscale = 0; + if ((rsurface.texture->glowtexture || rsurface.texture->backgroundglowtexture) && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) + permutation |= SHADERPERMUTATION_GLOW; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) + { + permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + + if (r_shadow_shadowmap2ddepthbuffer) + permutation |= SHADERPERMUTATION_DEPTHRGB; + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) + permutation |= SHADERPERMUTATION_REFLECTION; + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + // when using alphatocoverage, we don't need alphakill + if (vid.allowalphatocoverage) + { + if (r_transparent_alphatocoverage.integer) + { + GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); + permutation &= ~SHADERPERMUTATION_ALPHAKILL; + } + else + GL_AlphaToCoverage(false); + } + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT_DIRECTIONAL) + { + if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) + permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; + // directional model lighting + mode = SHADERMODE_LIGHTDIRECTION; + if ((rsurface.texture->glowtexture || rsurface.texture->backgroundglowtexture) && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) + permutation |= SHADERPERMUTATION_GLOW; + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) + { + permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + + if (r_shadow_shadowmap2ddepthbuffer) + permutation |= SHADERPERMUTATION_DEPTHRGB; + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) + permutation |= SHADERPERMUTATION_REFLECTION; + if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) + permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + if (r_shadow_bouncegridtexture && cl.csqc_vidvars.drawworld) + { + permutation |= SHADERPERMUTATION_BOUNCEGRID; + if (r_shadow_bouncegriddirectional) + permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; + } + GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + // when using alphatocoverage, we don't need alphakill + if (vid.allowalphatocoverage) + { + if (r_transparent_alphatocoverage.integer) + { + GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); + permutation &= ~SHADERPERMUTATION_ALPHAKILL; + } + else + GL_AlphaToCoverage(false); + } + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + { + if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) + permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; + // ambient model lighting + mode = SHADERMODE_LIGHTDIRECTION; + if ((rsurface.texture->glowtexture || rsurface.texture->backgroundglowtexture) && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) + permutation |= SHADERPERMUTATION_GLOW; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) + { + permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + + if (r_shadow_shadowmap2ddepthbuffer) + permutation |= SHADERPERMUTATION_DEPTHRGB; + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) + permutation |= SHADERPERMUTATION_REFLECTION; + if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) + permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + if (r_shadow_bouncegridtexture && cl.csqc_vidvars.drawworld) + { + permutation |= SHADERPERMUTATION_BOUNCEGRID; + if (r_shadow_bouncegriddirectional) + permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; + } + GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + // when using alphatocoverage, we don't need alphakill + if (vid.allowalphatocoverage) + { + if (r_transparent_alphatocoverage.integer) + { + GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); + permutation &= ~SHADERPERMUTATION_ALPHAKILL; + } + else + GL_AlphaToCoverage(false); + } + } + else + { + if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) + { + switch(rsurface.texture->offsetmapping) + { + case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; + case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; + case OFFSETMAPPING_OFF: break; + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) + permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) + permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; + // lightmapped wall + if ((rsurface.texture->glowtexture || rsurface.texture->backgroundglowtexture) && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) + permutation |= SHADERPERMUTATION_GLOW; + if (r_refdef.fogenabled) + permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); + if (rsurface.texture->colormapping) + permutation |= SHADERPERMUTATION_COLORMAPPING; + if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) + { + permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + + if (r_shadow_shadowmap2ddepthbuffer) + permutation |= SHADERPERMUTATION_DEPTHRGB; + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) + permutation |= SHADERPERMUTATION_REFLECTION; + if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) + permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; + if (rsurface.texture->reflectmasktexture) + permutation |= SHADERPERMUTATION_REFLECTCUBE; + if (FAKELIGHT_ENABLED) + { + // fake lightmapping (q1bsp, q3bsp, fullbright map) + mode = SHADERMODE_FAKELIGHT; + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + } + else if (r_glsl_deluxemapping.integer >= 1 && rsurface.uselightmaptexture && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brushq3.deluxemapping) + { + // deluxemapping (light direction texture) + if (rsurface.uselightmaptexture && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brushq3.deluxemapping && r_refdef.scene.worldmodel->brushq3.deluxemapping_modelspace) + mode = SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE; + else + mode = SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE; + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + } + else if (r_glsl_deluxemapping.integer >= 2) + { + // fake deluxemapping (uniform light direction in tangentspace) + if (rsurface.uselightmaptexture) + mode = SHADERMODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP; + else + mode = SHADERMODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR; + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + } + else if (rsurface.uselightmaptexture) + { + // ordinary lightmapping (q1bsp, q3bsp) + mode = SHADERMODE_LIGHTMAP; + } + else + { + // ordinary vertex coloring (q3bsp) + mode = SHADERMODE_VERTEXCOLOR; + } + if (r_shadow_bouncegridtexture && cl.csqc_vidvars.drawworld) + { + permutation |= SHADERPERMUTATION_BOUNCEGRID; + if (r_shadow_bouncegriddirectional) + permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; + } + GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); + // when using alphatocoverage, we don't need alphakill + if (vid.allowalphatocoverage) + { + if (r_transparent_alphatocoverage.integer) + { + GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); + permutation &= ~SHADERPERMUTATION_ALPHAKILL; + } + else + GL_AlphaToCoverage(false); + } + } + if(!(blendfuncflags & BLENDFUNC_ALLOWS_COLORMOD)) + colormod = dummy_colormod; + if(!(blendfuncflags & BLENDFUNC_ALLOWS_ANYFOG)) + permutation &= ~(SHADERPERMUTATION_FOGHEIGHTTEXTURE | SHADERPERMUTATION_FOGOUTSIDE | SHADERPERMUTATION_FOGINSIDE); + if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACKALPHA) + permutation |= SHADERPERMUTATION_FOGALPHAHACK; + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + RSurf_PrepareVerticesForBatch(BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_VERTEXMESH_VERTEXCOLOR : 0) | BATCHNEED_VERTEXMESH_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_VERTEXMESH_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmesh_vertexbuffer, rsurface.batchvertexmesh_bufferoffset); + R_SetupShader_SetPermutationHLSL(mode, permutation); + Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);hlslPSSetParameter16f(D3DPSREGISTER_ModelToReflectCube, m16f); + if (mode == SHADERMODE_LIGHTSOURCE) + { + Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);hlslVSSetParameter16f(D3DVSREGISTER_ModelToLight, m16f); + hlslVSSetParameter3f(D3DVSREGISTER_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); + } + else + { + if (mode == SHADERMODE_LIGHTDIRECTION) + { + hlslVSSetParameter3f(D3DVSREGISTER_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); + } + } + Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_TexMatrix, m16f); + Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_BackgroundTexMatrix, m16f); + Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_ShadowMapMatrix, m16f); + hlslVSSetParameter3f(D3DVSREGISTER_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); + hlslVSSetParameter4f(D3DVSREGISTER_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); + + if (mode == SHADERMODE_LIGHTSOURCE) + { + hlslPSSetParameter3f(D3DPSREGISTER_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); + hlslPSSetParameter3f(D3DPSREGISTER_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); + + // additive passes are only darkened by fog, not tinted + hlslPSSetParameter3f(D3DPSREGISTER_FogColor, 0, 0, 0); + hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); + } + else + { + if (mode == SHADERMODE_FLATCOLOR) + { + hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, colormod[0], colormod[1], colormod[2]); + } + else if (mode == SHADERMODE_LIGHTDIRECTION) + { + hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity) * colormod[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Diffuse, colormod[0], colormod[1], colormod[2]); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Specular, specularscale, specularscale, specularscale); + hlslPSSetParameter3f(D3DPSREGISTER_LightColor, rsurface.modellight_diffuse[0], rsurface.modellight_diffuse[1], rsurface.modellight_diffuse[2]); + hlslPSSetParameter3f(D3DPSREGISTER_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); + } + else + { + hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Specular, specularscale, specularscale, specularscale); + } + // additive passes are only darkened by fog, not tinted + if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) + hlslPSSetParameter3f(D3DPSREGISTER_FogColor, 0, 0, 0); + else + hlslPSSetParameter3f(D3DPSREGISTER_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); + hlslPSSetParameter4f(D3DPSREGISTER_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); + hlslPSSetParameter4f(D3DPSREGISTER_ScreenScaleRefractReflect, r_fb.water.screenscale[0], r_fb.water.screenscale[1], r_fb.water.screenscale[0], r_fb.water.screenscale[1]); + hlslPSSetParameter4f(D3DPSREGISTER_ScreenCenterRefractReflect, r_fb.water.screencenter[0], r_fb.water.screencenter[1], r_fb.water.screencenter[0], r_fb.water.screencenter[1]); + hlslPSSetParameter4f(D3DPSREGISTER_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); + hlslPSSetParameter4f(D3DPSREGISTER_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); + hlslPSSetParameter1f(D3DPSREGISTER_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); + hlslPSSetParameter1f(D3DPSREGISTER_ReflectOffset, rsurface.texture->reflectmin); + hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, (rsurface.texture->specularpower - 1.0f) * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); + if (mode == SHADERMODE_WATER) + hlslPSSetParameter2f(D3DPSREGISTER_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); + } + hlslPSSetParameter2f(D3DPSREGISTER_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + hlslPSSetParameter4f(D3DPSREGISTER_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + hlslPSSetParameter3f(D3DPSREGISTER_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); + hlslPSSetParameter1f(D3DPSREGISTER_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_fb.water.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); + hlslPSSetParameter3f(D3DPSREGISTER_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); + if (rsurface.texture->pantstexture) + hlslPSSetParameter3f(D3DPSREGISTER_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); + else + hlslPSSetParameter3f(D3DPSREGISTER_Color_Pants, 0, 0, 0); + if (rsurface.texture->shirttexture) + hlslPSSetParameter3f(D3DPSREGISTER_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); + else + hlslPSSetParameter3f(D3DPSREGISTER_Color_Shirt, 0, 0, 0); + hlslPSSetParameter4f(D3DPSREGISTER_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); + hlslPSSetParameter1f(D3DPSREGISTER_FogPlaneViewDist, rsurface.fogplaneviewdist); + hlslPSSetParameter1f(D3DPSREGISTER_FogRangeRecip, rsurface.fograngerecip); + hlslPSSetParameter1f(D3DPSREGISTER_FogHeightFade, rsurface.fogheightfade); + hlslPSSetParameter4f(D3DPSREGISTER_OffsetMapping_ScaleSteps, + r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, + max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) + ); + hlslPSSetParameter1f(D3DPSREGISTER_OffsetMapping_LodDistance, r_glsl_offsetmapping_lod_distance.integer * r_refdef.view.quality); + hlslPSSetParameter1f(D3DPSREGISTER_OffsetMapping_Bias, rsurface.texture->offsetbias); + hlslPSSetParameter2f(D3DPSREGISTER_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); + + R_Mesh_TexBind(GL20TU_NORMAL , rsurface.texture->nmaptexture ); + R_Mesh_TexBind(GL20TU_COLOR , rsurface.texture->basetexture ); + R_Mesh_TexBind(GL20TU_GLOSS , rsurface.texture->glosstexture ); + R_Mesh_TexBind(GL20TU_GLOW , rsurface.texture->glowtexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_NORMAL , rsurface.texture->backgroundnmaptexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_COLOR , rsurface.texture->backgroundbasetexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOSS , rsurface.texture->backgroundglosstexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOW , rsurface.texture->backgroundglowtexture ); + if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_PANTS , rsurface.texture->pantstexture ); + if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_SHIRT , rsurface.texture->shirttexture ); + if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTMASK , rsurface.texture->reflectmasktexture ); + if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTCUBE , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); + if (permutation & SHADERPERMUTATION_FOGHEIGHTTEXTURE) R_Mesh_TexBind(GL20TU_FOGHEIGHTTEXTURE , r_texture_fogheighttexture ); + if (permutation & (SHADERPERMUTATION_FOGINSIDE | SHADERPERMUTATION_FOGOUTSIDE)) R_Mesh_TexBind(GL20TU_FOGMASK , r_texture_fogattenuation ); + R_Mesh_TexBind(GL20TU_LIGHTMAP , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); + R_Mesh_TexBind(GL20TU_DELUXEMAP , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); + if (rsurface.rtlight ) R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); + if (rsurfacepass == RSURFPASS_BACKGROUND) + { + R_Mesh_TexBind(GL20TU_REFRACTION , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); + if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); + R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } + else + { + if (permutation & SHADERPERMUTATION_REFLECTION ) R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } +// if (rsurfacepass == RSURFPASS_DEFERREDLIGHT ) R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); + if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENDIFFUSE , r_shadow_prepasslightingdiffusetexture ); + if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENSPECULAR , r_shadow_prepasslightingspeculartexture ); + if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) + { + R_Mesh_TexBind(GL20TU_SHADOWMAP2D, r_shadow_shadowmap2ddepthtexture); + if (rsurface.rtlight) + { + if (permutation & SHADERPERMUTATION_CUBEFILTER ) R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); + if (permutation & SHADERPERMUTATION_SHADOWMAPVSDCT ) R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (!vid.useinterleavedarrays) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); + R_Mesh_VertexPointer( 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + R_Mesh_ColorPointer( 4, GL_FLOAT, sizeof(float[4]), rsurface.batchlightmapcolor4f, rsurface.batchlightmapcolor4f_vertexbuffer, rsurface.batchlightmapcolor4f_bufferoffset); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchsvector3f, rsurface.batchsvector3f_vertexbuffer, rsurface.batchsvector3f_bufferoffset); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchtvector3f, rsurface.batchtvector3f_vertexbuffer, rsurface.batchtvector3f_bufferoffset); + R_Mesh_TexCoordPointer(3, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchnormal3f, rsurface.batchnormal3f_vertexbuffer, rsurface.batchnormal3f_bufferoffset); + R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); + R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); + R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE | 0x80000000, sizeof(unsigned char[4]), rsurface.batchskeletalindex4ub, rsurface.batchskeletalindex4ub_vertexbuffer, rsurface.batchskeletalindex4ub_bufferoffset); + R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), rsurface.batchskeletalweight4ub, rsurface.batchskeletalweight4ub_vertexbuffer, rsurface.batchskeletalweight4ub_bufferoffset); + } + else + { + RSurf_PrepareVerticesForBatch(BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_VERTEXMESH_VERTEXCOLOR : 0) | BATCHNEED_VERTEXMESH_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_VERTEXMESH_LIGHTMAP : 0) | (rsurface.entityskeletaltransform3x4 ? BATCHNEED_VERTEXMESH_SKELETAL : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmesh_vertexbuffer, rsurface.batchvertexmesh_bufferoffset); + } + // this has to be after RSurf_PrepareVerticesForBatch + if (rsurface.batchskeletaltransform3x4buffer) + permutation |= SHADERPERMUTATION_SKELETAL; + R_SetupShader_SetPermutationGLSL(mode, permutation); + if (r_glsl_permutation->ubiloc_Skeletal_Transform12_UniformBlock >= 0 && rsurface.batchskeletaltransform3x4buffer) qglBindBufferRange(GL_UNIFORM_BUFFER, r_glsl_permutation->ubibind_Skeletal_Transform12_UniformBlock, rsurface.batchskeletaltransform3x4buffer->bufferobject, rsurface.batchskeletaltransform3x4offset, rsurface.batchskeletaltransform3x4size); + if (r_glsl_permutation->loc_ModelToReflectCube >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ModelToReflectCube, 1, false, m16f);} + if (mode == SHADERMODE_LIGHTSOURCE) + { + if (r_glsl_permutation->loc_ModelToLight >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ModelToLight, 1, false, m16f);} + if (r_glsl_permutation->loc_LightPosition >= 0) qglUniform3f(r_glsl_permutation->loc_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); + if (r_glsl_permutation->loc_LightColor >= 0) qglUniform3f(r_glsl_permutation->loc_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); + if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); + if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); + + // additive passes are only darkened by fog, not tinted + if (r_glsl_permutation->loc_FogColor >= 0) + qglUniform3f(r_glsl_permutation->loc_FogColor, 0, 0, 0); + if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f(r_glsl_permutation->loc_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); + } + else + { + if (mode == SHADERMODE_FLATCOLOR) + { + if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, colormod[0], colormod[1], colormod[2]); + } + else if (mode == SHADERMODE_LIGHTDIRECTION) + { + if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[2]); + if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); + if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Diffuse, colormod[0], colormod[1], colormod[2]); + if (r_glsl_permutation->loc_DeferredMod_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Specular, specularscale, specularscale, specularscale); + if (r_glsl_permutation->loc_LightColor >= 0) qglUniform3f(r_glsl_permutation->loc_LightColor, rsurface.modellight_diffuse[0] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[1] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[2] * r_refdef.scene.rtlightstylevalue[0]); + if (r_glsl_permutation->loc_LightDir >= 0) qglUniform3f(r_glsl_permutation->loc_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); + } + else + { + if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); + if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); + if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + if (r_glsl_permutation->loc_DeferredMod_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Specular, specularscale, specularscale, specularscale); + } + // additive passes are only darkened by fog, not tinted + if (r_glsl_permutation->loc_FogColor >= 0) + { + if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) + qglUniform3f(r_glsl_permutation->loc_FogColor, 0, 0, 0); + else + qglUniform3f(r_glsl_permutation->loc_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); + } + if (r_glsl_permutation->loc_DistortScaleRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); + if (r_glsl_permutation->loc_ScreenScaleRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_ScreenScaleRefractReflect, r_fb.water.screenscale[0], r_fb.water.screenscale[1], r_fb.water.screenscale[0], r_fb.water.screenscale[1]); + if (r_glsl_permutation->loc_ScreenCenterRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_ScreenCenterRefractReflect, r_fb.water.screencenter[0], r_fb.water.screencenter[1], r_fb.water.screencenter[0], r_fb.water.screencenter[1]); + if (r_glsl_permutation->loc_RefractColor >= 0) qglUniform4f(r_glsl_permutation->loc_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); + if (r_glsl_permutation->loc_ReflectColor >= 0) qglUniform4f(r_glsl_permutation->loc_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); + if (r_glsl_permutation->loc_ReflectFactor >= 0) qglUniform1f(r_glsl_permutation->loc_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); + if (r_glsl_permutation->loc_ReflectOffset >= 0) qglUniform1f(r_glsl_permutation->loc_ReflectOffset, rsurface.texture->reflectmin); + if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f(r_glsl_permutation->loc_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); + if (r_glsl_permutation->loc_NormalmapScrollBlend >= 0) qglUniform2f(r_glsl_permutation->loc_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); + } + if (r_glsl_permutation->loc_TexMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_TexMatrix, 1, false, m16f);} + if (r_glsl_permutation->loc_BackgroundTexMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_BackgroundTexMatrix, 1, false, m16f);} + if (r_glsl_permutation->loc_ShadowMapMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ShadowMapMatrix, 1, false, m16f);} + if (r_glsl_permutation->loc_ShadowMap_TextureScale >= 0) qglUniform2f(r_glsl_permutation->loc_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + if (r_glsl_permutation->loc_ShadowMap_Parameters >= 0) qglUniform4f(r_glsl_permutation->loc_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + + if (r_glsl_permutation->loc_Color_Glow >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); + if (r_glsl_permutation->loc_Alpha >= 0) qglUniform1f(r_glsl_permutation->loc_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_fb.water.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); + if (r_glsl_permutation->loc_EyePosition >= 0) qglUniform3f(r_glsl_permutation->loc_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); + if (r_glsl_permutation->loc_Color_Pants >= 0) + { + if (rsurface.texture->pantstexture) + qglUniform3f(r_glsl_permutation->loc_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); + else + qglUniform3f(r_glsl_permutation->loc_Color_Pants, 0, 0, 0); + } + if (r_glsl_permutation->loc_Color_Shirt >= 0) + { + if (rsurface.texture->shirttexture) + qglUniform3f(r_glsl_permutation->loc_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); + else + qglUniform3f(r_glsl_permutation->loc_Color_Shirt, 0, 0, 0); + } + if (r_glsl_permutation->loc_FogPlane >= 0) qglUniform4f(r_glsl_permutation->loc_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); + if (r_glsl_permutation->loc_FogPlaneViewDist >= 0) qglUniform1f(r_glsl_permutation->loc_FogPlaneViewDist, rsurface.fogplaneviewdist); + if (r_glsl_permutation->loc_FogRangeRecip >= 0) qglUniform1f(r_glsl_permutation->loc_FogRangeRecip, rsurface.fograngerecip); + if (r_glsl_permutation->loc_FogHeightFade >= 0) qglUniform1f(r_glsl_permutation->loc_FogHeightFade, rsurface.fogheightfade); + if (r_glsl_permutation->loc_OffsetMapping_ScaleSteps >= 0) qglUniform4f(r_glsl_permutation->loc_OffsetMapping_ScaleSteps, + r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, + max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) + ); + if (r_glsl_permutation->loc_OffsetMapping_LodDistance >= 0) qglUniform1f(r_glsl_permutation->loc_OffsetMapping_LodDistance, r_glsl_offsetmapping_lod_distance.integer * r_refdef.view.quality); + if (r_glsl_permutation->loc_OffsetMapping_Bias >= 0) qglUniform1f(r_glsl_permutation->loc_OffsetMapping_Bias, rsurface.texture->offsetbias); + if (r_glsl_permutation->loc_ScreenToDepth >= 0) qglUniform2f(r_glsl_permutation->loc_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f(r_glsl_permutation->loc_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + if (r_glsl_permutation->loc_BounceGridMatrix >= 0) {Matrix4x4_Concat(&tempmatrix, &r_shadow_bouncegridmatrix, &rsurface.matrix);Matrix4x4_ToArrayFloatGL(&tempmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_BounceGridMatrix, 1, false, m16f);} + if (r_glsl_permutation->loc_BounceGridIntensity >= 0) qglUniform1f(r_glsl_permutation->loc_BounceGridIntensity, r_shadow_bouncegridintensity*r_refdef.view.colorscale); + + if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , r_texture_white ); + if (r_glsl_permutation->tex_Texture_Second >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second , r_texture_white ); + if (r_glsl_permutation->tex_Texture_GammaRamps >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps , r_texture_gammaramps ); + if (r_glsl_permutation->tex_Texture_Normal >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Normal , rsurface.texture->nmaptexture ); + if (r_glsl_permutation->tex_Texture_Color >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Color , rsurface.texture->basetexture ); + if (r_glsl_permutation->tex_Texture_Gloss >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Gloss , rsurface.texture->glosstexture ); + if (r_glsl_permutation->tex_Texture_Glow >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Glow , rsurface.texture->glowtexture ); + if (r_glsl_permutation->tex_Texture_SecondaryNormal >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryNormal , rsurface.texture->backgroundnmaptexture ); + if (r_glsl_permutation->tex_Texture_SecondaryColor >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryColor , rsurface.texture->backgroundbasetexture ); + if (r_glsl_permutation->tex_Texture_SecondaryGloss >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryGloss , rsurface.texture->backgroundglosstexture ); + if (r_glsl_permutation->tex_Texture_SecondaryGlow >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryGlow , rsurface.texture->backgroundglowtexture ); + if (r_glsl_permutation->tex_Texture_Pants >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Pants , rsurface.texture->pantstexture ); + if (r_glsl_permutation->tex_Texture_Shirt >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Shirt , rsurface.texture->shirttexture ); + if (r_glsl_permutation->tex_Texture_ReflectMask >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ReflectMask , rsurface.texture->reflectmasktexture ); + if (r_glsl_permutation->tex_Texture_ReflectCube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ReflectCube , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); + if (r_glsl_permutation->tex_Texture_FogHeightTexture>= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_FogHeightTexture , r_texture_fogheighttexture ); + if (r_glsl_permutation->tex_Texture_FogMask >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_FogMask , r_texture_fogattenuation ); + if (r_glsl_permutation->tex_Texture_Lightmap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Lightmap , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); + if (r_glsl_permutation->tex_Texture_Deluxemap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Deluxemap , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); + if (r_glsl_permutation->tex_Texture_Attenuation >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Attenuation , r_shadow_attenuationgradienttexture ); + if (rsurfacepass == RSURFPASS_BACKGROUND) + { + if (r_glsl_permutation->tex_Texture_Refraction >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Refraction , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); + if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); + if (r_glsl_permutation->tex_Texture_Reflection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } + else + { + if (r_glsl_permutation->tex_Texture_Reflection >= 0 && waterplane) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } + if (r_glsl_permutation->tex_Texture_ScreenNormalMap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenNormalMap , r_shadow_prepassgeometrynormalmaptexture ); + if (r_glsl_permutation->tex_Texture_ScreenDiffuse >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenDiffuse , r_shadow_prepasslightingdiffusetexture ); + if (r_glsl_permutation->tex_Texture_ScreenSpecular >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenSpecular , r_shadow_prepasslightingspeculartexture ); + if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) + { + if (r_glsl_permutation->tex_Texture_ShadowMap2D >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ShadowMap2D, r_shadow_shadowmap2ddepthtexture ); + if (rsurface.rtlight) + { + if (r_glsl_permutation->tex_Texture_Cube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Cube , rsurface.rtlight->currentcubemap ); + if (r_glsl_permutation->tex_Texture_CubeProjection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_CubeProjection , r_shadow_shadowmapvsdcttexture ); + } + } + if (r_glsl_permutation->tex_Texture_BounceGrid >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_BounceGrid, r_shadow_bouncegridtexture); + CHECKGLERROR + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + case RENDERPATH_SOFT: + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Mesh_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchsvector3f, rsurface.batchtvector3f, rsurface.batchnormal3f, rsurface.batchlightmapcolor4f, rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordlightmap2f); + R_SetupShader_SetPermutationSoft(mode, permutation); + {Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelToReflectCubeM1, 1, false, m16f);} + if (mode == SHADERMODE_LIGHTSOURCE) + { + {Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelToLightM1, 1, false, m16f);} + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); + + // additive passes are only darkened by fog, not tinted + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, 0, 0, 0); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); + } + else + { + if (mode == SHADERMODE_FLATCOLOR) + { + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, colormod[0], colormod[1], colormod[2]); + } + else if (mode == SHADERMODE_LIGHTDIRECTION) + { + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, colormod[0], colormod[1], colormod[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Specular, specularscale, specularscale, specularscale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightColor, rsurface.modellight_diffuse[0] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[1] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[2] * r_refdef.scene.rtlightstylevalue[0]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); + } + else + { + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Specular, specularscale, specularscale, specularscale); + } + // additive passes are only darkened by fog, not tinted + if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, 0, 0, 0); + else + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect, r_fb.water.screenscale[0], r_fb.water.screenscale[1], r_fb.water.screenscale[0], r_fb.water.screenscale[1]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect, r_fb.water.screencenter[0], r_fb.water.screencenter[1], r_fb.water.screencenter[0], r_fb.water.screencenter[1]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ReflectOffset, rsurface.texture->reflectmin); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); + } + {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_TexMatrixM1, 1, false, m16f);} + {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_BackgroundTexMatrixM1, 1, false, m16f);} + {Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ShadowMapMatrixM1, 1, false, m16f);} + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_fb.water.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); + if (DPSOFTRAST_UNIFORM_Color_Pants >= 0) + { + if (rsurface.texture->pantstexture) + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); + else + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Pants, 0, 0, 0); + } + if (DPSOFTRAST_UNIFORM_Color_Shirt >= 0) + { + if (rsurface.texture->shirttexture) + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); + else + DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Shirt, 0, 0, 0); + } + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogPlaneViewDist, rsurface.fogplaneviewdist); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogRangeRecip, rsurface.fograngerecip); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogHeightFade, rsurface.fogheightfade); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_OffsetMapping_ScaleSteps, + r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, + max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), + max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) + ); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_OffsetMapping_LodDistance, r_glsl_offsetmapping_lod_distance.integer * r_refdef.view.quality); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_OffsetMapping_Bias, rsurface.texture->offsetbias); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + + R_Mesh_TexBind(GL20TU_NORMAL , rsurface.texture->nmaptexture ); + R_Mesh_TexBind(GL20TU_COLOR , rsurface.texture->basetexture ); + R_Mesh_TexBind(GL20TU_GLOSS , rsurface.texture->glosstexture ); + R_Mesh_TexBind(GL20TU_GLOW , rsurface.texture->glowtexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_NORMAL , rsurface.texture->backgroundnmaptexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_COLOR , rsurface.texture->backgroundbasetexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOSS , rsurface.texture->backgroundglosstexture ); + if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOW , rsurface.texture->backgroundglowtexture ); + if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_PANTS , rsurface.texture->pantstexture ); + if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_SHIRT , rsurface.texture->shirttexture ); + if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTMASK , rsurface.texture->reflectmasktexture ); + if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTCUBE , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); + if (permutation & SHADERPERMUTATION_FOGHEIGHTTEXTURE) R_Mesh_TexBind(GL20TU_FOGHEIGHTTEXTURE , r_texture_fogheighttexture ); + if (permutation & (SHADERPERMUTATION_FOGINSIDE | SHADERPERMUTATION_FOGOUTSIDE)) R_Mesh_TexBind(GL20TU_FOGMASK , r_texture_fogattenuation ); + R_Mesh_TexBind(GL20TU_LIGHTMAP , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); + R_Mesh_TexBind(GL20TU_DELUXEMAP , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); + if (rsurface.rtlight ) R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); + if (rsurfacepass == RSURFPASS_BACKGROUND) + { + R_Mesh_TexBind(GL20TU_REFRACTION , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); + if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); + R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } + else + { + if (permutation & SHADERPERMUTATION_REFLECTION ) R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); + } +// if (rsurfacepass == RSURFPASS_DEFERREDLIGHT ) R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); + if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENDIFFUSE , r_shadow_prepasslightingdiffusetexture ); + if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENSPECULAR , r_shadow_prepasslightingspeculartexture ); + if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) + { + R_Mesh_TexBind(GL20TU_SHADOWMAP2D, r_shadow_shadowmap2ddepthtexture); + if (rsurface.rtlight) + { + if (permutation & SHADERPERMUTATION_CUBEFILTER ) R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); + if (permutation & SHADERPERMUTATION_SHADOWMAPVSDCT ) R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); + } + } + break; + } +} + +void R_SetupShader_DeferredLight(const rtlight_t *rtlight) +{ + // select a permutation of the lighting shader appropriate to this + // combination of texture, entity, light source, and fogging, only use the + // minimum features necessary to avoid wasting rendering time in the + // fragment shader on features that are not being used + unsigned int permutation = 0; + unsigned int mode = 0; + const float *lightcolorbase = rtlight->currentcolor; + float ambientscale = rtlight->ambientscale; + float diffusescale = rtlight->diffusescale; + float specularscale = rtlight->specularscale; + // this is the location of the light in view space + vec3_t viewlightorigin; + // this transforms from view space (camera) to light space (cubemap) + matrix4x4_t viewtolight; + matrix4x4_t lighttoview; + float viewtolight16f[16]; + // light source + mode = SHADERMODE_DEFERREDLIGHTSOURCE; + if (rtlight->currentcubemap != r_texture_whitecube) + permutation |= SHADERPERMUTATION_CUBEFILTER; + if (diffusescale > 0) + permutation |= SHADERPERMUTATION_DIFFUSE; + if (specularscale > 0 && r_shadow_gloss.integer > 0) + permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; + if (r_shadow_usingshadowmap2d) + { + permutation |= SHADERPERMUTATION_SHADOWMAP2D; + if (r_shadow_shadowmapvsdct) + permutation |= SHADERPERMUTATION_SHADOWMAPVSDCT; + + if (r_shadow_shadowmap2ddepthbuffer) + permutation |= SHADERPERMUTATION_DEPTHRGB; + } + if (vid.allowalphatocoverage) + GL_AlphaToCoverage(false); + Matrix4x4_Transform(&r_refdef.view.viewport.viewmatrix, rtlight->shadoworigin, viewlightorigin); + Matrix4x4_Concat(&lighttoview, &r_refdef.view.viewport.viewmatrix, &rtlight->matrix_lighttoworld); + Matrix4x4_Invert_Simple(&viewtolight, &lighttoview); + Matrix4x4_ToArrayFloatGL(&viewtolight, viewtolight16f); + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + R_SetupShader_SetPermutationHLSL(mode, permutation); + hlslPSSetParameter3f(D3DPSREGISTER_LightPosition, viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); + hlslPSSetParameter16f(D3DPSREGISTER_ViewToLight, viewtolight16f); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Ambient , lightcolorbase[0] * ambientscale , lightcolorbase[1] * ambientscale , lightcolorbase[2] * ambientscale ); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale , lightcolorbase[1] * diffusescale , lightcolorbase[2] * diffusescale ); + hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Specular, lightcolorbase[0] * specularscale, lightcolorbase[1] * specularscale, lightcolorbase[2] * specularscale); + hlslPSSetParameter2f(D3DPSREGISTER_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + hlslPSSetParameter4f(D3DPSREGISTER_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); + hlslPSSetParameter2f(D3DPSREGISTER_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); + + R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); + R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); + R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); + R_Mesh_TexBind(GL20TU_SHADOWMAP2D , r_shadow_shadowmap2ddepthtexture ); + R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_SetupShader_SetPermutationGLSL(mode, permutation); + if (r_glsl_permutation->loc_LightPosition >= 0) qglUniform3f( r_glsl_permutation->loc_LightPosition , viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); + if (r_glsl_permutation->loc_ViewToLight >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ViewToLight , 1, false, viewtolight16f); + if (r_glsl_permutation->loc_DeferredColor_Ambient >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Ambient , lightcolorbase[0] * ambientscale , lightcolorbase[1] * ambientscale , lightcolorbase[2] * ambientscale ); + if (r_glsl_permutation->loc_DeferredColor_Diffuse >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale , lightcolorbase[1] * diffusescale , lightcolorbase[2] * diffusescale ); + if (r_glsl_permutation->loc_DeferredColor_Specular >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Specular , lightcolorbase[0] * specularscale, lightcolorbase[1] * specularscale, lightcolorbase[2] * specularscale); + if (r_glsl_permutation->loc_ShadowMap_TextureScale >= 0) qglUniform2f( r_glsl_permutation->loc_ShadowMap_TextureScale , r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + if (r_glsl_permutation->loc_ShadowMap_Parameters >= 0) qglUniform4f( r_glsl_permutation->loc_ShadowMap_Parameters , r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f( r_glsl_permutation->loc_SpecularPower , (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); + if (r_glsl_permutation->loc_ScreenToDepth >= 0) qglUniform2f( r_glsl_permutation->loc_ScreenToDepth , r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f( r_glsl_permutation->loc_PixelToScreenTexCoord , 1.0f/vid.width, 1.0f/vid.height); + + if (r_glsl_permutation->tex_Texture_Attenuation >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Attenuation , r_shadow_attenuationgradienttexture ); + if (r_glsl_permutation->tex_Texture_ScreenNormalMap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenNormalMap , r_shadow_prepassgeometrynormalmaptexture ); + if (r_glsl_permutation->tex_Texture_Cube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Cube , rsurface.rtlight->currentcubemap ); + if (r_glsl_permutation->tex_Texture_ShadowMap2D >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ShadowMap2D , r_shadow_shadowmap2ddepthtexture ); + if (r_glsl_permutation->tex_Texture_CubeProjection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_CubeProjection , r_shadow_shadowmapvsdcttexture ); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + case RENDERPATH_SOFT: + R_SetupShader_SetPermutationGLSL(mode, permutation); + DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_LightPosition , viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ViewToLightM1 , 1, false, viewtolight16f); + DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Ambient , lightcolorbase[0] * ambientscale , lightcolorbase[1] * ambientscale , lightcolorbase[2] * ambientscale ); + DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale , lightcolorbase[1] * diffusescale , lightcolorbase[2] * diffusescale ); + DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Specular , lightcolorbase[0] * specularscale, lightcolorbase[1] * specularscale, lightcolorbase[2] * specularscale); + DPSOFTRAST_Uniform2f( DPSOFTRAST_UNIFORM_ShadowMap_TextureScale , r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); + DPSOFTRAST_Uniform4f( DPSOFTRAST_UNIFORM_ShadowMap_Parameters , r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); + DPSOFTRAST_Uniform1f( DPSOFTRAST_UNIFORM_SpecularPower , (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); + DPSOFTRAST_Uniform2f( DPSOFTRAST_UNIFORM_ScreenToDepth , r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + + R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); + R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); + R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); + R_Mesh_TexBind(GL20TU_SHADOWMAP2D , r_shadow_shadowmap2ddepthtexture ); + R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); + break; + } +} + +#define SKINFRAME_HASH 1024 + +typedef struct +{ + int loadsequence; // incremented each level change + memexpandablearray_t array; + skinframe_t *hash[SKINFRAME_HASH]; +} +r_skinframe_t; +r_skinframe_t r_skinframe; + +void R_SkinFrame_PrepareForPurge(void) +{ + r_skinframe.loadsequence++; + // wrap it without hitting zero + if (r_skinframe.loadsequence >= 200) + r_skinframe.loadsequence = 1; +} + +void R_SkinFrame_MarkUsed(skinframe_t *skinframe) +{ + if (!skinframe) + return; + // mark the skinframe as used for the purging code + skinframe->loadsequence = r_skinframe.loadsequence; +} + +void R_SkinFrame_Purge(void) +{ + int i; + skinframe_t *s; + for (i = 0;i < SKINFRAME_HASH;i++) + { + for (s = r_skinframe.hash[i];s;s = s->next) + { + if (s->loadsequence && s->loadsequence != r_skinframe.loadsequence) + { + if (s->merged == s->base) + s->merged = NULL; + // FIXME: maybe pass a pointer to the pointer to R_PurgeTexture and reset it to NULL inside? [11/29/2007 Black] + R_PurgeTexture(s->stain );s->stain = NULL; + R_PurgeTexture(s->merged);s->merged = NULL; + R_PurgeTexture(s->base );s->base = NULL; + R_PurgeTexture(s->pants );s->pants = NULL; + R_PurgeTexture(s->shirt );s->shirt = NULL; + R_PurgeTexture(s->nmap );s->nmap = NULL; + R_PurgeTexture(s->gloss );s->gloss = NULL; + R_PurgeTexture(s->glow );s->glow = NULL; + R_PurgeTexture(s->fog );s->fog = NULL; + R_PurgeTexture(s->reflect);s->reflect = NULL; + s->loadsequence = 0; + } + } + } +} + +skinframe_t *R_SkinFrame_FindNextByName( skinframe_t *last, const char *name ) { + skinframe_t *item; + char basename[MAX_QPATH]; + + Image_StripImageExtension(name, basename, sizeof(basename)); + + if( last == NULL ) { + int hashindex; + hashindex = CRC_Block((unsigned char *)basename, strlen(basename)) & (SKINFRAME_HASH - 1); + item = r_skinframe.hash[hashindex]; + } else { + item = last->next; + } + + // linearly search through the hash bucket + for( ; item ; item = item->next ) { + if( !strcmp( item->basename, basename ) ) { + return item; + } + } + return NULL; +} + +skinframe_t *R_SkinFrame_Find(const char *name, int textureflags, int comparewidth, int compareheight, int comparecrc, qboolean add) +{ + skinframe_t *item; + int hashindex; + char basename[MAX_QPATH]; + + Image_StripImageExtension(name, basename, sizeof(basename)); + + hashindex = CRC_Block((unsigned char *)basename, strlen(basename)) & (SKINFRAME_HASH - 1); + for (item = r_skinframe.hash[hashindex];item;item = item->next) + if (!strcmp(item->basename, basename) && (comparecrc < 0 || (item->textureflags == textureflags && item->comparewidth == comparewidth && item->compareheight == compareheight && item->comparecrc == comparecrc))) + break; + + if (!item) { + rtexture_t *dyntexture; + // check whether its a dynamic texture + dyntexture = CL_GetDynTexture( basename ); + if (!add && !dyntexture) + return NULL; + item = (skinframe_t *)Mem_ExpandableArray_AllocRecord(&r_skinframe.array); + memset(item, 0, sizeof(*item)); + strlcpy(item->basename, basename, sizeof(item->basename)); + item->base = dyntexture; // either NULL or dyntexture handle + item->textureflags = textureflags & ~TEXF_FORCE_RELOAD; + item->comparewidth = comparewidth; + item->compareheight = compareheight; + item->comparecrc = comparecrc; + item->next = r_skinframe.hash[hashindex]; + r_skinframe.hash[hashindex] = item; + } + else if (textureflags & TEXF_FORCE_RELOAD) + { + rtexture_t *dyntexture; + // check whether its a dynamic texture + dyntexture = CL_GetDynTexture( basename ); + if (!add && !dyntexture) + return NULL; + if (item->merged == item->base) + item->merged = NULL; + // FIXME: maybe pass a pointer to the pointer to R_PurgeTexture and reset it to NULL inside? [11/29/2007 Black] + R_PurgeTexture(item->stain );item->stain = NULL; + R_PurgeTexture(item->merged);item->merged = NULL; + R_PurgeTexture(item->base );item->base = NULL; + R_PurgeTexture(item->pants );item->pants = NULL; + R_PurgeTexture(item->shirt );item->shirt = NULL; + R_PurgeTexture(item->nmap );item->nmap = NULL; + R_PurgeTexture(item->gloss );item->gloss = NULL; + R_PurgeTexture(item->glow );item->glow = NULL; + R_PurgeTexture(item->fog );item->fog = NULL; + R_PurgeTexture(item->reflect);item->reflect = NULL; + item->loadsequence = 0; + } + else if( item->base == NULL ) + { + rtexture_t *dyntexture; + // check whether its a dynamic texture + // this only needs to be done because Purge doesnt delete skinframes - only sets the texture pointers to NULL and we need to restore it before returing.. [11/29/2007 Black] + dyntexture = CL_GetDynTexture( basename ); + item->base = dyntexture; // either NULL or dyntexture handle + } + + R_SkinFrame_MarkUsed(item); + return item; +} + +#define R_SKINFRAME_LOAD_AVERAGE_COLORS(cnt, getpixel) \ + { \ + unsigned long long avgcolor[5], wsum; \ + int pix, comp, w; \ + avgcolor[0] = 0; \ + avgcolor[1] = 0; \ + avgcolor[2] = 0; \ + avgcolor[3] = 0; \ + avgcolor[4] = 0; \ + wsum = 0; \ + for(pix = 0; pix < cnt; ++pix) \ + { \ + w = 0; \ + for(comp = 0; comp < 3; ++comp) \ + w += getpixel; \ + if(w) /* ignore perfectly black pixels because that is better for model skins */ \ + { \ + ++wsum; \ + /* comp = 3; -- not needed, comp is always 3 when we get here */ \ + w = getpixel; \ + for(comp = 0; comp < 3; ++comp) \ + avgcolor[comp] += getpixel * w; \ + avgcolor[3] += w; \ + } \ + /* comp = 3; -- not needed, comp is always 3 when we get here */ \ + avgcolor[4] += getpixel; \ + } \ + if(avgcolor[3] == 0) /* no pixels seen? even worse */ \ + avgcolor[3] = 1; \ + skinframe->avgcolor[0] = avgcolor[2] / (255.0 * avgcolor[3]); \ + skinframe->avgcolor[1] = avgcolor[1] / (255.0 * avgcolor[3]); \ + skinframe->avgcolor[2] = avgcolor[0] / (255.0 * avgcolor[3]); \ + skinframe->avgcolor[3] = avgcolor[4] / (255.0 * cnt); \ + } + +extern cvar_t gl_picmip; +skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboolean complain) +{ + int j; + unsigned char *pixels; + unsigned char *bumppixels; + unsigned char *basepixels = NULL; + int basepixels_width = 0; + int basepixels_height = 0; + skinframe_t *skinframe; + rtexture_t *ddsbase = NULL; + qboolean ddshasalpha = false; + float ddsavgcolor[4]; + char basename[MAX_QPATH]; + int miplevel = R_PicmipForFlags(textureflags); + int savemiplevel = miplevel; + int mymiplevel; + char vabuf[1024]; + + if (cls.state == ca_dedicated) + return NULL; + + // return an existing skinframe if already loaded + // if loading of the first image fails, don't make a new skinframe as it + // would cause all future lookups of this to be missing + skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, false); + if (skinframe && skinframe->base) + return skinframe; + + Image_StripImageExtension(name, basename, sizeof(basename)); + + // check for DDS texture file first + if (!r_loaddds || !(ddsbase = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s.dds", basename), vid.sRGB3D, textureflags, &ddshasalpha, ddsavgcolor, miplevel, false))) + { + basepixels = loadimagepixelsbgra(name, complain, true, false, &miplevel); + if (basepixels == NULL) + return NULL; + } + + // FIXME handle miplevel + + if (developer_loading.integer) + Con_Printf("loading skin \"%s\"\n", name); + + // we've got some pixels to store, so really allocate this new texture now + if (!skinframe) + skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, true); + textureflags &= ~TEXF_FORCE_RELOAD; + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + if (ddsbase) + { + skinframe->base = ddsbase; + skinframe->hasalpha = ddshasalpha; + VectorCopy(ddsavgcolor, skinframe->avgcolor); + if (r_loadfog && skinframe->hasalpha) + skinframe->fog = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_mask.dds", skinframe->basename), false, textureflags | TEXF_ALPHA, NULL, NULL, miplevel, true); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + } + else + { + basepixels_width = image_width; + basepixels_height = image_height; + skinframe->base = R_LoadTexture2D (r_main_texturepool, skinframe->basename, basepixels_width, basepixels_height, basepixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), miplevel, NULL); + if (textureflags & TEXF_ALPHA) + { + for (j = 3;j < basepixels_width * basepixels_height * 4;j += 4) + { + if (basepixels[j] < 255) + { + skinframe->hasalpha = true; + break; + } + } + if (r_loadfog && skinframe->hasalpha) + { + // has transparent pixels + pixels = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + for (j = 0;j < image_width * image_height * 4;j += 4) + { + pixels[j+0] = 255; + pixels[j+1] = 255; + pixels[j+2] = 255; + pixels[j+3] = basepixels[j+3]; + } + skinframe->fog = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_mask", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), miplevel, NULL); + Mem_Free(pixels); + } + } + R_SKINFRAME_LOAD_AVERAGE_COLORS(basepixels_width * basepixels_height, basepixels[4 * pix + comp]); +#ifndef USE_GLES2 + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->base) + R_SaveTextureDDSFile(skinframe->base, va(vabuf, sizeof(vabuf), "dds/%s.dds", skinframe->basename), r_texture_dds_save.integer < 2, skinframe->hasalpha); + if (r_savedds && qglGetCompressedTexImageARB && skinframe->fog) + R_SaveTextureDDSFile(skinframe->fog, va(vabuf, sizeof(vabuf), "dds/%s_mask.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); +#endif + } + + if (r_loaddds) + { + mymiplevel = savemiplevel; + if (r_loadnormalmap) + skinframe->nmap = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_norm.dds", skinframe->basename), false, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), NULL, NULL, mymiplevel, true); + skinframe->glow = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_glow.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); + if (r_loadgloss) + skinframe->gloss = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_gloss.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); + skinframe->pants = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_pants.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); + skinframe->shirt = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_shirt.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); + skinframe->reflect = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_reflect.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); + } + + // _norm is the name used by tenebrae and has been adopted as standard + if (r_loadnormalmap && skinframe->nmap == NULL) + { + mymiplevel = savemiplevel; + if ((pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_norm", skinframe->basename), false, false, false, &mymiplevel)) != NULL) + { + skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + Mem_Free(pixels); + pixels = NULL; + } + else if (r_shadow_bumpscale_bumpmap.value > 0 && (bumppixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_bump", skinframe->basename), false, false, false, &mymiplevel)) != NULL) + { + pixels = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + Image_HeightmapToNormalmap_BGRA(bumppixels, pixels, image_width, image_height, false, r_shadow_bumpscale_bumpmap.value); + skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + Mem_Free(pixels); + Mem_Free(bumppixels); + } + else if (r_shadow_bumpscale_basetexture.value > 0) + { + pixels = (unsigned char *)Mem_Alloc(tempmempool, basepixels_width * basepixels_height * 4); + Image_HeightmapToNormalmap_BGRA(basepixels, pixels, basepixels_width, basepixels_height, false, r_shadow_bumpscale_basetexture.value); + skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), basepixels_width, basepixels_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); + Mem_Free(pixels); + } +#ifndef USE_GLES2 + if (r_savedds && qglGetCompressedTexImageARB && skinframe->nmap) + R_SaveTextureDDSFile(skinframe->nmap, va(vabuf, sizeof(vabuf), "dds/%s_norm.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); +#endif + } + + // _luma is supported only for tenebrae compatibility + // _glow is the preferred name + mymiplevel = savemiplevel; + if (skinframe->glow == NULL && ((pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_glow", skinframe->basename), false, false, false, &mymiplevel)) || (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_luma", skinframe->basename), false, false, false, &mymiplevel)))) + { + skinframe->glow = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_glow", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_glow.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); +#ifndef USE_GLES2 + if (r_savedds && qglGetCompressedTexImageARB && skinframe->glow) + R_SaveTextureDDSFile(skinframe->glow, va(vabuf, sizeof(vabuf), "dds/%s_glow.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); +#endif + Mem_Free(pixels);pixels = NULL; + } + + mymiplevel = savemiplevel; + if (skinframe->gloss == NULL && r_loadgloss && (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_gloss", skinframe->basename), false, false, false, &mymiplevel))) + { + skinframe->gloss = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_gloss", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (gl_texturecompression_gloss.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); +#ifndef USE_GLES2 + if (r_savedds && qglGetCompressedTexImageARB && skinframe->gloss) + R_SaveTextureDDSFile(skinframe->gloss, va(vabuf, sizeof(vabuf), "dds/%s_gloss.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); +#endif + Mem_Free(pixels); + pixels = NULL; + } + + mymiplevel = savemiplevel; + if (skinframe->pants == NULL && (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_pants", skinframe->basename), false, false, false, &mymiplevel))) + { + skinframe->pants = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_pants", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); +#ifndef USE_GLES2 + if (r_savedds && qglGetCompressedTexImageARB && skinframe->pants) + R_SaveTextureDDSFile(skinframe->pants, va(vabuf, sizeof(vabuf), "dds/%s_pants.dds", skinframe->basename), r_texture_dds_save.integer < 2, false); +#endif + Mem_Free(pixels); + pixels = NULL; + } + + mymiplevel = savemiplevel; + if (skinframe->shirt == NULL && (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_shirt", skinframe->basename), false, false, false, &mymiplevel))) + { + skinframe->shirt = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_shirt", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); +#ifndef USE_GLES2 + if (r_savedds && qglGetCompressedTexImageARB && skinframe->shirt) + R_SaveTextureDDSFile(skinframe->shirt, va(vabuf, sizeof(vabuf), "dds/%s_shirt.dds", skinframe->basename), r_texture_dds_save.integer < 2, false); +#endif + Mem_Free(pixels); + pixels = NULL; + } + + mymiplevel = savemiplevel; + if (skinframe->reflect == NULL && (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_reflect", skinframe->basename), false, false, false, &mymiplevel))) + { + skinframe->reflect = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_reflect", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_reflectmask.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); +#ifndef USE_GLES2 + if (r_savedds && qglGetCompressedTexImageARB && skinframe->reflect) + R_SaveTextureDDSFile(skinframe->reflect, va(vabuf, sizeof(vabuf), "dds/%s_reflect.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); +#endif + Mem_Free(pixels); + pixels = NULL; + } + + if (basepixels) + Mem_Free(basepixels); + + return skinframe; +} + +// this is only used by .spr32 sprites, HL .spr files, HL .bsp files +skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, const unsigned char *skindata, int width, int height, qboolean sRGB) +{ + int i; + unsigned char *temp1, *temp2; + skinframe_t *skinframe; + char vabuf[1024]; + + if (cls.state == ca_dedicated) + return NULL; + + // if already loaded just return it, otherwise make a new skinframe + skinframe = R_SkinFrame_Find(name, textureflags, width, height, (textureflags & TEXF_FORCE_RELOAD) ? -1 : skindata ? CRC_Block(skindata, width*height*4) : 0, true); + if (skinframe->base) + return skinframe; + textureflags &= ~TEXF_FORCE_RELOAD; + + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + // if no data was provided, then clearly the caller wanted to get a blank skinframe + if (!skindata) + return NULL; + + if (developer_loading.integer) + Con_Printf("loading 32bit skin \"%s\"\n", name); + + if (r_loadnormalmap && r_shadow_bumpscale_basetexture.value > 0) + { + temp1 = (unsigned char *)Mem_Alloc(tempmempool, width * height * 8); + temp2 = temp1 + width * height * 4; + Image_HeightmapToNormalmap_BGRA(skindata, temp2, width, height, false, r_shadow_bumpscale_basetexture.value); + skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), width, height, temp2, TEXTYPE_BGRA, (textureflags | TEXF_ALPHA) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), -1, NULL); + Mem_Free(temp1); + } + skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, sRGB ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags, -1, NULL); + if (textureflags & TEXF_ALPHA) + { + for (i = 3;i < width * height * 4;i += 4) + { + if (skindata[i] < 255) + { + skinframe->hasalpha = true; + break; + } + } + if (r_loadfog && skinframe->hasalpha) + { + unsigned char *fogpixels = (unsigned char *)Mem_Alloc(tempmempool, width * height * 4); + memcpy(fogpixels, skindata, width * height * 4); + for (i = 0;i < width * height * 4;i += 4) + fogpixels[i] = fogpixels[i+1] = fogpixels[i+2] = 255; + skinframe->fog = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_fog", skinframe->basename), width, height, fogpixels, TEXTYPE_BGRA, textureflags, -1, NULL); + Mem_Free(fogpixels); + } + } + + R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, skindata[4 * pix + comp]); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + + return skinframe; +} + +skinframe_t *R_SkinFrame_LoadInternalQuake(const char *name, int textureflags, int loadpantsandshirt, int loadglowtexture, const unsigned char *skindata, int width, int height) +{ + int i; + int featuresmask; + skinframe_t *skinframe; + + if (cls.state == ca_dedicated) + return NULL; + + // if already loaded just return it, otherwise make a new skinframe + skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height) : 0, true); + if (skinframe->base) + return skinframe; + //textureflags &= ~TEXF_FORCE_RELOAD; + + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + // if no data was provided, then clearly the caller wanted to get a blank skinframe + if (!skindata) + return NULL; + + if (developer_loading.integer) + Con_Printf("loading quake skin \"%s\"\n", name); + + // we actually don't upload anything until the first use, because mdl skins frequently go unused, and are almost never used in both modes (colormapped and non-colormapped) + skinframe->qpixels = (unsigned char *)Mem_Alloc(r_main_mempool, width*height); // FIXME LEAK + memcpy(skinframe->qpixels, skindata, width*height); + skinframe->qwidth = width; + skinframe->qheight = height; + + featuresmask = 0; + for (i = 0;i < width * height;i++) + featuresmask |= palette_featureflags[skindata[i]]; + + skinframe->hasalpha = false; + skinframe->qhascolormapping = loadpantsandshirt && (featuresmask & (PALETTEFEATURE_PANTS | PALETTEFEATURE_SHIRT)); + skinframe->qgeneratenmap = r_shadow_bumpscale_basetexture.value > 0; + skinframe->qgeneratemerged = true; + skinframe->qgeneratebase = skinframe->qhascolormapping; + skinframe->qgenerateglow = loadglowtexture && (featuresmask & PALETTEFEATURE_GLOW); + + R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, ((unsigned char *)palette_bgra_complete)[skindata[pix]*4 + comp]); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + + return skinframe; +} + +static void R_SkinFrame_GenerateTexturesFromQPixels(skinframe_t *skinframe, qboolean colormapped) +{ + int width; + int height; + unsigned char *skindata; + char vabuf[1024]; + + if (!skinframe->qpixels) + return; + + if (!skinframe->qhascolormapping) + colormapped = false; + + if (colormapped) + { + if (!skinframe->qgeneratebase) + return; + } + else + { + if (!skinframe->qgeneratemerged) + return; + } + + width = skinframe->qwidth; + height = skinframe->qheight; + skindata = skinframe->qpixels; + + if (skinframe->qgeneratenmap) + { + unsigned char *temp1, *temp2; + skinframe->qgeneratenmap = false; + temp1 = (unsigned char *)Mem_Alloc(tempmempool, width * height * 8); + temp2 = temp1 + width * height * 4; + // use either a custom palette or the quake palette + Image_Copy8bitBGRA(skindata, temp1, width * height, palette_bgra_complete); + Image_HeightmapToNormalmap_BGRA(temp1, temp2, width, height, false, r_shadow_bumpscale_basetexture.value); + skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), width, height, temp2, TEXTYPE_BGRA, (skinframe->textureflags | TEXF_ALPHA) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), -1, NULL); + Mem_Free(temp1); + } + + if (skinframe->qgenerateglow) + { + skinframe->qgenerateglow = false; + skinframe->glow = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_glow", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_onlyfullbrights); // glow + } + + if (colormapped) + { + skinframe->qgeneratebase = false; + skinframe->base = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nospecial", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, skinframe->glow ? palette_bgra_nocolormapnofullbrights : palette_bgra_nocolormap); + skinframe->pants = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_pants", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_pantsaswhite); + skinframe->shirt = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_shirt", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_shirtaswhite); + } + else + { + skinframe->qgeneratemerged = false; + skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, skinframe->glow ? palette_bgra_nofullbrights : palette_bgra_complete); + } + + if (!skinframe->qgeneratemerged && !skinframe->qgeneratebase) + { + Mem_Free(skinframe->qpixels); + skinframe->qpixels = NULL; + } +} + +skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, const unsigned char *skindata, int width, int height, const unsigned int *palette, const unsigned int *alphapalette) +{ + int i; + skinframe_t *skinframe; + char vabuf[1024]; + + if (cls.state == ca_dedicated) + return NULL; + + // if already loaded just return it, otherwise make a new skinframe + skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height) : 0, true); + if (skinframe->base) + return skinframe; + textureflags &= ~TEXF_FORCE_RELOAD; + + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + // if no data was provided, then clearly the caller wanted to get a blank skinframe + if (!skindata) + return NULL; + + if (developer_loading.integer) + Con_Printf("loading embedded 8bit image \"%s\"\n", name); + + skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_PALETTE, textureflags, -1, palette); + if (textureflags & TEXF_ALPHA) + { + for (i = 0;i < width * height;i++) + { + if (((unsigned char *)palette)[skindata[i]*4+3] < 255) + { + skinframe->hasalpha = true; + break; + } + } + if (r_loadfog && skinframe->hasalpha) + skinframe->fog = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_fog", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, textureflags, -1, alphapalette); + } + + R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, ((unsigned char *)palette)[skindata[pix]*4 + comp]); + //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); + + return skinframe; +} + +skinframe_t *R_SkinFrame_LoadMissing(void) +{ + skinframe_t *skinframe; + + if (cls.state == ca_dedicated) + return NULL; + + skinframe = R_SkinFrame_Find("missing", TEXF_FORCENEAREST, 0, 0, 0, true); + skinframe->stain = NULL; + skinframe->merged = NULL; + skinframe->base = NULL; + skinframe->pants = NULL; + skinframe->shirt = NULL; + skinframe->nmap = NULL; + skinframe->gloss = NULL; + skinframe->glow = NULL; + skinframe->fog = NULL; + skinframe->reflect = NULL; + skinframe->hasalpha = false; + + skinframe->avgcolor[0] = rand() / RAND_MAX; + skinframe->avgcolor[1] = rand() / RAND_MAX; + skinframe->avgcolor[2] = rand() / RAND_MAX; + skinframe->avgcolor[3] = 1; + + return skinframe; +} + +//static char *suffix[6] = {"ft", "bk", "rt", "lf", "up", "dn"}; +typedef struct suffixinfo_s +{ + const char *suffix; + qboolean flipx, flipy, flipdiagonal; +} +suffixinfo_t; +static suffixinfo_t suffix[3][6] = +{ + { + {"px", false, false, false}, + {"nx", false, false, false}, + {"py", false, false, false}, + {"ny", false, false, false}, + {"pz", false, false, false}, + {"nz", false, false, false} + }, + { + {"posx", false, false, false}, + {"negx", false, false, false}, + {"posy", false, false, false}, + {"negy", false, false, false}, + {"posz", false, false, false}, + {"negz", false, false, false} + }, + { + {"rt", true, false, true}, + {"lf", false, true, true}, + {"ft", true, true, false}, + {"bk", false, false, false}, + {"up", true, false, true}, + {"dn", true, false, true} + } +}; + +static int componentorder[4] = {0, 1, 2, 3}; + +static rtexture_t *R_LoadCubemap(const char *basename) +{ + int i, j, cubemapsize; + unsigned char *cubemappixels, *image_buffer; + rtexture_t *cubemaptexture; + char name[256]; + // must start 0 so the first loadimagepixels has no requested width/height + cubemapsize = 0; + cubemappixels = NULL; + cubemaptexture = NULL; + // keep trying different suffix groups (posx, px, rt) until one loads + for (j = 0;j < 3 && !cubemappixels;j++) + { + // load the 6 images in the suffix group + for (i = 0;i < 6;i++) + { + // generate an image name based on the base and and suffix + dpsnprintf(name, sizeof(name), "%s%s", basename, suffix[j][i].suffix); + // load it + if ((image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + { + // an image loaded, make sure width and height are equal + if (image_width == image_height && (!cubemappixels || image_width == cubemapsize)) + { + // if this is the first image to load successfully, allocate the cubemap memory + if (!cubemappixels && image_width >= 1) + { + cubemapsize = image_width; + // note this clears to black, so unavailable sides are black + cubemappixels = (unsigned char *)Mem_Alloc(tempmempool, 6*cubemapsize*cubemapsize*4); + } + // copy the image with any flipping needed by the suffix (px and posx types don't need flipping) + if (cubemappixels) + Image_CopyMux(cubemappixels+i*cubemapsize*cubemapsize*4, image_buffer, cubemapsize, cubemapsize, suffix[j][i].flipx, suffix[j][i].flipy, suffix[j][i].flipdiagonal, 4, 4, componentorder); + } + else + Con_Printf("Cubemap image \"%s\" (%ix%i) is not square, OpenGL requires square cubemaps.\n", name, image_width, image_height); + // free the image + Mem_Free(image_buffer); + } + } + } + // if a cubemap loaded, upload it + if (cubemappixels) + { + if (developer_loading.integer) + Con_Printf("loading cubemap \"%s\"\n", basename); + + cubemaptexture = R_LoadTextureCubeMap(r_main_texturepool, basename, cubemapsize, cubemappixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, (gl_texturecompression_lightcubemaps.integer && gl_texturecompression.integer ? TEXF_COMPRESS : 0) | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + Mem_Free(cubemappixels); + } + else + { + Con_DPrintf("failed to load cubemap \"%s\"\n", basename); + if (developer_loading.integer) + { + Con_Printf("(tried tried images "); + for (j = 0;j < 3;j++) + for (i = 0;i < 6;i++) + Con_Printf("%s\"%s%s.tga\"", j + i > 0 ? ", " : "", basename, suffix[j][i].suffix); + Con_Print(" and was unable to find any of them).\n"); + } + } + return cubemaptexture; +} + +rtexture_t *R_GetCubemap(const char *basename) +{ + int i; + for (i = 0;i < r_texture_numcubemaps;i++) + if (r_texture_cubemaps[i] != NULL) + if (!strcasecmp(r_texture_cubemaps[i]->basename, basename)) + return r_texture_cubemaps[i]->texture ? r_texture_cubemaps[i]->texture : r_texture_whitecube; + if (i >= MAX_CUBEMAPS || !r_main_mempool) + return r_texture_whitecube; + r_texture_numcubemaps++; + r_texture_cubemaps[i] = (cubemapinfo_t *)Mem_Alloc(r_main_mempool, sizeof(cubemapinfo_t)); + strlcpy(r_texture_cubemaps[i]->basename, basename, sizeof(r_texture_cubemaps[i]->basename)); + r_texture_cubemaps[i]->texture = R_LoadCubemap(r_texture_cubemaps[i]->basename); + return r_texture_cubemaps[i]->texture; +} + +static void R_Main_FreeViewCache(void) +{ + if (r_refdef.viewcache.entityvisible) + Mem_Free(r_refdef.viewcache.entityvisible); + if (r_refdef.viewcache.world_pvsbits) + Mem_Free(r_refdef.viewcache.world_pvsbits); + if (r_refdef.viewcache.world_leafvisible) + Mem_Free(r_refdef.viewcache.world_leafvisible); + if (r_refdef.viewcache.world_surfacevisible) + Mem_Free(r_refdef.viewcache.world_surfacevisible); + memset(&r_refdef.viewcache, 0, sizeof(r_refdef.viewcache)); +} + +static void R_Main_ResizeViewCache(void) +{ + int numentities = r_refdef.scene.numentities; + int numclusters = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_pvsclusters : 1; + int numclusterbytes = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_pvsclusterbytes : 1; + int numleafs = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_leafs : 1; + int numsurfaces = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->num_surfaces : 1; + if (r_refdef.viewcache.maxentities < numentities) + { + r_refdef.viewcache.maxentities = numentities; + if (r_refdef.viewcache.entityvisible) + Mem_Free(r_refdef.viewcache.entityvisible); + r_refdef.viewcache.entityvisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.maxentities); + } + if (r_refdef.viewcache.world_numclusters != numclusters) + { + r_refdef.viewcache.world_numclusters = numclusters; + r_refdef.viewcache.world_numclusterbytes = numclusterbytes; + if (r_refdef.viewcache.world_pvsbits) + Mem_Free(r_refdef.viewcache.world_pvsbits); + r_refdef.viewcache.world_pvsbits = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numclusterbytes); + } + if (r_refdef.viewcache.world_numleafs != numleafs) + { + r_refdef.viewcache.world_numleafs = numleafs; + if (r_refdef.viewcache.world_leafvisible) + Mem_Free(r_refdef.viewcache.world_leafvisible); + r_refdef.viewcache.world_leafvisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numleafs); + } + if (r_refdef.viewcache.world_numsurfaces != numsurfaces) + { + r_refdef.viewcache.world_numsurfaces = numsurfaces; + if (r_refdef.viewcache.world_surfacevisible) + Mem_Free(r_refdef.viewcache.world_surfacevisible); + r_refdef.viewcache.world_surfacevisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numsurfaces); + } +} + +extern rtexture_t *loadingscreentexture; +static void gl_main_start(void) +{ + loadingscreentexture = NULL; + r_texture_blanknormalmap = NULL; + r_texture_white = NULL; + r_texture_grey128 = NULL; + r_texture_black = NULL; + r_texture_whitecube = NULL; + r_texture_normalizationcube = NULL; + r_texture_fogattenuation = NULL; + r_texture_fogheighttexture = NULL; + r_texture_gammaramps = NULL; + r_texture_numcubemaps = 0; + r_uniformbufferalignment = 32; + + r_loaddds = r_texture_dds_load.integer != 0; + r_savedds = vid.support.arb_texture_compression && vid.support.ext_texture_compression_s3tc && r_texture_dds_save.integer; + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + Cvar_SetValueQuick(&r_textureunits, vid.texunits); + Cvar_SetValueQuick(&gl_combine, 1); + Cvar_SetValueQuick(&r_glsl, 1); + r_loadnormalmap = true; + r_loadgloss = true; + r_loadfog = false; + if (vid.support.arb_uniform_buffer_object) + qglGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &r_uniformbufferalignment); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + Cvar_SetValueQuick(&r_textureunits, vid.texunits); + Cvar_SetValueQuick(&gl_combine, 1); + Cvar_SetValueQuick(&r_glsl, 0); + r_loadnormalmap = false; + r_loadgloss = false; + r_loadfog = true; + break; + case RENDERPATH_GL11: + Cvar_SetValueQuick(&r_textureunits, vid.texunits); + Cvar_SetValueQuick(&gl_combine, 0); + Cvar_SetValueQuick(&r_glsl, 0); + r_loadnormalmap = false; + r_loadgloss = false; + r_loadfog = true; + break; + } + + R_AnimCache_Free(); + R_FrameData_Reset(); + R_BufferData_Reset(); + + r_numqueries = 0; + r_maxqueries = 0; + memset(r_queries, 0, sizeof(r_queries)); + + r_qwskincache = NULL; + r_qwskincache_size = 0; + + // due to caching of texture_t references, the collision cache must be reset + Collision_Cache_Reset(true); + + // set up r_skinframe loading system for textures + memset(&r_skinframe, 0, sizeof(r_skinframe)); + r_skinframe.loadsequence = 1; + Mem_ExpandableArray_NewArray(&r_skinframe.array, r_main_mempool, sizeof(skinframe_t), 256); + + r_main_texturepool = R_AllocTexturePool(); + R_BuildBlankTextures(); + R_BuildNoTexture(); + if (vid.support.arb_texture_cube_map) + { + R_BuildWhiteCube(); + R_BuildNormalizationCube(); + } + r_texture_fogattenuation = NULL; + r_texture_fogheighttexture = NULL; + r_texture_gammaramps = NULL; + //r_texture_fogintensity = NULL; + memset(&r_fb, 0, sizeof(r_fb)); + r_glsl_permutation = NULL; + memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); + Mem_ExpandableArray_NewArray(&r_glsl_permutationarray, r_main_mempool, sizeof(r_glsl_permutation_t), 256); + glslshaderstring = NULL; +#ifdef SUPPORTD3D + r_hlsl_permutation = NULL; + memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); + Mem_ExpandableArray_NewArray(&r_hlsl_permutationarray, r_main_mempool, sizeof(r_hlsl_permutation_t), 256); +#endif + hlslshaderstring = NULL; + memset(&r_svbsp, 0, sizeof (r_svbsp)); + + memset(r_texture_cubemaps, 0, sizeof(r_texture_cubemaps)); + r_texture_numcubemaps = 0; + + r_refdef.fogmasktable_density = 0; +} + +static void gl_main_shutdown(void) +{ + R_AnimCache_Free(); + R_FrameData_Reset(); + R_BufferData_Reset(); + + R_Main_FreeViewCache(); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: +#ifdef GL_SAMPLES_PASSED_ARB + if (r_maxqueries) + qglDeleteQueriesARB(r_maxqueries, r_queries); +#endif + break; + case RENDERPATH_D3D9: + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + + r_numqueries = 0; + r_maxqueries = 0; + memset(r_queries, 0, sizeof(r_queries)); + + r_qwskincache = NULL; + r_qwskincache_size = 0; + + // clear out the r_skinframe state + Mem_ExpandableArray_FreeArray(&r_skinframe.array); + memset(&r_skinframe, 0, sizeof(r_skinframe)); + + if (r_svbsp.nodes) + Mem_Free(r_svbsp.nodes); + memset(&r_svbsp, 0, sizeof (r_svbsp)); + R_FreeTexturePool(&r_main_texturepool); + loadingscreentexture = NULL; + r_texture_blanknormalmap = NULL; + r_texture_white = NULL; + r_texture_grey128 = NULL; + r_texture_black = NULL; + r_texture_whitecube = NULL; + r_texture_normalizationcube = NULL; + r_texture_fogattenuation = NULL; + r_texture_fogheighttexture = NULL; + r_texture_gammaramps = NULL; + r_texture_numcubemaps = 0; + //r_texture_fogintensity = NULL; + memset(&r_fb, 0, sizeof(r_fb)); + R_GLSL_Restart_f(); + + r_glsl_permutation = NULL; + memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); + Mem_ExpandableArray_FreeArray(&r_glsl_permutationarray); + glslshaderstring = NULL; +#ifdef SUPPORTD3D + r_hlsl_permutation = NULL; + memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); + Mem_ExpandableArray_FreeArray(&r_hlsl_permutationarray); +#endif + hlslshaderstring = NULL; +} + +static void gl_main_newmap(void) +{ + // FIXME: move this code to client + char *entities, entname[MAX_QPATH]; + if (r_qwskincache) + Mem_Free(r_qwskincache); + r_qwskincache = NULL; + r_qwskincache_size = 0; + if (cl.worldmodel) + { + dpsnprintf(entname, sizeof(entname), "%s.ent", cl.worldnamenoextension); + if ((entities = (char *)FS_LoadFile(entname, tempmempool, true, NULL))) + { + CL_ParseEntityLump(entities); + Mem_Free(entities); + return; + } + if (cl.worldmodel->brush.entities) + CL_ParseEntityLump(cl.worldmodel->brush.entities); + } + R_Main_FreeViewCache(); + + R_FrameData_Reset(); + R_BufferData_Reset(); +} + +void GL_Main_Init(void) +{ + int i; + r_main_mempool = Mem_AllocPool("Renderer", 0, NULL); + + Cmd_AddCommand("r_glsl_restart", R_GLSL_Restart_f, "unloads GLSL shaders, they will then be reloaded as needed"); + Cmd_AddCommand("r_glsl_dumpshader", R_GLSL_DumpShader_f, "dumps the engine internal default.glsl shader into glsl/default.glsl"); + // FIXME: the client should set up r_refdef.fog stuff including the fogmasktable + if (gamemode == GAME_NEHAHRA) + { + Cvar_RegisterVariable (&gl_fogenable); + Cvar_RegisterVariable (&gl_fogdensity); + Cvar_RegisterVariable (&gl_fogred); + Cvar_RegisterVariable (&gl_foggreen); + Cvar_RegisterVariable (&gl_fogblue); + Cvar_RegisterVariable (&gl_fogstart); + Cvar_RegisterVariable (&gl_fogend); + Cvar_RegisterVariable (&gl_skyclip); + } + Cvar_RegisterVariable(&r_worldscale); + Cvar_RegisterVariable(&r_motionblur); + Cvar_RegisterVariable(&r_damageblur); + Cvar_RegisterVariable(&r_motionblur_averaging); + Cvar_RegisterVariable(&r_motionblur_randomize); + Cvar_RegisterVariable(&r_motionblur_minblur); + Cvar_RegisterVariable(&r_motionblur_maxblur); + Cvar_RegisterVariable(&r_motionblur_velocityfactor); + Cvar_RegisterVariable(&r_motionblur_velocityfactor_minspeed); + Cvar_RegisterVariable(&r_motionblur_velocityfactor_maxspeed); + Cvar_RegisterVariable(&r_motionblur_mousefactor); + Cvar_RegisterVariable(&r_motionblur_mousefactor_minspeed); + Cvar_RegisterVariable(&r_motionblur_mousefactor_maxspeed); + Cvar_RegisterVariable(&r_equalize_entities_fullbright); + Cvar_RegisterVariable(&r_equalize_entities_minambient); + Cvar_RegisterVariable(&r_equalize_entities_by); + Cvar_RegisterVariable(&r_equalize_entities_to); + Cvar_RegisterVariable(&r_depthfirst); + Cvar_RegisterVariable(&r_useinfinitefarclip); + Cvar_RegisterVariable(&r_farclip_base); + Cvar_RegisterVariable(&r_farclip_world); + Cvar_RegisterVariable(&r_nearclip); + Cvar_RegisterVariable(&r_deformvertexes); + Cvar_RegisterVariable(&r_transparent); + Cvar_RegisterVariable(&r_transparent_alphatocoverage); + Cvar_RegisterVariable(&r_transparent_sortsurfacesbynearest); + Cvar_RegisterVariable(&r_transparent_useplanardistance); + Cvar_RegisterVariable(&r_showoverdraw); + Cvar_RegisterVariable(&r_showbboxes); + Cvar_RegisterVariable(&r_showsurfaces); + Cvar_RegisterVariable(&r_showtris); + Cvar_RegisterVariable(&r_shownormals); + Cvar_RegisterVariable(&r_showlighting); + Cvar_RegisterVariable(&r_showshadowvolumes); + Cvar_RegisterVariable(&r_showcollisionbrushes); + Cvar_RegisterVariable(&r_showcollisionbrushes_polygonfactor); + Cvar_RegisterVariable(&r_showcollisionbrushes_polygonoffset); + Cvar_RegisterVariable(&r_showdisabledepthtest); + Cvar_RegisterVariable(&r_drawportals); + Cvar_RegisterVariable(&r_drawentities); + Cvar_RegisterVariable(&r_draw2d); + Cvar_RegisterVariable(&r_drawworld); + Cvar_RegisterVariable(&r_cullentities_trace); + Cvar_RegisterVariable(&r_cullentities_trace_samples); + Cvar_RegisterVariable(&r_cullentities_trace_tempentitysamples); + Cvar_RegisterVariable(&r_cullentities_trace_enlarge); + Cvar_RegisterVariable(&r_cullentities_trace_delay); + Cvar_RegisterVariable(&r_sortentities); + Cvar_RegisterVariable(&r_drawviewmodel); + Cvar_RegisterVariable(&r_drawexteriormodel); + Cvar_RegisterVariable(&r_speeds); + Cvar_RegisterVariable(&r_fullbrights); + Cvar_RegisterVariable(&r_wateralpha); + Cvar_RegisterVariable(&r_dynamic); + Cvar_RegisterVariable(&r_fakelight); + Cvar_RegisterVariable(&r_fakelight_intensity); + Cvar_RegisterVariable(&r_fullbright); + Cvar_RegisterVariable(&r_shadows); + Cvar_RegisterVariable(&r_shadows_darken); + Cvar_RegisterVariable(&r_shadows_drawafterrtlighting); + Cvar_RegisterVariable(&r_shadows_castfrombmodels); + Cvar_RegisterVariable(&r_shadows_throwdistance); + Cvar_RegisterVariable(&r_shadows_throwdirection); + Cvar_RegisterVariable(&r_shadows_focus); + Cvar_RegisterVariable(&r_shadows_shadowmapscale); + Cvar_RegisterVariable(&r_shadows_shadowmapbias); + Cvar_RegisterVariable(&r_q1bsp_skymasking); + Cvar_RegisterVariable(&r_polygonoffset_submodel_factor); + Cvar_RegisterVariable(&r_polygonoffset_submodel_offset); + Cvar_RegisterVariable(&r_polygonoffset_decals_factor); + Cvar_RegisterVariable(&r_polygonoffset_decals_offset); + Cvar_RegisterVariable(&r_fog_exp2); + Cvar_RegisterVariable(&r_fog_clear); + Cvar_RegisterVariable(&r_drawfog); + Cvar_RegisterVariable(&r_transparentdepthmasking); + Cvar_RegisterVariable(&r_transparent_sortmindist); + Cvar_RegisterVariable(&r_transparent_sortmaxdist); + Cvar_RegisterVariable(&r_transparent_sortarraysize); + Cvar_RegisterVariable(&r_texture_dds_load); + Cvar_RegisterVariable(&r_texture_dds_save); + Cvar_RegisterVariable(&r_textureunits); + Cvar_RegisterVariable(&gl_combine); + Cvar_RegisterVariable(&r_usedepthtextures); + Cvar_RegisterVariable(&r_viewfbo); + Cvar_RegisterVariable(&r_viewscale); + Cvar_RegisterVariable(&r_viewscale_fpsscaling); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_min); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_multiply); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_stepsize); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_stepmax); + Cvar_RegisterVariable(&r_viewscale_fpsscaling_target); + Cvar_RegisterVariable(&r_glsl); + Cvar_RegisterVariable(&r_glsl_deluxemapping); + Cvar_RegisterVariable(&r_glsl_offsetmapping); + Cvar_RegisterVariable(&r_glsl_offsetmapping_steps); + Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping); + Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping_steps); + Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping_refinesteps); + Cvar_RegisterVariable(&r_glsl_offsetmapping_scale); + Cvar_RegisterVariable(&r_glsl_offsetmapping_lod); + Cvar_RegisterVariable(&r_glsl_offsetmapping_lod_distance); + Cvar_RegisterVariable(&r_glsl_postprocess); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec1); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec2); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec3); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec4); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec1_enable); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec2_enable); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec3_enable); + Cvar_RegisterVariable(&r_glsl_postprocess_uservec4_enable); + Cvar_RegisterVariable(&r_celshading); + Cvar_RegisterVariable(&r_celoutlines); + + Cvar_RegisterVariable(&r_water); + Cvar_RegisterVariable(&r_water_resolutionmultiplier); + Cvar_RegisterVariable(&r_water_clippingplanebias); + Cvar_RegisterVariable(&r_water_refractdistort); + Cvar_RegisterVariable(&r_water_reflectdistort); + Cvar_RegisterVariable(&r_water_scissormode); + Cvar_RegisterVariable(&r_water_lowquality); + Cvar_RegisterVariable(&r_water_hideplayer); + Cvar_RegisterVariable(&r_water_fbo); + + Cvar_RegisterVariable(&r_lerpsprites); + Cvar_RegisterVariable(&r_lerpmodels); + Cvar_RegisterVariable(&r_lerplightstyles); + Cvar_RegisterVariable(&r_waterscroll); + Cvar_RegisterVariable(&r_bloom); + Cvar_RegisterVariable(&r_bloom_colorscale); + Cvar_RegisterVariable(&r_bloom_brighten); + Cvar_RegisterVariable(&r_bloom_blur); + Cvar_RegisterVariable(&r_bloom_resolution); + Cvar_RegisterVariable(&r_bloom_colorexponent); + Cvar_RegisterVariable(&r_bloom_colorsubtract); + Cvar_RegisterVariable(&r_bloom_scenebrightness); + Cvar_RegisterVariable(&r_hdr_scenebrightness); + Cvar_RegisterVariable(&r_hdr_glowintensity); + Cvar_RegisterVariable(&r_hdr_irisadaptation); + Cvar_RegisterVariable(&r_hdr_irisadaptation_multiplier); + Cvar_RegisterVariable(&r_hdr_irisadaptation_minvalue); + Cvar_RegisterVariable(&r_hdr_irisadaptation_maxvalue); + Cvar_RegisterVariable(&r_hdr_irisadaptation_value); + Cvar_RegisterVariable(&r_hdr_irisadaptation_fade_up); + Cvar_RegisterVariable(&r_hdr_irisadaptation_fade_down); + Cvar_RegisterVariable(&r_hdr_irisadaptation_radius); + Cvar_RegisterVariable(&r_smoothnormals_areaweighting); + Cvar_RegisterVariable(&developer_texturelogging); + Cvar_RegisterVariable(&gl_lightmaps); + Cvar_RegisterVariable(&r_test); + Cvar_RegisterVariable(&r_batch_multidraw); + Cvar_RegisterVariable(&r_batch_multidraw_mintriangles); + Cvar_RegisterVariable(&r_batch_debugdynamicvertexpath); + Cvar_RegisterVariable(&r_glsl_skeletal); + Cvar_RegisterVariable(&r_glsl_saturation); + Cvar_RegisterVariable(&r_glsl_saturation_redcompensate); + Cvar_RegisterVariable(&r_glsl_vertextextureblend_usebothalphas); + Cvar_RegisterVariable(&r_framedatasize); + for (i = 0;i < R_BUFFERDATA_COUNT;i++) + Cvar_RegisterVariable(&r_buffermegs[i]); + Cvar_RegisterVariable(&r_batch_dynamicbuffer); + if (gamemode == GAME_NEHAHRA || gamemode == GAME_TENEBRAE) + Cvar_SetValue("r_fullbrights", 0); + R_RegisterModule("GL_Main", gl_main_start, gl_main_shutdown, gl_main_newmap, NULL, NULL); +} + +void Render_Init(void) +{ + gl_backend_init(); + R_Textures_Init(); + GL_Main_Init(); + Font_Init(); + GL_Draw_Init(); + R_Shadow_Init(); + R_Sky_Init(); + GL_Surf_Init(); + Sbar_Init(); + R_Particles_Init(); + R_Explosion_Init(); + R_LightningBeams_Init(); + Mod_RenderInit(); +} + +/* +=============== +GL_Init +=============== +*/ +#ifndef USE_GLES2 +extern char *ENGINE_EXTENSIONS; +void GL_Init (void) +{ + gl_renderer = (const char *)qglGetString(GL_RENDERER); + gl_vendor = (const char *)qglGetString(GL_VENDOR); + gl_version = (const char *)qglGetString(GL_VERSION); + gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); + + if (!gl_extensions) + gl_extensions = ""; + if (!gl_platformextensions) + gl_platformextensions = ""; + + Con_Printf("GL_VENDOR: %s\n", gl_vendor); + Con_Printf("GL_RENDERER: %s\n", gl_renderer); + Con_Printf("GL_VERSION: %s\n", gl_version); + Con_DPrintf("GL_EXTENSIONS: %s\n", gl_extensions); + Con_DPrintf("%s_EXTENSIONS: %s\n", gl_platform, gl_platformextensions); + + VID_CheckExtensions(); + + // LordHavoc: report supported extensions + Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); + + // clear to black (loading plaque will be seen over this) + GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128); +} +#endif + +int R_CullBox(const vec3_t mins, const vec3_t maxs) +{ + int i; + mplane_t *p; + if (r_trippy.integer) + return false; + for (i = 0;i < r_refdef.view.numfrustumplanes;i++) + { + // skip nearclip plane, it often culls portals when you are very close, and is almost never useful + if (i == 4) + continue; + p = r_refdef.view.frustum + i; + switch(p->signbits) + { + default: + case 0: + if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 1: + if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 2: + if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 3: + if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 4: + if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 5: + if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 6: + if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 7: + if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + } + } + return false; +} + +int R_CullBoxCustomPlanes(const vec3_t mins, const vec3_t maxs, int numplanes, const mplane_t *planes) +{ + int i; + const mplane_t *p; + if (r_trippy.integer) + return false; + for (i = 0;i < numplanes;i++) + { + p = planes + i; + switch(p->signbits) + { + default: + case 0: + if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 1: + if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 2: + if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 3: + if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) + return true; + break; + case 4: + if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 5: + if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 6: + if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + case 7: + if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) + return true; + break; + } + } + return false; +} + +//================================================================================== + +// LordHavoc: this stores temporary data used within the same frame + +typedef struct r_framedata_mem_s +{ + struct r_framedata_mem_s *purge; // older mem block to free on next frame + size_t size; // how much usable space + size_t current; // how much space in use + size_t mark; // last "mark" location, temporary memory can be freed by returning to this + size_t wantedsize; // how much space was allocated + unsigned char *data; // start of real data (16byte aligned) +} +r_framedata_mem_t; + +static r_framedata_mem_t *r_framedata_mem; + +void R_FrameData_Reset(void) +{ + while (r_framedata_mem) + { + r_framedata_mem_t *next = r_framedata_mem->purge; + Mem_Free(r_framedata_mem); + r_framedata_mem = next; + } +} + +static void R_FrameData_Resize(qboolean mustgrow) +{ + size_t wantedsize; + wantedsize = (size_t)(r_framedatasize.value * 1024*1024); + wantedsize = bound(65536, wantedsize, 1000*1024*1024); + if (!r_framedata_mem || r_framedata_mem->wantedsize != wantedsize || mustgrow) + { + r_framedata_mem_t *newmem = (r_framedata_mem_t *)Mem_Alloc(r_main_mempool, wantedsize); + newmem->wantedsize = wantedsize; + newmem->data = (unsigned char *)(((size_t)(newmem+1) + 15) & ~15); + newmem->size = (unsigned char *)newmem + wantedsize - newmem->data; + newmem->current = 0; + newmem->mark = 0; + newmem->purge = r_framedata_mem; + r_framedata_mem = newmem; + } +} + +void R_FrameData_NewFrame(void) +{ + R_FrameData_Resize(false); + if (!r_framedata_mem) + return; + // if we ran out of space on the last frame, free the old memory now + while (r_framedata_mem->purge) + { + // repeatedly remove the second item in the list, leaving only head + r_framedata_mem_t *next = r_framedata_mem->purge->purge; + Mem_Free(r_framedata_mem->purge); + r_framedata_mem->purge = next; + } + // reset the current mem pointer + r_framedata_mem->current = 0; + r_framedata_mem->mark = 0; +} + +void *R_FrameData_Alloc(size_t size) +{ + void *data; + float newvalue; + + // align to 16 byte boundary - the data pointer is already aligned, so we + // only need to ensure the size of every allocation is also aligned + size = (size + 15) & ~15; + + while (!r_framedata_mem || r_framedata_mem->current + size > r_framedata_mem->size) + { + // emergency - we ran out of space, allocate more memory + newvalue = bound(0.25f, r_framedatasize.value * 2.0f, 256.0f); + // this might not be a growing it, but we'll allocate another buffer every time + Cvar_SetValueQuick(&r_framedatasize, newvalue); + R_FrameData_Resize(true); + } + + data = r_framedata_mem->data + r_framedata_mem->current; + r_framedata_mem->current += size; + + // count the usage for stats + r_refdef.stats[r_stat_framedatacurrent] = max(r_refdef.stats[r_stat_framedatacurrent], (int)r_framedata_mem->current); + r_refdef.stats[r_stat_framedatasize] = max(r_refdef.stats[r_stat_framedatasize], (int)r_framedata_mem->size); + + return (void *)data; +} + +void *R_FrameData_Store(size_t size, void *data) +{ + void *d = R_FrameData_Alloc(size); + if (d && data) + memcpy(d, data, size); + return d; +} + +void R_FrameData_SetMark(void) +{ + if (!r_framedata_mem) + return; + r_framedata_mem->mark = r_framedata_mem->current; +} + +void R_FrameData_ReturnToMark(void) +{ + if (!r_framedata_mem) + return; + r_framedata_mem->current = r_framedata_mem->mark; +} + +//================================================================================== + +// avoid reusing the same buffer objects on consecutive frames +#define R_BUFFERDATA_CYCLE 3 + +typedef struct r_bufferdata_buffer_s +{ + struct r_bufferdata_buffer_s *purge; // older buffer to free on next frame + size_t size; // how much usable space + size_t current; // how much space in use + r_meshbuffer_t *buffer; // the buffer itself +} +r_bufferdata_buffer_t; + +static int r_bufferdata_cycle = 0; // incremented and wrapped each frame +static r_bufferdata_buffer_t *r_bufferdata_buffer[R_BUFFERDATA_CYCLE][R_BUFFERDATA_COUNT]; + +/// frees all dynamic buffers +void R_BufferData_Reset(void) +{ + int cycle, type; + r_bufferdata_buffer_t **p, *mem; + for (cycle = 0;cycle < R_BUFFERDATA_CYCLE;cycle++) + { + for (type = 0;type < R_BUFFERDATA_COUNT;type++) + { + // free all buffers + p = &r_bufferdata_buffer[cycle][type]; + while (*p) + { + mem = *p; + *p = (*p)->purge; + if (mem->buffer) + R_Mesh_DestroyMeshBuffer(mem->buffer); + Mem_Free(mem); + } + } + } +} + +// resize buffer as needed (this actually makes a new one, the old one will be recycled next frame) +static void R_BufferData_Resize(r_bufferdata_type_t type, qboolean mustgrow, size_t minsize) +{ + r_bufferdata_buffer_t *mem = r_bufferdata_buffer[r_bufferdata_cycle][type]; + size_t size; + float newvalue = r_buffermegs[type].value; + + // increase the cvar if we have to (but only if we already have a mem) + if (mustgrow && mem) + newvalue *= 2.0f; + newvalue = bound(0.25f, newvalue, 256.0f); + while (newvalue * 1024*1024 < minsize) + newvalue *= 2.0f; + + // clamp the cvar to valid range + newvalue = bound(0.25f, newvalue, 256.0f); + if (r_buffermegs[type].value != newvalue) + Cvar_SetValueQuick(&r_buffermegs[type], newvalue); + + // calculate size in bytes + size = (size_t)(newvalue * 1024*1024); + size = bound(131072, size, 256*1024*1024); + + // allocate a new buffer if the size is different (purge old one later) + // or if we were told we must grow the buffer + if (!mem || mem->size != size || mustgrow) + { + mem = (r_bufferdata_buffer_t *)Mem_Alloc(r_main_mempool, sizeof(*mem)); + mem->size = size; + mem->current = 0; + if (type == R_BUFFERDATA_VERTEX) + mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbuffervertex", false, false, true, false); + else if (type == R_BUFFERDATA_INDEX16) + mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferindex16", true, false, true, true); + else if (type == R_BUFFERDATA_INDEX32) + mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferindex32", true, false, true, false); + else if (type == R_BUFFERDATA_UNIFORM) + mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferuniform", false, true, true, false); + mem->purge = r_bufferdata_buffer[r_bufferdata_cycle][type]; + r_bufferdata_buffer[r_bufferdata_cycle][type] = mem; + } +} + +void R_BufferData_NewFrame(void) +{ + int type; + r_bufferdata_buffer_t **p, *mem; + // cycle to the next frame's buffers + r_bufferdata_cycle = (r_bufferdata_cycle + 1) % R_BUFFERDATA_CYCLE; + // if we ran out of space on the last time we used these buffers, free the old memory now + for (type = 0;type < R_BUFFERDATA_COUNT;type++) + { + if (r_bufferdata_buffer[r_bufferdata_cycle][type]) + { + R_BufferData_Resize((r_bufferdata_type_t)type, false, 131072); + // free all but the head buffer, this is how we recycle obsolete + // buffers after they are no longer in use + p = &r_bufferdata_buffer[r_bufferdata_cycle][type]->purge; + while (*p) + { + mem = *p; + *p = (*p)->purge; + if (mem->buffer) + R_Mesh_DestroyMeshBuffer(mem->buffer); + Mem_Free(mem); + } + // reset the current offset + r_bufferdata_buffer[r_bufferdata_cycle][type]->current = 0; + } + } +} + +r_meshbuffer_t *R_BufferData_Store(size_t datasize, const void *data, r_bufferdata_type_t type, int *returnbufferoffset) +{ + r_bufferdata_buffer_t *mem; + int offset = 0; + int padsize; + + *returnbufferoffset = 0; + + // align size to a byte boundary appropriate for the buffer type, this + // makes all allocations have aligned start offsets + if (type == R_BUFFERDATA_UNIFORM) + padsize = (datasize + r_uniformbufferalignment - 1) & ~(r_uniformbufferalignment - 1); + else + padsize = (datasize + 15) & ~15; + + // if we ran out of space in this buffer we must allocate a new one + if (!r_bufferdata_buffer[r_bufferdata_cycle][type] || r_bufferdata_buffer[r_bufferdata_cycle][type]->current + padsize > r_bufferdata_buffer[r_bufferdata_cycle][type]->size) + R_BufferData_Resize(type, true, padsize); + + // if the resize did not give us enough memory, fail + if (!r_bufferdata_buffer[r_bufferdata_cycle][type] || r_bufferdata_buffer[r_bufferdata_cycle][type]->current + padsize > r_bufferdata_buffer[r_bufferdata_cycle][type]->size) + Sys_Error("R_BufferData_Store: failed to create a new buffer of sufficient size\n"); + + mem = r_bufferdata_buffer[r_bufferdata_cycle][type]; + offset = mem->current; + mem->current += padsize; + + // upload the data to the buffer at the chosen offset + if (offset == 0) + R_Mesh_UpdateMeshBuffer(mem->buffer, NULL, mem->size, false, 0); + R_Mesh_UpdateMeshBuffer(mem->buffer, data, datasize, true, offset); + + // count the usage for stats + r_refdef.stats[r_stat_bufferdatacurrent_vertex + type] = max(r_refdef.stats[r_stat_bufferdatacurrent_vertex + type], (int)mem->current); + r_refdef.stats[r_stat_bufferdatasize_vertex + type] = max(r_refdef.stats[r_stat_bufferdatasize_vertex + type], (int)mem->size); + + // return the buffer offset + *returnbufferoffset = offset; + + return mem->buffer; +} + +//================================================================================== + +// LordHavoc: animcache originally written by Echon, rewritten since then + +/** + * Animation cache prevents re-generating mesh data for an animated model + * multiple times in one frame for lighting, shadowing, reflections, etc. + */ + +void R_AnimCache_Free(void) +{ +} + +void R_AnimCache_ClearCache(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + ent->animcache_vertex3f = NULL; + ent->animcache_vertex3f_vertexbuffer = NULL; + ent->animcache_vertex3f_bufferoffset = 0; + ent->animcache_normal3f = NULL; + ent->animcache_normal3f_vertexbuffer = NULL; + ent->animcache_normal3f_bufferoffset = 0; + ent->animcache_svector3f = NULL; + ent->animcache_svector3f_vertexbuffer = NULL; + ent->animcache_svector3f_bufferoffset = 0; + ent->animcache_tvector3f = NULL; + ent->animcache_tvector3f_vertexbuffer = NULL; + ent->animcache_tvector3f_bufferoffset = 0; + ent->animcache_vertexmesh = NULL; + ent->animcache_vertexmesh_vertexbuffer = NULL; + ent->animcache_vertexmesh_bufferoffset = 0; + ent->animcache_skeletaltransform3x4 = NULL; + ent->animcache_skeletaltransform3x4buffer = NULL; + ent->animcache_skeletaltransform3x4offset = 0; + ent->animcache_skeletaltransform3x4size = 0; + } +} + +static void R_AnimCache_UpdateEntityMeshBuffers(entity_render_t *ent, int numvertices) +{ + int i; + + // check if we need the meshbuffers + if (!vid.useinterleavedarrays) + return; + + if (!ent->animcache_vertexmesh && ent->animcache_normal3f) + ent->animcache_vertexmesh = (r_vertexmesh_t *)R_FrameData_Alloc(sizeof(r_vertexmesh_t)*numvertices); + // TODO: upload vertexbuffer? + if (ent->animcache_vertexmesh) + { + r_refdef.stats[r_stat_animcache_vertexmesh_count] += 1; + r_refdef.stats[r_stat_animcache_vertexmesh_vertices] += numvertices; + r_refdef.stats[r_stat_animcache_vertexmesh_maxvertices] = max(r_refdef.stats[r_stat_animcache_vertexmesh_maxvertices], numvertices); + memcpy(ent->animcache_vertexmesh, ent->model->surfmesh.data_vertexmesh, sizeof(r_vertexmesh_t)*numvertices); + for (i = 0;i < numvertices;i++) + memcpy(ent->animcache_vertexmesh[i].vertex3f, ent->animcache_vertex3f + 3*i, sizeof(float[3])); + if (ent->animcache_svector3f) + for (i = 0;i < numvertices;i++) + memcpy(ent->animcache_vertexmesh[i].svector3f, ent->animcache_svector3f + 3*i, sizeof(float[3])); + if (ent->animcache_tvector3f) + for (i = 0;i < numvertices;i++) + memcpy(ent->animcache_vertexmesh[i].tvector3f, ent->animcache_tvector3f + 3*i, sizeof(float[3])); + if (ent->animcache_normal3f) + for (i = 0;i < numvertices;i++) + memcpy(ent->animcache_vertexmesh[i].normal3f, ent->animcache_normal3f + 3*i, sizeof(float[3])); + } +} + +qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qboolean wanttangents) +{ + dp_model_t *model = ent->model; + int numvertices; + + // see if this ent is worth caching + if (!model || !model->Draw || !model->AnimateVertices) + return false; + // nothing to cache if it contains no animations and has no skeleton + if (!model->surfmesh.isanimated && !(model->num_bones && ent->skeleton && ent->skeleton->relativetransforms)) + return false; + // see if it is already cached for gpuskeletal + if (ent->animcache_skeletaltransform3x4) + return false; + // see if it is already cached as a mesh + if (ent->animcache_vertex3f) + { + // check if we need to add normals or tangents + if (ent->animcache_normal3f) + wantnormals = false; + if (ent->animcache_svector3f) + wanttangents = false; + if (!wantnormals && !wanttangents) + return false; + } + + // check which kind of cache we need to generate + if (r_gpuskeletal && model->num_bones > 0 && model->surfmesh.data_skeletalindex4ub) + { + // cache the skeleton so the vertex shader can use it + r_refdef.stats[r_stat_animcache_skeletal_count] += 1; + r_refdef.stats[r_stat_animcache_skeletal_bones] += model->num_bones; + r_refdef.stats[r_stat_animcache_skeletal_maxbones] = max(r_refdef.stats[r_stat_animcache_skeletal_maxbones], model->num_bones); + ent->animcache_skeletaltransform3x4 = (float *)R_FrameData_Alloc(sizeof(float[3][4]) * model->num_bones); + Mod_Skeletal_BuildTransforms(model, ent->frameblend, ent->skeleton, NULL, ent->animcache_skeletaltransform3x4); + // note: this can fail if the buffer is at the grow limit + ent->animcache_skeletaltransform3x4size = sizeof(float[3][4]) * model->num_bones; + ent->animcache_skeletaltransform3x4buffer = R_BufferData_Store(ent->animcache_skeletaltransform3x4size, ent->animcache_skeletaltransform3x4, R_BUFFERDATA_UNIFORM, &ent->animcache_skeletaltransform3x4offset); + } + else if (ent->animcache_vertex3f) + { + // mesh was already cached but we may need to add normals/tangents + // (this only happens with multiple views, reflections, cameras, etc) + if (wantnormals || wanttangents) + { + numvertices = model->surfmesh.num_vertices; + if (wantnormals) + ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + if (wanttangents) + { + ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + } + model->AnimateVertices(model, ent->frameblend, ent->skeleton, NULL, wantnormals ? ent->animcache_normal3f : NULL, wanttangents ? ent->animcache_svector3f : NULL, wanttangents ? ent->animcache_tvector3f : NULL); + R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices); + r_refdef.stats[r_stat_animcache_shade_count] += 1; + r_refdef.stats[r_stat_animcache_shade_vertices] += numvertices; + r_refdef.stats[r_stat_animcache_shade_maxvertices] = max(r_refdef.stats[r_stat_animcache_shade_maxvertices], numvertices); + } + } + else + { + // generate mesh cache + numvertices = model->surfmesh.num_vertices; + ent->animcache_vertex3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + if (wantnormals) + ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + if (wanttangents) + { + ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); + } + model->AnimateVertices(model, ent->frameblend, ent->skeleton, ent->animcache_vertex3f, ent->animcache_normal3f, ent->animcache_svector3f, ent->animcache_tvector3f); + R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices); + if (wantnormals || wanttangents) + { + r_refdef.stats[r_stat_animcache_shade_count] += 1; + r_refdef.stats[r_stat_animcache_shade_vertices] += numvertices; + r_refdef.stats[r_stat_animcache_shade_maxvertices] = max(r_refdef.stats[r_stat_animcache_shade_maxvertices], numvertices); + } + r_refdef.stats[r_stat_animcache_shape_count] += 1; + r_refdef.stats[r_stat_animcache_shape_vertices] += numvertices; + r_refdef.stats[r_stat_animcache_shape_maxvertices] = max(r_refdef.stats[r_stat_animcache_shape_maxvertices], numvertices); + } + return true; +} + +void R_AnimCache_CacheVisibleEntities(void) +{ + int i; + qboolean wantnormals = true; + qboolean wanttangents = !r_showsurfaces.integer; + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_GLES2: + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + wanttangents = false; + break; + case RENDERPATH_SOFT: + break; + } + + if (r_shownormals.integer) + wanttangents = wantnormals = true; + + // TODO: thread this + // NOTE: R_PrepareRTLights() also caches entities + + for (i = 0;i < r_refdef.scene.numentities;i++) + if (r_refdef.viewcache.entityvisible[i]) + R_AnimCache_GetEntity(r_refdef.scene.entities[i], wantnormals, wanttangents); +} + +//================================================================================== + +extern cvar_t r_overheadsprites_pushback; + +static void R_View_UpdateEntityLighting (void) +{ + int i; + entity_render_t *ent; + vec3_t tempdiffusenormal, avg; + vec_t f, fa, fd, fdd; + qboolean skipunseen = r_shadows.integer != 1; //|| R_Shadow_ShadowMappingEnabled(); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + + // skip unseen models + if ((!r_refdef.viewcache.entityvisible[i] && skipunseen)) + continue; + + // skip bsp models + if (ent->model && ent->model == cl.worldmodel) + { + // TODO: use modellight for r_ambient settings on world? + VectorSet(ent->modellight_ambient, 0, 0, 0); + VectorSet(ent->modellight_diffuse, 0, 0, 0); + VectorSet(ent->modellight_lightdir, 0, 0, 1); + continue; + } + + if (ent->flags & RENDER_CUSTOMIZEDMODELLIGHT) + { + // aleady updated by CSQC + // TODO: force modellight on BSP models in this case? + VectorCopy(ent->modellight_lightdir, tempdiffusenormal); + } + else + { + // fetch the lighting from the worldmodel data + VectorClear(ent->modellight_ambient); + VectorClear(ent->modellight_diffuse); + VectorClear(tempdiffusenormal); + if (ent->flags & RENDER_LIGHT) + { + vec3_t org; + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + + // complete lightning for lit sprites + // todo: make a EF_ field so small ents could be lit purely by modellight and skipping real rtlight pass (like EF_NORTLIGHT)? + if (ent->model->type == mod_sprite && !(ent->model->data_textures[0].basematerialflags & MATERIALFLAG_FULLBRIGHT)) + { + if (ent->model->sprite.sprnum_type == SPR_OVERHEAD) // apply offset for overhead sprites + org[2] = org[2] + r_overheadsprites_pushback.value; + R_LightPoint(ent->modellight_ambient, org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); + } + else + R_CompleteLightPoint(ent->modellight_ambient, ent->modellight_diffuse, tempdiffusenormal, org, LP_LIGHTMAP); + + if(ent->flags & RENDER_EQUALIZE) + { + // first fix up ambient lighting... + if(r_equalize_entities_minambient.value > 0) + { + fd = 0.299f * ent->modellight_diffuse[0] + 0.587f * ent->modellight_diffuse[1] + 0.114f * ent->modellight_diffuse[2]; + if(fd > 0) + { + fa = (0.299f * ent->modellight_ambient[0] + 0.587f * ent->modellight_ambient[1] + 0.114f * ent->modellight_ambient[2]); + if(fa < r_equalize_entities_minambient.value * fd) + { + // solve: + // fa'/fd' = minambient + // fa'+0.25*fd' = fa+0.25*fd + // ... + // fa' = fd' * minambient + // fd'*(0.25+minambient) = fa+0.25*fd + // ... + // fd' = (fa+0.25*fd) * 1 / (0.25+minambient) + // fa' = (fa+0.25*fd) * minambient / (0.25+minambient) + // ... + fdd = (fa + 0.25f * fd) / (0.25f + r_equalize_entities_minambient.value); + f = fdd / fd; // f>0 because all this is additive; f<1 because fddmodellight_ambient, (1-f)*0.25f, ent->modellight_diffuse, ent->modellight_ambient); + VectorScale(ent->modellight_diffuse, f, ent->modellight_diffuse); + } + } + } + + if(r_equalize_entities_to.value > 0 && r_equalize_entities_by.value != 0) + { + fa = 0.299f * ent->modellight_ambient[0] + 0.587f * ent->modellight_ambient[1] + 0.114f * ent->modellight_ambient[2]; + fd = 0.299f * ent->modellight_diffuse[0] + 0.587f * ent->modellight_diffuse[1] + 0.114f * ent->modellight_diffuse[2]; + f = fa + 0.25 * fd; + if(f > 0) + { + // adjust brightness and saturation to target + avg[0] = avg[1] = avg[2] = fa / f; + VectorLerp(ent->modellight_ambient, r_equalize_entities_by.value, avg, ent->modellight_ambient); + avg[0] = avg[1] = avg[2] = fd / f; + VectorLerp(ent->modellight_diffuse, r_equalize_entities_by.value, avg, ent->modellight_diffuse); + } + } + } + } + else // highly rare + VectorSet(ent->modellight_ambient, 1, 1, 1); + } + + // move the light direction into modelspace coordinates for lighting code + Matrix4x4_Transform3x3(&ent->inversematrix, tempdiffusenormal, ent->modellight_lightdir); + if(VectorLength2(ent->modellight_lightdir) == 0) + VectorSet(ent->modellight_lightdir, 0, 0, 1); // have to set SOME valid vector here + VectorNormalize(ent->modellight_lightdir); + } +} + +#define MAX_LINEOFSIGHTTRACES 64 + +static qboolean R_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs) +{ + int i; + vec3_t boxmins, boxmaxs; + vec3_t start; + vec3_t end; + dp_model_t *model = r_refdef.scene.worldmodel; + + if (!model || !model->brush.TraceLineOfSight) + return true; + + // expand the box a little + boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0]; + boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0]; + boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1]; + boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1]; + boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2]; + boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2]; + + // return true if eye is inside enlarged box + if (BoxesOverlap(boxmins, boxmaxs, eye, eye)) + return true; + + // try center + VectorCopy(eye, start); + VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, end); + if (model->brush.TraceLineOfSight(model, start, end)) + return true; + + // try various random positions + for (i = 0;i < numsamples;i++) + { + VectorSet(end, lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2])); + if (model->brush.TraceLineOfSight(model, start, end)) + return true; + } + + return false; +} + + +static void R_View_UpdateEntityVisible (void) +{ + int i; + int renderimask; + int samples; + entity_render_t *ent; + + renderimask = r_refdef.envmap ? (RENDER_EXTERIORMODEL | RENDER_VIEWMODEL) + : r_fb.water.hideplayer ? (RENDER_EXTERIORMODEL | RENDER_VIEWMODEL) + : (chase_active.integer || r_fb.water.renderingscene) ? RENDER_VIEWMODEL + : RENDER_EXTERIORMODEL; + if (!r_drawviewmodel.integer) + renderimask |= RENDER_VIEWMODEL; + if (!r_drawexteriormodel.integer) + renderimask |= RENDER_EXTERIORMODEL; + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs) + { + // worldmodel can check visibility + memset(r_refdef.viewcache.entityvisible, 0, r_refdef.scene.numentities); + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + if (!(ent->flags & renderimask)) + if (!R_CullBox(ent->mins, ent->maxs) || (ent->model && ent->model->type == mod_sprite && (ent->model->sprite.sprnum_type == SPR_LABEL || ent->model->sprite.sprnum_type == SPR_LABEL_SCALE))) + if ((ent->flags & (RENDER_NODEPTHTEST | RENDER_WORLDOBJECT | RENDER_VIEWMODEL)) || r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs(r_refdef.scene.worldmodel, r_refdef.viewcache.world_leafvisible, ent->mins, ent->maxs)) + r_refdef.viewcache.entityvisible[i] = true; + } + } + else + { + // no worldmodel or it can't check visibility + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + r_refdef.viewcache.entityvisible[i] = !(ent->flags & renderimask) && ((ent->model && ent->model->type == mod_sprite && (ent->model->sprite.sprnum_type == SPR_LABEL || ent->model->sprite.sprnum_type == SPR_LABEL_SCALE)) || !R_CullBox(ent->mins, ent->maxs)); + } + } + if(r_cullentities_trace.integer && r_refdef.scene.worldmodel->brush.TraceLineOfSight && !r_refdef.view.useclipplane && !r_trippy.integer) + // sorry, this check doesn't work for portal/reflection/refraction renders as the view origin is not useful for culling + { + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if(!(ent->flags & (RENDER_VIEWMODEL | RENDER_WORLDOBJECT | RENDER_NODEPTHTEST)) && !(ent->model && (ent->model->name[0] == '*'))) + { + samples = ent->entitynumber ? r_cullentities_trace_samples.integer : r_cullentities_trace_tempentitysamples.integer; + if (samples < 0) + continue; // temp entities do pvs only + if(R_CanSeeBox(samples, r_cullentities_trace_enlarge.value, r_refdef.view.origin, ent->mins, ent->maxs)) + ent->last_trace_visibility = realtime; + if(ent->last_trace_visibility < realtime - r_cullentities_trace_delay.value) + r_refdef.viewcache.entityvisible[i] = 0; + } + } + } +} + +/// only used if skyrendermasked, and normally returns false +static int R_DrawBrushModelsSky (void) +{ + int i, sky; + entity_render_t *ent; + + sky = false; + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (!ent->model || !ent->model->DrawSky) + continue; + ent->model->DrawSky(ent); + sky = true; + } + return sky; +} + +static void R_DrawNoModel(entity_render_t *ent); +static void R_DrawModels(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + r_refdef.stats[r_stat_entities]++; + /* + if (ent->model && !strncmp(ent->model->name, "models/proto_", 13)) + { + vec3_t f, l, u, o; + Matrix4x4_ToVectors(&ent->matrix, f, l, u, o); + Con_Printf("R_DrawModels\n"); + Con_Printf("model %s O %f %f %f F %f %f %f L %f %f %f U %f %f %f\n", ent->model->name, o[0], o[1], o[2], f[0], f[1], f[2], l[0], l[1], l[2], u[0], u[1], u[2]); + Con_Printf("group: %i %f %i %f %i %f %i %f\n", ent->framegroupblend[0].frame, ent->framegroupblend[0].lerp, ent->framegroupblend[1].frame, ent->framegroupblend[1].lerp, ent->framegroupblend[2].frame, ent->framegroupblend[2].lerp, ent->framegroupblend[3].frame, ent->framegroupblend[3].lerp); + Con_Printf("blend: %i %f %i %f %i %f %i %f %i %f %i %f %i %f %i %f\n", ent->frameblend[0].subframe, ent->frameblend[0].lerp, ent->frameblend[1].subframe, ent->frameblend[1].lerp, ent->frameblend[2].subframe, ent->frameblend[2].lerp, ent->frameblend[3].subframe, ent->frameblend[3].lerp, ent->frameblend[4].subframe, ent->frameblend[4].lerp, ent->frameblend[5].subframe, ent->frameblend[5].lerp, ent->frameblend[6].subframe, ent->frameblend[6].lerp, ent->frameblend[7].subframe, ent->frameblend[7].lerp); + } + */ + if (ent->model && ent->model->Draw != NULL) + ent->model->Draw(ent); + else + R_DrawNoModel(ent); + } +} + +static void R_DrawModelsDepth(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawDepth != NULL) + ent->model->DrawDepth(ent); + } +} + +static void R_DrawModelsDebug(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawDebug != NULL) + ent->model->DrawDebug(ent); + } +} + +static void R_DrawModelsAddWaterPlanes(void) +{ + int i; + entity_render_t *ent; + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawAddWaterPlanes != NULL) + ent->model->DrawAddWaterPlanes(ent); + } +} + +static float irisvecs[7][3] = {{0, 0, 0}, {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}}; + +void R_HDR_UpdateIrisAdaptation(const vec3_t point) +{ + if (r_hdr_irisadaptation.integer) + { + vec3_t p; + vec3_t ambient; + vec3_t diffuse; + vec3_t diffusenormal; + vec3_t forward; + vec_t brightness = 0.0f; + vec_t goal; + vec_t current; + vec_t d; + int c; + VectorCopy(r_refdef.view.forward, forward); + for (c = 0;c < (int)(sizeof(irisvecs)/sizeof(irisvecs[0]));c++) + { + p[0] = point[0] + irisvecs[c][0] * r_hdr_irisadaptation_radius.value; + p[1] = point[1] + irisvecs[c][1] * r_hdr_irisadaptation_radius.value; + p[2] = point[2] + irisvecs[c][2] * r_hdr_irisadaptation_radius.value; + R_CompleteLightPoint(ambient, diffuse, diffusenormal, p, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); + d = DotProduct(forward, diffusenormal); + brightness += VectorLength(ambient); + if (d > 0) + brightness += d * VectorLength(diffuse); + } + brightness *= 1.0f / c; + brightness += 0.00001f; // make sure it's never zero + goal = r_hdr_irisadaptation_multiplier.value / brightness; + goal = bound(r_hdr_irisadaptation_minvalue.value, goal, r_hdr_irisadaptation_maxvalue.value); + current = r_hdr_irisadaptation_value.value; + if (current < goal) + current = min(current + r_hdr_irisadaptation_fade_up.value * cl.realframetime, goal); + else if (current > goal) + current = max(current - r_hdr_irisadaptation_fade_down.value * cl.realframetime, goal); + if (fabs(r_hdr_irisadaptation_value.value - current) > 0.0001f) + Cvar_SetValueQuick(&r_hdr_irisadaptation_value, current); + } + else if (r_hdr_irisadaptation_value.value != 1.0f) + Cvar_SetValueQuick(&r_hdr_irisadaptation_value, 1.0f); +} + +static void R_View_SetFrustum(const int *scissor) +{ + int i; + double fpx = +1, fnx = -1, fpy = +1, fny = -1; + vec3_t forward, left, up, origin, v; + + if(scissor) + { + // flipped x coordinates (because x points left here) + fpx = 1.0 - 2.0 * (scissor[0] - r_refdef.view.viewport.x) / (double) (r_refdef.view.viewport.width); + fnx = 1.0 - 2.0 * (scissor[0] + scissor[2] - r_refdef.view.viewport.x) / (double) (r_refdef.view.viewport.width); + + // D3D Y coordinate is top to bottom, OpenGL is bottom to top, fix the D3D one + switch(vid.renderpath) + { + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + // non-flipped y coordinates + fny = -1.0 + 2.0 * (vid.height - scissor[1] - scissor[3] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); + fpy = -1.0 + 2.0 * (vid.height - scissor[1] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); + break; + case RENDERPATH_SOFT: + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // non-flipped y coordinates + fny = -1.0 + 2.0 * (scissor[1] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); + fpy = -1.0 + 2.0 * (scissor[1] + scissor[3] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); + break; + } + } + + // we can't trust r_refdef.view.forward and friends in reflected scenes + Matrix4x4_ToVectors(&r_refdef.view.matrix, forward, left, up, origin); + +#if 0 + r_refdef.view.frustum[0].normal[0] = 0 - 1.0 / r_refdef.view.frustum_x; + r_refdef.view.frustum[0].normal[1] = 0 - 0; + r_refdef.view.frustum[0].normal[2] = -1 - 0; + r_refdef.view.frustum[1].normal[0] = 0 + 1.0 / r_refdef.view.frustum_x; + r_refdef.view.frustum[1].normal[1] = 0 + 0; + r_refdef.view.frustum[1].normal[2] = -1 + 0; + r_refdef.view.frustum[2].normal[0] = 0 - 0; + r_refdef.view.frustum[2].normal[1] = 0 - 1.0 / r_refdef.view.frustum_y; + r_refdef.view.frustum[2].normal[2] = -1 - 0; + r_refdef.view.frustum[3].normal[0] = 0 + 0; + r_refdef.view.frustum[3].normal[1] = 0 + 1.0 / r_refdef.view.frustum_y; + r_refdef.view.frustum[3].normal[2] = -1 + 0; +#endif + +#if 0 + zNear = r_refdef.nearclip; + nudge = 1.0 - 1.0 / (1<<23); + r_refdef.view.frustum[4].normal[0] = 0 - 0; + r_refdef.view.frustum[4].normal[1] = 0 - 0; + r_refdef.view.frustum[4].normal[2] = -1 - -nudge; + r_refdef.view.frustum[4].dist = 0 - -2 * zNear * nudge; + r_refdef.view.frustum[5].normal[0] = 0 + 0; + r_refdef.view.frustum[5].normal[1] = 0 + 0; + r_refdef.view.frustum[5].normal[2] = -1 + -nudge; + r_refdef.view.frustum[5].dist = 0 + -2 * zNear * nudge; +#endif + + + +#if 0 + r_refdef.view.frustum[0].normal[0] = m[3] - m[0]; + r_refdef.view.frustum[0].normal[1] = m[7] - m[4]; + r_refdef.view.frustum[0].normal[2] = m[11] - m[8]; + r_refdef.view.frustum[0].dist = m[15] - m[12]; + + r_refdef.view.frustum[1].normal[0] = m[3] + m[0]; + r_refdef.view.frustum[1].normal[1] = m[7] + m[4]; + r_refdef.view.frustum[1].normal[2] = m[11] + m[8]; + r_refdef.view.frustum[1].dist = m[15] + m[12]; + + r_refdef.view.frustum[2].normal[0] = m[3] - m[1]; + r_refdef.view.frustum[2].normal[1] = m[7] - m[5]; + r_refdef.view.frustum[2].normal[2] = m[11] - m[9]; + r_refdef.view.frustum[2].dist = m[15] - m[13]; + + r_refdef.view.frustum[3].normal[0] = m[3] + m[1]; + r_refdef.view.frustum[3].normal[1] = m[7] + m[5]; + r_refdef.view.frustum[3].normal[2] = m[11] + m[9]; + r_refdef.view.frustum[3].dist = m[15] + m[13]; + + r_refdef.view.frustum[4].normal[0] = m[3] - m[2]; + r_refdef.view.frustum[4].normal[1] = m[7] - m[6]; + r_refdef.view.frustum[4].normal[2] = m[11] - m[10]; + r_refdef.view.frustum[4].dist = m[15] - m[14]; + + r_refdef.view.frustum[5].normal[0] = m[3] + m[2]; + r_refdef.view.frustum[5].normal[1] = m[7] + m[6]; + r_refdef.view.frustum[5].normal[2] = m[11] + m[10]; + r_refdef.view.frustum[5].dist = m[15] + m[14]; +#endif + + if (r_refdef.view.useperspective) + { + // calculate frustum corners, which are used to calculate deformed frustum planes for shadow caster culling + VectorMAMAM(1024, forward, fnx * 1024.0 * r_refdef.view.frustum_x, left, fny * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[0]); + VectorMAMAM(1024, forward, fpx * 1024.0 * r_refdef.view.frustum_x, left, fny * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[1]); + VectorMAMAM(1024, forward, fnx * 1024.0 * r_refdef.view.frustum_x, left, fpy * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[2]); + VectorMAMAM(1024, forward, fpx * 1024.0 * r_refdef.view.frustum_x, left, fpy * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[3]); + + // then the normals from the corners relative to origin + CrossProduct(r_refdef.view.frustumcorner[2], r_refdef.view.frustumcorner[0], r_refdef.view.frustum[0].normal); + CrossProduct(r_refdef.view.frustumcorner[1], r_refdef.view.frustumcorner[3], r_refdef.view.frustum[1].normal); + CrossProduct(r_refdef.view.frustumcorner[0], r_refdef.view.frustumcorner[1], r_refdef.view.frustum[2].normal); + CrossProduct(r_refdef.view.frustumcorner[3], r_refdef.view.frustumcorner[2], r_refdef.view.frustum[3].normal); + + // in a NORMAL view, forward cross left == up + // in a REFLECTED view, forward cross left == down + // so our cross products above need to be adjusted for a left handed coordinate system + CrossProduct(forward, left, v); + if(DotProduct(v, up) < 0) + { + VectorNegate(r_refdef.view.frustum[0].normal, r_refdef.view.frustum[0].normal); + VectorNegate(r_refdef.view.frustum[1].normal, r_refdef.view.frustum[1].normal); + VectorNegate(r_refdef.view.frustum[2].normal, r_refdef.view.frustum[2].normal); + VectorNegate(r_refdef.view.frustum[3].normal, r_refdef.view.frustum[3].normal); + } + + // Leaving those out was a mistake, those were in the old code, and they + // fix a reproducable bug in this one: frustum culling got fucked up when viewmatrix was an identity matrix + // I couldn't reproduce it after adding those normalizations. --blub + VectorNormalize(r_refdef.view.frustum[0].normal); + VectorNormalize(r_refdef.view.frustum[1].normal); + VectorNormalize(r_refdef.view.frustum[2].normal); + VectorNormalize(r_refdef.view.frustum[3].normal); + + // make the corners absolute + VectorAdd(r_refdef.view.frustumcorner[0], r_refdef.view.origin, r_refdef.view.frustumcorner[0]); + VectorAdd(r_refdef.view.frustumcorner[1], r_refdef.view.origin, r_refdef.view.frustumcorner[1]); + VectorAdd(r_refdef.view.frustumcorner[2], r_refdef.view.origin, r_refdef.view.frustumcorner[2]); + VectorAdd(r_refdef.view.frustumcorner[3], r_refdef.view.origin, r_refdef.view.frustumcorner[3]); + + // one more normal + VectorCopy(forward, r_refdef.view.frustum[4].normal); + + r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[0].normal); + r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[1].normal); + r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[2].normal); + r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[3].normal); + r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[4].normal) + r_refdef.nearclip; + } + else + { + VectorScale(left, -r_refdef.view.ortho_x, r_refdef.view.frustum[0].normal); + VectorScale(left, r_refdef.view.ortho_x, r_refdef.view.frustum[1].normal); + VectorScale(up, -r_refdef.view.ortho_y, r_refdef.view.frustum[2].normal); + VectorScale(up, r_refdef.view.ortho_y, r_refdef.view.frustum[3].normal); + VectorCopy(forward, r_refdef.view.frustum[4].normal); + r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[0].normal) + r_refdef.view.ortho_x; + r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[1].normal) + r_refdef.view.ortho_x; + r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[2].normal) + r_refdef.view.ortho_y; + r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[3].normal) + r_refdef.view.ortho_y; + r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[4].normal) + r_refdef.nearclip; + } + r_refdef.view.numfrustumplanes = 5; + + if (r_refdef.view.useclipplane) + { + r_refdef.view.numfrustumplanes = 6; + r_refdef.view.frustum[5] = r_refdef.view.clipplane; + } + + for (i = 0;i < r_refdef.view.numfrustumplanes;i++) + PlaneClassify(r_refdef.view.frustum + i); + + // LordHavoc: note to all quake engine coders, Quake had a special case + // for 90 degrees which assumed a square view (wrong), so I removed it, + // Quake2 has it disabled as well. + + // rotate R_VIEWFORWARD right by FOV_X/2 degrees + //RotatePointAroundVector( r_refdef.view.frustum[0].normal, up, forward, -(90 - r_refdef.fov_x / 2)); + //r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, frustum[0].normal); + //PlaneClassify(&frustum[0]); + + // rotate R_VIEWFORWARD left by FOV_X/2 degrees + //RotatePointAroundVector( r_refdef.view.frustum[1].normal, up, forward, (90 - r_refdef.fov_x / 2)); + //r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, frustum[1].normal); + //PlaneClassify(&frustum[1]); + + // rotate R_VIEWFORWARD up by FOV_X/2 degrees + //RotatePointAroundVector( r_refdef.view.frustum[2].normal, left, forward, -(90 - r_refdef.fov_y / 2)); + //r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, frustum[2].normal); + //PlaneClassify(&frustum[2]); + + // rotate R_VIEWFORWARD down by FOV_X/2 degrees + //RotatePointAroundVector( r_refdef.view.frustum[3].normal, left, forward, (90 - r_refdef.fov_y / 2)); + //r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, frustum[3].normal); + //PlaneClassify(&frustum[3]); + + // nearclip plane + //VectorCopy(forward, r_refdef.view.frustum[4].normal); + //r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, frustum[4].normal) + r_nearclip.value; + //PlaneClassify(&frustum[4]); +} + +static void R_View_UpdateWithScissor(const int *myscissor) +{ + R_Main_ResizeViewCache(); + R_View_SetFrustum(myscissor); + R_View_WorldVisibility(r_refdef.view.useclipplane); + R_View_UpdateEntityVisible(); + R_View_UpdateEntityLighting(); +} + +static void R_View_Update(void) +{ + R_Main_ResizeViewCache(); + R_View_SetFrustum(NULL); + R_View_WorldVisibility(r_refdef.view.useclipplane); + R_View_UpdateEntityVisible(); + R_View_UpdateEntityLighting(); +} + +float viewscalefpsadjusted = 1.0f; + +static void R_GetScaledViewSize(int width, int height, int *outwidth, int *outheight) +{ + float scale = r_viewscale.value * sqrt(viewscalefpsadjusted); + scale = bound(0.03125f, scale, 1.0f); + *outwidth = (int)ceil(width * scale); + *outheight = (int)ceil(height * scale); +} + +void R_SetupView(qboolean allowwaterclippingplane, int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + const float *customclipplane = NULL; + float plane[4]; + int /*rtwidth,*/ rtheight, scaledwidth, scaledheight; + if (r_refdef.view.useclipplane && allowwaterclippingplane) + { + // LordHavoc: couldn't figure out how to make this approach the + vec_t dist = r_refdef.view.clipplane.dist - r_water_clippingplanebias.value; + vec_t viewdist = DotProduct(r_refdef.view.origin, r_refdef.view.clipplane.normal); + if (viewdist < r_refdef.view.clipplane.dist + r_water_clippingplanebias.value) + dist = r_refdef.view.clipplane.dist; + plane[0] = r_refdef.view.clipplane.normal[0]; + plane[1] = r_refdef.view.clipplane.normal[1]; + plane[2] = r_refdef.view.clipplane.normal[2]; + plane[3] = -dist; + if(vid.renderpath != RENDERPATH_SOFT) customclipplane = plane; + } + + //rtwidth = fbo ? R_TextureWidth(depthtexture ? depthtexture : colortexture) : vid.width; + rtheight = fbo ? R_TextureHeight(depthtexture ? depthtexture : colortexture) : vid.height; + + R_GetScaledViewSize(r_refdef.view.width, r_refdef.view.height, &scaledwidth, &scaledheight); + if (!r_refdef.view.useperspective) + R_Viewport_InitOrtho(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, rtheight - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, -r_refdef.view.ortho_x, -r_refdef.view.ortho_y, r_refdef.view.ortho_x, r_refdef.view.ortho_y, -r_refdef.farclip, r_refdef.farclip, customclipplane); + else if (vid.stencil && r_useinfinitefarclip.integer) + R_Viewport_InitPerspectiveInfinite(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, rtheight - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, r_refdef.view.frustum_x, r_refdef.view.frustum_y, r_refdef.nearclip, customclipplane); + else + R_Viewport_InitPerspective(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, rtheight - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, r_refdef.view.frustum_x, r_refdef.view.frustum_y, r_refdef.nearclip, r_refdef.farclip, customclipplane); + R_Mesh_SetRenderTargets(fbo, depthtexture, colortexture, NULL, NULL, NULL); + R_SetViewport(&r_refdef.view.viewport); + if (r_refdef.view.useclipplane && allowwaterclippingplane && vid.renderpath == RENDERPATH_SOFT) + { + matrix4x4_t mvpmatrix, invmvpmatrix, invtransmvpmatrix; + float screenplane[4]; + Matrix4x4_Concat(&mvpmatrix, &r_refdef.view.viewport.projectmatrix, &r_refdef.view.viewport.viewmatrix); + Matrix4x4_Invert_Full(&invmvpmatrix, &mvpmatrix); + Matrix4x4_Transpose(&invtransmvpmatrix, &invmvpmatrix); + Matrix4x4_Transform4(&invtransmvpmatrix, plane, screenplane); + DPSOFTRAST_ClipPlane(screenplane[0], screenplane[1], screenplane[2], screenplane[3]); + } +} + +void R_EntityMatrix(const matrix4x4_t *matrix) +{ + if (gl_modelmatrixchanged || memcmp(matrix, &gl_modelmatrix, sizeof(matrix4x4_t))) + { + gl_modelmatrixchanged = false; + gl_modelmatrix = *matrix; + Matrix4x4_Concat(&gl_modelviewmatrix, &gl_viewmatrix, &gl_modelmatrix); + Matrix4x4_Concat(&gl_modelviewprojectionmatrix, &gl_projectionmatrix, &gl_modelviewmatrix); + Matrix4x4_ToArrayFloatGL(&gl_modelviewmatrix, gl_modelview16f); + Matrix4x4_ToArrayFloatGL(&gl_modelviewprojectionmatrix, gl_modelviewprojection16f); + CHECKGLERROR + switch(vid.renderpath) + { + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + hlslVSSetParameter16f(D3DVSREGISTER_ModelViewProjectionMatrix, gl_modelviewprojection16f); + hlslVSSetParameter16f(D3DVSREGISTER_ModelViewMatrix, gl_modelview16f); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 shader %s:%i\n", __FILE__, __LINE__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 shader %s:%i\n", __FILE__, __LINE__); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + qglLoadMatrixf(gl_modelview16f);CHECKGLERROR + break; + case RENDERPATH_SOFT: + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, 1, false, gl_modelviewprojection16f); + DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewMatrixM1, 1, false, gl_modelview16f); + break; + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + if (r_glsl_permutation && r_glsl_permutation->loc_ModelViewProjectionMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewProjectionMatrix, 1, false, gl_modelviewprojection16f); + if (r_glsl_permutation && r_glsl_permutation->loc_ModelViewMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewMatrix, 1, false, gl_modelview16f); + break; + } + } +} + +void R_ResetViewRendering2D_Common(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, float x2, float y2) +{ + r_viewport_t viewport; + + CHECKGLERROR + + // GL is weird because it's bottom to top, r_refdef.view.y is top to bottom + R_Viewport_InitOrtho(&viewport, &identitymatrix, r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height, 0, 0, x2, y2, -10, 100, NULL); + R_Mesh_SetRenderTargets(fbo, depthtexture, colortexture, NULL, NULL, NULL); + R_SetViewport(&viewport); + GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); + GL_Color(1, 1, 1, 1); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_ScissorTest(false); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_DepthTest(false); + GL_DepthFunc(GL_LEQUAL); + R_EntityMatrix(&identitymatrix); + R_Mesh_ResetTextureState(); + GL_PolygonOffset(0, 0); + R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglEnable(GL_POLYGON_OFFSET_FILL);CHECKGLERROR + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } + GL_CullFace(GL_NONE); + + CHECKGLERROR +} + +void R_ResetViewRendering2D(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + DrawQ_Finish(); + + R_ResetViewRendering2D_Common(fbo, depthtexture, colortexture, 1, 1); +} + +void R_ResetViewRendering3D(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + DrawQ_Finish(); + + R_SetupView(true, fbo, depthtexture, colortexture); + GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + GL_Color(1, 1, 1, 1); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_ScissorTest(true); + GL_DepthMask(true); + GL_DepthRange(0, 1); + GL_DepthTest(true); + GL_DepthFunc(GL_LEQUAL); + R_EntityMatrix(&identitymatrix); + R_Mesh_ResetTextureState(); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + qglEnable(GL_POLYGON_OFFSET_FILL);CHECKGLERROR + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } + GL_CullFace(r_refdef.view.cullface_back); +} + +/* +================ +R_RenderView_UpdateViewVectors +================ +*/ +void R_RenderView_UpdateViewVectors(void) +{ + // break apart the view matrix into vectors for various purposes + // it is important that this occurs outside the RenderScene function because that can be called from reflection renders, where the vectors come out wrong + // however the r_refdef.view.origin IS updated in RenderScene intentionally - otherwise the sky renders at the wrong origin, etc + Matrix4x4_ToVectors(&r_refdef.view.matrix, r_refdef.view.forward, r_refdef.view.left, r_refdef.view.up, r_refdef.view.origin); + VectorNegate(r_refdef.view.left, r_refdef.view.right); + // make an inverted copy of the view matrix for tracking sprites + Matrix4x4_Invert_Simple(&r_refdef.view.inverse_matrix, &r_refdef.view.matrix); +} + +void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); +void R_RenderWaterPlanes(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); + +static void R_Water_StartFrame(void) +{ + int i; + int waterwidth, waterheight, texturewidth, textureheight, camerawidth, cameraheight; + r_waterstate_waterplane_t *p; + qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2; + + if (vid.width > (int)vid.maxtexturesize_2d || vid.height > (int)vid.maxtexturesize_2d) + return; + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + return; + } + + // set waterwidth and waterheight to the water resolution that will be + // used (often less than the screen resolution for faster rendering) + R_GetScaledViewSize(bound(1, vid.width * r_water_resolutionmultiplier.value, vid.width), bound(1, vid.height * r_water_resolutionmultiplier.value, vid.height), &waterwidth, &waterheight); + + // calculate desired texture sizes + // can't use water if the card does not support the texture size + if (!r_water.integer || r_showsurfaces.integer) + texturewidth = textureheight = waterwidth = waterheight = camerawidth = cameraheight = 0; + else if (vid.support.arb_texture_non_power_of_two) + { + texturewidth = waterwidth; + textureheight = waterheight; + camerawidth = waterwidth; + cameraheight = waterheight; + } + else + { + for (texturewidth = 1;texturewidth < waterwidth ;texturewidth *= 2); + for (textureheight = 1;textureheight < waterheight;textureheight *= 2); + for (camerawidth = 1;camerawidth * 2 <= waterwidth ;camerawidth *= 2); + for (cameraheight = 1;cameraheight * 2 <= waterheight;cameraheight *= 2); + } + + // allocate textures as needed + if (r_fb.water.texturewidth != texturewidth || r_fb.water.textureheight != textureheight || r_fb.water.camerawidth != camerawidth || r_fb.water.cameraheight != cameraheight || (r_fb.depthtexture && !usewaterfbo)) + { + r_fb.water.maxwaterplanes = MAX_WATERPLANES; + for (i = 0, p = r_fb.water.waterplanes;i < r_fb.water.maxwaterplanes;i++, p++) + { + if (p->texture_refraction) + R_FreeTexture(p->texture_refraction); + p->texture_refraction = NULL; + if (p->fbo_refraction) + R_Mesh_DestroyFramebufferObject(p->fbo_refraction); + p->fbo_refraction = 0; + if (p->texture_reflection) + R_FreeTexture(p->texture_reflection); + p->texture_reflection = NULL; + if (p->fbo_reflection) + R_Mesh_DestroyFramebufferObject(p->fbo_reflection); + p->fbo_reflection = 0; + if (p->texture_camera) + R_FreeTexture(p->texture_camera); + p->texture_camera = NULL; + if (p->fbo_camera) + R_Mesh_DestroyFramebufferObject(p->fbo_camera); + p->fbo_camera = 0; + } + memset(&r_fb.water, 0, sizeof(r_fb.water)); + r_fb.water.texturewidth = texturewidth; + r_fb.water.textureheight = textureheight; + r_fb.water.camerawidth = camerawidth; + r_fb.water.cameraheight = cameraheight; + } + + if (r_fb.water.texturewidth) + { + int scaledwidth, scaledheight; + + r_fb.water.enabled = true; + + // water resolution is usually reduced + r_fb.water.waterwidth = (int)bound(1, r_refdef.view.width * r_water_resolutionmultiplier.value, r_refdef.view.width); + r_fb.water.waterheight = (int)bound(1, r_refdef.view.height * r_water_resolutionmultiplier.value, r_refdef.view.height); + R_GetScaledViewSize(r_fb.water.waterwidth, r_fb.water.waterheight, &scaledwidth, &scaledheight); + + // set up variables that will be used in shader setup + r_fb.water.screenscale[0] = 0.5f * (float)scaledwidth / (float)r_fb.water.texturewidth; + r_fb.water.screenscale[1] = 0.5f * (float)scaledheight / (float)r_fb.water.textureheight; + r_fb.water.screencenter[0] = 0.5f * (float)scaledwidth / (float)r_fb.water.texturewidth; + r_fb.water.screencenter[1] = 0.5f * (float)scaledheight / (float)r_fb.water.textureheight; + } + + r_fb.water.maxwaterplanes = MAX_WATERPLANES; + r_fb.water.numwaterplanes = 0; +} + +void R_Water_AddWaterPlane(msurface_t *surface, int entno) +{ + int planeindex, bestplaneindex, vertexindex; + vec3_t mins, maxs, normal, center, v, n; + vec_t planescore, bestplanescore; + mplane_t plane; + r_waterstate_waterplane_t *p; + texture_t *t = R_GetCurrentTexture(surface->texture); + + rsurface.texture = t; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS, 1, ((const msurface_t **)&surface)); + // if the model has no normals, it's probably off-screen and they were not generated, so don't add it anyway + if (!rsurface.batchnormal3f || rsurface.batchnumvertices < 1) + return; + // average the vertex normals, find the surface bounds (after deformvertexes) + Matrix4x4_Transform(&rsurface.matrix, rsurface.batchvertex3f, v); + Matrix4x4_Transform3x3(&rsurface.matrix, rsurface.batchnormal3f, n); + VectorCopy(n, normal); + VectorCopy(v, mins); + VectorCopy(v, maxs); + for (vertexindex = 1;vertexindex < rsurface.batchnumvertices;vertexindex++) + { + Matrix4x4_Transform(&rsurface.matrix, rsurface.batchvertex3f + vertexindex*3, v); + Matrix4x4_Transform3x3(&rsurface.matrix, rsurface.batchnormal3f + vertexindex*3, n); + VectorAdd(normal, n, normal); + mins[0] = min(mins[0], v[0]); + mins[1] = min(mins[1], v[1]); + mins[2] = min(mins[2], v[2]); + maxs[0] = max(maxs[0], v[0]); + maxs[1] = max(maxs[1], v[1]); + maxs[2] = max(maxs[2], v[2]); + } + VectorNormalize(normal); + VectorMAM(0.5f, mins, 0.5f, maxs, center); + + VectorCopy(normal, plane.normal); + VectorNormalize(plane.normal); + plane.dist = DotProduct(center, plane.normal); + PlaneClassify(&plane); + if (PlaneDiff(r_refdef.view.origin, &plane) < 0) + { + // skip backfaces (except if nocullface is set) +// if (!(t->currentmaterialflags & MATERIALFLAG_NOCULLFACE)) +// return; + VectorNegate(plane.normal, plane.normal); + plane.dist *= -1; + PlaneClassify(&plane); + } + + + // find a matching plane if there is one + bestplaneindex = -1; + bestplanescore = 1048576.0f; + for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++) + { + if(p->camera_entity == t->camera_entity) + { + planescore = 1.0f - DotProduct(plane.normal, p->plane.normal) + fabs(plane.dist - p->plane.dist) * 0.001f; + if (bestplaneindex < 0 || bestplanescore > planescore) + { + bestplaneindex = planeindex; + bestplanescore = planescore; + } + } + } + planeindex = bestplaneindex; + p = r_fb.water.waterplanes + planeindex; + + // if this surface does not fit any known plane rendered this frame, add one + if ((planeindex < 0 || bestplanescore > 0.001f) && r_fb.water.numwaterplanes < r_fb.water.maxwaterplanes) + { + // store the new plane + planeindex = r_fb.water.numwaterplanes++; + p = r_fb.water.waterplanes + planeindex; + p->plane = plane; + // clear materialflags and pvs + p->materialflags = 0; + p->pvsvalid = false; + p->camera_entity = t->camera_entity; + VectorCopy(mins, p->mins); + VectorCopy(maxs, p->maxs); + } + else + { + // merge mins/maxs when we're adding this surface to the plane + p->mins[0] = min(p->mins[0], mins[0]); + p->mins[1] = min(p->mins[1], mins[1]); + p->mins[2] = min(p->mins[2], mins[2]); + p->maxs[0] = max(p->maxs[0], maxs[0]); + p->maxs[1] = max(p->maxs[1], maxs[1]); + p->maxs[2] = max(p->maxs[2], maxs[2]); + } + // merge this surface's materialflags into the waterplane + p->materialflags |= t->currentmaterialflags; + if(!(p->materialflags & MATERIALFLAG_CAMERA)) + { + // merge this surface's PVS into the waterplane + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS + && r_refdef.scene.worldmodel->brush.PointInLeaf && r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, center)->clusterindex >= 0) + { + r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, center, 2, p->pvsbits, sizeof(p->pvsbits), p->pvsvalid); + p->pvsvalid = true; + } + } +} + +extern cvar_t r_drawparticles; +extern cvar_t r_drawdecals; + +static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + int myscissor[4]; + r_refdef_view_t originalview; + r_refdef_view_t myview; + int planeindex, qualityreduction = 0, old_r_dynamic = 0, old_r_shadows = 0, old_r_worldrtlight = 0, old_r_dlight = 0, old_r_particles = 0, old_r_decals = 0; + r_waterstate_waterplane_t *p; + vec3_t visorigin; + qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2; + char vabuf[1024]; + + originalview = r_refdef.view; + + // lowquality hack, temporarily shut down some cvars and restore afterwards + qualityreduction = r_water_lowquality.integer; + if (qualityreduction > 0) + { + if (qualityreduction >= 1) + { + old_r_shadows = r_shadows.integer; + old_r_worldrtlight = r_shadow_realtime_world.integer; + old_r_dlight = r_shadow_realtime_dlight.integer; + Cvar_SetValueQuick(&r_shadows, 0); + Cvar_SetValueQuick(&r_shadow_realtime_world, 0); + Cvar_SetValueQuick(&r_shadow_realtime_dlight, 0); + } + if (qualityreduction >= 2) + { + old_r_dynamic = r_dynamic.integer; + old_r_particles = r_drawparticles.integer; + old_r_decals = r_drawdecals.integer; + Cvar_SetValueQuick(&r_dynamic, 0); + Cvar_SetValueQuick(&r_drawparticles, 0); + Cvar_SetValueQuick(&r_drawdecals, 0); + } + } + + // make sure enough textures are allocated + for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++) + { + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) + { + if (!p->texture_refraction) + p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "waterplane%i_refraction", planeindex), r_fb.water.texturewidth, r_fb.water.textureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + if (!p->texture_refraction) + goto error; + if (usewaterfbo) + { + if (r_fb.water.depthtexture == NULL) + r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8); + if (p->fbo_refraction == 0) + p->fbo_refraction = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_refraction, NULL, NULL, NULL); + } + } + else if (p->materialflags & MATERIALFLAG_CAMERA) + { + if (!p->texture_camera) + p->texture_camera = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "waterplane%i_camera", planeindex), r_fb.water.camerawidth, r_fb.water.cameraheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR, -1, NULL); + if (!p->texture_camera) + goto error; + if (usewaterfbo) + { + if (r_fb.water.depthtexture == NULL) + r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8); + if (p->fbo_camera == 0) + p->fbo_camera = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_camera, NULL, NULL, NULL); + } + } + + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION)) + { + if (!p->texture_reflection) + p->texture_reflection = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "waterplane%i_reflection", planeindex), r_fb.water.texturewidth, r_fb.water.textureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + if (!p->texture_reflection) + goto error; + if (usewaterfbo) + { + if (r_fb.water.depthtexture == NULL) + r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8); + if (p->fbo_reflection == 0) + p->fbo_reflection = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_reflection, NULL, NULL, NULL); + } + } + } + + // render views + r_refdef.view = originalview; + r_refdef.view.showdebug = false; + r_refdef.view.width = r_fb.water.waterwidth; + r_refdef.view.height = r_fb.water.waterheight; + r_refdef.view.useclipplane = true; + myview = r_refdef.view; + r_fb.water.renderingscene = true; + for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++) + { + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION)) + { + r_refdef.view = myview; + if(r_water_scissormode.integer) + { + R_SetupView(true, p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection); + if(R_ScissorForBBox(p->mins, p->maxs, myscissor)) + continue; // FIXME the plane then still may get rendered but with broken texture, but it sure won't be visible + } + + // render reflected scene and copy into texture + Matrix4x4_Reflect(&r_refdef.view.matrix, p->plane.normal[0], p->plane.normal[1], p->plane.normal[2], p->plane.dist, -2); + // update the r_refdef.view.origin because otherwise the sky renders at the wrong location (amongst other problems) + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, r_refdef.view.origin); + r_refdef.view.clipplane = p->plane; + // reverse the cullface settings for this render + r_refdef.view.cullface_front = GL_FRONT; + r_refdef.view.cullface_back = GL_BACK; + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.num_pvsclusterbytes) + { + r_refdef.view.usecustompvs = true; + if (p->pvsvalid) + memcpy(r_refdef.viewcache.world_pvsbits, p->pvsbits, r_refdef.scene.worldmodel->brush.num_pvsclusterbytes); + else + memset(r_refdef.viewcache.world_pvsbits, 0xFF, r_refdef.scene.worldmodel->brush.num_pvsclusterbytes); + } + + r_fb.water.hideplayer = r_water_hideplayer.integer >= 2; + R_ResetViewRendering3D(p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection); + R_ClearScreen(r_refdef.fogenabled); + if(r_water_scissormode.integer & 2) + R_View_UpdateWithScissor(myscissor); + else + R_View_Update(); + R_AnimCache_CacheVisibleEntities(); + if(r_water_scissormode.integer & 1) + GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]); + R_RenderScene(p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection); + + if (!p->fbo_reflection) + R_Mesh_CopyToTexture(p->texture_reflection, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_fb.water.hideplayer = false; + } + + // render the normal view scene and copy into texture + // (except that a clipping plane should be used to hide everything on one side of the water, and the viewer's weapon model should be omitted) + if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) + { + r_refdef.view = myview; + if(r_water_scissormode.integer) + { + R_SetupView(true, p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction); + if(R_ScissorForBBox(p->mins, p->maxs, myscissor)) + continue; // FIXME the plane then still may get rendered but with broken texture, but it sure won't be visible + } + + r_fb.water.hideplayer = r_water_hideplayer.integer >= 1; + + r_refdef.view.clipplane = p->plane; + VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal); + r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist; + + if((p->materialflags & MATERIALFLAG_CAMERA) && p->camera_entity) + { + // we need to perform a matrix transform to render the view... so let's get the transformation matrix + r_fb.water.hideplayer = false; // we don't want to hide the player model from these ones + CL_VM_TransformView(p->camera_entity - MAX_EDICTS, &r_refdef.view.matrix, &r_refdef.view.clipplane, visorigin); + R_RenderView_UpdateViewVectors(); + if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS) + { + r_refdef.view.usecustompvs = true; + r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, visorigin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); + } + } + + PlaneClassify(&r_refdef.view.clipplane); + + R_ResetViewRendering3D(p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction); + R_ClearScreen(r_refdef.fogenabled); + if(r_water_scissormode.integer & 2) + R_View_UpdateWithScissor(myscissor); + else + R_View_Update(); + R_AnimCache_CacheVisibleEntities(); + if(r_water_scissormode.integer & 1) + GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]); + R_RenderScene(p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction); + + if (!p->fbo_refraction) + R_Mesh_CopyToTexture(p->texture_refraction, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_fb.water.hideplayer = false; + } + else if (p->materialflags & MATERIALFLAG_CAMERA) + { + r_refdef.view = myview; + + r_refdef.view.clipplane = p->plane; + VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal); + r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist; + + r_refdef.view.width = r_fb.water.camerawidth; + r_refdef.view.height = r_fb.water.cameraheight; + r_refdef.view.frustum_x = 1; // tan(45 * M_PI / 180.0); + r_refdef.view.frustum_y = 1; // tan(45 * M_PI / 180.0); + r_refdef.view.ortho_x = 90; // abused as angle by VM_CL_R_SetView + r_refdef.view.ortho_y = 90; // abused as angle by VM_CL_R_SetView + + if(p->camera_entity) + { + // we need to perform a matrix transform to render the view... so let's get the transformation matrix + CL_VM_TransformView(p->camera_entity - MAX_EDICTS, &r_refdef.view.matrix, &r_refdef.view.clipplane, visorigin); + } + + // note: all of the view is used for displaying... so + // there is no use in scissoring + + // reverse the cullface settings for this render + r_refdef.view.cullface_front = GL_FRONT; + r_refdef.view.cullface_back = GL_BACK; + // also reverse the view matrix + Matrix4x4_ConcatScale3(&r_refdef.view.matrix, 1, 1, -1); // this serves to invert texcoords in the result, as the copied texture is mapped the wrong way round + R_RenderView_UpdateViewVectors(); + if(p->camera_entity && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS) + { + r_refdef.view.usecustompvs = true; + r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, visorigin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); + } + + // camera needs no clipplane + r_refdef.view.useclipplane = false; + + PlaneClassify(&r_refdef.view.clipplane); + + r_fb.water.hideplayer = false; + + R_ResetViewRendering3D(p->fbo_camera, r_fb.water.depthtexture, p->texture_camera); + R_ClearScreen(r_refdef.fogenabled); + R_View_Update(); + R_AnimCache_CacheVisibleEntities(); + R_RenderScene(p->fbo_camera, r_fb.water.depthtexture, p->texture_camera); + + if (!p->fbo_camera) + R_Mesh_CopyToTexture(p->texture_camera, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_fb.water.hideplayer = false; + } + + } + if(vid.renderpath==RENDERPATH_SOFT) DPSOFTRAST_ClipPlane(0, 0, 0, 1); + r_fb.water.renderingscene = false; + r_refdef.view = originalview; + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + if (!r_fb.water.depthtexture) + R_ClearScreen(r_refdef.fogenabled); + R_View_Update(); + R_AnimCache_CacheVisibleEntities(); + goto finish; +error: + r_refdef.view = originalview; + r_fb.water.renderingscene = false; + Cvar_SetValueQuick(&r_water, 0); + Con_Printf("R_Water_ProcessPlanes: Error: texture creation failed! Turned off r_water.\n"); +finish: + // lowquality hack, restore cvars + if (qualityreduction > 0) + { + if (qualityreduction >= 1) + { + Cvar_SetValueQuick(&r_shadows, old_r_shadows); + Cvar_SetValueQuick(&r_shadow_realtime_world, old_r_worldrtlight); + Cvar_SetValueQuick(&r_shadow_realtime_dlight, old_r_dlight); + } + if (qualityreduction >= 2) + { + Cvar_SetValueQuick(&r_dynamic, old_r_dynamic); + Cvar_SetValueQuick(&r_drawparticles, old_r_particles); + Cvar_SetValueQuick(&r_drawdecals, old_r_decals); + } + } +} + +qboolean R_Stereo_Active() +{ + return true; +} + +static void R_Bloom_StartFrame(void) +{ + int i; + int bloomtexturewidth, bloomtextureheight, screentexturewidth, screentextureheight; + int viewwidth, viewheight; + qboolean useviewfbo = r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2; + textype_t textype = TEXTYPE_COLORBUFFER; + + switch (vid.renderpath) + { + case RENDERPATH_GL20: + r_fb.usedepthtextures = r_usedepthtextures.integer != 0; + if (vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two) + { + if (r_viewfbo.integer == 2) textype = TEXTYPE_COLORBUFFER16F; + if (r_viewfbo.integer == 3) textype = TEXTYPE_COLORBUFFER32F; + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + r_fb.usedepthtextures = false; + break; + case RENDERPATH_SOFT: + r_fb.usedepthtextures = true; + break; + } + + if (r_viewscale_fpsscaling.integer) + { + double actualframetime; + double targetframetime; + double adjust; + actualframetime = r_refdef.lastdrawscreentime; + targetframetime = (1.0 / r_viewscale_fpsscaling_target.value); + adjust = (targetframetime - actualframetime) * r_viewscale_fpsscaling_multiply.value; + adjust = bound(-r_viewscale_fpsscaling_stepmax.value, adjust, r_viewscale_fpsscaling_stepmax.value); + if (r_viewscale_fpsscaling_stepsize.value > 0) + adjust = (int)(adjust / r_viewscale_fpsscaling_stepsize.value) * r_viewscale_fpsscaling_stepsize.value; + viewscalefpsadjusted += adjust; + viewscalefpsadjusted = bound(r_viewscale_fpsscaling_min.value, viewscalefpsadjusted, 1.0f); + } + else + viewscalefpsadjusted = 1.0f; + + R_GetScaledViewSize(r_refdef.view.width, r_refdef.view.height, &viewwidth, &viewheight); + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + return; + } + + // set bloomwidth and bloomheight to the bloom resolution that will be + // used (often less than the screen resolution for faster rendering) + r_fb.bloomwidth = bound(1, r_bloom_resolution.integer, vid.width); + r_fb.bloomheight = r_fb.bloomwidth * vid.height / vid.width; + r_fb.bloomheight = bound(1, r_fb.bloomheight, vid.height); + r_fb.bloomwidth = bound(1, r_fb.bloomwidth, (int)vid.maxtexturesize_2d); + r_fb.bloomheight = bound(1, r_fb.bloomheight, (int)vid.maxtexturesize_2d); + + // calculate desired texture sizes + if (vid.support.arb_texture_non_power_of_two) + { + screentexturewidth = vid.width; + screentextureheight = vid.height; + bloomtexturewidth = r_fb.bloomwidth; + bloomtextureheight = r_fb.bloomheight; + } + else + { + for (screentexturewidth = 1;screentexturewidth < vid.width ;screentexturewidth *= 2); + for (screentextureheight = 1;screentextureheight < vid.height ;screentextureheight *= 2); + for (bloomtexturewidth = 1;bloomtexturewidth < r_fb.bloomwidth ;bloomtexturewidth *= 2); + for (bloomtextureheight = 1;bloomtextureheight < r_fb.bloomheight;bloomtextureheight *= 2); + } + + if ((r_bloom.integer || (!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0))) && ((r_bloom_resolution.integer < 4 || r_bloom_blur.value < 1 || r_bloom_blur.value >= 512) || r_refdef.view.width > (int)vid.maxtexturesize_2d || r_refdef.view.height > (int)vid.maxtexturesize_2d)) + { + Cvar_SetValueQuick(&r_bloom, 0); + Cvar_SetValueQuick(&r_motionblur, 0); + Cvar_SetValueQuick(&r_damageblur, 0); + } + + if (!(r_glsl_postprocess.integer || (r_glsl_saturation.value != 1) || (v_glslgamma.integer && !vid_gammatables_trivial)) + && !r_bloom.integer + && (R_Stereo_Active() || (r_motionblur.value <= 0 && r_damageblur.value <= 0)) + && !useviewfbo + && r_viewscale.value == 1.0f + && !r_viewscale_fpsscaling.integer) + screentexturewidth = screentextureheight = 0; + if (!r_bloom.integer) + bloomtexturewidth = bloomtextureheight = 0; + + // allocate textures as needed + if (r_fb.screentexturewidth != screentexturewidth + || r_fb.screentextureheight != screentextureheight + || r_fb.bloomtexturewidth != bloomtexturewidth + || r_fb.bloomtextureheight != bloomtextureheight + || r_fb.textype != textype + || useviewfbo != (r_fb.fbo != 0)) + { + for (i = 0;i < (int)(sizeof(r_fb.bloomtexture)/sizeof(r_fb.bloomtexture[i]));i++) + { + if (r_fb.bloomtexture[i]) + R_FreeTexture(r_fb.bloomtexture[i]); + r_fb.bloomtexture[i] = NULL; + + if (r_fb.bloomfbo[i]) + R_Mesh_DestroyFramebufferObject(r_fb.bloomfbo[i]); + r_fb.bloomfbo[i] = 0; + } + + if (r_fb.fbo) + R_Mesh_DestroyFramebufferObject(r_fb.fbo); + r_fb.fbo = 0; + + if (r_fb.colortexture) + R_FreeTexture(r_fb.colortexture); + r_fb.colortexture = NULL; + + if (r_fb.depthtexture) + R_FreeTexture(r_fb.depthtexture); + r_fb.depthtexture = NULL; + + if (r_fb.ghosttexture) + R_FreeTexture(r_fb.ghosttexture); + r_fb.ghosttexture = NULL; + + r_fb.screentexturewidth = screentexturewidth; + r_fb.screentextureheight = screentextureheight; + r_fb.bloomtexturewidth = bloomtexturewidth; + r_fb.bloomtextureheight = bloomtextureheight; + r_fb.textype = textype; + + if (r_fb.screentexturewidth && r_fb.screentextureheight) + { + if (r_motionblur.value > 0 || r_damageblur.value > 0) + r_fb.ghosttexture = R_LoadTexture2D(r_main_texturepool, "framebuffermotionblur", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + r_fb.ghosttexture_valid = false; + r_fb.colortexture = R_LoadTexture2D(r_main_texturepool, "framebuffercolor", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + if (useviewfbo) + { + r_fb.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "framebufferdepth", r_fb.screentexturewidth, r_fb.screentextureheight, TEXTYPE_DEPTHBUFFER24STENCIL8); + r_fb.fbo = R_Mesh_CreateFramebufferObject(r_fb.depthtexture, r_fb.colortexture, NULL, NULL, NULL); + R_Mesh_SetRenderTargets(r_fb.fbo, r_fb.depthtexture, r_fb.colortexture, NULL, NULL, NULL); + } + } + + if (r_fb.bloomtexturewidth && r_fb.bloomtextureheight) + { + for (i = 0;i < (int)(sizeof(r_fb.bloomtexture)/sizeof(r_fb.bloomtexture[i]));i++) + { + r_fb.bloomtexture[i] = R_LoadTexture2D(r_main_texturepool, "framebufferbloom", r_fb.bloomtexturewidth, r_fb.bloomtextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); + if (useviewfbo) + r_fb.bloomfbo[i] = R_Mesh_CreateFramebufferObject(NULL, r_fb.bloomtexture[i], NULL, NULL, NULL); + } + } + } + + // bloom texture is a different resolution + r_fb.bloomwidth = bound(1, r_bloom_resolution.integer, r_refdef.view.width); + r_fb.bloomheight = r_fb.bloomwidth * r_refdef.view.height / r_refdef.view.width; + r_fb.bloomheight = bound(1, r_fb.bloomheight, r_refdef.view.height); + r_fb.bloomwidth = bound(1, r_fb.bloomwidth, r_fb.bloomtexturewidth); + r_fb.bloomheight = bound(1, r_fb.bloomheight, r_fb.bloomtextureheight); + + // set up a texcoord array for the full resolution screen image + // (we have to keep this around to copy back during final render) + r_fb.screentexcoord2f[0] = 0; + r_fb.screentexcoord2f[1] = (float)viewheight / (float)r_fb.screentextureheight; + r_fb.screentexcoord2f[2] = (float)viewwidth / (float)r_fb.screentexturewidth; + r_fb.screentexcoord2f[3] = (float)viewheight / (float)r_fb.screentextureheight; + r_fb.screentexcoord2f[4] = (float)viewwidth / (float)r_fb.screentexturewidth; + r_fb.screentexcoord2f[5] = 0; + r_fb.screentexcoord2f[6] = 0; + r_fb.screentexcoord2f[7] = 0; + + if(r_fb.fbo) + { + for (i = 1;i < 8;i += 2) + { + r_fb.screentexcoord2f[i] += 1 - (float)(viewheight + r_refdef.view.y) / (float)r_fb.screentextureheight; + } + } + + // set up a texcoord array for the reduced resolution bloom image + // (which will be additive blended over the screen image) + r_fb.bloomtexcoord2f[0] = 0; + r_fb.bloomtexcoord2f[1] = (float)r_fb.bloomheight / (float)r_fb.bloomtextureheight; + r_fb.bloomtexcoord2f[2] = (float)r_fb.bloomwidth / (float)r_fb.bloomtexturewidth; + r_fb.bloomtexcoord2f[3] = (float)r_fb.bloomheight / (float)r_fb.bloomtextureheight; + r_fb.bloomtexcoord2f[4] = (float)r_fb.bloomwidth / (float)r_fb.bloomtexturewidth; + r_fb.bloomtexcoord2f[5] = 0; + r_fb.bloomtexcoord2f[6] = 0; + r_fb.bloomtexcoord2f[7] = 0; + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + for (i = 0;i < 4;i++) + { + r_fb.screentexcoord2f[i*2+0] += 0.5f / (float)r_fb.screentexturewidth; + r_fb.screentexcoord2f[i*2+1] += 0.5f / (float)r_fb.screentextureheight; + r_fb.bloomtexcoord2f[i*2+0] += 0.5f / (float)r_fb.bloomtexturewidth; + r_fb.bloomtexcoord2f[i*2+1] += 0.5f / (float)r_fb.bloomtextureheight; + } + break; + } + + R_Viewport_InitOrtho(&r_fb.bloomviewport, &identitymatrix, 0, 0, r_fb.bloomwidth, r_fb.bloomheight, 0, 0, 1, 1, -10, 100, NULL); + + if (r_fb.fbo) + r_refdef.view.clear = true; +} + +static void R_Bloom_MakeTexture(void) +{ + int x, range, dir; + float xoffset, yoffset, r, brighten; + rtexture_t *intex; + float colorscale = r_bloom_colorscale.value; + + r_refdef.stats[r_stat_bloom]++; + +#if 0 + // this copy is unnecessary since it happens in R_BlendView already + if (!r_fb.fbo) + { + R_Mesh_CopyToTexture(r_fb.colortexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height; + } +#endif + + // scale down screen texture to the bloom texture size + CHECKGLERROR + r_fb.bloomindex = 0; + R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL); + R_SetViewport(&r_fb.bloomviewport); + GL_DepthTest(false); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_Color(colorscale, colorscale, colorscale, 1); + // D3D has upside down Y coords, the easiest way to flip this is to flip the screen vertices rather than the texcoords, so we just use a different array for that... + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_SOFT: + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.screentexcoord2f); + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_fb.screentexcoord2f); + break; + } + // TODO: do boxfilter scale-down in shader? + R_SetupShader_Generic(r_fb.colortexture, NULL, GL_MODULATE, 1, false, true, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight; + + // we now have a properly scaled bloom image + if (!r_fb.bloomfbo[r_fb.bloomindex]) + { + // copy it into the bloom texture + R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height); + r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height; + } + + // multiply bloom image by itself as many times as desired + for (x = 1;x < min(r_bloom_colorexponent.value, 32);) + { + intex = r_fb.bloomtexture[r_fb.bloomindex]; + r_fb.bloomindex ^= 1; + R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL); + x *= 2; + r = bound(0, r_bloom_colorexponent.value / x, 1); // always 0.5 to 1 + if (!r_fb.bloomfbo[r_fb.bloomindex]) + { + GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); // square it and multiply by two + GL_Color(r,r,r,1); // apply fix factor + } + else + { + if(x <= 2) + GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128); + GL_BlendFunc(GL_SRC_COLOR, GL_ZERO); // square it + GL_Color(1,1,1,1); // no fix factor supported here + } + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.bloomtexcoord2f); + R_SetupShader_Generic(intex, NULL, GL_MODULATE, 1, false, true, false); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight; + + if (!r_fb.bloomfbo[r_fb.bloomindex]) + { + // copy the darkened image to a texture + R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height); + r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height; + } + } + + range = r_bloom_blur.integer * r_fb.bloomwidth / 320; + brighten = r_bloom_brighten.value; + brighten = sqrt(brighten); + if(range >= 1) + brighten *= (3 * range) / (2 * range - 1); // compensate for the "dot particle" + + for (dir = 0;dir < 2;dir++) + { + intex = r_fb.bloomtexture[r_fb.bloomindex]; + r_fb.bloomindex ^= 1; + R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL); + // blend on at multiple vertical offsets to achieve a vertical blur + // TODO: do offset blends using GLSL + // TODO instead of changing the texcoords, change the target positions to prevent artifacts at edges + GL_BlendFunc(GL_ONE, GL_ZERO); + R_SetupShader_Generic(intex, NULL, GL_MODULATE, 1, false, true, false); + for (x = -range;x <= range;x++) + { + if (!dir){xoffset = 0;yoffset = x;} + else {xoffset = x;yoffset = 0;} + xoffset /= (float)r_fb.bloomtexturewidth; + yoffset /= (float)r_fb.bloomtextureheight; + // compute a texcoord array with the specified x and y offset + r_fb.offsettexcoord2f[0] = xoffset+r_fb.bloomtexcoord2f[0]; + r_fb.offsettexcoord2f[1] = yoffset+r_fb.bloomtexcoord2f[1]; + r_fb.offsettexcoord2f[2] = xoffset+r_fb.bloomtexcoord2f[2]; + r_fb.offsettexcoord2f[3] = yoffset+r_fb.bloomtexcoord2f[3]; + r_fb.offsettexcoord2f[4] = xoffset+r_fb.bloomtexcoord2f[4]; + r_fb.offsettexcoord2f[5] = yoffset+r_fb.bloomtexcoord2f[5]; + r_fb.offsettexcoord2f[6] = xoffset+r_fb.bloomtexcoord2f[6]; + r_fb.offsettexcoord2f[7] = yoffset+r_fb.bloomtexcoord2f[7]; + // this r value looks like a 'dot' particle, fading sharply to + // black at the edges + // (probably not realistic but looks good enough) + //r = ((range*range+1)/((float)(x*x+1)))/(range*2+1); + //r = brighten/(range*2+1); + r = brighten / (range * 2 + 1); + if(range >= 1) + r *= (1 - x*x/(float)(range*range)); + GL_Color(r, r, r, 1); + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.offsettexcoord2f); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight; + GL_BlendFunc(GL_ONE, GL_ONE); + } + + if (!r_fb.bloomfbo[r_fb.bloomindex]) + { + // copy the vertically or horizontally blurred bloom view to a texture + R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height); + r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height; + } + } +} + +static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + unsigned int permutation; + float uservecs[4][4]; + + R_EntityMatrix(&identitymatrix); + + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + permutation = + (r_fb.bloomtexture[r_fb.bloomindex] ? SHADERPERMUTATION_BLOOM : 0) + | (r_refdef.viewblend[3] > 0 ? SHADERPERMUTATION_VIEWTINT : 0) + | ((v_glslgamma.value && !vid_gammatables_trivial) ? SHADERPERMUTATION_GAMMARAMPS : 0) + | (r_glsl_postprocess.integer ? SHADERPERMUTATION_POSTPROCESSING : 0) + | ((r_glsl_saturation.value != 1) ? SHADERPERMUTATION_SATURATION : 0); + + if (r_fb.colortexture) + { + if (!r_fb.fbo) + { + R_Mesh_CopyToTexture(r_fb.colortexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height; + } + + if(!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0) && r_fb.ghosttexture) + { + // declare variables + float blur_factor, blur_mouseaccel, blur_velocity; + static float blur_average; + static vec3_t blur_oldangles; // used to see how quickly the mouse is moving + + // set a goal for the factoring + blur_velocity = bound(0, (VectorLength(cl.movement_velocity) - r_motionblur_velocityfactor_minspeed.value) + / max(1, r_motionblur_velocityfactor_maxspeed.value - r_motionblur_velocityfactor_minspeed.value), 1); + blur_mouseaccel = bound(0, ((fabs(VectorLength(cl.viewangles) - VectorLength(blur_oldangles)) * 10) - r_motionblur_mousefactor_minspeed.value) + / max(1, r_motionblur_mousefactor_maxspeed.value - r_motionblur_mousefactor_minspeed.value), 1); + blur_factor = ((blur_velocity * r_motionblur_velocityfactor.value) + + (blur_mouseaccel * r_motionblur_mousefactor.value)); + + // from the goal, pick an averaged value between goal and last value + cl.motionbluralpha = bound(0, (cl.time - cl.oldtime) / max(0.001, r_motionblur_averaging.value), 1); + blur_average = blur_average * (1 - cl.motionbluralpha) + blur_factor * cl.motionbluralpha; + + // enforce minimum amount of blur + blur_factor = blur_average * (1 - r_motionblur_minblur.value) + r_motionblur_minblur.value; + + //Con_Printf("motionblur: direct factor: %f, averaged factor: %f, velocity: %f, mouse accel: %f \n", blur_factor, blur_average, blur_velocity, blur_mouseaccel); + + // calculate values into a standard alpha + cl.motionbluralpha = 1 - exp(- + ( + (r_motionblur.value * blur_factor / 80) + + + (r_damageblur.value * (cl.cshifts[CSHIFT_DAMAGE].percent / 1600)) + ) + / + max(0.0001, cl.time - cl.oldtime) // fps independent + ); + + // randomization for the blur value to combat persistent ghosting + cl.motionbluralpha *= lhrandom(1 - r_motionblur_randomize.value, 1 + r_motionblur_randomize.value); + cl.motionbluralpha = bound(0, cl.motionbluralpha, r_motionblur_maxblur.value); + + // apply the blur + R_ResetViewRendering2D(fbo, depthtexture, colortexture); + if (cl.motionbluralpha > 0 && !r_refdef.envmap && r_fb.ghosttexture_valid) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_Color(1, 1, 1, cl.motionbluralpha); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + case RENDERPATH_SOFT: + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.screentexcoord2f); + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_fb.screentexcoord2f); + break; + } + R_SetupShader_Generic(r_fb.ghosttexture, NULL, GL_MODULATE, 1, false, true, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats[r_stat_bloom_drawpixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height; + } + + // updates old view angles for next pass + VectorCopy(cl.viewangles, blur_oldangles); + + // copy view into the ghost texture + R_Mesh_CopyToTexture(r_fb.ghosttexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height; + r_fb.ghosttexture_valid = true; + } + } + else + { + // no r_fb.colortexture means we're rendering to the real fb + // we may still have to do view tint... + if (r_refdef.viewblend[3] >= (1.0f / 256.0f)) + { + // apply a color tint to the whole view + R_ResetViewRendering2D(0, NULL, NULL); + GL_Color(r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); + R_SetupShader_Generic_NoTexture(false, true); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } + break; // no screen processing, no bloom, skip it + } + + if (r_fb.bloomtexture[0]) + { + // make the bloom texture + R_Bloom_MakeTexture(); + } + +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + memset(uservecs, 0, sizeof(uservecs)); + if (r_glsl_postprocess_uservec1_enable.integer) + sscanf(r_glsl_postprocess_uservec1.string, "%f %f %f %f", &uservecs[0][0], &uservecs[0][1], &uservecs[0][2], &uservecs[0][3]); + if (r_glsl_postprocess_uservec2_enable.integer) + sscanf(r_glsl_postprocess_uservec2.string, "%f %f %f %f", &uservecs[1][0], &uservecs[1][1], &uservecs[1][2], &uservecs[1][3]); + if (r_glsl_postprocess_uservec3_enable.integer) + sscanf(r_glsl_postprocess_uservec3.string, "%f %f %f %f", &uservecs[2][0], &uservecs[2][1], &uservecs[2][2], &uservecs[2][3]); + if (r_glsl_postprocess_uservec4_enable.integer) + sscanf(r_glsl_postprocess_uservec4.string, "%f %f %f %f", &uservecs[3][0], &uservecs[3][1], &uservecs[3][2], &uservecs[3][3]); + + R_ResetViewRendering2D(0, NULL, NULL); // here we render to the real framebuffer! + GL_Color(1, 1, 1, 1); + GL_BlendFunc(GL_ONE, GL_ZERO); + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_GLES2: + R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f); + R_SetupShader_SetPermutationGLSL(SHADERMODE_POSTPROCESS, permutation); + if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , r_fb.colortexture); + if (r_glsl_permutation->tex_Texture_Second >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second , r_fb.bloomtexture[r_fb.bloomindex]); + if (r_glsl_permutation->tex_Texture_GammaRamps >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps ); + if (r_glsl_permutation->loc_ViewTintColor >= 0) qglUniform4f(r_glsl_permutation->loc_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + if (r_glsl_permutation->loc_PixelSize >= 0) qglUniform2f(r_glsl_permutation->loc_PixelSize , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight); + if (r_glsl_permutation->loc_UserVec1 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); + if (r_glsl_permutation->loc_UserVec2 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); + if (r_glsl_permutation->loc_UserVec3 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); + if (r_glsl_permutation->loc_UserVec4 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); + if (r_glsl_permutation->loc_Saturation >= 0) qglUniform1f(r_glsl_permutation->loc_Saturation , r_glsl_saturation.value); + if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f(r_glsl_permutation->loc_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + if (r_glsl_permutation->loc_BloomColorSubtract >= 0) qglUniform4f(r_glsl_permutation->loc_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + // D3D has upside down Y coords, the easiest way to flip this is to flip the screen vertices rather than the texcoords, so we just use a different array for that... + R_Mesh_PrepareVertices_Mesh_Arrays(4, r_d3dscreenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f); + R_SetupShader_SetPermutationHLSL(SHADERMODE_POSTPROCESS, permutation); + R_Mesh_TexBind(GL20TU_FIRST , r_fb.colortexture); + R_Mesh_TexBind(GL20TU_SECOND , r_fb.bloomtexture[r_fb.bloomindex]); + R_Mesh_TexBind(GL20TU_GAMMARAMPS, r_texture_gammaramps ); + hlslPSSetParameter4f(D3DPSREGISTER_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + hlslPSSetParameter2f(D3DPSREGISTER_PixelSize , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight); + hlslPSSetParameter4f(D3DPSREGISTER_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); + hlslPSSetParameter4f(D3DPSREGISTER_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); + hlslPSSetParameter4f(D3DPSREGISTER_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); + hlslPSSetParameter4f(D3DPSREGISTER_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); + hlslPSSetParameter1f(D3DPSREGISTER_Saturation , r_glsl_saturation.value); + hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); + hlslPSSetParameter4f(D3DPSREGISTER_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f); + R_SetupShader_SetPermutationSoft(SHADERMODE_POSTPROCESS, permutation); + R_Mesh_TexBind(GL20TU_FIRST , r_fb.colortexture); + R_Mesh_TexBind(GL20TU_SECOND , r_fb.bloomtexture[r_fb.bloomindex]); + R_Mesh_TexBind(GL20TU_GAMMARAMPS, r_texture_gammaramps ); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelSize , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); + DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_Saturation , r_glsl_saturation.value); + DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); + DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); + break; + default: + break; + } + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + r_refdef.stats[r_stat_bloom_drawpixels] += r_refdef.view.width * r_refdef.view.height; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (r_refdef.viewblend[3] >= (1.0f / 256.0f)) + { + // apply a color tint to the whole view + R_ResetViewRendering2D(0, NULL, NULL); + GL_Color(r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); + R_SetupShader_Generic_NoTexture(false, true); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + } + break; + } +} + +matrix4x4_t r_waterscrollmatrix; + +void R_UpdateFog(void) +{ + // Nehahra fog + if (gamemode == GAME_NEHAHRA) + { + if (gl_fogenable.integer) + { + r_refdef.oldgl_fogenable = true; + r_refdef.fog_density = gl_fogdensity.value; + r_refdef.fog_red = gl_fogred.value; + r_refdef.fog_green = gl_foggreen.value; + r_refdef.fog_blue = gl_fogblue.value; + r_refdef.fog_alpha = 1; + r_refdef.fog_start = 0; + r_refdef.fog_end = gl_skyclip.value; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; + } + else if (r_refdef.oldgl_fogenable) + { + r_refdef.oldgl_fogenable = false; + r_refdef.fog_density = 0; + r_refdef.fog_red = 0; + r_refdef.fog_green = 0; + r_refdef.fog_blue = 0; + r_refdef.fog_alpha = 0; + r_refdef.fog_start = 0; + r_refdef.fog_end = 0; + r_refdef.fog_height = 1<<30; + r_refdef.fog_fadedepth = 128; + } + } + + // fog parms + r_refdef.fog_alpha = bound(0, r_refdef.fog_alpha, 1); + r_refdef.fog_start = max(0, r_refdef.fog_start); + r_refdef.fog_end = max(r_refdef.fog_start + 0.01, r_refdef.fog_end); + + if (r_refdef.fog_density && r_drawfog.integer) + { + r_refdef.fogenabled = true; + // this is the point where the fog reaches 0.9986 alpha, which we + // consider a good enough cutoff point for the texture + // (0.9986 * 256 == 255.6) + if (r_fog_exp2.integer) + r_refdef.fogrange = 32 / (r_refdef.fog_density * r_refdef.fog_density) + r_refdef.fog_start; + else + r_refdef.fogrange = 2048 / r_refdef.fog_density + r_refdef.fog_start; + r_refdef.fogrange = bound(r_refdef.fog_start, r_refdef.fogrange, r_refdef.fog_end); + r_refdef.fograngerecip = 1.0f / r_refdef.fogrange; + r_refdef.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * r_refdef.fograngerecip; + if (strcmp(r_refdef.fogheighttexturename, r_refdef.fog_height_texturename)) + R_BuildFogHeightTexture(); + // fog color was already set + // update the fog texture + if (r_refdef.fogmasktable_start != r_refdef.fog_start || r_refdef.fogmasktable_alpha != r_refdef.fog_alpha || r_refdef.fogmasktable_density != r_refdef.fog_density || r_refdef.fogmasktable_range != r_refdef.fogrange) + R_BuildFogTexture(); + r_refdef.fog_height_texcoordscale = 1.0f / max(0.125f, r_refdef.fog_fadedepth); + r_refdef.fog_height_tablescale = r_refdef.fog_height_tablesize * r_refdef.fog_height_texcoordscale; + } + else + r_refdef.fogenabled = false; + + // fog color + if (r_refdef.fog_density) + { + r_refdef.fogcolor[0] = r_refdef.fog_red; + r_refdef.fogcolor[1] = r_refdef.fog_green; + r_refdef.fogcolor[2] = r_refdef.fog_blue; + + Vector4Set(r_refdef.fogplane, 0, 0, 1, -r_refdef.fog_height); + r_refdef.fogplaneviewdist = DotProduct(r_refdef.fogplane, r_refdef.view.origin) + r_refdef.fogplane[3]; + r_refdef.fogplaneviewabove = r_refdef.fogplaneviewdist >= 0; + r_refdef.fogheightfade = -0.5f/max(0.125f, r_refdef.fog_fadedepth); + + { + vec3_t fogvec; + VectorCopy(r_refdef.fogcolor, fogvec); + // color.rgb *= ContrastBoost * SceneBrightness; + VectorScale(fogvec, r_refdef.view.colorscale, fogvec); + r_refdef.fogcolor[0] = bound(0.0f, fogvec[0], 1.0f); + r_refdef.fogcolor[1] = bound(0.0f, fogvec[1], 1.0f); + r_refdef.fogcolor[2] = bound(0.0f, fogvec[2], 1.0f); + } + } +} + +void R_UpdateVariables(void) +{ + R_Textures_Frame(); + + r_refdef.scene.ambient = r_ambient.value * (1.0f / 64.0f); + + r_refdef.farclip = r_farclip_base.value; + if (r_refdef.scene.worldmodel) + r_refdef.farclip += r_refdef.scene.worldmodel->radius * r_farclip_world.value * 2; + r_refdef.nearclip = bound (0.001f, r_nearclip.value, r_refdef.farclip - 1.0f); + + if (r_shadow_frontsidecasting.integer < 0 || r_shadow_frontsidecasting.integer > 1) + Cvar_SetValueQuick(&r_shadow_frontsidecasting, 1); + r_refdef.polygonfactor = 0; + r_refdef.polygonoffset = 0; + r_refdef.shadowpolygonfactor = r_refdef.polygonfactor + r_shadow_polygonfactor.value * (r_shadow_frontsidecasting.integer ? 1 : -1); + r_refdef.shadowpolygonoffset = r_refdef.polygonoffset + r_shadow_polygonoffset.value * (r_shadow_frontsidecasting.integer ? 1 : -1); + + r_refdef.scene.rtworld = r_shadow_realtime_world.integer != 0; + r_refdef.scene.rtworldshadows = r_shadow_realtime_world_shadows.integer && vid.stencil; + r_refdef.scene.rtdlight = r_shadow_realtime_dlight.integer != 0 && !gl_flashblend.integer && r_dynamic.integer; + r_refdef.scene.rtdlightshadows = r_refdef.scene.rtdlight && r_shadow_realtime_dlight_shadows.integer && vid.stencil; + r_refdef.lightmapintensity = r_refdef.scene.rtworld ? r_shadow_realtime_world_lightmaps.value : 1; + if (FAKELIGHT_ENABLED) + { + r_refdef.lightmapintensity *= r_fakelight_intensity.value; + } + else if (r_refdef.scene.worldmodel) + { + r_refdef.lightmapintensity *= r_refdef.scene.worldmodel->lightmapscale; + } + if (r_showsurfaces.integer) + { + r_refdef.scene.rtworld = false; + r_refdef.scene.rtworldshadows = false; + r_refdef.scene.rtdlight = false; + r_refdef.scene.rtdlightshadows = false; + r_refdef.lightmapintensity = 0; + } + + r_gpuskeletal = false; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + r_gpuskeletal = vid.support.arb_uniform_buffer_object && r_glsl_skeletal.integer && !r_showsurfaces.integer; // FIXME add r_showsurfaces support to GLSL skeletal! + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + if(v_glslgamma.integer && !vid_gammatables_trivial) + { + if(!r_texture_gammaramps || vid_gammatables_serial != r_texture_gammaramps_serial) + { + // build GLSL gamma texture +#define RAMPWIDTH 256 + unsigned short ramp[RAMPWIDTH * 3]; + unsigned char rampbgr[RAMPWIDTH][4]; + int i; + + r_texture_gammaramps_serial = vid_gammatables_serial; + + VID_BuildGammaTables(&ramp[0], RAMPWIDTH); + for(i = 0; i < RAMPWIDTH; ++i) + { + rampbgr[i][0] = (unsigned char) (ramp[i + 2 * RAMPWIDTH] * 255.0 / 65535.0 + 0.5); + rampbgr[i][1] = (unsigned char) (ramp[i + RAMPWIDTH] * 255.0 / 65535.0 + 0.5); + rampbgr[i][2] = (unsigned char) (ramp[i] * 255.0 / 65535.0 + 0.5); + rampbgr[i][3] = 0; + } + if (r_texture_gammaramps) + { + R_UpdateTexture(r_texture_gammaramps, &rampbgr[0][0], 0, 0, 0, RAMPWIDTH, 1, 1); + } + else + { + r_texture_gammaramps = R_LoadTexture2D(r_main_texturepool, "gammaramps", RAMPWIDTH, 1, &rampbgr[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); + } + } + } + else + { + // remove GLSL gamma texture + } + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + } +} + +static r_refdef_scene_type_t r_currentscenetype = RST_CLIENT; +static r_refdef_scene_t r_scenes_store[ RST_COUNT ]; +/* +================ +R_SelectScene +================ +*/ +void R_SelectScene( r_refdef_scene_type_t scenetype ) { + if( scenetype != r_currentscenetype ) { + // store the old scenetype + r_scenes_store[ r_currentscenetype ] = r_refdef.scene; + r_currentscenetype = scenetype; + // move in the new scene + r_refdef.scene = r_scenes_store[ r_currentscenetype ]; + } +} + +/* +================ +R_GetScenePointer +================ +*/ +r_refdef_scene_t * R_GetScenePointer( r_refdef_scene_type_t scenetype ) +{ + // of course, we could also add a qboolean that provides a lock state and a ReleaseScenePointer function.. + if( scenetype == r_currentscenetype ) { + return &r_refdef.scene; + } else { + return &r_scenes_store[ scenetype ]; + } +} + +static int R_SortEntities_Compare(const void *ap, const void *bp) +{ + const entity_render_t *a = *(const entity_render_t **)ap; + const entity_render_t *b = *(const entity_render_t **)bp; + + // 1. compare model + if(a->model < b->model) + return -1; + if(a->model > b->model) + return +1; + + // 2. compare skin + // TODO possibly calculate the REAL skinnum here first using + // skinscenes? + if(a->skinnum < b->skinnum) + return -1; + if(a->skinnum > b->skinnum) + return +1; + + // everything we compared is equal + return 0; +} +static void R_SortEntities(void) +{ + // below or equal 2 ents, sorting never gains anything + if(r_refdef.scene.numentities <= 2) + return; + // sort + qsort(r_refdef.scene.entities, r_refdef.scene.numentities, sizeof(*r_refdef.scene.entities), R_SortEntities_Compare); +} + +/* +================ +R_RenderView +================ +*/ +int dpsoftrast_test; +extern cvar_t r_shadow_bouncegrid; +void R_RenderView() +{ + matrix4x4_t originalmatrix = r_refdef.view.matrix, offsetmatrix; + int fbo; + rtexture_t *depthtexture; + rtexture_t *colortexture; + + dpsoftrast_test = r_test.integer; + + if (r_timereport_active) + R_TimeReport("start"); + r_textureframe++; // used only by R_GetCurrentTexture + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + + if(R_CompileShader_CheckStaticParms()) + R_GLSL_Restart_f(); + + if (!r_drawentities.integer) + r_refdef.scene.numentities = 0; + else if (r_sortentities.integer) + R_SortEntities(); + + R_AnimCache_ClearCache(); + + /* adjust for stereo display */ + Matrix4x4_CreateFromQuakeEntity(&offsetmatrix, 0, GetStereoSeparation() * (0.5f - r_stereo_side), 0, 0, r_stereo_angle.value * (0.5f - r_stereo_side), 0, 1); + Matrix4x4_Concat(&r_refdef.view.matrix, &originalmatrix, &offsetmatrix); + + if (r_refdef.view.isoverlay) + { + // TODO: FIXME: move this into its own backend function maybe? [2/5/2008 Andreas] + R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); + GL_Clear(GL_DEPTH_BUFFER_BIT, NULL, 1.0f, 0); + R_TimeReport("depthclear"); + + r_refdef.view.showdebug = false; + + r_fb.water.enabled = false; + r_fb.water.numwaterplanes = 0; + + R_RenderScene(0, NULL, NULL); + + r_refdef.view.matrix = originalmatrix; + + CHECKGLERROR + return; + } + + if (!r_refdef.scene.entities || r_refdef.view.width * r_refdef.view.height == 0 || !r_renderview.integer || cl_videoplaying/* || !r_refdef.scene.worldmodel*/) + { + r_refdef.view.matrix = originalmatrix; + return; + } + + r_refdef.view.colorscale = r_hdr_scenebrightness.value * r_hdr_irisadaptation_value.value; + + if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + // in sRGB fallback, behave similar to true sRGB: convert this + // value from linear to sRGB + r_refdef.view.colorscale = Image_sRGBFloatFromLinearFloat(r_refdef.view.colorscale); + + R_RenderView_UpdateViewVectors(); + + R_Shadow_UpdateWorldLightSelection(); + + R_Bloom_StartFrame(); + + // apply bloom brightness offset + if(r_fb.bloomtexture[0]) + r_refdef.view.colorscale *= r_bloom_scenebrightness.value; + + R_Water_StartFrame(); + + // now we probably have an fbo to render into + fbo = r_fb.fbo; + depthtexture = r_fb.depthtexture; + colortexture = r_fb.colortexture; + + CHECKGLERROR + if (r_timereport_active) + R_TimeReport("viewsetup"); + + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + + if (r_refdef.view.clear || r_refdef.fogenabled || fbo) + { + R_ClearScreen(r_refdef.fogenabled); + if (r_timereport_active) + R_TimeReport("viewclear"); + } + r_refdef.view.clear = true; + + r_refdef.view.showdebug = true; + + R_View_Update(); + if (r_timereport_active) + R_TimeReport("visibility"); + + R_AnimCache_CacheVisibleEntities(); + if (r_timereport_active) + R_TimeReport("animcache"); + + R_Shadow_UpdateBounceGridTexture(); + if (r_timereport_active && r_shadow_bouncegrid.integer) + R_TimeReport("bouncegrid"); + + r_fb.water.numwaterplanes = 0; + if (r_fb.water.enabled) + R_RenderWaterPlanes(fbo, depthtexture, colortexture); + + R_RenderScene(fbo, depthtexture, colortexture); + r_fb.water.numwaterplanes = 0; + + R_BlendView(fbo, depthtexture, colortexture); + if (r_timereport_active) + R_TimeReport("blendview"); + + GL_Scissor(0, 0, vid.width, vid.height); + GL_ScissorTest(false); + + r_refdef.view.matrix = originalmatrix; + + CHECKGLERROR +} + +void R_RenderWaterPlanes(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawAddWaterPlanes) + { + r_refdef.scene.worldmodel->DrawAddWaterPlanes(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("waterworld"); + } + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + R_DrawModelsAddWaterPlanes(); + if (r_timereport_active) + R_TimeReport("watermodels"); + + if (r_fb.water.numwaterplanes) + { + R_Water_ProcessPlanes(fbo, depthtexture, colortexture); + if (r_timereport_active) + R_TimeReport("waterscenes"); + } +} + +extern cvar_t cl_locs_show; +static void R_DrawLocs(void); +static void R_DrawEntityBBoxes(void); +static void R_DrawModelDecals(void); +extern cvar_t cl_decals_newsystem; +extern qboolean r_shadow_usingdeferredprepass; +void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + qboolean shadowmapping = false; + + if (r_timereport_active) + R_TimeReport("beginscene"); + + r_refdef.stats[r_stat_renders]++; + + R_UpdateFog(); + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + R_MeshQueue_BeginScene(); + + R_SkyStartFrame(); + + Matrix4x4_CreateTranslate(&r_waterscrollmatrix, sin(r_refdef.scene.time) * 0.025 * r_waterscroll.value, sin(r_refdef.scene.time * 0.8f) * 0.025 * r_waterscroll.value, 0); + + if (r_timereport_active) + R_TimeReport("skystartframe"); + + if (cl.csqc_vidvars.drawworld) + { + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawSky) + { + r_refdef.scene.worldmodel->DrawSky(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("worldsky"); + } + + if (R_DrawBrushModelsSky() && r_timereport_active) + R_TimeReport("bmodelsky"); + + if (skyrendermasked && skyrenderlater) + { + // we have to force off the water clipping plane while rendering sky + R_SetupView(false, fbo, depthtexture, colortexture); + R_Sky(); + R_SetupView(true, fbo, depthtexture, colortexture); + if (r_timereport_active) + R_TimeReport("sky"); + } + } + + R_Shadow_PrepareLights(fbo, depthtexture, colortexture); + if (r_shadows.integer > 0 && r_refdef.lightmapintensity > 0) + R_Shadow_PrepareModelShadows(); + if (r_timereport_active) + R_TimeReport("preparelights"); + + if (R_Shadow_ShadowMappingEnabled()) + shadowmapping = true; + + if (r_shadow_usingdeferredprepass) + R_Shadow_DrawPrepass(); + + if (r_depthfirst.integer >= 1 && cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawDepth) + { + r_refdef.scene.worldmodel->DrawDepth(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("worlddepth"); + } + if (r_depthfirst.integer >= 2) + { + R_DrawModelsDepth(); + if (r_timereport_active) + R_TimeReport("modeldepth"); + } + + if (r_shadows.integer >= 2 && shadowmapping && r_refdef.lightmapintensity > 0) + { + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + R_DrawModelShadowMaps(fbo, depthtexture, colortexture); + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + } + + if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->Draw) + { + r_refdef.scene.worldmodel->Draw(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("world"); + } + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + R_DrawModels(); + if (r_timereport_active) + R_TimeReport("models"); + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && !r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0) + { + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + R_DrawModelShadows(fbo, depthtexture, colortexture); + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + } + + if (!r_shadow_usingdeferredprepass) + { + R_Shadow_DrawLights(); + if (r_timereport_active) + R_TimeReport("rtlights"); + } + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0) + { + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + R_DrawModelShadows(fbo, depthtexture, colortexture); + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + } + + if (cl.csqc_vidvars.drawworld) + { + if (cl_decals_newsystem.integer) + { + R_DrawModelDecals(); + if (r_timereport_active) + R_TimeReport("modeldecals"); + } + else + { + R_DrawDecals(); + if (r_timereport_active) + R_TimeReport("decals"); + } + + R_DrawParticles(); + if (r_timereport_active) + R_TimeReport("particles"); + + R_DrawExplosions(); + if (r_timereport_active) + R_TimeReport("explosions"); + + R_DrawLightningBeams(); + if (r_timereport_active) + R_TimeReport("lightning"); + } + + if (cl.csqc_loaded) + VM_CL_AddPolygonsToMeshQueue(CLVM_prog); + + if (r_refdef.view.showdebug) + { + if (cl_locs_show.integer) + { + R_DrawLocs(); + if (r_timereport_active) + R_TimeReport("showlocs"); + } + + if (r_drawportals.integer) + { + R_DrawPortals(); + if (r_timereport_active) + R_TimeReport("portals"); + } + + if (r_showbboxes.value > 0) + { + R_DrawEntityBBoxes(); + if (r_timereport_active) + R_TimeReport("bboxes"); + } + } + + if (r_transparent.integer) + { + R_MeshQueue_RenderTransparent(); + if (r_timereport_active) + R_TimeReport("drawtrans"); + } + + if (r_refdef.view.showdebug && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawDebug && (r_showtris.value > 0 || r_shownormals.value != 0 || r_showcollisionbrushes.value > 0 || r_showoverdraw.value > 0)) + { + r_refdef.scene.worldmodel->DrawDebug(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("worlddebug"); + R_DrawModelsDebug(); + if (r_timereport_active) + R_TimeReport("modeldebug"); + } + + if (cl.csqc_vidvars.drawworld) + { + R_Shadow_DrawCoronas(); + if (r_timereport_active) + R_TimeReport("coronas"); + } + +#if 0 + { + GL_DepthTest(false); + qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + GL_Color(1, 1, 1, 1); + qglBegin(GL_POLYGON); + qglVertex3f(r_refdef.view.frustumcorner[0][0], r_refdef.view.frustumcorner[0][1], r_refdef.view.frustumcorner[0][2]); + qglVertex3f(r_refdef.view.frustumcorner[1][0], r_refdef.view.frustumcorner[1][1], r_refdef.view.frustumcorner[1][2]); + qglVertex3f(r_refdef.view.frustumcorner[3][0], r_refdef.view.frustumcorner[3][1], r_refdef.view.frustumcorner[3][2]); + qglVertex3f(r_refdef.view.frustumcorner[2][0], r_refdef.view.frustumcorner[2][1], r_refdef.view.frustumcorner[2][2]); + qglEnd(); + qglBegin(GL_POLYGON); + qglVertex3f(r_refdef.view.frustumcorner[0][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[0][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[0][2] + 1000 * r_refdef.view.forward[2]); + qglVertex3f(r_refdef.view.frustumcorner[1][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[1][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[1][2] + 1000 * r_refdef.view.forward[2]); + qglVertex3f(r_refdef.view.frustumcorner[3][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[3][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[3][2] + 1000 * r_refdef.view.forward[2]); + qglVertex3f(r_refdef.view.frustumcorner[2][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[2][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[2][2] + 1000 * r_refdef.view.forward[2]); + qglEnd(); + qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } +#endif + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); +} + +static const unsigned short bboxelements[36] = +{ + 5, 1, 3, 5, 3, 7, + 6, 2, 0, 6, 0, 4, + 7, 3, 2, 7, 2, 6, + 4, 0, 1, 4, 1, 5, + 4, 5, 7, 4, 7, 6, + 1, 0, 2, 1, 2, 3, +}; + +static void R_DrawBBoxMesh(vec3_t mins, vec3_t maxs, float cr, float cg, float cb, float ca) +{ + int i; + float *v, *c, f1, f2, vertex3f[8*3], color4f[8*4]; + + RSurf_ActiveWorldEntity(); + + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); +// R_Mesh_ResetTextureState(); + + vertex3f[ 0] = mins[0];vertex3f[ 1] = mins[1];vertex3f[ 2] = mins[2]; // + vertex3f[ 3] = maxs[0];vertex3f[ 4] = mins[1];vertex3f[ 5] = mins[2]; + vertex3f[ 6] = mins[0];vertex3f[ 7] = maxs[1];vertex3f[ 8] = mins[2]; + vertex3f[ 9] = maxs[0];vertex3f[10] = maxs[1];vertex3f[11] = mins[2]; + vertex3f[12] = mins[0];vertex3f[13] = mins[1];vertex3f[14] = maxs[2]; + vertex3f[15] = maxs[0];vertex3f[16] = mins[1];vertex3f[17] = maxs[2]; + vertex3f[18] = mins[0];vertex3f[19] = maxs[1];vertex3f[20] = maxs[2]; + vertex3f[21] = maxs[0];vertex3f[22] = maxs[1];vertex3f[23] = maxs[2]; + R_FillColors(color4f, 8, cr, cg, cb, ca); + if (r_refdef.fogenabled) + { + for (i = 0, v = vertex3f, c = color4f;i < 8;i++, v += 3, c += 4) + { + f1 = RSurf_FogVertex(v); + f2 = 1 - f1; + c[0] = c[0] * f1 + r_refdef.fogcolor[0] * f2; + c[1] = c[1] * f1 + r_refdef.fogcolor[1] * f2; + c[2] = c[2] * f1 + r_refdef.fogcolor[2] * f2; + } + } + R_Mesh_PrepareVertices_Generic_Arrays(8, vertex3f, color4f, NULL); + R_Mesh_ResetTextureState(); + R_SetupShader_Generic_NoTexture(false, false); + R_Mesh_Draw(0, 8, 0, 12, NULL, NULL, 0, bboxelements, NULL, 0); +} + +static void R_DrawEntityBBoxes_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + float color[4]; + prvm_edict_t *edict; + + // this function draws bounding boxes of server entities + if (!sv.active) + return; + + GL_CullFace(GL_NONE); + R_SetupShader_Generic_NoTexture(false, false); + + for (i = 0;i < numsurfaces;i++) + { + edict = PRVM_EDICT_NUM(surfacelist[i]); + switch ((int)PRVM_serveredictfloat(edict, solid)) + { + case SOLID_NOT: Vector4Set(color, 1, 1, 1, 0.05);break; + case SOLID_TRIGGER: Vector4Set(color, 1, 0, 1, 0.10);break; + case SOLID_BBOX: Vector4Set(color, 0, 1, 0, 0.10);break; + case SOLID_SLIDEBOX: Vector4Set(color, 1, 0, 0, 0.10);break; + case SOLID_BSP: Vector4Set(color, 0, 0, 1, 0.05);break; + case SOLID_CORPSE: Vector4Set(color, 1, 0.5, 0, 0.05);break; + default: Vector4Set(color, 0, 0, 0, 0.50);break; + } + color[3] *= r_showbboxes.value; + color[3] = bound(0, color[3], 1); + GL_DepthTest(!r_showdisabledepthtest.integer); + GL_CullFace(r_refdef.view.cullface_front); + R_DrawBBoxMesh(edict->priv.server->areamins, edict->priv.server->areamaxs, color[0], color[1], color[2], color[3]); + } +} + +static void R_DrawEntityBBoxes(void) +{ + int i; + prvm_edict_t *edict; + vec3_t center; + prvm_prog_t *prog = SVVM_prog; + + // this function draws bounding boxes of server entities + if (!sv.active) + return; + + for (i = 0;i < prog->num_edicts;i++) + { + edict = PRVM_EDICT_NUM(i); + if (edict->priv.server->free) + continue; + // exclude the following for now, as they don't live in world coordinate space and can't be solid: + if(PRVM_serveredictedict(edict, tag_entity) != 0) + continue; + if(PRVM_serveredictedict(edict, viewmodelforclient) != 0) + continue; + VectorLerp(edict->priv.server->areamins, 0.5f, edict->priv.server->areamaxs, center); + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, R_DrawEntityBBoxes_Callback, (entity_render_t *)NULL, i, (rtlight_t *)NULL); + } +} + +static const int nomodelelement3i[24] = +{ + 5, 2, 0, + 5, 1, 2, + 5, 0, 3, + 5, 3, 1, + 0, 2, 4, + 2, 1, 4, + 3, 0, 4, + 1, 3, 4 +}; + +static const unsigned short nomodelelement3s[24] = +{ + 5, 2, 0, + 5, 1, 2, + 5, 0, 3, + 5, 3, 1, + 0, 2, 4, + 2, 1, 4, + 3, 0, 4, + 1, 3, 4 +}; + +static const float nomodelvertex3f[6*3] = +{ + -16, 0, 0, + 16, 0, 0, + 0, -16, 0, + 0, 16, 0, + 0, 0, -16, + 0, 0, 16 +}; + +static const float nomodelcolor4f[6*4] = +{ + 0.0f, 0.0f, 0.5f, 1.0f, + 0.0f, 0.0f, 0.5f, 1.0f, + 0.0f, 0.5f, 0.0f, 1.0f, + 0.0f, 0.5f, 0.0f, 1.0f, + 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, 0.0f, 0.0f, 1.0f +}; + +static void R_DrawNoModel_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i; + float f1, f2, *c; + float color4f[6*4]; + + RSurf_ActiveCustomEntity(&ent->matrix, &ent->inversematrix, ent->flags, ent->shadertime, ent->colormod[0], ent->colormod[1], ent->colormod[2], ent->alpha, 6, nomodelvertex3f, NULL, NULL, NULL, NULL, nomodelcolor4f, 8, nomodelelement3i, nomodelelement3s, false, false); + + // this is only called once per entity so numsurfaces is always 1, and + // surfacelist is always {0}, so this code does not handle batches + + if (rsurface.ent_flags & RENDER_ADDITIVE) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + GL_DepthMask(false); + } + else if (rsurface.colormod[3] < 1) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + } + else + { + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); + } + GL_DepthRange(0, (rsurface.ent_flags & RENDER_VIEWMODEL) ? 0.0625 : 1); + GL_PolygonOffset(rsurface.basepolygonfactor, rsurface.basepolygonoffset); + GL_DepthTest(!(rsurface.ent_flags & RENDER_NODEPTHTEST)); + GL_CullFace((rsurface.ent_flags & RENDER_DOUBLESIDED) ? GL_NONE : r_refdef.view.cullface_back); + memcpy(color4f, nomodelcolor4f, sizeof(float[6*4])); + for (i = 0, c = color4f;i < 6;i++, c += 4) + { + c[0] *= rsurface.colormod[0]; + c[1] *= rsurface.colormod[1]; + c[2] *= rsurface.colormod[2]; + c[3] *= rsurface.colormod[3]; + } + if (r_refdef.fogenabled) + { + for (i = 0, c = color4f;i < 6;i++, c += 4) + { + f1 = RSurf_FogVertex(nomodelvertex3f + 3*i); + f2 = 1 - f1; + c[0] = (c[0] * f1 + r_refdef.fogcolor[0] * f2); + c[1] = (c[1] * f1 + r_refdef.fogcolor[1] * f2); + c[2] = (c[2] * f1 + r_refdef.fogcolor[2] * f2); + } + } +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic_NoTexture(false, false); + R_Mesh_PrepareVertices_Generic_Arrays(6, nomodelvertex3f, color4f, NULL); + R_Mesh_Draw(0, 6, 0, 8, nomodelelement3i, NULL, 0, nomodelelement3s, NULL, 0); +} + +void R_DrawNoModel(entity_render_t *ent) +{ + vec3_t org; + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + if ((ent->flags & RENDER_ADDITIVE) || (ent->alpha < 1)) + R_MeshQueue_AddTransparent((ent->flags & RENDER_NODEPTHTEST) ? TRANSPARENTSORT_HUD : TRANSPARENTSORT_DISTANCE, org, R_DrawNoModel_TransparentCallback, ent, 0, rsurface.rtlight); + else + R_DrawNoModel_TransparentCallback(ent, rsurface.rtlight, 0, NULL); +} + +void R_CalcBeam_Vertex3f (float *vert, const float *org1, const float *org2, float width) +{ + vec3_t right1, right2, diff, normal; + + VectorSubtract (org2, org1, normal); + + // calculate 'right' vector for start + VectorSubtract (r_refdef.view.origin, org1, diff); + CrossProduct (normal, diff, right1); + VectorNormalize (right1); + + // calculate 'right' vector for end + VectorSubtract (r_refdef.view.origin, org2, diff); + CrossProduct (normal, diff, right2); + VectorNormalize (right2); + + vert[ 0] = org1[0] + width * right1[0]; + vert[ 1] = org1[1] + width * right1[1]; + vert[ 2] = org1[2] + width * right1[2]; + vert[ 3] = org1[0] - width * right1[0]; + vert[ 4] = org1[1] - width * right1[1]; + vert[ 5] = org1[2] - width * right1[2]; + vert[ 6] = org2[0] - width * right2[0]; + vert[ 7] = org2[1] - width * right2[1]; + vert[ 8] = org2[2] - width * right2[2]; + vert[ 9] = org2[0] + width * right2[0]; + vert[10] = org2[1] + width * right2[1]; + vert[11] = org2[2] + width * right2[2]; +} + +void R_CalcSprite_Vertex3f(float *vertex3f, const vec3_t origin, const vec3_t left, const vec3_t up, float scalex1, float scalex2, float scaley1, float scaley2) +{ + vertex3f[ 0] = origin[0] + left[0] * scalex2 + up[0] * scaley1; + vertex3f[ 1] = origin[1] + left[1] * scalex2 + up[1] * scaley1; + vertex3f[ 2] = origin[2] + left[2] * scalex2 + up[2] * scaley1; + vertex3f[ 3] = origin[0] + left[0] * scalex2 + up[0] * scaley2; + vertex3f[ 4] = origin[1] + left[1] * scalex2 + up[1] * scaley2; + vertex3f[ 5] = origin[2] + left[2] * scalex2 + up[2] * scaley2; + vertex3f[ 6] = origin[0] + left[0] * scalex1 + up[0] * scaley2; + vertex3f[ 7] = origin[1] + left[1] * scalex1 + up[1] * scaley2; + vertex3f[ 8] = origin[2] + left[2] * scalex1 + up[2] * scaley2; + vertex3f[ 9] = origin[0] + left[0] * scalex1 + up[0] * scaley1; + vertex3f[10] = origin[1] + left[1] * scalex1 + up[1] * scaley1; + vertex3f[11] = origin[2] + left[2] * scalex1 + up[2] * scaley1; +} + +static int R_Mesh_AddVertex(rmesh_t *mesh, float x, float y, float z) +{ + int i; + float *vertex3f; + float v[3]; + VectorSet(v, x, y, z); + for (i = 0, vertex3f = mesh->vertex3f;i < mesh->numvertices;i++, vertex3f += 3) + if (VectorDistance2(v, vertex3f) < mesh->epsilon2) + break; + if (i == mesh->numvertices) + { + if (mesh->numvertices < mesh->maxvertices) + { + VectorCopy(v, vertex3f); + mesh->numvertices++; + } + return mesh->numvertices; + } + else + return i; +} + +void R_Mesh_AddPolygon3f(rmesh_t *mesh, int numvertices, float *vertex3f) +{ + int i; + int *e, element[3]; + element[0] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]);vertex3f += 3; + element[1] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]);vertex3f += 3; + e = mesh->element3i + mesh->numtriangles * 3; + for (i = 0;i < numvertices - 2;i++, vertex3f += 3) + { + element[2] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]); + if (mesh->numtriangles < mesh->maxtriangles) + { + *e++ = element[0]; + *e++ = element[1]; + *e++ = element[2]; + mesh->numtriangles++; + } + element[1] = element[2]; + } +} + +static void R_Mesh_AddPolygon3d(rmesh_t *mesh, int numvertices, double *vertex3d) +{ + int i; + int *e, element[3]; + element[0] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]);vertex3d += 3; + element[1] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]);vertex3d += 3; + e = mesh->element3i + mesh->numtriangles * 3; + for (i = 0;i < numvertices - 2;i++, vertex3d += 3) + { + element[2] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]); + if (mesh->numtriangles < mesh->maxtriangles) + { + *e++ = element[0]; + *e++ = element[1]; + *e++ = element[2]; + mesh->numtriangles++; + } + element[1] = element[2]; + } +} + +#define R_MESH_PLANE_DIST_EPSILON (1.0 / 32.0) +void R_Mesh_AddBrushMeshFromPlanes(rmesh_t *mesh, int numplanes, mplane_t *planes) +{ + int planenum, planenum2; + int w; + int tempnumpoints; + mplane_t *plane, *plane2; + double maxdist; + double temppoints[2][256*3]; + // figure out how large a bounding box we need to properly compute this brush + maxdist = 0; + for (w = 0;w < numplanes;w++) + maxdist = max(maxdist, fabs(planes[w].dist)); + // now make it large enough to enclose the entire brush, and round it off to a reasonable multiple of 1024 + maxdist = floor(maxdist * (4.0 / 1024.0) + 1) * 1024.0; + for (planenum = 0, plane = planes;planenum < numplanes;planenum++, plane++) + { + w = 0; + tempnumpoints = 4; + PolygonD_QuadForPlane(temppoints[w], plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, maxdist); + for (planenum2 = 0, plane2 = planes;planenum2 < numplanes && tempnumpoints >= 3;planenum2++, plane2++) + { + if (planenum2 == planenum) + continue; + PolygonD_Divide(tempnumpoints, temppoints[w], plane2->normal[0], plane2->normal[1], plane2->normal[2], plane2->dist, R_MESH_PLANE_DIST_EPSILON, 0, NULL, NULL, 256, temppoints[!w], &tempnumpoints, NULL); + w = !w; + } + if (tempnumpoints < 3) + continue; + // generate elements forming a triangle fan for this polygon + R_Mesh_AddPolygon3d(mesh, tempnumpoints, temppoints[w]); + } +} + +static void R_Texture_AddLayer(texture_t *t, qboolean depthmask, int blendfunc1, int blendfunc2, texturelayertype_t type, rtexture_t *texture, const matrix4x4_t *matrix, float r, float g, float b, float a) +{ + texturelayer_t *layer; + layer = t->currentlayers + t->currentnumlayers++; + layer->type = type; + layer->depthmask = depthmask; + layer->blendfunc1 = blendfunc1; + layer->blendfunc2 = blendfunc2; + layer->texture = texture; + layer->texmatrix = *matrix; + layer->color[0] = r; + layer->color[1] = g; + layer->color[2] = b; + layer->color[3] = a; +} + +static qboolean R_TestQ3WaveFunc(q3wavefunc_t func, const float *parms) +{ + if(parms[0] == 0 && parms[1] == 0) + return false; + if(func >> Q3WAVEFUNC_USER_SHIFT) // assumes rsurface to be set! + if(rsurface.userwavefunc_param[bound(0, (func >> Q3WAVEFUNC_USER_SHIFT) - 1, Q3WAVEFUNC_USER_COUNT - 1)] == 0) + return false; + return true; +} + +static float R_EvaluateQ3WaveFunc(q3wavefunc_t func, const float *parms) +{ + double index, f; + index = parms[2] + rsurface.shadertime * parms[3]; + index -= floor(index); + switch (func & ((1 << Q3WAVEFUNC_USER_SHIFT) - 1)) + { + default: + case Q3WAVEFUNC_NONE: + case Q3WAVEFUNC_NOISE: + case Q3WAVEFUNC_COUNT: + f = 0; + break; + case Q3WAVEFUNC_SIN: f = sin(index * M_PI * 2);break; + case Q3WAVEFUNC_SQUARE: f = index < 0.5 ? 1 : -1;break; + case Q3WAVEFUNC_SAWTOOTH: f = index;break; + case Q3WAVEFUNC_INVERSESAWTOOTH: f = 1 - index;break; + case Q3WAVEFUNC_TRIANGLE: + index *= 4; + f = index - floor(index); + if (index < 1) + { + // f = f; + } + else if (index < 2) + f = 1 - f; + else if (index < 3) + f = -f; + else + f = -(1 - f); + break; + } + f = parms[0] + parms[1] * f; + if(func >> Q3WAVEFUNC_USER_SHIFT) // assumes rsurface to be set! + f *= rsurface.userwavefunc_param[bound(0, (func >> Q3WAVEFUNC_USER_SHIFT) - 1, Q3WAVEFUNC_USER_COUNT - 1)]; + return (float) f; +} + +static void R_tcMod_ApplyToMatrix(matrix4x4_t *texmatrix, q3shaderinfo_layer_tcmod_t *tcmod, int currentmaterialflags) +{ + int w, h, idx; + double f; + double offsetd[2]; + float tcmat[12]; + matrix4x4_t matrix, temp; + switch(tcmod->tcmod) + { + case Q3TCMOD_COUNT: + case Q3TCMOD_NONE: + if (currentmaterialflags & MATERIALFLAG_WATERSCROLL) + matrix = r_waterscrollmatrix; + else + matrix = identitymatrix; + break; + case Q3TCMOD_ENTITYTRANSLATE: + // this is used in Q3 to allow the gamecode to control texcoord + // scrolling on the entity, which is not supported in darkplaces yet. + Matrix4x4_CreateTranslate(&matrix, 0, 0, 0); + break; + case Q3TCMOD_ROTATE: + f = tcmod->parms[0] * rsurface.shadertime; + Matrix4x4_CreateTranslate(&matrix, 0.5, 0.5, 0); + Matrix4x4_ConcatRotate(&matrix, (f / 360 - floor(f / 360)) * 360, 0, 0, 1); + Matrix4x4_ConcatTranslate(&matrix, -0.5, -0.5, 0); + break; + case Q3TCMOD_SCALE: + Matrix4x4_CreateScale3(&matrix, tcmod->parms[0], tcmod->parms[1], 1); + break; + case Q3TCMOD_SCROLL: + // extra care is needed because of precision breakdown with large values of time + offsetd[0] = tcmod->parms[0] * rsurface.shadertime; + offsetd[1] = tcmod->parms[1] * rsurface.shadertime; + Matrix4x4_CreateTranslate(&matrix, offsetd[0] - floor(offsetd[0]), offsetd[1] - floor(offsetd[1]), 0); + break; + case Q3TCMOD_PAGE: // poor man's animmap (to store animations into a single file, useful for HTTP downloaded textures) + w = (int) tcmod->parms[0]; + h = (int) tcmod->parms[1]; + f = rsurface.shadertime / (tcmod->parms[2] * w * h); + f = f - floor(f); + idx = (int) floor(f * w * h); + Matrix4x4_CreateTranslate(&matrix, (idx % w) / tcmod->parms[0], (idx / w) / tcmod->parms[1], 0); + break; + case Q3TCMOD_STRETCH: + f = 1.0f / R_EvaluateQ3WaveFunc(tcmod->wavefunc, tcmod->waveparms); + Matrix4x4_CreateFromQuakeEntity(&matrix, 0.5f * (1 - f), 0.5 * (1 - f), 0, 0, 0, 0, f); + break; + case Q3TCMOD_TRANSFORM: + VectorSet(tcmat + 0, tcmod->parms[0], tcmod->parms[1], 0); + VectorSet(tcmat + 3, tcmod->parms[2], tcmod->parms[3], 0); + VectorSet(tcmat + 6, 0 , 0 , 1); + VectorSet(tcmat + 9, tcmod->parms[4], tcmod->parms[5], 0); + Matrix4x4_FromArray12FloatGL(&matrix, tcmat); + break; + case Q3TCMOD_TURBULENT: + // this is handled in the RSurf_PrepareVertices function + matrix = identitymatrix; + break; + } + temp = *texmatrix; + Matrix4x4_Concat(texmatrix, &matrix, &temp); +} + +static void R_LoadQWSkin(r_qwskincache_t *cache, const char *skinname) +{ + int textureflags = (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_PICMIP; + char name[MAX_QPATH]; + skinframe_t *skinframe; + unsigned char pixels[296*194]; + strlcpy(cache->name, skinname, sizeof(cache->name)); + dpsnprintf(name, sizeof(name), "skins/%s.pcx", cache->name); + if (developer_loading.integer) + Con_Printf("loading %s\n", name); + skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, false); + if (!skinframe || !skinframe->base) + { + unsigned char *f; + fs_offset_t filesize; + skinframe = NULL; + f = FS_LoadFile(name, tempmempool, true, &filesize); + if (f) + { + if (LoadPCX_QWSkin(f, (int)filesize, pixels, 296, 194)) + skinframe = R_SkinFrame_LoadInternalQuake(name, textureflags, true, r_fullbrights.integer, pixels, image_width, image_height); + Mem_Free(f); + } + } + cache->skinframe = skinframe; +} + +texture_t *R_GetCurrentTexture(texture_t *t) +{ + int i; + const entity_render_t *ent = rsurface.entity; + dp_model_t *model = ent->model; // when calling this, ent must not be NULL + q3shaderinfo_layer_tcmod_t *tcmod; + + if (t->update_lastrenderframe == r_textureframe && t->update_lastrenderentity == (void *)ent && !rsurface.forcecurrenttextureupdate) + return t->currentframe; + t->update_lastrenderframe = r_textureframe; + t->update_lastrenderentity = (void *)ent; + + if(ent->entitynumber >= MAX_EDICTS && ent->entitynumber < 2 * MAX_EDICTS) + t->camera_entity = ent->entitynumber; + else + t->camera_entity = 0; + + // switch to an alternate material if this is a q1bsp animated material + { + texture_t *texture = t; + int s = rsurface.ent_skinnum; + if ((unsigned int)s >= (unsigned int)model->numskins) + s = 0; + if (model->skinscenes) + { + if (model->skinscenes[s].framecount > 1) + s = model->skinscenes[s].firstframe + (unsigned int) (rsurface.shadertime * model->skinscenes[s].framerate) % model->skinscenes[s].framecount; + else + s = model->skinscenes[s].firstframe; + } + if (s > 0) + t = t + s * model->num_surfaces; + if (t->animated) + { + // use an alternate animation if the entity's frame is not 0, + // and only if the texture has an alternate animation + if (rsurface.ent_alttextures && t->anim_total[1]) + t = t->anim_frames[1][(t->anim_total[1] >= 2) ? ((int)(rsurface.shadertime * 5.0f) % t->anim_total[1]) : 0]; + else + t = t->anim_frames[0][(t->anim_total[0] >= 2) ? ((int)(rsurface.shadertime * 5.0f) % t->anim_total[0]) : 0]; + } + texture->currentframe = t; + } + + // update currentskinframe to be a qw skin or animation frame + if (rsurface.ent_qwskin >= 0) + { + i = rsurface.ent_qwskin; + if (!r_qwskincache || r_qwskincache_size != cl.maxclients) + { + r_qwskincache_size = cl.maxclients; + if (r_qwskincache) + Mem_Free(r_qwskincache); + r_qwskincache = (r_qwskincache_t *)Mem_Alloc(r_main_mempool, sizeof(*r_qwskincache) * r_qwskincache_size); + } + if (strcmp(r_qwskincache[i].name, cl.scores[i].qw_skin)) + R_LoadQWSkin(&r_qwskincache[i], cl.scores[i].qw_skin); + t->currentskinframe = r_qwskincache[i].skinframe; + if (t->currentskinframe == NULL) + t->currentskinframe = t->skinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->skinframerate, t->numskinframes)]; + } + else if (t->numskinframes >= 2) + t->currentskinframe = t->skinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->skinframerate, t->numskinframes)]; + if (t->backgroundnumskinframes >= 2) + t->backgroundcurrentskinframe = t->backgroundskinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->backgroundskinframerate, t->backgroundnumskinframes)]; + + t->currentmaterialflags = t->basematerialflags; + t->currentalpha = rsurface.colormod[3]; + if (t->basematerialflags & MATERIALFLAG_WATERALPHA && (model->brush.supportwateralpha || r_novis.integer || r_trippy.integer)) + t->currentalpha *= r_wateralpha.value; + if(t->basematerialflags & MATERIALFLAG_WATERSHADER && r_fb.water.enabled && !r_refdef.view.isoverlay) + t->currentmaterialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; // we apply wateralpha later + if(!r_fb.water.enabled || r_refdef.view.isoverlay) + t->currentmaterialflags &= ~(MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA); + if (!(rsurface.ent_flags & RENDER_LIGHT)) + t->currentmaterialflags |= MATERIALFLAG_FULLBRIGHT; + else if (FAKELIGHT_ENABLED) + { + // no modellight if using fakelight for the map + } + else if ((rsurface.modeltexcoordlightmap2f == NULL || (rsurface.ent_flags & (RENDER_DYNAMICMODELLIGHT | RENDER_CUSTOMIZEDMODELLIGHT))) && !(t->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) + { + // pick a model lighting mode + if (VectorLength2(rsurface.modellight_diffuse) >= (1.0f / 256.0f)) + t->currentmaterialflags |= MATERIALFLAG_MODELLIGHT | MATERIALFLAG_MODELLIGHT_DIRECTIONAL; + else + t->currentmaterialflags |= MATERIALFLAG_MODELLIGHT; + } + if (rsurface.ent_flags & RENDER_ADDITIVE) + t->currentmaterialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else if (t->currentalpha < 1) + t->currentmaterialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + // LordHavoc: prevent bugs where code checks add or alpha at higher priority than customblend by clearing these flags + if (t->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) + t->currentmaterialflags &= ~(MATERIALFLAG_ADD | MATERIALFLAG_ALPHA); + if (rsurface.ent_flags & RENDER_DOUBLESIDED) + t->currentmaterialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_NOCULLFACE; + if (rsurface.ent_flags & (RENDER_NODEPTHTEST | RENDER_VIEWMODEL)) + t->currentmaterialflags |= MATERIALFLAG_SHORTDEPTHRANGE; + if (t->backgroundnumskinframes) + t->currentmaterialflags |= MATERIALFLAG_VERTEXTEXTUREBLEND; + if (t->currentmaterialflags & MATERIALFLAG_BLENDED) + { + if (t->currentmaterialflags & (MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER | MATERIALFLAG_CAMERA)) + t->currentmaterialflags &= ~MATERIALFLAG_BLENDED; + } + else + t->currentmaterialflags &= ~(MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER | MATERIALFLAG_CAMERA); + if (vid.allowalphatocoverage && r_transparent_alphatocoverage.integer >= 2 && ((t->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA | MATERIALFLAG_ADD | MATERIALFLAG_CUSTOMBLEND)) == (MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA))) + { + // promote alphablend to alphatocoverage (a type of alphatest) if antialiasing is on + t->currentmaterialflags = (t->currentmaterialflags & ~(MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA)) | MATERIALFLAG_ALPHATEST; + } + if ((t->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST)) == MATERIALFLAG_BLENDED && r_transparentdepthmasking.integer && !(t->basematerialflags & MATERIALFLAG_BLENDED)) + t->currentmaterialflags |= MATERIALFLAG_TRANSDEPTH; + + // there is no tcmod + if (t->currentmaterialflags & MATERIALFLAG_WATERSCROLL) + { + t->currenttexmatrix = r_waterscrollmatrix; + t->currentbackgroundtexmatrix = r_waterscrollmatrix; + } + else if (!(t->currentmaterialflags & MATERIALFLAG_CUSTOMSURFACE)) + { + Matrix4x4_CreateIdentity(&t->currenttexmatrix); + Matrix4x4_CreateIdentity(&t->currentbackgroundtexmatrix); + } + + for (i = 0, tcmod = t->tcmods;i < Q3MAXTCMODS && tcmod->tcmod;i++, tcmod++) + R_tcMod_ApplyToMatrix(&t->currenttexmatrix, tcmod, t->currentmaterialflags); + for (i = 0, tcmod = t->backgroundtcmods;i < Q3MAXTCMODS && tcmod->tcmod;i++, tcmod++) + R_tcMod_ApplyToMatrix(&t->currentbackgroundtexmatrix, tcmod, t->currentmaterialflags); + + t->colormapping = VectorLength2(rsurface.colormap_pantscolor) + VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f); + if (t->currentskinframe->qpixels) + R_SkinFrame_GenerateTexturesFromQPixels(t->currentskinframe, t->colormapping); + t->basetexture = (!t->colormapping && t->currentskinframe->merged) ? t->currentskinframe->merged : t->currentskinframe->base; + if (!t->basetexture) + t->basetexture = r_texture_notexture; + t->pantstexture = t->colormapping ? t->currentskinframe->pants : NULL; + t->shirttexture = t->colormapping ? t->currentskinframe->shirt : NULL; + t->nmaptexture = t->currentskinframe->nmap; + if (!t->nmaptexture) + t->nmaptexture = r_texture_blanknormalmap; + t->glosstexture = r_texture_black; + t->glowtexture = t->currentskinframe->glow; + t->fogtexture = t->currentskinframe->fog; + t->reflectmasktexture = t->currentskinframe->reflect; + if (t->backgroundnumskinframes) + { + t->backgroundbasetexture = (!t->colormapping && t->backgroundcurrentskinframe->merged) ? t->backgroundcurrentskinframe->merged : t->backgroundcurrentskinframe->base; + t->backgroundnmaptexture = t->backgroundcurrentskinframe->nmap; + t->backgroundglosstexture = r_texture_black; + t->backgroundglowtexture = t->backgroundcurrentskinframe->glow; + if (!t->backgroundnmaptexture) + t->backgroundnmaptexture = r_texture_blanknormalmap; + // make sure that if glow is going to be used, both textures are not NULL + if (!t->backgroundglowtexture && t->glowtexture) + t->backgroundglowtexture = r_texture_black; + if (!t->glowtexture && t->backgroundglowtexture) + t->glowtexture = r_texture_black; + } + else + { + t->backgroundbasetexture = r_texture_white; + t->backgroundnmaptexture = r_texture_blanknormalmap; + t->backgroundglosstexture = r_texture_black; + t->backgroundglowtexture = NULL; + } + t->specularpower = r_shadow_glossexponent.value; + // TODO: store reference values for these in the texture? + t->specularscale = 0; + if (r_shadow_gloss.integer > 0) + { + if (t->currentskinframe->gloss || (t->backgroundcurrentskinframe && t->backgroundcurrentskinframe->gloss)) + { + if (r_shadow_glossintensity.value > 0) + { + t->glosstexture = t->currentskinframe->gloss ? t->currentskinframe->gloss : r_texture_white; + t->backgroundglosstexture = (t->backgroundcurrentskinframe && t->backgroundcurrentskinframe->gloss) ? t->backgroundcurrentskinframe->gloss : r_texture_white; + t->specularscale = r_shadow_glossintensity.value; + } + } + else if (r_shadow_gloss.integer >= 2 && r_shadow_gloss2intensity.value > 0) + { + t->glosstexture = r_texture_white; + t->backgroundglosstexture = r_texture_white; + t->specularscale = r_shadow_gloss2intensity.value; + t->specularpower = r_shadow_gloss2exponent.value; + } + } + t->specularscale *= t->specularscalemod; + t->specularpower *= t->specularpowermod; + t->rtlightambient = 0; + + // lightmaps mode looks bad with dlights using actual texturing, so turn + // off the colormap and glossmap, but leave the normalmap on as it still + // accurately represents the shading involved + if (gl_lightmaps.integer) + { + t->basetexture = r_texture_grey128; + t->pantstexture = r_texture_black; + t->shirttexture = r_texture_black; + if (gl_lightmaps.integer < 2) + t->nmaptexture = r_texture_blanknormalmap; + t->glosstexture = r_texture_black; + t->glowtexture = NULL; + t->fogtexture = NULL; + t->reflectmasktexture = NULL; + t->backgroundbasetexture = NULL; + if (gl_lightmaps.integer < 2) + t->backgroundnmaptexture = r_texture_blanknormalmap; + t->backgroundglosstexture = r_texture_black; + t->backgroundglowtexture = NULL; + t->specularscale = 0; + t->currentmaterialflags = MATERIALFLAG_WALL | (t->currentmaterialflags & (MATERIALFLAG_NOCULLFACE | MATERIALFLAG_MODELLIGHT | MATERIALFLAG_MODELLIGHT_DIRECTIONAL | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SHORTDEPTHRANGE)); + } + + Vector4Set(t->lightmapcolor, rsurface.colormod[0], rsurface.colormod[1], rsurface.colormod[2], t->currentalpha); + VectorClear(t->dlightcolor); + t->currentnumlayers = 0; + if (t->currentmaterialflags & MATERIALFLAG_WALL) + { + int blendfunc1, blendfunc2; + qboolean depthmask; + if (t->currentmaterialflags & MATERIALFLAG_ADD) + { + blendfunc1 = GL_SRC_ALPHA; + blendfunc2 = GL_ONE; + } + else if (t->currentmaterialflags & MATERIALFLAG_ALPHA) + { + blendfunc1 = GL_SRC_ALPHA; + blendfunc2 = GL_ONE_MINUS_SRC_ALPHA; + } + else if (t->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) + { + blendfunc1 = t->customblendfunc[0]; + blendfunc2 = t->customblendfunc[1]; + } + else + { + blendfunc1 = GL_ONE; + blendfunc2 = GL_ZERO; + } + // don't colormod evilblend textures + if(!(R_BlendFuncFlags(blendfunc1, blendfunc2) & BLENDFUNC_ALLOWS_COLORMOD)) + VectorSet(t->lightmapcolor, 1, 1, 1); + depthmask = !(t->currentmaterialflags & MATERIALFLAG_BLENDED); + if (t->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) + { + // fullbright is not affected by r_refdef.lightmapintensity + R_Texture_AddLayer(t, depthmask, blendfunc1, blendfunc2, TEXTURELAYERTYPE_TEXTURE, t->basetexture, &t->currenttexmatrix, t->lightmapcolor[0], t->lightmapcolor[1], t->lightmapcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * t->lightmapcolor[0], rsurface.colormap_pantscolor[1] * t->lightmapcolor[1], rsurface.colormap_pantscolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * t->lightmapcolor[0], rsurface.colormap_shirtcolor[1] * t->lightmapcolor[1], rsurface.colormap_shirtcolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); + } + else + { + vec3_t ambientcolor; + float colorscale; + // set the color tint used for lights affecting this surface + VectorSet(t->dlightcolor, t->lightmapcolor[0] * t->lightmapcolor[3], t->lightmapcolor[1] * t->lightmapcolor[3], t->lightmapcolor[2] * t->lightmapcolor[3]); + colorscale = 2; + // q3bsp has no lightmap updates, so the lightstylevalue that + // would normally be baked into the lightmap must be + // applied to the color + // FIXME: r_glsl 1 rendering doesn't support overbright lightstyles with this (the default light style is not overbright) + if (model->type == mod_brushq3) + colorscale *= r_refdef.scene.rtlightstylevalue[0]; + colorscale *= r_refdef.lightmapintensity; + VectorScale(t->lightmapcolor, r_refdef.scene.ambient, ambientcolor); + VectorScale(t->lightmapcolor, colorscale, t->lightmapcolor); + // basic lit geometry + R_Texture_AddLayer(t, depthmask, blendfunc1, blendfunc2, TEXTURELAYERTYPE_LITTEXTURE, t->basetexture, &t->currenttexmatrix, t->lightmapcolor[0], t->lightmapcolor[1], t->lightmapcolor[2], t->lightmapcolor[3]); + // add pants/shirt if needed + if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_LITTEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * t->lightmapcolor[0], rsurface.colormap_pantscolor[1] * t->lightmapcolor[1], rsurface.colormap_pantscolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_LITTEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * t->lightmapcolor[0], rsurface.colormap_shirtcolor[1] * t->lightmapcolor[1], rsurface.colormap_shirtcolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); + // now add ambient passes if needed + if (VectorLength2(ambientcolor) >= (1.0f/1048576.0f)) + { + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->basetexture, &t->currenttexmatrix, ambientcolor[0], ambientcolor[1], ambientcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * ambientcolor[0], rsurface.colormap_pantscolor[1] * ambientcolor[1], rsurface.colormap_pantscolor[2] * ambientcolor[2], t->lightmapcolor[3]); + if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * ambientcolor[0], rsurface.colormap_shirtcolor[1] * ambientcolor[1], rsurface.colormap_shirtcolor[2] * ambientcolor[2], t->lightmapcolor[3]); + } + } + if (t->glowtexture != NULL && !gl_lightmaps.integer) + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->glowtexture, &t->currenttexmatrix, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2], t->lightmapcolor[3]); + if (r_refdef.fogenabled && !(t->currentmaterialflags & MATERIALFLAG_ADD)) + { + // if this is opaque use alpha blend which will darken the earlier + // passes cheaply. + // + // if this is an alpha blended material, all the earlier passes + // were darkened by fog already, so we only need to add the fog + // color ontop through the fog mask texture + // + // if this is an additive blended material, all the earlier passes + // were darkened by fog already, and we should not add fog color + // (because the background was not darkened, there is no fog color + // that was lost behind it). + R_Texture_AddLayer(t, false, GL_SRC_ALPHA, (t->currentmaterialflags & MATERIALFLAG_BLENDED) ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA, TEXTURELAYERTYPE_FOG, t->fogtexture, &t->currenttexmatrix, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2], t->lightmapcolor[3]); + } + } + + return t->currentframe; +} + +rsurfacestate_t rsurface; + +void RSurf_ActiveWorldEntity(void) +{ + dp_model_t *model = r_refdef.scene.worldmodel; + //if (rsurface.entity == r_refdef.scene.worldentity) + // return; + rsurface.entity = r_refdef.scene.worldentity; + rsurface.skeleton = NULL; + memset(rsurface.userwavefunc_param, 0, sizeof(rsurface.userwavefunc_param)); + rsurface.ent_skinnum = 0; + rsurface.ent_qwskin = -1; + rsurface.ent_flags = r_refdef.scene.worldentity->flags; + rsurface.shadertime = r_refdef.scene.time; + rsurface.matrix = identitymatrix; + rsurface.inversematrix = identitymatrix; + rsurface.matrixscale = 1; + rsurface.inversematrixscale = 1; + R_EntityMatrix(&identitymatrix); + VectorCopy(r_refdef.view.origin, rsurface.localvieworigin); + Vector4Copy(r_refdef.fogplane, rsurface.fogplane); + rsurface.fograngerecip = r_refdef.fograngerecip; + rsurface.fogheightfade = r_refdef.fogheightfade; + rsurface.fogplaneviewdist = r_refdef.fogplaneviewdist; + rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; + VectorSet(rsurface.modellight_ambient, 0, 0, 0); + VectorSet(rsurface.modellight_diffuse, 0, 0, 0); + VectorSet(rsurface.modellight_lightdir, 0, 0, 1); + VectorSet(rsurface.colormap_pantscolor, 0, 0, 0); + VectorSet(rsurface.colormap_shirtcolor, 0, 0, 0); + VectorSet(rsurface.colormod, r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale); + rsurface.colormod[3] = 1; + VectorSet(rsurface.glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value); + memset(rsurface.frameblend, 0, sizeof(rsurface.frameblend)); + rsurface.frameblend[0].lerp = 1; + rsurface.ent_alttextures = false; + rsurface.basepolygonfactor = r_refdef.polygonfactor; + rsurface.basepolygonoffset = r_refdef.polygonoffset; + rsurface.entityskeletaltransform3x4 = NULL; + rsurface.entityskeletaltransform3x4buffer = NULL; + rsurface.entityskeletaltransform3x4offset = 0; + rsurface.entityskeletaltransform3x4size = 0;; + rsurface.entityskeletalnumtransforms = 0; + rsurface.modelvertex3f = model->surfmesh.data_vertex3f; + rsurface.modelvertex3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelvertex3f_bufferoffset = model->surfmesh.vbooffset_vertex3f; + rsurface.modelsvector3f = model->surfmesh.data_svector3f; + rsurface.modelsvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelsvector3f_bufferoffset = model->surfmesh.vbooffset_svector3f; + rsurface.modeltvector3f = model->surfmesh.data_tvector3f; + rsurface.modeltvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltvector3f_bufferoffset = model->surfmesh.vbooffset_tvector3f; + rsurface.modelnormal3f = model->surfmesh.data_normal3f; + rsurface.modelnormal3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelnormal3f_bufferoffset = model->surfmesh.vbooffset_normal3f; + rsurface.modellightmapcolor4f = model->surfmesh.data_lightmapcolor4f; + rsurface.modellightmapcolor4f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modellightmapcolor4f_bufferoffset = model->surfmesh.vbooffset_lightmapcolor4f; + rsurface.modeltexcoordtexture2f = model->surfmesh.data_texcoordtexture2f; + rsurface.modeltexcoordtexture2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltexcoordtexture2f_bufferoffset = model->surfmesh.vbooffset_texcoordtexture2f; + rsurface.modeltexcoordlightmap2f = model->surfmesh.data_texcoordlightmap2f; + rsurface.modeltexcoordlightmap2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltexcoordlightmap2f_bufferoffset = model->surfmesh.vbooffset_texcoordlightmap2f; + rsurface.modelskeletalindex4ub = model->surfmesh.data_skeletalindex4ub; + rsurface.modelskeletalindex4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelskeletalindex4ub_bufferoffset = model->surfmesh.vbooffset_skeletalindex4ub; + rsurface.modelskeletalweight4ub = model->surfmesh.data_skeletalweight4ub; + rsurface.modelskeletalweight4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelskeletalweight4ub_bufferoffset = model->surfmesh.vbooffset_skeletalweight4ub; + rsurface.modelelement3i = model->surfmesh.data_element3i; + rsurface.modelelement3i_indexbuffer = model->surfmesh.data_element3i_indexbuffer; + rsurface.modelelement3i_bufferoffset = model->surfmesh.data_element3i_bufferoffset; + rsurface.modelelement3s = model->surfmesh.data_element3s; + rsurface.modelelement3s_indexbuffer = model->surfmesh.data_element3s_indexbuffer; + rsurface.modelelement3s_bufferoffset = model->surfmesh.data_element3s_bufferoffset; + rsurface.modellightmapoffsets = model->surfmesh.data_lightmapoffsets; + rsurface.modelnumvertices = model->surfmesh.num_vertices; + rsurface.modelnumtriangles = model->surfmesh.num_triangles; + rsurface.modelsurfaces = model->data_surfaces; + rsurface.modelvertexmesh = model->surfmesh.data_vertexmesh; + rsurface.modelvertexmesh_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelvertexmesh_bufferoffset = model->surfmesh.vbooffset_vertex3f; + rsurface.modelgeneratedvertex = false; + rsurface.batchgeneratedvertex = false; + rsurface.batchfirstvertex = 0; + rsurface.batchnumvertices = 0; + rsurface.batchfirsttriangle = 0; + rsurface.batchnumtriangles = 0; + rsurface.batchvertex3f = NULL; + rsurface.batchvertex3f_vertexbuffer = NULL; + rsurface.batchvertex3f_bufferoffset = 0; + rsurface.batchsvector3f = NULL; + rsurface.batchsvector3f_vertexbuffer = NULL; + rsurface.batchsvector3f_bufferoffset = 0; + rsurface.batchtvector3f = NULL; + rsurface.batchtvector3f_vertexbuffer = NULL; + rsurface.batchtvector3f_bufferoffset = 0; + rsurface.batchnormal3f = NULL; + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + rsurface.batchlightmapcolor4f = NULL; + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + rsurface.batchtexcoordtexture2f = NULL; + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + rsurface.batchtexcoordlightmap2f = NULL; + rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; + rsurface.batchtexcoordlightmap2f_bufferoffset = 0; + rsurface.batchskeletalindex4ub = NULL; + rsurface.batchskeletalindex4ub_vertexbuffer = NULL; + rsurface.batchskeletalindex4ub_bufferoffset = 0; + rsurface.batchskeletalweight4ub = NULL; + rsurface.batchskeletalweight4ub_vertexbuffer = NULL; + rsurface.batchskeletalweight4ub_bufferoffset = 0; + rsurface.batchvertexmesh = NULL; + rsurface.batchvertexmesh_vertexbuffer = NULL; + rsurface.batchvertexmesh_bufferoffset = 0; + rsurface.batchelement3i = NULL; + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = NULL; + rsurface.passcolor4f_bufferoffset = 0; + rsurface.forcecurrenttextureupdate = false; +} + +void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, qboolean wanttangents, qboolean prepass) +{ + dp_model_t *model = ent->model; + //if (rsurface.entity == ent && (!model->surfmesh.isanimated || (!wantnormals && !wanttangents))) + // return; + rsurface.entity = (entity_render_t *)ent; + rsurface.skeleton = ent->skeleton; + memcpy(rsurface.userwavefunc_param, ent->userwavefunc_param, sizeof(rsurface.userwavefunc_param)); + rsurface.ent_skinnum = ent->skinnum; + rsurface.ent_qwskin = (ent->entitynumber <= cl.maxclients && ent->entitynumber >= 1 && cls.protocol == PROTOCOL_QUAKEWORLD && cl.scores[ent->entitynumber - 1].qw_skin[0] && !strcmp(ent->model->name, "progs/player.mdl")) ? (ent->entitynumber - 1) : -1; + rsurface.ent_flags = ent->flags; + rsurface.shadertime = r_refdef.scene.time - ent->shadertime; + rsurface.matrix = ent->matrix; + rsurface.inversematrix = ent->inversematrix; + rsurface.matrixscale = Matrix4x4_ScaleFromMatrix(&rsurface.matrix); + rsurface.inversematrixscale = 1.0f / rsurface.matrixscale; + R_EntityMatrix(&rsurface.matrix); + Matrix4x4_Transform(&rsurface.inversematrix, r_refdef.view.origin, rsurface.localvieworigin); + Matrix4x4_TransformStandardPlane(&rsurface.inversematrix, r_refdef.fogplane[0], r_refdef.fogplane[1], r_refdef.fogplane[2], r_refdef.fogplane[3], rsurface.fogplane); + rsurface.fogplaneviewdist *= rsurface.inversematrixscale; + rsurface.fograngerecip = r_refdef.fograngerecip * rsurface.matrixscale; + rsurface.fogheightfade = r_refdef.fogheightfade * rsurface.matrixscale; + rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; + VectorCopy(ent->modellight_ambient, rsurface.modellight_ambient); + VectorCopy(ent->modellight_diffuse, rsurface.modellight_diffuse); + VectorCopy(ent->modellight_lightdir, rsurface.modellight_lightdir); + VectorCopy(ent->colormap_pantscolor, rsurface.colormap_pantscolor); + VectorCopy(ent->colormap_shirtcolor, rsurface.colormap_shirtcolor); + VectorScale(ent->colormod, r_refdef.view.colorscale, rsurface.colormod); + rsurface.colormod[3] = ent->alpha; + VectorScale(ent->glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, rsurface.glowmod); + memcpy(rsurface.frameblend, ent->frameblend, sizeof(ent->frameblend)); + rsurface.ent_alttextures = ent->framegroupblend[0].frame != 0; + rsurface.basepolygonfactor = r_refdef.polygonfactor; + rsurface.basepolygonoffset = r_refdef.polygonoffset; + if (ent->model->brush.submodel && !prepass) + { + rsurface.basepolygonfactor += r_polygonoffset_submodel_factor.value; + rsurface.basepolygonoffset += r_polygonoffset_submodel_offset.value; + } + // if the animcache code decided it should use the shader path, skip the deform step + rsurface.entityskeletaltransform3x4 = ent->animcache_skeletaltransform3x4; + rsurface.entityskeletaltransform3x4buffer = ent->animcache_skeletaltransform3x4buffer; + rsurface.entityskeletaltransform3x4offset = ent->animcache_skeletaltransform3x4offset; + rsurface.entityskeletaltransform3x4size = ent->animcache_skeletaltransform3x4size; + rsurface.entityskeletalnumtransforms = rsurface.entityskeletaltransform3x4 ? model->num_bones : 0; + if (model->surfmesh.isanimated && model->AnimateVertices && !rsurface.entityskeletaltransform3x4) + { + if (ent->animcache_vertex3f) + { + r_refdef.stats[r_stat_batch_entitycache_count]++; + r_refdef.stats[r_stat_batch_entitycache_surfaces] += model->num_surfaces; + r_refdef.stats[r_stat_batch_entitycache_vertices] += model->surfmesh.num_vertices; + r_refdef.stats[r_stat_batch_entitycache_triangles] += model->surfmesh.num_triangles; + rsurface.modelvertex3f = ent->animcache_vertex3f; + rsurface.modelvertex3f_vertexbuffer = ent->animcache_vertex3f_vertexbuffer; + rsurface.modelvertex3f_bufferoffset = ent->animcache_vertex3f_bufferoffset; + rsurface.modelsvector3f = wanttangents ? ent->animcache_svector3f : NULL; + rsurface.modelsvector3f_vertexbuffer = wanttangents ? ent->animcache_svector3f_vertexbuffer : NULL; + rsurface.modelsvector3f_bufferoffset = wanttangents ? ent->animcache_svector3f_bufferoffset : 0; + rsurface.modeltvector3f = wanttangents ? ent->animcache_tvector3f : NULL; + rsurface.modeltvector3f_vertexbuffer = wanttangents ? ent->animcache_tvector3f_vertexbuffer : NULL; + rsurface.modeltvector3f_bufferoffset = wanttangents ? ent->animcache_tvector3f_bufferoffset : 0; + rsurface.modelnormal3f = wantnormals ? ent->animcache_normal3f : NULL; + rsurface.modelnormal3f_vertexbuffer = wantnormals ? ent->animcache_normal3f_vertexbuffer : NULL; + rsurface.modelnormal3f_bufferoffset = wantnormals ? ent->animcache_normal3f_bufferoffset : 0; + rsurface.modelvertexmesh = ent->animcache_vertexmesh; + rsurface.modelvertexmesh_vertexbuffer = ent->animcache_vertexmesh_vertexbuffer; + rsurface.modelvertexmesh_bufferoffset = ent->animcache_vertexmesh_bufferoffset; + } + else if (wanttangents) + { + r_refdef.stats[r_stat_batch_entityanimate_count]++; + r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces; + r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices; + r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles; + rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modelsvector3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modeltvector3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modelnormal3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, rsurface.modelnormal3f, rsurface.modelsvector3f, rsurface.modeltvector3f); + rsurface.modelvertexmesh = NULL; + rsurface.modelvertexmesh_vertexbuffer = NULL; + rsurface.modelvertexmesh_bufferoffset = 0; + rsurface.modelvertex3f_vertexbuffer = NULL; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelvertex3f_vertexbuffer = 0; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelsvector3f_vertexbuffer = 0; + rsurface.modelsvector3f_bufferoffset = 0; + rsurface.modeltvector3f_vertexbuffer = 0; + rsurface.modeltvector3f_bufferoffset = 0; + rsurface.modelnormal3f_vertexbuffer = 0; + rsurface.modelnormal3f_bufferoffset = 0; + } + else if (wantnormals) + { + r_refdef.stats[r_stat_batch_entityanimate_count]++; + r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces; + r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices; + r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles; + rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modelsvector3f = NULL; + rsurface.modeltvector3f = NULL; + rsurface.modelnormal3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, rsurface.modelnormal3f, NULL, NULL); + rsurface.modelvertexmesh = NULL; + rsurface.modelvertexmesh_vertexbuffer = NULL; + rsurface.modelvertexmesh_bufferoffset = 0; + rsurface.modelvertex3f_vertexbuffer = NULL; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelvertex3f_vertexbuffer = 0; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelsvector3f_vertexbuffer = 0; + rsurface.modelsvector3f_bufferoffset = 0; + rsurface.modeltvector3f_vertexbuffer = 0; + rsurface.modeltvector3f_bufferoffset = 0; + rsurface.modelnormal3f_vertexbuffer = 0; + rsurface.modelnormal3f_bufferoffset = 0; + } + else + { + r_refdef.stats[r_stat_batch_entityanimate_count]++; + r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces; + r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices; + r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles; + rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); + rsurface.modelsvector3f = NULL; + rsurface.modeltvector3f = NULL; + rsurface.modelnormal3f = NULL; + model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, NULL, NULL, NULL); + rsurface.modelvertexmesh = NULL; + rsurface.modelvertexmesh_vertexbuffer = NULL; + rsurface.modelvertexmesh_bufferoffset = 0; + rsurface.modelvertex3f_vertexbuffer = NULL; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelvertex3f_vertexbuffer = 0; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelsvector3f_vertexbuffer = 0; + rsurface.modelsvector3f_bufferoffset = 0; + rsurface.modeltvector3f_vertexbuffer = 0; + rsurface.modeltvector3f_bufferoffset = 0; + rsurface.modelnormal3f_vertexbuffer = 0; + rsurface.modelnormal3f_bufferoffset = 0; + } + rsurface.modelgeneratedvertex = true; + } + else + { + if (rsurface.entityskeletaltransform3x4) + { + r_refdef.stats[r_stat_batch_entityskeletal_count]++; + r_refdef.stats[r_stat_batch_entityskeletal_surfaces] += model->num_surfaces; + r_refdef.stats[r_stat_batch_entityskeletal_vertices] += model->surfmesh.num_vertices; + r_refdef.stats[r_stat_batch_entityskeletal_triangles] += model->surfmesh.num_triangles; + } + else + { + r_refdef.stats[r_stat_batch_entitystatic_count]++; + r_refdef.stats[r_stat_batch_entitystatic_surfaces] += model->num_surfaces; + r_refdef.stats[r_stat_batch_entitystatic_vertices] += model->surfmesh.num_vertices; + r_refdef.stats[r_stat_batch_entitystatic_triangles] += model->surfmesh.num_triangles; + } + rsurface.modelvertex3f = model->surfmesh.data_vertex3f; + rsurface.modelvertex3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelvertex3f_bufferoffset = model->surfmesh.vbooffset_vertex3f; + rsurface.modelsvector3f = model->surfmesh.data_svector3f; + rsurface.modelsvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelsvector3f_bufferoffset = model->surfmesh.vbooffset_svector3f; + rsurface.modeltvector3f = model->surfmesh.data_tvector3f; + rsurface.modeltvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltvector3f_bufferoffset = model->surfmesh.vbooffset_tvector3f; + rsurface.modelnormal3f = model->surfmesh.data_normal3f; + rsurface.modelnormal3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelnormal3f_bufferoffset = model->surfmesh.vbooffset_normal3f; + rsurface.modelvertexmesh = model->surfmesh.data_vertexmesh; + rsurface.modelvertexmesh_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelvertexmesh_bufferoffset = model->surfmesh.vbooffset_vertex3f; + rsurface.modelgeneratedvertex = false; + } + rsurface.modellightmapcolor4f = model->surfmesh.data_lightmapcolor4f; + rsurface.modellightmapcolor4f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modellightmapcolor4f_bufferoffset = model->surfmesh.vbooffset_lightmapcolor4f; + rsurface.modeltexcoordtexture2f = model->surfmesh.data_texcoordtexture2f; + rsurface.modeltexcoordtexture2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltexcoordtexture2f_bufferoffset = model->surfmesh.vbooffset_texcoordtexture2f; + rsurface.modeltexcoordlightmap2f = model->surfmesh.data_texcoordlightmap2f; + rsurface.modeltexcoordlightmap2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modeltexcoordlightmap2f_bufferoffset = model->surfmesh.vbooffset_texcoordlightmap2f; + rsurface.modelskeletalindex4ub = model->surfmesh.data_skeletalindex4ub; + rsurface.modelskeletalindex4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelskeletalindex4ub_bufferoffset = model->surfmesh.vbooffset_skeletalindex4ub; + rsurface.modelskeletalweight4ub = model->surfmesh.data_skeletalweight4ub; + rsurface.modelskeletalweight4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer; + rsurface.modelskeletalweight4ub_bufferoffset = model->surfmesh.vbooffset_skeletalweight4ub; + rsurface.modelelement3i = model->surfmesh.data_element3i; + rsurface.modelelement3i_indexbuffer = model->surfmesh.data_element3i_indexbuffer; + rsurface.modelelement3i_bufferoffset = model->surfmesh.data_element3i_bufferoffset; + rsurface.modelelement3s = model->surfmesh.data_element3s; + rsurface.modelelement3s_indexbuffer = model->surfmesh.data_element3s_indexbuffer; + rsurface.modelelement3s_bufferoffset = model->surfmesh.data_element3s_bufferoffset; + rsurface.modellightmapoffsets = model->surfmesh.data_lightmapoffsets; + rsurface.modelnumvertices = model->surfmesh.num_vertices; + rsurface.modelnumtriangles = model->surfmesh.num_triangles; + rsurface.modelsurfaces = model->data_surfaces; + rsurface.batchgeneratedvertex = false; + rsurface.batchfirstvertex = 0; + rsurface.batchnumvertices = 0; + rsurface.batchfirsttriangle = 0; + rsurface.batchnumtriangles = 0; + rsurface.batchvertex3f = NULL; + rsurface.batchvertex3f_vertexbuffer = NULL; + rsurface.batchvertex3f_bufferoffset = 0; + rsurface.batchsvector3f = NULL; + rsurface.batchsvector3f_vertexbuffer = NULL; + rsurface.batchsvector3f_bufferoffset = 0; + rsurface.batchtvector3f = NULL; + rsurface.batchtvector3f_vertexbuffer = NULL; + rsurface.batchtvector3f_bufferoffset = 0; + rsurface.batchnormal3f = NULL; + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + rsurface.batchlightmapcolor4f = NULL; + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + rsurface.batchtexcoordtexture2f = NULL; + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + rsurface.batchtexcoordlightmap2f = NULL; + rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; + rsurface.batchtexcoordlightmap2f_bufferoffset = 0; + rsurface.batchskeletalindex4ub = NULL; + rsurface.batchskeletalindex4ub_vertexbuffer = NULL; + rsurface.batchskeletalindex4ub_bufferoffset = 0; + rsurface.batchskeletalweight4ub = NULL; + rsurface.batchskeletalweight4ub_vertexbuffer = NULL; + rsurface.batchskeletalweight4ub_bufferoffset = 0; + rsurface.batchvertexmesh = NULL; + rsurface.batchvertexmesh_vertexbuffer = NULL; + rsurface.batchvertexmesh_bufferoffset = 0; + rsurface.batchelement3i = NULL; + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = NULL; + rsurface.passcolor4f_bufferoffset = 0; + rsurface.forcecurrenttextureupdate = false; +} + +void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, int entflags, double shadertime, float r, float g, float b, float a, int numvertices, const float *vertex3f, const float *texcoord2f, const float *normal3f, const float *svector3f, const float *tvector3f, const float *color4f, int numtriangles, const int *element3i, const unsigned short *element3s, qboolean wantnormals, qboolean wanttangents) +{ + rsurface.entity = r_refdef.scene.worldentity; + rsurface.skeleton = NULL; + rsurface.ent_skinnum = 0; + rsurface.ent_qwskin = -1; + rsurface.ent_flags = entflags; + rsurface.shadertime = r_refdef.scene.time - shadertime; + rsurface.modelnumvertices = numvertices; + rsurface.modelnumtriangles = numtriangles; + rsurface.matrix = *matrix; + rsurface.inversematrix = *inversematrix; + rsurface.matrixscale = Matrix4x4_ScaleFromMatrix(&rsurface.matrix); + rsurface.inversematrixscale = 1.0f / rsurface.matrixscale; + R_EntityMatrix(&rsurface.matrix); + Matrix4x4_Transform(&rsurface.inversematrix, r_refdef.view.origin, rsurface.localvieworigin); + Matrix4x4_TransformStandardPlane(&rsurface.inversematrix, r_refdef.fogplane[0], r_refdef.fogplane[1], r_refdef.fogplane[2], r_refdef.fogplane[3], rsurface.fogplane); + rsurface.fogplaneviewdist *= rsurface.inversematrixscale; + rsurface.fograngerecip = r_refdef.fograngerecip * rsurface.matrixscale; + rsurface.fogheightfade = r_refdef.fogheightfade * rsurface.matrixscale; + rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; + VectorSet(rsurface.modellight_ambient, 0, 0, 0); + VectorSet(rsurface.modellight_diffuse, 0, 0, 0); + VectorSet(rsurface.modellight_lightdir, 0, 0, 1); + VectorSet(rsurface.colormap_pantscolor, 0, 0, 0); + VectorSet(rsurface.colormap_shirtcolor, 0, 0, 0); + Vector4Set(rsurface.colormod, r * r_refdef.view.colorscale, g * r_refdef.view.colorscale, b * r_refdef.view.colorscale, a); + VectorSet(rsurface.glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value); + memset(rsurface.frameblend, 0, sizeof(rsurface.frameblend)); + rsurface.frameblend[0].lerp = 1; + rsurface.ent_alttextures = false; + rsurface.basepolygonfactor = r_refdef.polygonfactor; + rsurface.basepolygonoffset = r_refdef.polygonoffset; + rsurface.entityskeletaltransform3x4 = NULL; + rsurface.entityskeletaltransform3x4buffer = NULL; + rsurface.entityskeletaltransform3x4offset = 0; + rsurface.entityskeletaltransform3x4size = 0; + rsurface.entityskeletalnumtransforms = 0; + r_refdef.stats[r_stat_batch_entitycustom_count]++; + r_refdef.stats[r_stat_batch_entitycustom_surfaces] += 1; + r_refdef.stats[r_stat_batch_entitycustom_vertices] += rsurface.modelnumvertices; + r_refdef.stats[r_stat_batch_entitycustom_triangles] += rsurface.modelnumtriangles; + if (wanttangents) + { + rsurface.modelvertex3f = (float *)vertex3f; + rsurface.modelsvector3f = svector3f ? (float *)svector3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + rsurface.modeltvector3f = tvector3f ? (float *)tvector3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + rsurface.modelnormal3f = normal3f ? (float *)normal3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + } + else if (wantnormals) + { + rsurface.modelvertex3f = (float *)vertex3f; + rsurface.modelsvector3f = NULL; + rsurface.modeltvector3f = NULL; + rsurface.modelnormal3f = normal3f ? (float *)normal3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + } + else + { + rsurface.modelvertex3f = (float *)vertex3f; + rsurface.modelsvector3f = NULL; + rsurface.modeltvector3f = NULL; + rsurface.modelnormal3f = NULL; + } + rsurface.modelvertexmesh = NULL; + rsurface.modelvertexmesh_vertexbuffer = NULL; + rsurface.modelvertexmesh_bufferoffset = 0; + rsurface.modelvertex3f_vertexbuffer = 0; + rsurface.modelvertex3f_bufferoffset = 0; + rsurface.modelsvector3f_vertexbuffer = 0; + rsurface.modelsvector3f_bufferoffset = 0; + rsurface.modeltvector3f_vertexbuffer = 0; + rsurface.modeltvector3f_bufferoffset = 0; + rsurface.modelnormal3f_vertexbuffer = 0; + rsurface.modelnormal3f_bufferoffset = 0; + rsurface.modelgeneratedvertex = true; + rsurface.modellightmapcolor4f = (float *)color4f; + rsurface.modellightmapcolor4f_vertexbuffer = 0; + rsurface.modellightmapcolor4f_bufferoffset = 0; + rsurface.modeltexcoordtexture2f = (float *)texcoord2f; + rsurface.modeltexcoordtexture2f_vertexbuffer = 0; + rsurface.modeltexcoordtexture2f_bufferoffset = 0; + rsurface.modeltexcoordlightmap2f = NULL; + rsurface.modeltexcoordlightmap2f_vertexbuffer = 0; + rsurface.modeltexcoordlightmap2f_bufferoffset = 0; + rsurface.modelskeletalindex4ub = NULL; + rsurface.modelskeletalindex4ub_vertexbuffer = NULL; + rsurface.modelskeletalindex4ub_bufferoffset = 0; + rsurface.modelskeletalweight4ub = NULL; + rsurface.modelskeletalweight4ub_vertexbuffer = NULL; + rsurface.modelskeletalweight4ub_bufferoffset = 0; + rsurface.modelelement3i = (int *)element3i; + rsurface.modelelement3i_indexbuffer = NULL; + rsurface.modelelement3i_bufferoffset = 0; + rsurface.modelelement3s = (unsigned short *)element3s; + rsurface.modelelement3s_indexbuffer = NULL; + rsurface.modelelement3s_bufferoffset = 0; + rsurface.modellightmapoffsets = NULL; + rsurface.modelsurfaces = NULL; + rsurface.batchgeneratedvertex = false; + rsurface.batchfirstvertex = 0; + rsurface.batchnumvertices = 0; + rsurface.batchfirsttriangle = 0; + rsurface.batchnumtriangles = 0; + rsurface.batchvertex3f = NULL; + rsurface.batchvertex3f_vertexbuffer = NULL; + rsurface.batchvertex3f_bufferoffset = 0; + rsurface.batchsvector3f = NULL; + rsurface.batchsvector3f_vertexbuffer = NULL; + rsurface.batchsvector3f_bufferoffset = 0; + rsurface.batchtvector3f = NULL; + rsurface.batchtvector3f_vertexbuffer = NULL; + rsurface.batchtvector3f_bufferoffset = 0; + rsurface.batchnormal3f = NULL; + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + rsurface.batchlightmapcolor4f = NULL; + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + rsurface.batchtexcoordtexture2f = NULL; + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + rsurface.batchtexcoordlightmap2f = NULL; + rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; + rsurface.batchtexcoordlightmap2f_bufferoffset = 0; + rsurface.batchskeletalindex4ub = NULL; + rsurface.batchskeletalindex4ub_vertexbuffer = NULL; + rsurface.batchskeletalindex4ub_bufferoffset = 0; + rsurface.batchskeletalweight4ub = NULL; + rsurface.batchskeletalweight4ub_vertexbuffer = NULL; + rsurface.batchskeletalweight4ub_bufferoffset = 0; + rsurface.batchvertexmesh = NULL; + rsurface.batchvertexmesh_vertexbuffer = NULL; + rsurface.batchvertexmesh_bufferoffset = 0; + rsurface.batchelement3i = NULL; + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = NULL; + rsurface.passcolor4f_bufferoffset = 0; + rsurface.forcecurrenttextureupdate = true; + + if (rsurface.modelnumvertices && rsurface.modelelement3i) + { + if ((wantnormals || wanttangents) && !normal3f) + { + rsurface.modelnormal3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + Mod_BuildNormals(0, rsurface.modelnumvertices, rsurface.modelnumtriangles, rsurface.modelvertex3f, rsurface.modelelement3i, rsurface.modelnormal3f, r_smoothnormals_areaweighting.integer != 0); + } + if (wanttangents && !svector3f) + { + rsurface.modelsvector3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + rsurface.modeltvector3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); + Mod_BuildTextureVectorsFromNormals(0, rsurface.modelnumvertices, rsurface.modelnumtriangles, rsurface.modelvertex3f, rsurface.modeltexcoordtexture2f, rsurface.modelnormal3f, rsurface.modelelement3i, rsurface.modelsvector3f, rsurface.modeltvector3f, r_smoothnormals_areaweighting.integer != 0); + } + } +} + +float RSurf_FogPoint(const float *v) +{ + // this code is identical to the USEFOGINSIDE/USEFOGOUTSIDE code in the shader + float FogPlaneViewDist = r_refdef.fogplaneviewdist; + float FogPlaneVertexDist = DotProduct(r_refdef.fogplane, v) + r_refdef.fogplane[3]; + float FogHeightFade = r_refdef.fogheightfade; + float fogfrac; + unsigned int fogmasktableindex; + if (r_refdef.fogplaneviewabove) + fogfrac = min(0.0f, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0f, min(0.0f, FogPlaneVertexDist) * FogHeightFade); + else + fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0f, FogPlaneVertexDist)) * min(1.0f, (min(0.0f, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade); + fogmasktableindex = (unsigned int)(VectorDistance(r_refdef.view.origin, v) * fogfrac * r_refdef.fogmasktabledistmultiplier); + return r_refdef.fogmasktable[min(fogmasktableindex, FOGMASKTABLEWIDTH - 1)]; +} + +float RSurf_FogVertex(const float *v) +{ + // this code is identical to the USEFOGINSIDE/USEFOGOUTSIDE code in the shader + float FogPlaneViewDist = rsurface.fogplaneviewdist; + float FogPlaneVertexDist = DotProduct(rsurface.fogplane, v) + rsurface.fogplane[3]; + float FogHeightFade = rsurface.fogheightfade; + float fogfrac; + unsigned int fogmasktableindex; + if (r_refdef.fogplaneviewabove) + fogfrac = min(0.0f, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0f, min(0.0f, FogPlaneVertexDist) * FogHeightFade); + else + fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0f, FogPlaneVertexDist)) * min(1.0f, (min(0.0f, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade); + fogmasktableindex = (unsigned int)(VectorDistance(rsurface.localvieworigin, v) * fogfrac * rsurface.fogmasktabledistmultiplier); + return r_refdef.fogmasktable[min(fogmasktableindex, FOGMASKTABLEWIDTH - 1)]; +} + +static void RSurf_RenumberElements(const int *inelement3i, int *outelement3i, int numelements, int adjust) +{ + int i; + for (i = 0;i < numelements;i++) + outelement3i[i] = inelement3i[i] + adjust; +} + +static const int quadedges[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}}; +extern cvar_t gl_vbo; +void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + int deformindex; + int firsttriangle; + int numtriangles; + int firstvertex; + int endvertex; + int numvertices; + int surfacefirsttriangle; + int surfacenumtriangles; + int surfacefirstvertex; + int surfaceendvertex; + int surfacenumvertices; + int batchnumsurfaces = texturenumsurfaces; + int batchnumvertices; + int batchnumtriangles; + int needsupdate; + int i, j; + qboolean gaps; + qboolean dynamicvertex; + float amplitude; + float animpos; + float scale; + float center[3], forward[3], right[3], up[3], v[3], newforward[3], newright[3], newup[3]; + float waveparms[4]; + unsigned char *ub; + q3shaderinfo_deform_t *deform; + const msurface_t *surface, *firstsurface; + r_vertexmesh_t *vertexmesh; + if (!texturenumsurfaces) + return; + // find vertex range of this surface batch + gaps = false; + firstsurface = texturesurfacelist[0]; + firsttriangle = firstsurface->num_firsttriangle; + batchnumvertices = 0; + batchnumtriangles = 0; + firstvertex = endvertex = firstsurface->num_firstvertex; + for (i = 0;i < texturenumsurfaces;i++) + { + surface = texturesurfacelist[i]; + if (surface != firstsurface + i) + gaps = true; + surfacefirstvertex = surface->num_firstvertex; + surfaceendvertex = surfacefirstvertex + surface->num_vertices; + surfacenumvertices = surface->num_vertices; + surfacenumtriangles = surface->num_triangles; + if (firstvertex > surfacefirstvertex) + firstvertex = surfacefirstvertex; + if (endvertex < surfaceendvertex) + endvertex = surfaceendvertex; + batchnumvertices += surfacenumvertices; + batchnumtriangles += surfacenumtriangles; + } + + r_refdef.stats[r_stat_batch_batches]++; + if (gaps) + r_refdef.stats[r_stat_batch_withgaps]++; + r_refdef.stats[r_stat_batch_surfaces] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_vertices] += batchnumvertices; + r_refdef.stats[r_stat_batch_triangles] += batchnumtriangles; + + // we now know the vertex range used, and if there are any gaps in it + rsurface.batchfirstvertex = firstvertex; + rsurface.batchnumvertices = endvertex - firstvertex; + rsurface.batchfirsttriangle = firsttriangle; + rsurface.batchnumtriangles = batchnumtriangles; + + // this variable holds flags for which properties have been updated that + // may require regenerating vertexmesh array... + needsupdate = 0; + + // check if any dynamic vertex processing must occur + dynamicvertex = false; + + // a cvar to force the dynamic vertex path to be taken, for debugging + if (r_batch_debugdynamicvertexpath.integer) + { + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_cvar] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_cvar] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_cvar] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_cvar] += batchnumtriangles; + } + dynamicvertex = true; + } + + // if there is a chance of animated vertex colors, it's a dynamic batch + if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_ARRAY_VERTEXCOLOR)) && texturesurfacelist[0]->lightmapinfo) + { + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_lightmapvertex] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_lightmapvertex] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_lightmapvertex] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_lightmapvertex] += batchnumtriangles; + } + dynamicvertex = true; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEXCOLOR; + } + + for (deformindex = 0, deform = rsurface.texture->deforms;deformindex < Q3MAXDEFORMS && deform->deform && r_deformvertexes.integer;deformindex++, deform++) + { + switch (deform->deform) + { + default: + case Q3DEFORM_PROJECTIONSHADOW: + case Q3DEFORM_TEXT0: + case Q3DEFORM_TEXT1: + case Q3DEFORM_TEXT2: + case Q3DEFORM_TEXT3: + case Q3DEFORM_TEXT4: + case Q3DEFORM_TEXT5: + case Q3DEFORM_TEXT6: + case Q3DEFORM_TEXT7: + case Q3DEFORM_NONE: + break; + case Q3DEFORM_AUTOSPRITE: + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_autosprite] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_ARRAY_TEXCOORD; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_AUTOSPRITE2: + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_autosprite2] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite2] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite2] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite2] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_NORMAL: + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_normal] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_normal] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_normal] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_normal] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD; + needsupdate |= BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_WAVE: + if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) + break; // if wavefunc is a nop, ignore this transform + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_wave] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_wave] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_wave] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_wave] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_BULGE: + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_bulge] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_bulge] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_bulge] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_bulge] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; + break; + case Q3DEFORM_MOVE: + if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) + break; // if wavefunc is a nop, ignore this transform + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_move] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_move] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_move] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_move] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX; + needsupdate |= BATCHNEED_VERTEXMESH_VERTEX; + break; + } + } + switch(rsurface.texture->tcgen.tcgen) + { + default: + case Q3TCGEN_TEXTURE: + break; + case Q3TCGEN_LIGHTMAP: + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_lightmap] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_lightmap] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_lightmap] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_lightmap] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_LIGHTMAP; + needsupdate |= BATCHNEED_VERTEXMESH_LIGHTMAP; + break; + case Q3TCGEN_VECTOR: + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_vector] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_vector] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_vector] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_vector] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX; + needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; + break; + case Q3TCGEN_ENVIRONMENT: + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_environment] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_environment] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_environment] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_environment] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL; + needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; + break; + } + if (rsurface.texture->tcmods[0].tcmod == Q3TCMOD_TURBULENT) + { + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_tcmod_turbulent] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcmod_turbulent] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcmod_turbulent] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcmod_turbulent] += batchnumtriangles; + } + dynamicvertex = true; + batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD; + needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; + } + + if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP))) + { + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_interleavedarrays] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_interleavedarrays] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_interleavedarrays] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_interleavedarrays] += batchnumtriangles; + } + dynamicvertex = true; + needsupdate |= (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)); + } + + // when the model data has no vertex buffer (dynamic mesh), we need to + // eliminate gaps + if (vid.useinterleavedarrays && !rsurface.modelvertexmesh_vertexbuffer) + batchneed |= BATCHNEED_NOGAPS; + + // the caller can specify BATCHNEED_NOGAPS to force a batch with + // firstvertex = 0 and endvertex = numvertices (no gaps, no firstvertex), + // we ensure this by treating the vertex batch as dynamic... + if ((batchneed & BATCHNEED_NOGAPS) && (gaps || firstvertex > 0)) + { + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_nogaps] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_nogaps] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_nogaps] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_nogaps] += batchnumtriangles; + } + dynamicvertex = true; + } + + if (dynamicvertex) + { + // when copying, we need to consider the regeneration of vertexmesh, any dependencies it may have must be set... + if (batchneed & BATCHNEED_VERTEXMESH_VERTEX) batchneed |= BATCHNEED_ARRAY_VERTEX; + if (batchneed & BATCHNEED_VERTEXMESH_NORMAL) batchneed |= BATCHNEED_ARRAY_NORMAL; + if (batchneed & BATCHNEED_VERTEXMESH_VECTOR) batchneed |= BATCHNEED_ARRAY_VECTOR; + if (batchneed & BATCHNEED_VERTEXMESH_VERTEXCOLOR) batchneed |= BATCHNEED_ARRAY_VERTEXCOLOR; + if (batchneed & BATCHNEED_VERTEXMESH_TEXCOORD) batchneed |= BATCHNEED_ARRAY_TEXCOORD; + if (batchneed & BATCHNEED_VERTEXMESH_LIGHTMAP) batchneed |= BATCHNEED_ARRAY_LIGHTMAP; + if (batchneed & BATCHNEED_VERTEXMESH_SKELETAL) batchneed |= BATCHNEED_ARRAY_SKELETAL; + } + + // if needsupdate, we have to do a dynamic vertex batch for sure + if (needsupdate & batchneed) + { + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_derived] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_derived] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_derived] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_derived] += batchnumtriangles; + } + dynamicvertex = true; + } + + // see if we need to build vertexmesh from arrays + if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP))) + { + if (!dynamicvertex) + { + r_refdef.stats[r_stat_batch_dynamic_batches_because_interleavedarrays] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces_because_interleavedarrays] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices_because_interleavedarrays] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles_because_interleavedarrays] += batchnumtriangles; + } + dynamicvertex = true; + } + + // if we're going to have to apply the skeletal transform manually, we need to batch the skeletal data + if (dynamicvertex && rsurface.entityskeletaltransform3x4) + batchneed |= BATCHNEED_ARRAY_SKELETAL; + + rsurface.batchvertex3f = rsurface.modelvertex3f; + rsurface.batchvertex3f_vertexbuffer = rsurface.modelvertex3f_vertexbuffer; + rsurface.batchvertex3f_bufferoffset = rsurface.modelvertex3f_bufferoffset; + rsurface.batchsvector3f = rsurface.modelsvector3f; + rsurface.batchsvector3f_vertexbuffer = rsurface.modelsvector3f_vertexbuffer; + rsurface.batchsvector3f_bufferoffset = rsurface.modelsvector3f_bufferoffset; + rsurface.batchtvector3f = rsurface.modeltvector3f; + rsurface.batchtvector3f_vertexbuffer = rsurface.modeltvector3f_vertexbuffer; + rsurface.batchtvector3f_bufferoffset = rsurface.modeltvector3f_bufferoffset; + rsurface.batchnormal3f = rsurface.modelnormal3f; + rsurface.batchnormal3f_vertexbuffer = rsurface.modelnormal3f_vertexbuffer; + rsurface.batchnormal3f_bufferoffset = rsurface.modelnormal3f_bufferoffset; + rsurface.batchlightmapcolor4f = rsurface.modellightmapcolor4f; + rsurface.batchlightmapcolor4f_vertexbuffer = rsurface.modellightmapcolor4f_vertexbuffer; + rsurface.batchlightmapcolor4f_bufferoffset = rsurface.modellightmapcolor4f_bufferoffset; + rsurface.batchtexcoordtexture2f = rsurface.modeltexcoordtexture2f; + rsurface.batchtexcoordtexture2f_vertexbuffer = rsurface.modeltexcoordtexture2f_vertexbuffer; + rsurface.batchtexcoordtexture2f_bufferoffset = rsurface.modeltexcoordtexture2f_bufferoffset; + rsurface.batchtexcoordlightmap2f = rsurface.modeltexcoordlightmap2f; + rsurface.batchtexcoordlightmap2f_vertexbuffer = rsurface.modeltexcoordlightmap2f_vertexbuffer; + rsurface.batchtexcoordlightmap2f_bufferoffset = rsurface.modeltexcoordlightmap2f_bufferoffset; + rsurface.batchskeletalindex4ub = rsurface.modelskeletalindex4ub; + rsurface.batchskeletalindex4ub_vertexbuffer = rsurface.modelskeletalindex4ub_vertexbuffer; + rsurface.batchskeletalindex4ub_bufferoffset = rsurface.modelskeletalindex4ub_bufferoffset; + rsurface.batchskeletalweight4ub = rsurface.modelskeletalweight4ub; + rsurface.batchskeletalweight4ub_vertexbuffer = rsurface.modelskeletalweight4ub_vertexbuffer; + rsurface.batchskeletalweight4ub_bufferoffset = rsurface.modelskeletalweight4ub_bufferoffset; + rsurface.batchvertexmesh = rsurface.modelvertexmesh; + rsurface.batchvertexmesh_vertexbuffer = rsurface.modelvertexmesh_vertexbuffer; + rsurface.batchvertexmesh_bufferoffset = rsurface.modelvertexmesh_bufferoffset; + rsurface.batchelement3i = rsurface.modelelement3i; + rsurface.batchelement3i_indexbuffer = rsurface.modelelement3i_indexbuffer; + rsurface.batchelement3i_bufferoffset = rsurface.modelelement3i_bufferoffset; + rsurface.batchelement3s = rsurface.modelelement3s; + rsurface.batchelement3s_indexbuffer = rsurface.modelelement3s_indexbuffer; + rsurface.batchelement3s_bufferoffset = rsurface.modelelement3s_bufferoffset; + rsurface.batchskeletaltransform3x4 = rsurface.entityskeletaltransform3x4; + rsurface.batchskeletaltransform3x4buffer = rsurface.entityskeletaltransform3x4buffer; + rsurface.batchskeletaltransform3x4offset = rsurface.entityskeletaltransform3x4offset; + rsurface.batchskeletaltransform3x4size = rsurface.entityskeletaltransform3x4size; + rsurface.batchskeletalnumtransforms = rsurface.entityskeletalnumtransforms; + + // if any dynamic vertex processing has to occur in software, we copy the + // entire surface list together before processing to rebase the vertices + // to start at 0 (otherwise we waste a lot of room in a vertex buffer). + // + // if any gaps exist and we do not have a static vertex buffer, we have to + // copy the surface list together to avoid wasting upload bandwidth on the + // vertices in the gaps. + // + // if gaps exist and we have a static vertex buffer, we can choose whether + // to combine the index buffer ranges into one dynamic index buffer or + // simply issue multiple glDrawElements calls (BATCHNEED_ALLOWMULTIDRAW). + // + // in many cases the batch is reduced to one draw call. + + rsurface.batchmultidraw = false; + rsurface.batchmultidrawnumsurfaces = 0; + rsurface.batchmultidrawsurfacelist = NULL; + + if (!dynamicvertex) + { + // static vertex data, just set pointers... + rsurface.batchgeneratedvertex = false; + // if there are gaps, we want to build a combined index buffer, + // otherwise use the original static buffer with an appropriate offset + if (gaps) + { + r_refdef.stats[r_stat_batch_copytriangles_batches] += 1; + r_refdef.stats[r_stat_batch_copytriangles_surfaces] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_copytriangles_vertices] += batchnumvertices; + r_refdef.stats[r_stat_batch_copytriangles_triangles] += batchnumtriangles; + if ((batchneed & BATCHNEED_ALLOWMULTIDRAW) && r_batch_multidraw.integer && batchnumtriangles >= r_batch_multidraw_mintriangles.integer) + { + rsurface.batchmultidraw = true; + rsurface.batchmultidrawnumsurfaces = texturenumsurfaces; + rsurface.batchmultidrawsurfacelist = texturesurfacelist; + return; + } + // build a new triangle elements array for this batch + rsurface.batchelement3i = (int *)R_FrameData_Alloc(batchnumtriangles * sizeof(int[3])); + rsurface.batchfirsttriangle = 0; + numtriangles = 0; + for (i = 0;i < texturenumsurfaces;i++) + { + surfacefirsttriangle = texturesurfacelist[i]->num_firsttriangle; + surfacenumtriangles = texturesurfacelist[i]->num_triangles; + memcpy(rsurface.batchelement3i + 3*numtriangles, rsurface.modelelement3i + 3*surfacefirsttriangle, surfacenumtriangles*sizeof(int[3])); + numtriangles += surfacenumtriangles; + } + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + if (endvertex <= 65536) + { + // make a 16bit (unsigned short) index array if possible + rsurface.batchelement3s = (unsigned short *)R_FrameData_Alloc(batchnumtriangles * sizeof(unsigned short[3])); + for (i = 0;i < numtriangles*3;i++) + rsurface.batchelement3s[i] = rsurface.batchelement3i[i]; + } + // upload buffer data for the copytriangles batch + if (((r_batch_dynamicbuffer.integer || gl_vbo_dynamicindex.integer) && vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo) + { + if (rsurface.batchelement3s) + rsurface.batchelement3s_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(short[3]), rsurface.batchelement3s, R_BUFFERDATA_INDEX16, &rsurface.batchelement3s_bufferoffset); + else if (rsurface.batchelement3i) + rsurface.batchelement3i_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(int[3]), rsurface.batchelement3i, R_BUFFERDATA_INDEX32, &rsurface.batchelement3i_bufferoffset); + } + } + else + { + r_refdef.stats[r_stat_batch_fast_batches] += 1; + r_refdef.stats[r_stat_batch_fast_surfaces] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_fast_vertices] += batchnumvertices; + r_refdef.stats[r_stat_batch_fast_triangles] += batchnumtriangles; + } + return; + } + + // something needs software processing, do it for real... + // we only directly handle separate array data in this case and then + // generate interleaved data if needed... + rsurface.batchgeneratedvertex = true; + r_refdef.stats[r_stat_batch_dynamic_batches] += 1; + r_refdef.stats[r_stat_batch_dynamic_surfaces] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamic_vertices] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamic_triangles] += batchnumtriangles; + + // now copy the vertex data into a combined array and make an index array + // (this is what Quake3 does all the time) + // we also apply any skeletal animation here that would have been done in + // the vertex shader, because most of the dynamic vertex animation cases + // need actual vertex positions and normals + //if (dynamicvertex) + { + rsurface.batchvertexmesh = NULL; + rsurface.batchvertexmesh_vertexbuffer = NULL; + rsurface.batchvertexmesh_bufferoffset = 0; + rsurface.batchvertex3f = NULL; + rsurface.batchvertex3f_vertexbuffer = NULL; + rsurface.batchvertex3f_bufferoffset = 0; + rsurface.batchsvector3f = NULL; + rsurface.batchsvector3f_vertexbuffer = NULL; + rsurface.batchsvector3f_bufferoffset = 0; + rsurface.batchtvector3f = NULL; + rsurface.batchtvector3f_vertexbuffer = NULL; + rsurface.batchtvector3f_bufferoffset = 0; + rsurface.batchnormal3f = NULL; + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + rsurface.batchlightmapcolor4f = NULL; + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + rsurface.batchtexcoordtexture2f = NULL; + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + rsurface.batchtexcoordlightmap2f = NULL; + rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; + rsurface.batchtexcoordlightmap2f_bufferoffset = 0; + rsurface.batchskeletalindex4ub = NULL; + rsurface.batchskeletalindex4ub_vertexbuffer = NULL; + rsurface.batchskeletalindex4ub_bufferoffset = 0; + rsurface.batchskeletalweight4ub = NULL; + rsurface.batchskeletalweight4ub_vertexbuffer = NULL; + rsurface.batchskeletalweight4ub_bufferoffset = 0; + rsurface.batchelement3i = (int *)R_FrameData_Alloc(batchnumtriangles * sizeof(int[3])); + rsurface.batchelement3i_indexbuffer = NULL; + rsurface.batchelement3i_bufferoffset = 0; + rsurface.batchelement3s = NULL; + rsurface.batchelement3s_indexbuffer = NULL; + rsurface.batchelement3s_bufferoffset = 0; + rsurface.batchskeletaltransform3x4buffer = NULL; + rsurface.batchskeletaltransform3x4offset = 0; + rsurface.batchskeletaltransform3x4size = 0; + // we'll only be setting up certain arrays as needed + if (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) + rsurface.batchvertexmesh = (r_vertexmesh_t *)R_FrameData_Alloc(batchnumvertices * sizeof(r_vertexmesh_t)); + if (batchneed & BATCHNEED_ARRAY_VERTEX) + rsurface.batchvertex3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); + if (batchneed & BATCHNEED_ARRAY_NORMAL) + rsurface.batchnormal3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); + if (batchneed & BATCHNEED_ARRAY_VECTOR) + { + rsurface.batchsvector3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); + rsurface.batchtvector3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); + } + if (batchneed & BATCHNEED_ARRAY_VERTEXCOLOR) + rsurface.batchlightmapcolor4f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[4])); + if (batchneed & BATCHNEED_ARRAY_TEXCOORD) + rsurface.batchtexcoordtexture2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); + if (batchneed & BATCHNEED_ARRAY_LIGHTMAP) + rsurface.batchtexcoordlightmap2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); + if (batchneed & BATCHNEED_ARRAY_SKELETAL) + { + rsurface.batchskeletalindex4ub = (unsigned char *)R_FrameData_Alloc(batchnumvertices * sizeof(unsigned char[4])); + rsurface.batchskeletalweight4ub = (unsigned char *)R_FrameData_Alloc(batchnumvertices * sizeof(unsigned char[4])); + } + numvertices = 0; + numtriangles = 0; + for (i = 0;i < texturenumsurfaces;i++) + { + surfacefirstvertex = texturesurfacelist[i]->num_firstvertex; + surfacenumvertices = texturesurfacelist[i]->num_vertices; + surfacefirsttriangle = texturesurfacelist[i]->num_firsttriangle; + surfacenumtriangles = texturesurfacelist[i]->num_triangles; + // copy only the data requested + if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) && rsurface.modelvertexmesh) + memcpy(rsurface.batchvertexmesh + numvertices, rsurface.modelvertexmesh + surfacefirstvertex, surfacenumvertices * sizeof(rsurface.batchvertexmesh[0])); + if (batchneed & (BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_ARRAY_VERTEXCOLOR | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_ARRAY_LIGHTMAP)) + { + if (batchneed & BATCHNEED_ARRAY_VERTEX) + { + if (rsurface.batchvertex3f) + memcpy(rsurface.batchvertex3f + 3*numvertices, rsurface.modelvertex3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); + else + memset(rsurface.batchvertex3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); + } + if (batchneed & BATCHNEED_ARRAY_NORMAL) + { + if (rsurface.modelnormal3f) + memcpy(rsurface.batchnormal3f + 3*numvertices, rsurface.modelnormal3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); + else + memset(rsurface.batchnormal3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); + } + if (batchneed & BATCHNEED_ARRAY_VECTOR) + { + if (rsurface.modelsvector3f) + { + memcpy(rsurface.batchsvector3f + 3*numvertices, rsurface.modelsvector3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); + memcpy(rsurface.batchtvector3f + 3*numvertices, rsurface.modeltvector3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); + } + else + { + memset(rsurface.batchsvector3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); + memset(rsurface.batchtvector3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); + } + } + if (batchneed & BATCHNEED_ARRAY_VERTEXCOLOR) + { + if (rsurface.modellightmapcolor4f) + memcpy(rsurface.batchlightmapcolor4f + 4*numvertices, rsurface.modellightmapcolor4f + 4*surfacefirstvertex, surfacenumvertices * sizeof(float[4])); + else + memset(rsurface.batchlightmapcolor4f + 4*numvertices, 0, surfacenumvertices * sizeof(float[4])); + } + if (batchneed & BATCHNEED_ARRAY_TEXCOORD) + { + if (rsurface.modeltexcoordtexture2f) + memcpy(rsurface.batchtexcoordtexture2f + 2*numvertices, rsurface.modeltexcoordtexture2f + 2*surfacefirstvertex, surfacenumvertices * sizeof(float[2])); + else + memset(rsurface.batchtexcoordtexture2f + 2*numvertices, 0, surfacenumvertices * sizeof(float[2])); + } + if (batchneed & BATCHNEED_ARRAY_LIGHTMAP) + { + if (rsurface.modeltexcoordlightmap2f) + memcpy(rsurface.batchtexcoordlightmap2f + 2*numvertices, rsurface.modeltexcoordlightmap2f + 2*surfacefirstvertex, surfacenumvertices * sizeof(float[2])); + else + memset(rsurface.batchtexcoordlightmap2f + 2*numvertices, 0, surfacenumvertices * sizeof(float[2])); + } + if (batchneed & BATCHNEED_ARRAY_SKELETAL) + { + if (rsurface.modelskeletalindex4ub) + { + memcpy(rsurface.batchskeletalindex4ub + 4*numvertices, rsurface.modelskeletalindex4ub + 4*surfacefirstvertex, surfacenumvertices * sizeof(unsigned char[4])); + memcpy(rsurface.batchskeletalweight4ub + 4*numvertices, rsurface.modelskeletalweight4ub + 4*surfacefirstvertex, surfacenumvertices * sizeof(unsigned char[4])); + } + else + { + memset(rsurface.batchskeletalindex4ub + 4*numvertices, 0, surfacenumvertices * sizeof(unsigned char[4])); + memset(rsurface.batchskeletalweight4ub + 4*numvertices, 0, surfacenumvertices * sizeof(unsigned char[4])); + ub = rsurface.batchskeletalweight4ub + 4*numvertices; + for (j = 0;j < surfacenumvertices;j++) + ub[j*4] = 255; + } + } + } + RSurf_RenumberElements(rsurface.modelelement3i + 3*surfacefirsttriangle, rsurface.batchelement3i + 3*numtriangles, 3*surfacenumtriangles, numvertices - surfacefirstvertex); + numvertices += surfacenumvertices; + numtriangles += surfacenumtriangles; + } + + // generate a 16bit index array as well if possible + // (in general, dynamic batches fit) + if (numvertices <= 65536) + { + rsurface.batchelement3s = (unsigned short *)R_FrameData_Alloc(batchnumtriangles * sizeof(unsigned short[3])); + for (i = 0;i < numtriangles*3;i++) + rsurface.batchelement3s[i] = rsurface.batchelement3i[i]; + } + + // since we've copied everything, the batch now starts at 0 + rsurface.batchfirstvertex = 0; + rsurface.batchnumvertices = batchnumvertices; + rsurface.batchfirsttriangle = 0; + rsurface.batchnumtriangles = batchnumtriangles; + } + + // apply skeletal animation that would have been done in the vertex shader + if (rsurface.batchskeletaltransform3x4) + { + const unsigned char *si; + const unsigned char *sw; + const float *t[4]; + const float *b = rsurface.batchskeletaltransform3x4; + float *vp, *vs, *vt, *vn; + float w[4]; + float m[3][4], n[3][4]; + float tp[3], ts[3], tt[3], tn[3]; + r_refdef.stats[r_stat_batch_dynamicskeletal_batches] += 1; + r_refdef.stats[r_stat_batch_dynamicskeletal_surfaces] += batchnumsurfaces; + r_refdef.stats[r_stat_batch_dynamicskeletal_vertices] += batchnumvertices; + r_refdef.stats[r_stat_batch_dynamicskeletal_triangles] += batchnumtriangles; + si = rsurface.batchskeletalindex4ub; + sw = rsurface.batchskeletalweight4ub; + vp = rsurface.batchvertex3f; + vs = rsurface.batchsvector3f; + vt = rsurface.batchtvector3f; + vn = rsurface.batchnormal3f; + memset(m[0], 0, sizeof(m)); + memset(n[0], 0, sizeof(n)); + for (i = 0;i < batchnumvertices;i++) + { + t[0] = b + si[0]*12; + if (sw[0] == 255) + { + // common case - only one matrix + m[0][0] = t[0][ 0]; + m[0][1] = t[0][ 1]; + m[0][2] = t[0][ 2]; + m[0][3] = t[0][ 3]; + m[1][0] = t[0][ 4]; + m[1][1] = t[0][ 5]; + m[1][2] = t[0][ 6]; + m[1][3] = t[0][ 7]; + m[2][0] = t[0][ 8]; + m[2][1] = t[0][ 9]; + m[2][2] = t[0][10]; + m[2][3] = t[0][11]; + } + else if (sw[2] + sw[3]) + { + // blend 4 matrices + t[1] = b + si[1]*12; + t[2] = b + si[2]*12; + t[3] = b + si[3]*12; + w[0] = sw[0] * (1.0f / 255.0f); + w[1] = sw[1] * (1.0f / 255.0f); + w[2] = sw[2] * (1.0f / 255.0f); + w[3] = sw[3] * (1.0f / 255.0f); + // blend the matrices + m[0][0] = t[0][ 0] * w[0] + t[1][ 0] * w[1] + t[2][ 0] * w[2] + t[3][ 0] * w[3]; + m[0][1] = t[0][ 1] * w[0] + t[1][ 1] * w[1] + t[2][ 1] * w[2] + t[3][ 1] * w[3]; + m[0][2] = t[0][ 2] * w[0] + t[1][ 2] * w[1] + t[2][ 2] * w[2] + t[3][ 2] * w[3]; + m[0][3] = t[0][ 3] * w[0] + t[1][ 3] * w[1] + t[2][ 3] * w[2] + t[3][ 3] * w[3]; + m[1][0] = t[0][ 4] * w[0] + t[1][ 4] * w[1] + t[2][ 4] * w[2] + t[3][ 4] * w[3]; + m[1][1] = t[0][ 5] * w[0] + t[1][ 5] * w[1] + t[2][ 5] * w[2] + t[3][ 5] * w[3]; + m[1][2] = t[0][ 6] * w[0] + t[1][ 6] * w[1] + t[2][ 6] * w[2] + t[3][ 6] * w[3]; + m[1][3] = t[0][ 7] * w[0] + t[1][ 7] * w[1] + t[2][ 7] * w[2] + t[3][ 7] * w[3]; + m[2][0] = t[0][ 8] * w[0] + t[1][ 8] * w[1] + t[2][ 8] * w[2] + t[3][ 8] * w[3]; + m[2][1] = t[0][ 9] * w[0] + t[1][ 9] * w[1] + t[2][ 9] * w[2] + t[3][ 9] * w[3]; + m[2][2] = t[0][10] * w[0] + t[1][10] * w[1] + t[2][10] * w[2] + t[3][10] * w[3]; + m[2][3] = t[0][11] * w[0] + t[1][11] * w[1] + t[2][11] * w[2] + t[3][11] * w[3]; + } + else + { + // blend 2 matrices + t[1] = b + si[1]*12; + w[0] = sw[0] * (1.0f / 255.0f); + w[1] = sw[1] * (1.0f / 255.0f); + // blend the matrices + m[0][0] = t[0][ 0] * w[0] + t[1][ 0] * w[1]; + m[0][1] = t[0][ 1] * w[0] + t[1][ 1] * w[1]; + m[0][2] = t[0][ 2] * w[0] + t[1][ 2] * w[1]; + m[0][3] = t[0][ 3] * w[0] + t[1][ 3] * w[1]; + m[1][0] = t[0][ 4] * w[0] + t[1][ 4] * w[1]; + m[1][1] = t[0][ 5] * w[0] + t[1][ 5] * w[1]; + m[1][2] = t[0][ 6] * w[0] + t[1][ 6] * w[1]; + m[1][3] = t[0][ 7] * w[0] + t[1][ 7] * w[1]; + m[2][0] = t[0][ 8] * w[0] + t[1][ 8] * w[1]; + m[2][1] = t[0][ 9] * w[0] + t[1][ 9] * w[1]; + m[2][2] = t[0][10] * w[0] + t[1][10] * w[1]; + m[2][3] = t[0][11] * w[0] + t[1][11] * w[1]; + } + si += 4; + sw += 4; + // modify the vertex + VectorCopy(vp, tp); + vp[0] = tp[0] * m[0][0] + tp[1] * m[0][1] + tp[2] * m[0][2] + m[0][3]; + vp[1] = tp[0] * m[1][0] + tp[1] * m[1][1] + tp[2] * m[1][2] + m[1][3]; + vp[2] = tp[0] * m[2][0] + tp[1] * m[2][1] + tp[2] * m[2][2] + m[2][3]; + vp += 3; + if (vn) + { + // the normal transformation matrix is a set of cross products... + CrossProduct(m[1], m[2], n[0]); + CrossProduct(m[2], m[0], n[1]); + CrossProduct(m[0], m[1], n[2]); // is actually transpose(inverse(m)) * det(m) + VectorCopy(vn, tn); + vn[0] = tn[0] * n[0][0] + tn[1] * n[0][1] + tn[2] * n[0][2]; + vn[1] = tn[0] * n[1][0] + tn[1] * n[1][1] + tn[2] * n[1][2]; + vn[2] = tn[0] * n[2][0] + tn[1] * n[2][1] + tn[2] * n[2][2]; + VectorNormalize(vn); + vn += 3; + if (vs) + { + VectorCopy(vs, ts); + vs[0] = ts[0] * n[0][0] + ts[1] * n[0][1] + ts[2] * n[0][2]; + vs[1] = ts[0] * n[1][0] + ts[1] * n[1][1] + ts[2] * n[1][2]; + vs[2] = ts[0] * n[2][0] + ts[1] * n[2][1] + ts[2] * n[2][2]; + VectorNormalize(vs); + vs += 3; + VectorCopy(vt, tt); + vt[0] = tt[0] * n[0][0] + tt[1] * n[0][1] + tt[2] * n[0][2]; + vt[1] = tt[0] * n[1][0] + tt[1] * n[1][1] + tt[2] * n[1][2]; + vt[2] = tt[0] * n[2][0] + tt[1] * n[2][1] + tt[2] * n[2][2]; + VectorNormalize(vt); + vt += 3; + } + } + } + rsurface.batchskeletaltransform3x4 = NULL; + rsurface.batchskeletalnumtransforms = 0; + } + + // q1bsp surfaces rendered in vertex color mode have to have colors + // calculated based on lightstyles + if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_ARRAY_VERTEXCOLOR)) && texturesurfacelist[0]->lightmapinfo) + { + // generate color arrays for the surfaces in this list + int c[4]; + int scale; + int size3; + const int *offsets; + const unsigned char *lm; + rsurface.batchlightmapcolor4f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[4])); + rsurface.batchlightmapcolor4f_vertexbuffer = NULL; + rsurface.batchlightmapcolor4f_bufferoffset = 0; + numvertices = 0; + for (i = 0;i < texturenumsurfaces;i++) + { + surface = texturesurfacelist[i]; + offsets = rsurface.modellightmapoffsets + surface->num_firstvertex; + surfacenumvertices = surface->num_vertices; + if (surface->lightmapinfo->samples) + { + for (j = 0;j < surfacenumvertices;j++) + { + lm = surface->lightmapinfo->samples + offsets[j]; + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[0]]; + VectorScale(lm, scale, c); + if (surface->lightmapinfo->styles[1] != 255) + { + size3 = ((surface->lightmapinfo->extents[0]>>4)+1)*((surface->lightmapinfo->extents[1]>>4)+1)*3; + lm += size3; + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[1]]; + VectorMA(c, scale, lm, c); + if (surface->lightmapinfo->styles[2] != 255) + { + lm += size3; + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[2]]; + VectorMA(c, scale, lm, c); + if (surface->lightmapinfo->styles[3] != 255) + { + lm += size3; + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[3]]; + VectorMA(c, scale, lm, c); + } + } + } + c[0] >>= 7; + c[1] >>= 7; + c[2] >>= 7; + Vector4Set(rsurface.batchlightmapcolor4f + 4*numvertices, min(c[0], 255) * (1.0f / 255.0f), min(c[1], 255) * (1.0f / 255.0f), min(c[2], 255) * (1.0f / 255.0f), 1); + numvertices++; + } + } + else + { + for (j = 0;j < surfacenumvertices;j++) + { + Vector4Set(rsurface.batchlightmapcolor4f + 4*numvertices, 0, 0, 0, 1); + numvertices++; + } + } + } + } + + // if vertices are deformed (sprite flares and things in maps, possibly + // water waves, bulges and other deformations), modify the copied vertices + // in place + for (deformindex = 0, deform = rsurface.texture->deforms;deformindex < Q3MAXDEFORMS && deform->deform && r_deformvertexes.integer;deformindex++, deform++) + { + switch (deform->deform) + { + default: + case Q3DEFORM_PROJECTIONSHADOW: + case Q3DEFORM_TEXT0: + case Q3DEFORM_TEXT1: + case Q3DEFORM_TEXT2: + case Q3DEFORM_TEXT3: + case Q3DEFORM_TEXT4: + case Q3DEFORM_TEXT5: + case Q3DEFORM_TEXT6: + case Q3DEFORM_TEXT7: + case Q3DEFORM_NONE: + break; + case Q3DEFORM_AUTOSPRITE: + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, newforward); + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.right, newright); + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.up, newup); + VectorNormalize(newforward); + VectorNormalize(newright); + VectorNormalize(newup); +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; +// rsurface.batchsvector3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchsvector3f); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchtvector3f); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; +// rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); +// rsurface.batchnormal3f_vertexbuffer = NULL; +// rsurface.batchnormal3f_bufferoffset = 0; + // sometimes we're on a renderpath that does not use vectors (GL11/GL13/GLES1) + if (!VectorLength2(rsurface.batchnormal3f + 3*rsurface.batchfirstvertex)) + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + if (!VectorLength2(rsurface.batchsvector3f + 3*rsurface.batchfirstvertex)) + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + // a single autosprite surface can contain multiple sprites... + for (j = 0;j < batchnumvertices - 3;j += 4) + { + VectorClear(center); + for (i = 0;i < 4;i++) + VectorAdd(center, rsurface.batchvertex3f + 3*(j+i), center); + VectorScale(center, 0.25f, center); + VectorCopy(rsurface.batchnormal3f + 3*j, forward); + VectorCopy(rsurface.batchsvector3f + 3*j, right); + VectorCopy(rsurface.batchtvector3f + 3*j, up); + for (i = 0;i < 4;i++) + { + VectorSubtract(rsurface.batchvertex3f + 3*(j+i), center, v); + VectorMAMAMAM(1, center, DotProduct(forward, v), newforward, DotProduct(right, v), newright, DotProduct(up, v), newup, rsurface.batchvertex3f + 3*(j+i)); + } + } + // if we get here, BATCHNEED_ARRAY_NORMAL and BATCHNEED_ARRAY_VECTOR are in batchneed, so no need to check + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + break; + case Q3DEFORM_AUTOSPRITE2: + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, newforward); + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.right, newright); + Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.up, newup); + VectorNormalize(newforward); + VectorNormalize(newright); + VectorNormalize(newup); +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; + { + const float *v1, *v2; + vec3_t start, end; + float f, l; + struct + { + float length2; + const float *v1; + const float *v2; + } + shortest[2]; + memset(shortest, 0, sizeof(shortest)); + // a single autosprite surface can contain multiple sprites... + for (j = 0;j < batchnumvertices - 3;j += 4) + { + VectorClear(center); + for (i = 0;i < 4;i++) + VectorAdd(center, rsurface.batchvertex3f + 3*(j+i), center); + VectorScale(center, 0.25f, center); + // find the two shortest edges, then use them to define the + // axis vectors for rotating around the central axis + for (i = 0;i < 6;i++) + { + v1 = rsurface.batchvertex3f + 3*(j+quadedges[i][0]); + v2 = rsurface.batchvertex3f + 3*(j+quadedges[i][1]); + l = VectorDistance2(v1, v2); + // this length bias tries to make sense of square polygons, assuming they are meant to be upright + if (v1[2] != v2[2]) + l += (1.0f / 1024.0f); + if (shortest[0].length2 > l || i == 0) + { + shortest[1] = shortest[0]; + shortest[0].length2 = l; + shortest[0].v1 = v1; + shortest[0].v2 = v2; + } + else if (shortest[1].length2 > l || i == 1) + { + shortest[1].length2 = l; + shortest[1].v1 = v1; + shortest[1].v2 = v2; + } + } + VectorLerp(shortest[0].v1, 0.5f, shortest[0].v2, start); + VectorLerp(shortest[1].v1, 0.5f, shortest[1].v2, end); + // this calculates the right vector from the shortest edge + // and the up vector from the edge midpoints + VectorSubtract(shortest[0].v1, shortest[0].v2, right); + VectorNormalize(right); + VectorSubtract(end, start, up); + VectorNormalize(up); + // calculate a forward vector to use instead of the original plane normal (this is how we get a new right vector) + VectorSubtract(rsurface.localvieworigin, center, forward); + //Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, forward); + VectorNegate(forward, forward); + VectorReflect(forward, 0, up, forward); + VectorNormalize(forward); + CrossProduct(up, forward, newright); + VectorNormalize(newright); + // rotate the quad around the up axis vector, this is made + // especially easy by the fact we know the quad is flat, + // so we only have to subtract the center position and + // measure distance along the right vector, and then + // multiply that by the newright vector and add back the + // center position + // we also need to subtract the old position to undo the + // displacement from the center, which we do with a + // DotProduct, the subtraction/addition of center is also + // optimized into DotProducts here + l = DotProduct(right, center); + for (i = 0;i < 4;i++) + { + v1 = rsurface.batchvertex3f + 3*(j+i); + f = DotProduct(right, v1) - l; + VectorMAMAM(1, v1, -f, right, f, newright, rsurface.batchvertex3f + 3*(j+i)); + } + } + } + if(batchneed & (BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR)) // otherwise these can stay NULL + { +// rsurface.batchnormal3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchnormal3f_vertexbuffer = NULL; +// rsurface.batchnormal3f_bufferoffset = 0; + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + } + if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL + { +// rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + } + break; + case Q3DEFORM_NORMAL: + // deform the normals to make reflections wavey + rsurface.batchnormal3f = (float *)R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); + rsurface.batchnormal3f_vertexbuffer = NULL; + rsurface.batchnormal3f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + float vertex[3]; + float *normal = rsurface.batchnormal3f + 3*j; + VectorScale(rsurface.batchvertex3f + 3*j, 0.98f, vertex); + normal[0] = rsurface.batchnormal3f[j*3+0] + deform->parms[0] * noise4f( vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); + normal[1] = rsurface.batchnormal3f[j*3+1] + deform->parms[0] * noise4f( 98 + vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); + normal[2] = rsurface.batchnormal3f[j*3+2] + deform->parms[0] * noise4f(196 + vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); + VectorNormalize(normal); + } + if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL + { +// rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + } + break; + case Q3DEFORM_WAVE: + // deform vertex array to make wavey water and flags and such + waveparms[0] = deform->waveparms[0]; + waveparms[1] = deform->waveparms[1]; + waveparms[2] = deform->waveparms[2]; + waveparms[3] = deform->waveparms[3]; + if(!R_TestQ3WaveFunc(deform->wavefunc, waveparms)) + break; // if wavefunc is a nop, don't make a dynamic vertex array + // this is how a divisor of vertex influence on deformation + animpos = deform->parms[0] ? 1.0f / deform->parms[0] : 100.0f; + scale = R_EvaluateQ3WaveFunc(deform->wavefunc, waveparms); +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; +// rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); +// rsurface.batchnormal3f_vertexbuffer = NULL; +// rsurface.batchnormal3f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + // if the wavefunc depends on time, evaluate it per-vertex + if (waveparms[3]) + { + waveparms[2] = deform->waveparms[2] + (rsurface.batchvertex3f[j*3+0] + rsurface.batchvertex3f[j*3+1] + rsurface.batchvertex3f[j*3+2]) * animpos; + scale = R_EvaluateQ3WaveFunc(deform->wavefunc, waveparms); + } + VectorMA(rsurface.batchvertex3f + 3*j, scale, rsurface.batchnormal3f + 3*j, rsurface.batchvertex3f + 3*j); + } + // if we get here, BATCHNEED_ARRAY_NORMAL is in batchneed, so no need to check + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL + { +// rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + } + break; + case Q3DEFORM_BULGE: + // deform vertex array to make the surface have moving bulges +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; +// rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); +// rsurface.batchnormal3f_vertexbuffer = NULL; +// rsurface.batchnormal3f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + scale = sin(rsurface.batchtexcoordtexture2f[j*2+0] * deform->parms[0] + rsurface.shadertime * deform->parms[2]) * deform->parms[1]; + VectorMA(rsurface.batchvertex3f + 3*j, scale, rsurface.batchnormal3f + 3*j, rsurface.batchvertex3f + 3*j); + } + // if we get here, BATCHNEED_ARRAY_NORMAL is in batchneed, so no need to check + Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); + if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL + { +// rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchsvector3f_vertexbuffer = NULL; +// rsurface.batchsvector3f_bufferoffset = 0; +// rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); +// rsurface.batchtvector3f_vertexbuffer = NULL; +// rsurface.batchtvector3f_bufferoffset = 0; + Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); + } + break; + case Q3DEFORM_MOVE: + // deform vertex array + if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) + break; // if wavefunc is a nop, don't make a dynamic vertex array + scale = R_EvaluateQ3WaveFunc(deform->wavefunc, deform->waveparms); + VectorScale(deform->parms, scale, waveparms); +// rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); +// rsurface.batchvertex3f_vertexbuffer = NULL; +// rsurface.batchvertex3f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + VectorAdd(rsurface.batchvertex3f + 3*j, waveparms, rsurface.batchvertex3f + 3*j); + break; + } + } + + // generate texcoords based on the chosen texcoord source + switch(rsurface.texture->tcgen.tcgen) + { + default: + case Q3TCGEN_TEXTURE: + break; + case Q3TCGEN_LIGHTMAP: +// rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); +// rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; +// rsurface.batchtexcoordtexture2f_bufferoffset = 0; + if (rsurface.batchtexcoordlightmap2f) + memcpy(rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordtexture2f, batchnumvertices * sizeof(float[2])); + break; + case Q3TCGEN_VECTOR: +// rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); +// rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; +// rsurface.batchtexcoordtexture2f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + rsurface.batchtexcoordtexture2f[j*2+0] = DotProduct(rsurface.batchvertex3f + 3*j, rsurface.texture->tcgen.parms); + rsurface.batchtexcoordtexture2f[j*2+1] = DotProduct(rsurface.batchvertex3f + 3*j, rsurface.texture->tcgen.parms + 3); + } + break; + case Q3TCGEN_ENVIRONMENT: + // make environment reflections using a spheremap + rsurface.batchtexcoordtexture2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); + rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; + rsurface.batchtexcoordtexture2f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + // identical to Q3A's method, but executed in worldspace so + // carried models can be shiny too + + float viewer[3], d, reflected[3], worldreflected[3]; + + VectorSubtract(rsurface.localvieworigin, rsurface.batchvertex3f + 3*j, viewer); + // VectorNormalize(viewer); + + d = DotProduct(rsurface.batchnormal3f + 3*j, viewer); + + reflected[0] = rsurface.batchnormal3f[j*3+0]*2*d - viewer[0]; + reflected[1] = rsurface.batchnormal3f[j*3+1]*2*d - viewer[1]; + reflected[2] = rsurface.batchnormal3f[j*3+2]*2*d - viewer[2]; + // note: this is proportinal to viewer, so we can normalize later + + Matrix4x4_Transform3x3(&rsurface.matrix, reflected, worldreflected); + VectorNormalize(worldreflected); + + // note: this sphere map only uses world x and z! + // so positive and negative y will LOOK THE SAME. + rsurface.batchtexcoordtexture2f[j*2+0] = 0.5 + 0.5 * worldreflected[1]; + rsurface.batchtexcoordtexture2f[j*2+1] = 0.5 - 0.5 * worldreflected[2]; + } + break; + } + // the only tcmod that needs software vertex processing is turbulent, so + // check for it here and apply the changes if needed + // and we only support that as the first one + // (handling a mixture of turbulent and other tcmods would be problematic + // without punting it entirely to a software path) + if (rsurface.texture->tcmods[0].tcmod == Q3TCMOD_TURBULENT) + { + amplitude = rsurface.texture->tcmods[0].parms[1]; + animpos = rsurface.texture->tcmods[0].parms[2] + rsurface.shadertime * rsurface.texture->tcmods[0].parms[3]; +// rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); +// rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; +// rsurface.batchtexcoordtexture2f_bufferoffset = 0; + for (j = 0;j < batchnumvertices;j++) + { + rsurface.batchtexcoordtexture2f[j*2+0] += amplitude * sin(((rsurface.batchvertex3f[j*3+0] + rsurface.batchvertex3f[j*3+2]) * 1.0 / 1024.0f + animpos) * M_PI * 2); + rsurface.batchtexcoordtexture2f[j*2+1] += amplitude * sin(((rsurface.batchvertex3f[j*3+1] ) * 1.0 / 1024.0f + animpos) * M_PI * 2); + } + } + + if (needsupdate & batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) + { + // convert the modified arrays to vertex structs +// rsurface.batchvertexmesh = R_FrameData_Alloc(batchnumvertices * sizeof(r_vertexmesh_t)); +// rsurface.batchvertexmesh_vertexbuffer = NULL; +// rsurface.batchvertexmesh_bufferoffset = 0; + if (batchneed & BATCHNEED_VERTEXMESH_VERTEX) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + VectorCopy(rsurface.batchvertex3f + 3*j, vertexmesh->vertex3f); + if (batchneed & BATCHNEED_VERTEXMESH_NORMAL) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + VectorCopy(rsurface.batchnormal3f + 3*j, vertexmesh->normal3f); + if (batchneed & BATCHNEED_VERTEXMESH_VECTOR) + { + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + { + VectorCopy(rsurface.batchsvector3f + 3*j, vertexmesh->svector3f); + VectorCopy(rsurface.batchtvector3f + 3*j, vertexmesh->tvector3f); + } + } + if ((batchneed & BATCHNEED_VERTEXMESH_VERTEXCOLOR) && rsurface.batchlightmapcolor4f) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + Vector4Copy(rsurface.batchlightmapcolor4f + 4*j, vertexmesh->color4f); + if (batchneed & BATCHNEED_VERTEXMESH_TEXCOORD) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + Vector2Copy(rsurface.batchtexcoordtexture2f + 2*j, vertexmesh->texcoordtexture2f); + if ((batchneed & BATCHNEED_VERTEXMESH_LIGHTMAP) && rsurface.batchtexcoordlightmap2f) + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + Vector2Copy(rsurface.batchtexcoordlightmap2f + 2*j, vertexmesh->texcoordlightmap2f); + if ((batchneed & BATCHNEED_VERTEXMESH_SKELETAL) && rsurface.batchskeletalindex4ub) + { + for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) + { + Vector4Copy(rsurface.batchskeletalindex4ub + 4*j, vertexmesh->skeletalindex4ub); + Vector4Copy(rsurface.batchskeletalweight4ub + 4*j, vertexmesh->skeletalweight4ub); + } + } + } + + // upload buffer data for the dynamic batch + if (((r_batch_dynamicbuffer.integer || gl_vbo_dynamicvertex.integer || gl_vbo_dynamicindex.integer) && vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo) + { + if (rsurface.batchvertexmesh) + rsurface.batchvertexmesh_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(r_vertexmesh_t), rsurface.batchvertexmesh, R_BUFFERDATA_VERTEX, &rsurface.batchvertexmesh_bufferoffset); + else + { + if (rsurface.batchvertex3f) + rsurface.batchvertex3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f, R_BUFFERDATA_VERTEX, &rsurface.batchvertex3f_bufferoffset); + if (rsurface.batchsvector3f) + rsurface.batchsvector3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchsvector3f, R_BUFFERDATA_VERTEX, &rsurface.batchsvector3f_bufferoffset); + if (rsurface.batchtvector3f) + rsurface.batchtvector3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchtvector3f, R_BUFFERDATA_VERTEX, &rsurface.batchtvector3f_bufferoffset); + if (rsurface.batchnormal3f) + rsurface.batchnormal3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f, R_BUFFERDATA_VERTEX, &rsurface.batchnormal3f_bufferoffset); + if (rsurface.batchlightmapcolor4f) + rsurface.batchlightmapcolor4f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[4]), rsurface.batchlightmapcolor4f, R_BUFFERDATA_VERTEX, &rsurface.batchlightmapcolor4f_bufferoffset); + if (rsurface.batchtexcoordtexture2f) + rsurface.batchtexcoordtexture2f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[2]), rsurface.batchtexcoordtexture2f, R_BUFFERDATA_VERTEX, &rsurface.batchtexcoordtexture2f_bufferoffset); + if (rsurface.batchtexcoordlightmap2f) + rsurface.batchtexcoordlightmap2f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[2]), rsurface.batchtexcoordlightmap2f, R_BUFFERDATA_VERTEX, &rsurface.batchtexcoordlightmap2f_bufferoffset); + if (rsurface.batchskeletalindex4ub) + rsurface.batchskeletalindex4ub_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(unsigned char[4]), rsurface.batchskeletalindex4ub, R_BUFFERDATA_VERTEX, &rsurface.batchskeletalindex4ub_bufferoffset); + if (rsurface.batchskeletalweight4ub) + rsurface.batchskeletalweight4ub_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(unsigned char[4]), rsurface.batchskeletalweight4ub, R_BUFFERDATA_VERTEX, &rsurface.batchskeletalweight4ub_bufferoffset); + } + if (rsurface.batchelement3s) + rsurface.batchelement3s_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(short[3]), rsurface.batchelement3s, R_BUFFERDATA_INDEX16, &rsurface.batchelement3s_bufferoffset); + else if (rsurface.batchelement3i) + rsurface.batchelement3i_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(int[3]), rsurface.batchelement3i, R_BUFFERDATA_INDEX32, &rsurface.batchelement3i_bufferoffset); + } +} + +void RSurf_DrawBatch(void) +{ + // sometimes a zero triangle surface (usually a degenerate patch) makes it + // through the pipeline, killing it earlier in the pipeline would have + // per-surface overhead rather than per-batch overhead, so it's best to + // reject it here, before it hits glDraw. + if (rsurface.batchnumtriangles == 0) + return; +#if 0 + // batch debugging code + if (r_test.integer && rsurface.entity == r_refdef.scene.worldentity && rsurface.batchvertex3f == r_refdef.scene.worldentity->model->surfmesh.data_vertex3f) + { + int i; + int j; + int c; + const int *e; + e = rsurface.batchelement3i + rsurface.batchfirsttriangle*3; + for (i = 0;i < rsurface.batchnumtriangles*3;i++) + { + c = e[i]; + for (j = 0;j < rsurface.entity->model->num_surfaces;j++) + { + if (c >= rsurface.modelsurfaces[j].num_firstvertex && c < (rsurface.modelsurfaces[j].num_firstvertex + rsurface.modelsurfaces[j].num_vertices)) + { + if (rsurface.modelsurfaces[j].texture != rsurface.texture) + Sys_Error("RSurf_DrawBatch: index %i uses different texture (%s) than surface %i which it belongs to (which uses %s)\n", c, rsurface.texture->name, j, rsurface.modelsurfaces[j].texture->name); + break; + } + } + } + } +#endif + if (rsurface.batchmultidraw) + { + // issue multiple draws rather than copying index data + int numsurfaces = rsurface.batchmultidrawnumsurfaces; + const msurface_t **surfacelist = rsurface.batchmultidrawsurfacelist; + int i, j, k, firstvertex, endvertex, firsttriangle, endtriangle; + for (i = 0;i < numsurfaces;) + { + // combine consecutive surfaces as one draw + for (k = i, j = i + 1;j < numsurfaces;k = j, j++) + if (surfacelist[j] != surfacelist[k] + 1) + break; + firstvertex = surfacelist[i]->num_firstvertex; + endvertex = surfacelist[k]->num_firstvertex + surfacelist[k]->num_vertices; + firsttriangle = surfacelist[i]->num_firsttriangle; + endtriangle = surfacelist[k]->num_firsttriangle + surfacelist[k]->num_triangles; + R_Mesh_Draw(firstvertex, endvertex - firstvertex, firsttriangle, endtriangle - firsttriangle, rsurface.batchelement3i, rsurface.batchelement3i_indexbuffer, rsurface.batchelement3i_bufferoffset, rsurface.batchelement3s, rsurface.batchelement3s_indexbuffer, rsurface.batchelement3s_bufferoffset); + i = j; + } + } + else + { + // there is only one consecutive run of index data (may have been combined) + R_Mesh_Draw(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchfirsttriangle, rsurface.batchnumtriangles, rsurface.batchelement3i, rsurface.batchelement3i_indexbuffer, rsurface.batchelement3i_bufferoffset, rsurface.batchelement3s, rsurface.batchelement3s_indexbuffer, rsurface.batchelement3s_bufferoffset); + } +} + +static int RSurf_FindWaterPlaneForSurface(const msurface_t *surface) +{ + // pick the closest matching water plane + int planeindex, vertexindex, bestplaneindex = -1; + float d, bestd; + vec3_t vert; + const float *v; + r_waterstate_waterplane_t *p; + qboolean prepared = false; + bestd = 0; + for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++) + { + if(p->camera_entity != rsurface.texture->camera_entity) + continue; + d = 0; + if(!prepared) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX, 1, &surface); + prepared = true; + if(rsurface.batchnumvertices == 0) + break; + } + for (vertexindex = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3;vertexindex < rsurface.batchnumvertices;vertexindex++, v += 3) + { + Matrix4x4_Transform(&rsurface.matrix, v, vert); + d += fabs(PlaneDiff(vert, &p->plane)); + } + if (bestd > d || bestplaneindex < 0) + { + bestd = d; + bestplaneindex = planeindex; + } + } + return bestplaneindex; + // NOTE: this MAY return a totally unrelated water plane; we can ignore + // this situation though, as it might be better to render single larger + // batches with useless stuff (backface culled for example) than to + // render multiple smaller batches +} + +static void RSurf_DrawBatch_GL11_MakeFullbrightLightmapColorArray(void) +{ + int i; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0;i < rsurface.batchnumvertices;i++) + Vector4Set(rsurface.passcolor4f + 4*i, 0.5f, 0.5f, 0.5f, 1.0f); +} + +static void RSurf_DrawBatch_GL11_ApplyFog(void) +{ + int i; + float f; + const float *v; + const float *c; + float *c2; + if (rsurface.passcolor4f) + { + // generate color arrays + c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4, c2 += 4) + { + f = RSurf_FogVertex(v); + c2[0] = c[0] * f; + c2[1] = c[1] * f; + c2[2] = c[2] * f; + c2[3] = c[3]; + } + } + else + { + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c2 += 4) + { + f = RSurf_FogVertex(v); + c2[0] = f; + c2[1] = f; + c2[2] = f; + c2[3] = 1; + } + } +} + +static void RSurf_DrawBatch_GL11_ApplyFogToFinishedVertexColors(void) +{ + int i; + float f; + const float *v; + const float *c; + float *c2; + if (!rsurface.passcolor4f) + return; + c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4, c2 += 4) + { + f = RSurf_FogVertex(v); + c2[0] = c[0] * f + r_refdef.fogcolor[0] * (1 - f); + c2[1] = c[1] * f + r_refdef.fogcolor[1] * (1 - f); + c2[2] = c[2] * f + r_refdef.fogcolor[2] * (1 - f); + c2[3] = c[3]; + } +} + +static void RSurf_DrawBatch_GL11_ApplyColor(float r, float g, float b, float a) +{ + int i; + const float *c; + float *c2; + if (!rsurface.passcolor4f) + return; + c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, c += 4, c2 += 4) + { + c2[0] = c[0] * r; + c2[1] = c[1] * g; + c2[2] = c[2] * b; + c2[3] = c[3] * a; + } +} + +static void RSurf_DrawBatch_GL11_ApplyAmbient(void) +{ + int i; + const float *c; + float *c2; + if (!rsurface.passcolor4f) + return; + c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, c += 4, c2 += 4) + { + c2[0] = c[0] + r_refdef.scene.ambient; + c2[1] = c[1] + r_refdef.scene.ambient; + c2[2] = c[2] + r_refdef.scene.ambient; + c2[3] = c[3]; + } +} + +static void RSurf_DrawBatch_GL11_Lightmap(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + // TODO: optimize + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + R_Mesh_TexBind(0, rsurface.lightmaptexture); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexMatrix(0, NULL); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_Unlit(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + // TODO: optimize applyfog && applycolor case + // just apply fog if necessary, and tint the fog color array if necessary + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_VertexColor(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + // TODO: optimize + rsurface.passcolor4f = rsurface.batchlightmapcolor4f; + rsurface.passcolor4f_vertexbuffer = rsurface.batchlightmapcolor4f_vertexbuffer; + rsurface.passcolor4f_bufferoffset = rsurface.batchlightmapcolor4f_bufferoffset; + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_ClampColor(void) +{ + int i; + const float *c1; + float *c2; + if (!rsurface.passcolor4f) + return; + for (i = 0, c1 = rsurface.passcolor4f + 4*rsurface.batchfirstvertex, c2 = rsurface.passcolor4f + 4*rsurface.batchfirstvertex;i < rsurface.batchnumvertices;i++, c1 += 4, c2 += 4) + { + c2[0] = bound(0.0f, c1[0], 1.0f); + c2[1] = bound(0.0f, c1[1], 1.0f); + c2[2] = bound(0.0f, c1[2], 1.0f); + c2[3] = bound(0.0f, c1[3], 1.0f); + } +} + +static void RSurf_DrawBatch_GL11_ApplyFakeLight(void) +{ + int i; + float f; + const float *v; + const float *n; + float *c; + //vec3_t eyedir; + + // fake shading + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, n = rsurface.batchnormal3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, n += 3, c += 4) + { + f = -DotProduct(r_refdef.view.forward, n); + f = max(0, f); + f = f * 0.85 + 0.15; // work around so stuff won't get black + f *= r_refdef.lightmapintensity; + Vector4Set(c, f, f, f, 1); + } +} + +static void RSurf_DrawBatch_GL11_FakeLight(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + RSurf_DrawBatch_GL11_ApplyFakeLight(); + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_ApplyVertexShade(float *r, float *g, float *b, float *a, qboolean *applycolor) +{ + int i; + float f; + float alpha; + const float *v; + const float *n; + float *c; + vec3_t ambientcolor; + vec3_t diffusecolor; + vec3_t lightdir; + // TODO: optimize + // model lighting + VectorCopy(rsurface.modellight_lightdir, lightdir); + f = 0.5f * r_refdef.lightmapintensity; + ambientcolor[0] = rsurface.modellight_ambient[0] * *r * f; + ambientcolor[1] = rsurface.modellight_ambient[1] * *g * f; + ambientcolor[2] = rsurface.modellight_ambient[2] * *b * f; + diffusecolor[0] = rsurface.modellight_diffuse[0] * *r * f; + diffusecolor[1] = rsurface.modellight_diffuse[1] * *g * f; + diffusecolor[2] = rsurface.modellight_diffuse[2] * *b * f; + alpha = *a; + if (VectorLength2(diffusecolor) > 0) + { + // q3-style directional shading + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, n = rsurface.batchnormal3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, n += 3, c += 4) + { + if ((f = DotProduct(n, lightdir)) > 0) + VectorMA(ambientcolor, f, diffusecolor, c); + else + VectorCopy(ambientcolor, c); + c[3] = alpha; + } + *r = 1; + *g = 1; + *b = 1; + *a = 1; + *applycolor = false; + } + else + { + *r = ambientcolor[0]; + *g = ambientcolor[1]; + *b = ambientcolor[2]; + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + } +} + +static void RSurf_DrawBatch_GL11_VertexShade(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) +{ + RSurf_DrawBatch_GL11_ApplyVertexShade(&r, &g, &b, &a, &applycolor); + if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); + if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); + GL_Color(r, g, b, a); + RSurf_DrawBatch(); +} + +static void RSurf_DrawBatch_GL11_MakeFogColor(float r, float g, float b, float a) +{ + int i; + float f; + const float *v; + float *c; + + // fake shading + rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + + for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4) + { + f = 1 - RSurf_FogVertex(v); + c[0] = r; + c[1] = g; + c[2] = b; + c[3] = f * a; + } +} + +void RSurf_SetupDepthAndCulling(void) +{ + // submodels are biased to avoid z-fighting with world surfaces that they + // may be exactly overlapping (avoids z-fighting artifacts on certain + // doors and things in Quake maps) + GL_DepthRange(0, (rsurface.texture->currentmaterialflags & MATERIALFLAG_SHORTDEPTHRANGE) ? 0.0625 : 1); + GL_PolygonOffset(rsurface.basepolygonfactor + rsurface.texture->biaspolygonfactor, rsurface.basepolygonoffset + rsurface.texture->biaspolygonoffset); + GL_DepthTest(!(rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST)); + GL_CullFace((rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE) ? GL_NONE : r_refdef.view.cullface_back); +} + +static void R_DrawTextureSurfaceList_Sky(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + // transparent sky would be ridiculous + if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) + return; + R_SetupShader_Generic_NoTexture(false, false); + skyrenderlater = true; + RSurf_SetupDepthAndCulling(); + GL_DepthMask(true); + // LordHavoc: HalfLife maps have freaky skypolys so don't use + // skymasking on them, and Quake3 never did sky masking (unlike + // software Quake and software Quake2), so disable the sky masking + // in Quake3 maps as it causes problems with q3map2 sky tricks, + // and skymasking also looks very bad when noclipping outside the + // level, so don't use it then either. + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->type == mod_brushq1 && r_q1bsp_skymasking.integer && !r_refdef.viewcache.world_novis && !r_trippy.integer) + { + R_Mesh_ResetTextureState(); + if (skyrendermasked) + { + R_SetupShader_DepthOrShadow(false, false, false); + // depth-only (masking) + GL_ColorMask(0,0,0,0); + // just to make sure that braindead drivers don't draw + // anything despite that colormask... + GL_BlendFunc(GL_ZERO, GL_ONE); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + } + else + { + R_SetupShader_Generic_NoTexture(false, false); + // fog sky + GL_BlendFunc(GL_ONE, GL_ZERO); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + GL_Color(r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2], 1); + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); + } + RSurf_DrawBatch(); + if (skyrendermasked) + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + } + R_Mesh_ResetTextureState(); + GL_Color(1, 1, 1, 1); +} + +extern rtexture_t *r_shadow_prepasslightingdiffusetexture; +extern rtexture_t *r_shadow_prepasslightingspeculartexture; +static void R_DrawTextureSurfaceList_GL20(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) +{ + if (r_fb.water.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA))) + return; + if (prepass) + { + // render screenspace normalmap to texture + GL_DepthMask(true); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_DEFERREDGEOMETRY, texturenumsurfaces, texturesurfacelist, NULL, false); + RSurf_DrawBatch(); + return; + } + + // bind lightmap texture + + // water/refraction/reflection/camera surfaces have to be handled specially + if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_CAMERA | MATERIALFLAG_REFLECTION))) + { + int start, end, startplaneindex; + for (start = 0;start < texturenumsurfaces;start = end) + { + startplaneindex = RSurf_FindWaterPlaneForSurface(texturesurfacelist[start]); + if(startplaneindex < 0) + { + // this happens if the plane e.g. got backface culled and thus didn't get a water plane. We can just ignore this. + // Con_Printf("No matching water plane for surface with material flags 0x%08x - PLEASE DEBUG THIS\n", rsurface.texture->currentmaterialflags); + end = start + 1; + continue; + } + for (end = start + 1;end < texturenumsurfaces && startplaneindex == RSurf_FindWaterPlaneForSurface(texturesurfacelist[end]);end++) + ; + // now that we have a batch using the same planeindex, render it + if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_CAMERA))) + { + // render water or distortion background + GL_DepthMask(true); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BACKGROUND, end-start, texturesurfacelist + start, (void *)(r_fb.water.waterplanes + startplaneindex), false); + RSurf_DrawBatch(); + // blend surface on top + GL_DepthMask(false); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, end-start, texturesurfacelist + start, NULL, false); + RSurf_DrawBatch(); + } + else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION)) + { + // render surface with reflection texture as input + GL_DepthMask(writedepth && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, end-start, texturesurfacelist + start, (void *)(r_fb.water.waterplanes + startplaneindex), false); + RSurf_DrawBatch(); + } + } + return; + } + + // render surface batch normally + GL_DepthMask(writedepth && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)); + R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, texturenumsurfaces, texturesurfacelist, NULL, (rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) != 0); + RSurf_DrawBatch(); +} + +static void R_DrawTextureSurfaceList_GL13(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) +{ + // OpenGL 1.3 path - anything not completely ancient + qboolean applycolor; + qboolean applyfog; + int layerindex; + const texturelayer_t *layer; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | ((!rsurface.uselightmaptexture && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.modeltexcoordlightmap2f ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + + for (layerindex = 0, layer = rsurface.texture->currentlayers;layerindex < rsurface.texture->currentnumlayers;layerindex++, layer++) + { + vec4_t layercolor; + int layertexrgbscale; + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + if (layerindex == 0) + GL_AlphaTest(true); + else + { + GL_AlphaTest(false); + GL_DepthFunc(GL_EQUAL); + } + } + GL_DepthMask(layer->depthmask && writedepth); + GL_BlendFunc(layer->blendfunc1, layer->blendfunc2); + if (layer->color[0] > 2 || layer->color[1] > 2 || layer->color[2] > 2) + { + layertexrgbscale = 4; + VectorScale(layer->color, 0.25f, layercolor); + } + else if (layer->color[0] > 1 || layer->color[1] > 1 || layer->color[2] > 1) + { + layertexrgbscale = 2; + VectorScale(layer->color, 0.5f, layercolor); + } + else + { + layertexrgbscale = 1; + VectorScale(layer->color, 1.0f, layercolor); + } + layercolor[3] = layer->color[3]; + applycolor = layercolor[0] != 1 || layercolor[1] != 1 || layercolor[2] != 1 || layercolor[3] != 1; + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, 0, 0); + applyfog = r_refdef.fogenabled && (rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED); + switch (layer->type) + { + case TEXTURELAYERTYPE_LITTEXTURE: + // single-pass lightmapped texture with 2x rgbscale + R_Mesh_TexBind(0, r_texture_white); + R_Mesh_TexMatrix(0, NULL); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); + R_Mesh_TexBind(1, layer->texture); + R_Mesh_TexMatrix(1, &layer->texmatrix); + R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + RSurf_DrawBatch_GL11_VertexShade(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + else if (FAKELIGHT_ENABLED) + RSurf_DrawBatch_GL11_FakeLight(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + else if (rsurface.uselightmaptexture) + RSurf_DrawBatch_GL11_Lightmap(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + else + RSurf_DrawBatch_GL11_VertexColor(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + break; + case TEXTURELAYERTYPE_TEXTURE: + // singletexture unlit texture with transparency support + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + R_Mesh_TexBind(1, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); + RSurf_DrawBatch_GL11_Unlit(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); + break; + case TEXTURELAYERTYPE_FOG: + // singletexture fogging + if (layer->texture) + { + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + } + else + { + R_Mesh_TexBind(0, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); + } + R_Mesh_TexBind(1, 0); + R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); + // generate a color array for the fog pass + RSurf_DrawBatch_GL11_MakeFogColor(layercolor[0], layercolor[1], layercolor[2], layercolor[3]); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); + RSurf_DrawBatch(); + break; + default: + Con_Printf("R_DrawTextureSurfaceList: unknown layer type %i\n", layer->type); + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + GL_DepthFunc(GL_LEQUAL); + GL_AlphaTest(false); + } +} + +static void R_DrawTextureSurfaceList_GL11(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) +{ + // OpenGL 1.1 - crusty old voodoo path + qboolean applyfog; + int layerindex; + const texturelayer_t *layer; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | ((!rsurface.uselightmaptexture && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.modeltexcoordlightmap2f ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + + for (layerindex = 0, layer = rsurface.texture->currentlayers;layerindex < rsurface.texture->currentnumlayers;layerindex++, layer++) + { + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + if (layerindex == 0) + GL_AlphaTest(true); + else + { + GL_AlphaTest(false); + GL_DepthFunc(GL_EQUAL); + } + } + GL_DepthMask(layer->depthmask && writedepth); + GL_BlendFunc(layer->blendfunc1, layer->blendfunc2); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, 0, 0); + applyfog = r_refdef.fogenabled && (rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED); + switch (layer->type) + { + case TEXTURELAYERTYPE_LITTEXTURE: + if (layer->blendfunc1 == GL_ONE && layer->blendfunc2 == GL_ZERO && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST)) + { + // two-pass lit texture with 2x rgbscale + // first the lightmap pass + R_Mesh_TexBind(0, r_texture_white); + R_Mesh_TexMatrix(0, NULL); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + RSurf_DrawBatch_GL11_VertexShade(1, 1, 1, 1, false, false); + else if (FAKELIGHT_ENABLED) + RSurf_DrawBatch_GL11_FakeLight(1, 1, 1, 1, false, false); + else if (rsurface.uselightmaptexture) + RSurf_DrawBatch_GL11_Lightmap(1, 1, 1, 1, false, false); + else + RSurf_DrawBatch_GL11_VertexColor(1, 1, 1, 1, false, false); + // then apply the texture to it + GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + RSurf_DrawBatch_GL11_Unlit(layer->color[0] * 0.5f, layer->color[1] * 0.5f, layer->color[2] * 0.5f, layer->color[3], layer->color[0] != 2 || layer->color[1] != 2 || layer->color[2] != 2 || layer->color[3] != 1, false); + } + else + { + // single pass vertex-lighting-only texture with 1x rgbscale and transparency support + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + RSurf_DrawBatch_GL11_VertexShade(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); + else if (FAKELIGHT_ENABLED) + RSurf_DrawBatch_GL11_FakeLight(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); + else + RSurf_DrawBatch_GL11_VertexColor(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); + } + break; + case TEXTURELAYERTYPE_TEXTURE: + // singletexture unlit texture with transparency support + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + RSurf_DrawBatch_GL11_Unlit(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); + break; + case TEXTURELAYERTYPE_FOG: + // singletexture fogging + if (layer->texture) + { + R_Mesh_TexBind(0, layer->texture); + R_Mesh_TexMatrix(0, &layer->texmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + } + else + { + R_Mesh_TexBind(0, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); + } + // generate a color array for the fog pass + RSurf_DrawBatch_GL11_MakeFogColor(layer->color[0], layer->color[1], layer->color[2], layer->color[3]); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); + RSurf_DrawBatch(); + break; + default: + Con_Printf("R_DrawTextureSurfaceList: unknown layer type %i\n", layer->type); + } + } + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + GL_DepthFunc(GL_LEQUAL); + GL_AlphaTest(false); + } +} + +static void R_DrawTextureSurfaceList_ShowSurfaces(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) +{ + int vi; + int j; + r_vertexgeneric_t *batchvertex; + float c[4]; + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic_NoTexture(false, false); + + if(rsurface.texture && rsurface.texture->currentskinframe) + { + memcpy(c, rsurface.texture->currentskinframe->avgcolor, sizeof(c)); + c[3] *= rsurface.texture->currentalpha; + } + else + { + c[0] = 1; + c[1] = 0; + c[2] = 1; + c[3] = 1; + } + + if (rsurface.texture->pantstexture || rsurface.texture->shirttexture) + { + c[0] = 0.5 * (rsurface.colormap_pantscolor[0] * 0.3 + rsurface.colormap_shirtcolor[0] * 0.7); + c[1] = 0.5 * (rsurface.colormap_pantscolor[1] * 0.3 + rsurface.colormap_shirtcolor[1] * 0.7); + c[2] = 0.5 * (rsurface.colormap_pantscolor[2] * 0.3 + rsurface.colormap_shirtcolor[2] * 0.7); + } + + // brighten it up (as texture value 127 means "unlit") + c[0] *= 2 * r_refdef.view.colorscale; + c[1] *= 2 * r_refdef.view.colorscale; + c[2] *= 2 * r_refdef.view.colorscale; + + if(rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERALPHA) + c[3] *= r_wateralpha.value; + + if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHA && c[3] != 1) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + } + else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ADD) + { + GL_BlendFunc(GL_ONE, GL_ONE); + GL_DepthMask(false); + } + else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // can't do alpha test without texture, so let's blend instead + GL_DepthMask(false); + } + else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) + { + GL_BlendFunc(rsurface.texture->customblendfunc[0], rsurface.texture->customblendfunc[1]); + GL_DepthMask(false); + } + else + { + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(writedepth); + } + + if (r_showsurfaces.integer == 3) + { + rsurface.passcolor4f = NULL; + + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + + rsurface.passcolor4f = NULL; + rsurface.passcolor4f_vertexbuffer = 0; + rsurface.passcolor4f_bufferoffset = 0; + } + else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) + { + qboolean applycolor = true; + float one = 1.0; + + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + + r_refdef.lightmapintensity = 1; + RSurf_DrawBatch_GL11_ApplyVertexShade(&one, &one, &one, &one, &applycolor); + r_refdef.lightmapintensity = 0; // we're in showsurfaces, after all + } + else if (FAKELIGHT_ENABLED) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + + r_refdef.lightmapintensity = r_fakelight_intensity.value; + RSurf_DrawBatch_GL11_ApplyFakeLight(); + r_refdef.lightmapintensity = 0; // we're in showsurfaces, after all + } + else + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_VERTEXCOLOR | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + + rsurface.passcolor4f = rsurface.batchlightmapcolor4f; + rsurface.passcolor4f_vertexbuffer = rsurface.batchlightmapcolor4f_vertexbuffer; + rsurface.passcolor4f_bufferoffset = rsurface.batchlightmapcolor4f_bufferoffset; + } + + if(!rsurface.passcolor4f) + RSurf_DrawBatch_GL11_MakeFullbrightLightmapColorArray(); + + RSurf_DrawBatch_GL11_ApplyAmbient(); + RSurf_DrawBatch_GL11_ApplyColor(c[0], c[1], c[2], c[3]); + if(r_refdef.fogenabled) + RSurf_DrawBatch_GL11_ApplyFogToFinishedVertexColors(); + RSurf_DrawBatch_GL11_ClampColor(); + + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.passcolor4f, NULL); + R_SetupShader_Generic_NoTexture(false, false); + RSurf_DrawBatch(); + } + else if (!r_refdef.view.showdebug) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchnumvertices); + for (j = 0, vi = 0;j < rsurface.batchnumvertices;j++, vi++) + { + VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); + Vector4Set(batchvertex[vi].color4f, 0, 0, 0, 1); + } + R_Mesh_PrepareVertices_Generic_Unlock(); + RSurf_DrawBatch(); + } + else if (r_showsurfaces.integer == 4) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchnumvertices); + for (j = 0, vi = 0;j < rsurface.batchnumvertices;j++, vi++) + { + unsigned char c = (vi << 3) * (1.0f / 256.0f); + VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); + Vector4Set(batchvertex[vi].color4f, c, c, c, 1); + } + R_Mesh_PrepareVertices_Generic_Unlock(); + RSurf_DrawBatch(); + } + else if (r_showsurfaces.integer == 2) + { + const int *e; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + batchvertex = R_Mesh_PrepareVertices_Generic_Lock(3*rsurface.batchnumtriangles); + for (j = 0, e = rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle;j < rsurface.batchnumtriangles;j++, e += 3) + { + unsigned char c = ((j + rsurface.batchfirsttriangle) << 3) * (1.0f / 256.0f); + VectorCopy(rsurface.batchvertex3f + 3*e[0], batchvertex[j*3+0].vertex3f); + VectorCopy(rsurface.batchvertex3f + 3*e[1], batchvertex[j*3+1].vertex3f); + VectorCopy(rsurface.batchvertex3f + 3*e[2], batchvertex[j*3+2].vertex3f); + Vector4Set(batchvertex[j*3+0].color4f, c, c, c, 1); + Vector4Set(batchvertex[j*3+1].color4f, c, c, c, 1); + Vector4Set(batchvertex[j*3+2].color4f, c, c, c, 1); + } + R_Mesh_PrepareVertices_Generic_Unlock(); + R_Mesh_Draw(0, rsurface.batchnumtriangles*3, 0, rsurface.batchnumtriangles, NULL, NULL, 0, NULL, NULL, 0); + } + else + { + int texturesurfaceindex; + int k; + const msurface_t *surface; + float surfacecolor4f[4]; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchfirstvertex + rsurface.batchnumvertices); + vi = 0; + for (texturesurfaceindex = 0;texturesurfaceindex < texturenumsurfaces;texturesurfaceindex++) + { + surface = texturesurfacelist[texturesurfaceindex]; + k = (int)(((size_t)surface) / sizeof(msurface_t)); + Vector4Set(surfacecolor4f, (k & 0xF) * (1.0f / 16.0f), (k & 0xF0) * (1.0f / 256.0f), (k & 0xF00) * (1.0f / 4096.0f), 1); + for (j = 0;j < surface->num_vertices;j++) + { + VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); + Vector4Copy(surfacecolor4f, batchvertex[vi].color4f); + vi++; + } + } + R_Mesh_PrepareVertices_Generic_Unlock(); + RSurf_DrawBatch(); + } +} + +static void R_DrawWorldTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) +{ + CHECKGLERROR + RSurf_SetupDepthAndCulling(); + if (r_showsurfaces.integer) + { + R_DrawTextureSurfaceList_ShowSurfaces(texturenumsurfaces, texturesurfacelist, writedepth); + return; + } + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + R_DrawTextureSurfaceList_GL20(texturenumsurfaces, texturesurfacelist, writedepth, prepass); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + R_DrawTextureSurfaceList_GL13(texturenumsurfaces, texturesurfacelist, writedepth); + break; + case RENDERPATH_GL11: + R_DrawTextureSurfaceList_GL11(texturenumsurfaces, texturesurfacelist, writedepth); + break; + } + CHECKGLERROR +} + +static void R_DrawModelTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) +{ + CHECKGLERROR + RSurf_SetupDepthAndCulling(); + if (r_showsurfaces.integer) + { + R_DrawTextureSurfaceList_ShowSurfaces(texturenumsurfaces, texturesurfacelist, writedepth); + return; + } + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + R_DrawTextureSurfaceList_GL20(texturenumsurfaces, texturesurfacelist, writedepth, prepass); + break; + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + R_DrawTextureSurfaceList_GL13(texturenumsurfaces, texturesurfacelist, writedepth); + break; + case RENDERPATH_GL11: + R_DrawTextureSurfaceList_GL11(texturenumsurfaces, texturesurfacelist, writedepth); + break; + } + CHECKGLERROR +} + +static void R_DrawSurface_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i, j; + int texturenumsurfaces, endsurface; + texture_t *texture; + const msurface_t *surface; + const msurface_t *texturesurfacelist[MESHQUEUE_TRANSPARENT_BATCHSIZE]; + + // if the model is static it doesn't matter what value we give for + // wantnormals and wanttangents, so this logic uses only rules applicable + // to a model, knowing that they are meaningless otherwise + if (ent == r_refdef.scene.worldentity) + RSurf_ActiveWorldEntity(); + else if (r_showsurfaces.integer && r_showsurfaces.integer != 3) + RSurf_ActiveModelEntity(ent, false, false, false); + else + { + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + RSurf_ActiveModelEntity(ent, true, true, false); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + RSurf_ActiveModelEntity(ent, true, false, false); + break; + } + } + + if (r_transparentdepthmasking.integer) + { + qboolean setup = false; + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + surface = rsurface.modelsurfaces + surfacelist[i]; + texture = surface->texture; + rsurface.texture = R_GetCurrentTexture(texture); + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + // scan ahead until we find a different texture + endsurface = min(i + 1024, numsurfaces); + texturenumsurfaces = 0; + texturesurfacelist[texturenumsurfaces++] = surface; + for (;j < endsurface;j++) + { + surface = rsurface.modelsurfaces + surfacelist[j]; + if (texture != surface->texture) + break; + texturesurfacelist[texturenumsurfaces++] = surface; + } + if (!(rsurface.texture->currentmaterialflags & MATERIALFLAG_TRANSDEPTH)) + continue; + // render the range of surfaces as depth + if (!setup) + { + setup = true; + GL_ColorMask(0,0,0,0); + GL_Color(1,1,1,1); + GL_DepthTest(true); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); +// R_Mesh_ResetTextureState(); + } + RSurf_SetupDepthAndCulling(); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); + R_SetupShader_DepthOrShadow(false, false, !!rsurface.batchskeletaltransform3x4); + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + RSurf_DrawBatch(); + } + if (setup) + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + } + + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + surface = rsurface.modelsurfaces + surfacelist[i]; + texture = surface->texture; + rsurface.texture = R_GetCurrentTexture(texture); + // scan ahead until we find a different texture + endsurface = min(i + MESHQUEUE_TRANSPARENT_BATCHSIZE, numsurfaces); + texturenumsurfaces = 0; + texturesurfacelist[texturenumsurfaces++] = surface; + if(FAKELIGHT_ENABLED) + { + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + for (;j < endsurface;j++) + { + surface = rsurface.modelsurfaces + surfacelist[j]; + if (texture != surface->texture) + break; + texturesurfacelist[texturenumsurfaces++] = surface; + } + } + else + { + rsurface.lightmaptexture = surface->lightmaptexture; + rsurface.deluxemaptexture = surface->deluxemaptexture; + rsurface.uselightmaptexture = surface->lightmaptexture != NULL; + for (;j < endsurface;j++) + { + surface = rsurface.modelsurfaces + surfacelist[j]; + if (texture != surface->texture || rsurface.lightmaptexture != surface->lightmaptexture) + break; + texturesurfacelist[texturenumsurfaces++] = surface; + } + } + // render the range of surfaces + if (ent == r_refdef.scene.worldentity) + R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, false, false); + else + R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, false, false); + } + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +static void R_ProcessTransparentTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + // transparent surfaces get pushed off into the transparent queue + int surfacelistindex; + const msurface_t *surface; + vec3_t tempcenter, center; + for (surfacelistindex = 0;surfacelistindex < texturenumsurfaces;surfacelistindex++) + { + surface = texturesurfacelist[surfacelistindex]; + if (r_transparent_sortsurfacesbynearest.integer) + { + tempcenter[0] = bound(surface->mins[0], rsurface.localvieworigin[0], surface->maxs[0]); + tempcenter[1] = bound(surface->mins[1], rsurface.localvieworigin[1], surface->maxs[1]); + tempcenter[2] = bound(surface->mins[2], rsurface.localvieworigin[2], surface->maxs[2]); + } + else + { + tempcenter[0] = (surface->mins[0] + surface->maxs[0]) * 0.5f; + tempcenter[1] = (surface->mins[1] + surface->maxs[1]) * 0.5f; + tempcenter[2] = (surface->mins[2] + surface->maxs[2]) * 0.5f; + } + Matrix4x4_Transform(&rsurface.matrix, tempcenter, center); + if (rsurface.entity->transparent_offset) // transparent offset + { + center[0] += r_refdef.view.forward[0]*rsurface.entity->transparent_offset; + center[1] += r_refdef.view.forward[1]*rsurface.entity->transparent_offset; + center[2] += r_refdef.view.forward[2]*rsurface.entity->transparent_offset; + } + R_MeshQueue_AddTransparent((rsurface.entity->flags & RENDER_WORLDOBJECT) ? TRANSPARENTSORT_SKY : (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) ? TRANSPARENTSORT_HUD : rsurface.texture->transparentsort, center, R_DrawSurface_TransparentCallback, rsurface.entity, surface - rsurface.modelsurfaces, rsurface.rtlight); + } +} + +static void R_DrawTextureSurfaceList_DepthOnly(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHATEST))) + return; + if (r_fb.water.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION))) + return; + RSurf_SetupDepthAndCulling(); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + R_SetupShader_DepthOrShadow(false, false, !!rsurface.batchskeletaltransform3x4); + RSurf_DrawBatch(); +} + +static void R_ProcessWorldTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean depthonly, qboolean prepass) +{ + CHECKGLERROR + if (depthonly) + R_DrawTextureSurfaceList_DepthOnly(texturenumsurfaces, texturesurfacelist); + else if (prepass) + { + if (!rsurface.texture->currentnumlayers) + return; + if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) + R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist); + else + R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth, prepass); + } + else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) && (!r_showsurfaces.integer || r_showsurfaces.integer == 3)) + R_DrawTextureSurfaceList_Sky(texturenumsurfaces, texturesurfacelist); + else if (!rsurface.texture->currentnumlayers) + return; + else if (((rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) || (r_showsurfaces.integer == 3 && (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST)))) + { + // in the deferred case, transparent surfaces were queued during prepass + if (!r_shadow_usingdeferredprepass) + R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist); + } + else + { + // the alphatest check is to make sure we write depth for anything we skipped on the depth-only pass earlier + R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth || (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST), prepass); + } + CHECKGLERROR +} + +static void R_QueueWorldSurfaceList(int numsurfaces, const msurface_t **surfacelist, int flagsmask, qboolean writedepth, qboolean depthonly, qboolean prepass) +{ + int i, j; + texture_t *texture; + R_FrameData_SetMark(); + // break the surface list down into batches by texture and use of lightmapping + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + // texture is the base texture pointer, rsurface.texture is the + // current frame/skin the texture is directing us to use (for example + // if a model has 2 skins and it is on skin 1, then skin 0 tells us to + // use skin 1 instead) + texture = surfacelist[i]->texture; + rsurface.texture = R_GetCurrentTexture(texture); + if (!(rsurface.texture->currentmaterialflags & flagsmask) || (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODRAW)) + { + // if this texture is not the kind we want, skip ahead to the next one + for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) + ; + continue; + } + if(FAKELIGHT_ENABLED || depthonly || prepass) + { + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + // simply scan ahead until we find a different texture or lightmap state + for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) + ; + } + else + { + rsurface.lightmaptexture = surfacelist[i]->lightmaptexture; + rsurface.deluxemaptexture = surfacelist[i]->deluxemaptexture; + rsurface.uselightmaptexture = surfacelist[i]->lightmaptexture != NULL; + // simply scan ahead until we find a different texture or lightmap state + for (;j < numsurfaces && texture == surfacelist[j]->texture && rsurface.lightmaptexture == surfacelist[j]->lightmaptexture;j++) + ; + } + // render the range of surfaces + R_ProcessWorldTextureSurfaceList(j - i, surfacelist + i, writedepth, depthonly, prepass); + } + R_FrameData_ReturnToMark(); +} + +static void R_ProcessModelTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean depthonly, qboolean prepass) +{ + CHECKGLERROR + if (depthonly) + R_DrawTextureSurfaceList_DepthOnly(texturenumsurfaces, texturesurfacelist); + else if (prepass) + { + if (!rsurface.texture->currentnumlayers) + return; + if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) + R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist); + else + R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth, prepass); + } + else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) && (!r_showsurfaces.integer || r_showsurfaces.integer == 3)) + R_DrawTextureSurfaceList_Sky(texturenumsurfaces, texturesurfacelist); + else if (!rsurface.texture->currentnumlayers) + return; + else if (((rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) || (r_showsurfaces.integer == 3 && (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST)))) + { + // in the deferred case, transparent surfaces were queued during prepass + if (!r_shadow_usingdeferredprepass) + R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist); + } + else + { + // the alphatest check is to make sure we write depth for anything we skipped on the depth-only pass earlier + R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth || (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST), prepass); + } + CHECKGLERROR +} + +static void R_QueueModelSurfaceList(entity_render_t *ent, int numsurfaces, const msurface_t **surfacelist, int flagsmask, qboolean writedepth, qboolean depthonly, qboolean prepass) +{ + int i, j; + texture_t *texture; + R_FrameData_SetMark(); + // break the surface list down into batches by texture and use of lightmapping + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + // texture is the base texture pointer, rsurface.texture is the + // current frame/skin the texture is directing us to use (for example + // if a model has 2 skins and it is on skin 1, then skin 0 tells us to + // use skin 1 instead) + texture = surfacelist[i]->texture; + rsurface.texture = R_GetCurrentTexture(texture); + if (!(rsurface.texture->currentmaterialflags & flagsmask) || (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODRAW)) + { + // if this texture is not the kind we want, skip ahead to the next one + for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) + ; + continue; + } + if(FAKELIGHT_ENABLED || depthonly || prepass) + { + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + // simply scan ahead until we find a different texture or lightmap state + for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) + ; + } + else + { + rsurface.lightmaptexture = surfacelist[i]->lightmaptexture; + rsurface.deluxemaptexture = surfacelist[i]->deluxemaptexture; + rsurface.uselightmaptexture = surfacelist[i]->lightmaptexture != NULL; + // simply scan ahead until we find a different texture or lightmap state + for (;j < numsurfaces && texture == surfacelist[j]->texture && rsurface.lightmaptexture == surfacelist[j]->lightmaptexture;j++) + ; + } + // render the range of surfaces + R_ProcessModelTextureSurfaceList(j - i, surfacelist + i, writedepth, depthonly, prepass); + } + R_FrameData_ReturnToMark(); +} + +float locboxvertex3f[6*4*3] = +{ + 1,0,1, 1,0,0, 1,1,0, 1,1,1, + 0,1,1, 0,1,0, 0,0,0, 0,0,1, + 1,1,1, 1,1,0, 0,1,0, 0,1,1, + 0,0,1, 0,0,0, 1,0,0, 1,0,1, + 0,0,1, 1,0,1, 1,1,1, 0,1,1, + 1,0,0, 0,0,0, 0,1,0, 1,1,0 +}; + +unsigned short locboxelements[6*2*3] = +{ + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9,10, 8,10,11, + 12,13,14, 12,14,15, + 16,17,18, 16,18,19, + 20,21,22, 20,22,23 +}; + +static void R_DrawLoc_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i, j; + cl_locnode_t *loc = (cl_locnode_t *)ent; + vec3_t mins, size; + float vertex3f[6*4*3]; + CHECKGLERROR + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + R_EntityMatrix(&identitymatrix); + +// R_Mesh_ResetTextureState(); + + i = surfacelist[0]; + GL_Color(((i & 0x0007) >> 0) * (1.0f / 7.0f) * r_refdef.view.colorscale, + ((i & 0x0038) >> 3) * (1.0f / 7.0f) * r_refdef.view.colorscale, + ((i & 0x01C0) >> 6) * (1.0f / 7.0f) * r_refdef.view.colorscale, + surfacelist[0] < 0 ? 0.5f : 0.125f); + + if (VectorCompare(loc->mins, loc->maxs)) + { + VectorSet(size, 2, 2, 2); + VectorMA(loc->mins, -0.5f, size, mins); + } + else + { + VectorCopy(loc->mins, mins); + VectorSubtract(loc->maxs, loc->mins, size); + } + + for (i = 0;i < 6*4*3;) + for (j = 0;j < 3;j++, i++) + vertex3f[i] = mins[j] + size[j] * locboxvertex3f[i]; + + R_Mesh_PrepareVertices_Generic_Arrays(6*4, vertex3f, NULL, NULL); + R_SetupShader_Generic_NoTexture(false, false); + R_Mesh_Draw(0, 6*4, 0, 6*2, NULL, NULL, 0, locboxelements, NULL, 0); +} + +void R_DrawLocs(void) +{ + int index; + cl_locnode_t *loc, *nearestloc; + vec3_t center; + nearestloc = CL_Locs_FindNearest(cl.movement_origin); + for (loc = cl.locnodes, index = 0;loc;loc = loc->next, index++) + { + VectorLerp(loc->mins, 0.5f, loc->maxs, center); + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, R_DrawLoc_Callback, (entity_render_t *)loc, loc == nearestloc ? -1 : index, NULL); + } +} + +void R_DecalSystem_Reset(decalsystem_t *decalsystem) +{ + if (decalsystem->decals) + Mem_Free(decalsystem->decals); + memset(decalsystem, 0, sizeof(*decalsystem)); +} + +static void R_DecalSystem_SpawnTriangle(decalsystem_t *decalsystem, const float *v0, const float *v1, const float *v2, const float *t0, const float *t1, const float *t2, const float *c0, const float *c1, const float *c2, int triangleindex, int surfaceindex, int decalsequence) +{ + tridecal_t *decal; + tridecal_t *decals; + int i; + + // expand or initialize the system + if (decalsystem->maxdecals <= decalsystem->numdecals) + { + decalsystem_t old = *decalsystem; + qboolean useshortelements; + decalsystem->maxdecals = max(16, decalsystem->maxdecals * 2); + useshortelements = decalsystem->maxdecals * 3 <= 65536; + decalsystem->decals = (tridecal_t *)Mem_Alloc(cls.levelmempool, decalsystem->maxdecals * (sizeof(tridecal_t) + sizeof(float[3][3]) + sizeof(float[3][2]) + sizeof(float[3][4]) + sizeof(int[3]) + (useshortelements ? sizeof(unsigned short[3]) : 0))); + decalsystem->color4f = (float *)(decalsystem->decals + decalsystem->maxdecals); + decalsystem->texcoord2f = (float *)(decalsystem->color4f + decalsystem->maxdecals*12); + decalsystem->vertex3f = (float *)(decalsystem->texcoord2f + decalsystem->maxdecals*6); + decalsystem->element3i = (int *)(decalsystem->vertex3f + decalsystem->maxdecals*9); + decalsystem->element3s = (useshortelements ? ((unsigned short *)(decalsystem->element3i + decalsystem->maxdecals*3)) : NULL); + if (decalsystem->numdecals) + memcpy(decalsystem->decals, old.decals, decalsystem->numdecals * sizeof(tridecal_t)); + if (old.decals) + Mem_Free(old.decals); + for (i = 0;i < decalsystem->maxdecals*3;i++) + decalsystem->element3i[i] = i; + if (useshortelements) + for (i = 0;i < decalsystem->maxdecals*3;i++) + decalsystem->element3s[i] = i; + } + + // grab a decal and search for another free slot for the next one + decals = decalsystem->decals; + decal = decalsystem->decals + (i = decalsystem->freedecal++); + for (i = decalsystem->freedecal;i < decalsystem->numdecals && decals[i].color4f[0][3];i++) + ; + decalsystem->freedecal = i; + if (decalsystem->numdecals <= i) + decalsystem->numdecals = i + 1; + + // initialize the decal + decal->lived = 0; + decal->triangleindex = triangleindex; + decal->surfaceindex = surfaceindex; + decal->decalsequence = decalsequence; + decal->color4f[0][0] = c0[0]; + decal->color4f[0][1] = c0[1]; + decal->color4f[0][2] = c0[2]; + decal->color4f[0][3] = 1; + decal->color4f[1][0] = c1[0]; + decal->color4f[1][1] = c1[1]; + decal->color4f[1][2] = c1[2]; + decal->color4f[1][3] = 1; + decal->color4f[2][0] = c2[0]; + decal->color4f[2][1] = c2[1]; + decal->color4f[2][2] = c2[2]; + decal->color4f[2][3] = 1; + decal->vertex3f[0][0] = v0[0]; + decal->vertex3f[0][1] = v0[1]; + decal->vertex3f[0][2] = v0[2]; + decal->vertex3f[1][0] = v1[0]; + decal->vertex3f[1][1] = v1[1]; + decal->vertex3f[1][2] = v1[2]; + decal->vertex3f[2][0] = v2[0]; + decal->vertex3f[2][1] = v2[1]; + decal->vertex3f[2][2] = v2[2]; + decal->texcoord2f[0][0] = t0[0]; + decal->texcoord2f[0][1] = t0[1]; + decal->texcoord2f[1][0] = t1[0]; + decal->texcoord2f[1][1] = t1[1]; + decal->texcoord2f[2][0] = t2[0]; + decal->texcoord2f[2][1] = t2[1]; + TriangleNormal(v0, v1, v2, decal->plane); + VectorNormalize(decal->plane); + decal->plane[3] = DotProduct(v0, decal->plane); +} + +extern cvar_t cl_decals_bias; +extern cvar_t cl_decals_models; +extern cvar_t cl_decals_newsystem_intensitymultiplier; +// baseparms, parms, temps +static void R_DecalSystem_SplatTriangle(decalsystem_t *decalsystem, float r, float g, float b, float a, float s1, float t1, float s2, float t2, int decalsequence, qboolean dynamic, float (*planes)[4], matrix4x4_t *projection, int triangleindex, int surfaceindex) +{ + int cornerindex; + int index; + float v[9][3]; + const float *vertex3f; + const float *normal3f; + int numpoints; + float points[2][9][3]; + float temp[3]; + float tc[9][2]; + float f; + float c[9][4]; + const int *e; + + e = rsurface.modelelement3i + 3*triangleindex; + + vertex3f = rsurface.modelvertex3f; + normal3f = rsurface.modelnormal3f; + + if (normal3f) + { + for (cornerindex = 0;cornerindex < 3;cornerindex++) + { + index = 3*e[cornerindex]; + VectorMA(vertex3f + index, cl_decals_bias.value, normal3f + index, v[cornerindex]); + } + } + else + { + for (cornerindex = 0;cornerindex < 3;cornerindex++) + { + index = 3*e[cornerindex]; + VectorCopy(vertex3f + index, v[cornerindex]); + } + } + + // cull backfaces + //TriangleNormal(v[0], v[1], v[2], normal); + //if (DotProduct(normal, localnormal) < 0.0f) + // continue; + // clip by each of the box planes formed from the projection matrix + // if anything survives, we emit the decal + numpoints = PolygonF_Clip(3 , v[0] , planes[0][0], planes[0][1], planes[0][2], planes[0][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[1][0], planes[1][0], planes[1][1], planes[1][2], planes[1][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[0][0], planes[2][0], planes[2][1], planes[2][2], planes[2][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[1][0], planes[3][0], planes[3][1], planes[3][2], planes[3][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[0][0], planes[4][0], planes[4][1], planes[4][2], planes[4][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); + if (numpoints < 3) + return; + numpoints = PolygonF_Clip(numpoints, points[1][0], planes[5][0], planes[5][1], planes[5][2], planes[5][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), v[0]); + if (numpoints < 3) + return; + // some part of the triangle survived, so we have to accept it... + if (dynamic) + { + // dynamic always uses the original triangle + numpoints = 3; + for (cornerindex = 0;cornerindex < 3;cornerindex++) + { + index = 3*e[cornerindex]; + VectorCopy(vertex3f + index, v[cornerindex]); + } + } + for (cornerindex = 0;cornerindex < numpoints;cornerindex++) + { + // convert vertex positions to texcoords + Matrix4x4_Transform(projection, v[cornerindex], temp); + tc[cornerindex][0] = (temp[1]+1.0f)*0.5f * (s2-s1) + s1; + tc[cornerindex][1] = (temp[2]+1.0f)*0.5f * (t2-t1) + t1; + // calculate distance fade from the projection origin + f = a * (1.0f-fabs(temp[0])) * cl_decals_newsystem_intensitymultiplier.value; + f = bound(0.0f, f, 1.0f); + c[cornerindex][0] = r * f; + c[cornerindex][1] = g * f; + c[cornerindex][2] = b * f; + c[cornerindex][3] = 1.0f; + //VectorMA(v[cornerindex], cl_decals_bias.value, localnormal, v[cornerindex]); + } + if (dynamic) + R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[1], v[2], tc[0], tc[1], tc[2], c[0], c[1], c[2], triangleindex, surfaceindex, decalsequence); + else + for (cornerindex = 0;cornerindex < numpoints-2;cornerindex++) + R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[cornerindex+1], v[cornerindex+2], tc[0], tc[cornerindex+1], tc[cornerindex+2], c[0], c[cornerindex+1], c[cornerindex+2], -1, surfaceindex, decalsequence); +} +static void R_DecalSystem_SplatEntity(entity_render_t *ent, const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize, int decalsequence) +{ + matrix4x4_t projection; + decalsystem_t *decalsystem; + qboolean dynamic; + dp_model_t *model; + const msurface_t *surface; + const msurface_t *surfaces; + const int *surfacelist; + const texture_t *texture; + int numtriangles; + int numsurfacelist; + int surfacelistindex; + int surfaceindex; + int triangleindex; + float localorigin[3]; + float localnormal[3]; + float localmins[3]; + float localmaxs[3]; + float localsize; + //float normal[3]; + float planes[6][4]; + float angles[3]; + bih_t *bih; + int bih_triangles_count; + int bih_triangles[256]; + int bih_surfaces[256]; + + decalsystem = &ent->decalsystem; + model = ent->model; + if (!model || !ent->allowdecals || ent->alpha < 1 || (ent->flags & (RENDER_ADDITIVE | RENDER_NODEPTHTEST))) + { + R_DecalSystem_Reset(&ent->decalsystem); + return; + } + + if (!model->brush.data_leafs && !cl_decals_models.integer) + { + if (decalsystem->model) + R_DecalSystem_Reset(decalsystem); + return; + } + + if (decalsystem->model != model) + R_DecalSystem_Reset(decalsystem); + decalsystem->model = model; + + RSurf_ActiveModelEntity(ent, true, false, false); + + Matrix4x4_Transform(&rsurface.inversematrix, worldorigin, localorigin); + Matrix4x4_Transform3x3(&rsurface.inversematrix, worldnormal, localnormal); + VectorNormalize(localnormal); + localsize = worldsize*rsurface.inversematrixscale; + localmins[0] = localorigin[0] - localsize; + localmins[1] = localorigin[1] - localsize; + localmins[2] = localorigin[2] - localsize; + localmaxs[0] = localorigin[0] + localsize; + localmaxs[1] = localorigin[1] + localsize; + localmaxs[2] = localorigin[2] + localsize; + + //VectorCopy(localnormal, planes[4]); + //VectorVectors(planes[4], planes[2], planes[0]); + AnglesFromVectors(angles, localnormal, NULL, false); + AngleVectors(angles, planes[0], planes[2], planes[4]); + VectorNegate(planes[0], planes[1]); + VectorNegate(planes[2], planes[3]); + VectorNegate(planes[4], planes[5]); + planes[0][3] = DotProduct(planes[0], localorigin) - localsize; + planes[1][3] = DotProduct(planes[1], localorigin) - localsize; + planes[2][3] = DotProduct(planes[2], localorigin) - localsize; + planes[3][3] = DotProduct(planes[3], localorigin) - localsize; + planes[4][3] = DotProduct(planes[4], localorigin) - localsize; + planes[5][3] = DotProduct(planes[5], localorigin) - localsize; + +#if 1 +// works +{ + matrix4x4_t forwardprojection; + Matrix4x4_CreateFromQuakeEntity(&forwardprojection, localorigin[0], localorigin[1], localorigin[2], angles[0], angles[1], angles[2], localsize); + Matrix4x4_Invert_Simple(&projection, &forwardprojection); +} +#else +// broken +{ + float projectionvector[4][3]; + VectorScale(planes[0], ilocalsize, projectionvector[0]); + VectorScale(planes[2], ilocalsize, projectionvector[1]); + VectorScale(planes[4], ilocalsize, projectionvector[2]); + projectionvector[0][0] = planes[0][0] * ilocalsize; + projectionvector[0][1] = planes[1][0] * ilocalsize; + projectionvector[0][2] = planes[2][0] * ilocalsize; + projectionvector[1][0] = planes[0][1] * ilocalsize; + projectionvector[1][1] = planes[1][1] * ilocalsize; + projectionvector[1][2] = planes[2][1] * ilocalsize; + projectionvector[2][0] = planes[0][2] * ilocalsize; + projectionvector[2][1] = planes[1][2] * ilocalsize; + projectionvector[2][2] = planes[2][2] * ilocalsize; + projectionvector[3][0] = -(localorigin[0]*projectionvector[0][0]+localorigin[1]*projectionvector[1][0]+localorigin[2]*projectionvector[2][0]); + projectionvector[3][1] = -(localorigin[0]*projectionvector[0][1]+localorigin[1]*projectionvector[1][1]+localorigin[2]*projectionvector[2][1]); + projectionvector[3][2] = -(localorigin[0]*projectionvector[0][2]+localorigin[1]*projectionvector[1][2]+localorigin[2]*projectionvector[2][2]); + Matrix4x4_FromVectors(&projection, projectionvector[0], projectionvector[1], projectionvector[2], projectionvector[3]); +} +#endif + + dynamic = model->surfmesh.isanimated; + numsurfacelist = model->nummodelsurfaces; + surfacelist = model->sortedmodelsurfaces; + surfaces = model->data_surfaces; + + bih = NULL; + bih_triangles_count = -1; + if(!dynamic) + { + if(model->render_bih.numleafs) + bih = &model->render_bih; + else if(model->collision_bih.numleafs) + bih = &model->collision_bih; + } + if(bih) + bih_triangles_count = BIH_GetTriangleListForBox(bih, sizeof(bih_triangles) / sizeof(*bih_triangles), bih_triangles, bih_surfaces, localmins, localmaxs); + if(bih_triangles_count == 0) + return; + if(bih_triangles_count > (int) (sizeof(bih_triangles) / sizeof(*bih_triangles))) // hit too many, likely bad anyway + return; + if(bih_triangles_count > 0) + { + for (triangleindex = 0; triangleindex < bih_triangles_count; ++triangleindex) + { + surfaceindex = bih_surfaces[triangleindex]; + surface = surfaces + surfaceindex; + texture = surface->texture; + if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) + continue; + if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS) + continue; + R_DecalSystem_SplatTriangle(decalsystem, r, g, b, a, s1, t1, s2, t2, decalsequence, dynamic, planes, &projection, bih_triangles[triangleindex], surfaceindex); + } + } + else + { + for (surfacelistindex = 0;surfacelistindex < numsurfacelist;surfacelistindex++) + { + surfaceindex = surfacelist[surfacelistindex]; + surface = surfaces + surfaceindex; + // check cull box first because it rejects more than any other check + if (!dynamic && !BoxesOverlap(surface->mins, surface->maxs, localmins, localmaxs)) + continue; + // skip transparent surfaces + texture = surface->texture; + if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) + continue; + if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS) + continue; + numtriangles = surface->num_triangles; + for (triangleindex = 0; triangleindex < numtriangles; triangleindex++) + R_DecalSystem_SplatTriangle(decalsystem, r, g, b, a, s1, t1, s2, t2, decalsequence, dynamic, planes, &projection, triangleindex + surface->num_firsttriangle, surfaceindex); + } + } +} + +// do not call this outside of rendering code - use R_DecalSystem_SplatEntities instead +static void R_DecalSystem_ApplySplatEntities(const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize, int decalsequence) +{ + int renderentityindex; + float worldmins[3]; + float worldmaxs[3]; + entity_render_t *ent; + + if (!cl_decals_newsystem.integer) + return; + + worldmins[0] = worldorigin[0] - worldsize; + worldmins[1] = worldorigin[1] - worldsize; + worldmins[2] = worldorigin[2] - worldsize; + worldmaxs[0] = worldorigin[0] + worldsize; + worldmaxs[1] = worldorigin[1] + worldsize; + worldmaxs[2] = worldorigin[2] + worldsize; + + R_DecalSystem_SplatEntity(r_refdef.scene.worldentity, worldorigin, worldnormal, r, g, b, a, s1, t1, s2, t2, worldsize, decalsequence); + + for (renderentityindex = 0;renderentityindex < r_refdef.scene.numentities;renderentityindex++) + { + ent = r_refdef.scene.entities[renderentityindex]; + if (!BoxesOverlap(ent->mins, ent->maxs, worldmins, worldmaxs)) + continue; + + R_DecalSystem_SplatEntity(ent, worldorigin, worldnormal, r, g, b, a, s1, t1, s2, t2, worldsize, decalsequence); + } +} + +typedef struct r_decalsystem_splatqueue_s +{ + vec3_t worldorigin; + vec3_t worldnormal; + float color[4]; + float tcrange[4]; + float worldsize; + int decalsequence; +} +r_decalsystem_splatqueue_t; + +int r_decalsystem_numqueued = 0; +r_decalsystem_splatqueue_t r_decalsystem_queue[MAX_DECALSYSTEM_QUEUE]; + +void R_DecalSystem_SplatEntities(const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize) +{ + r_decalsystem_splatqueue_t *queue; + + if (!cl_decals_newsystem.integer || r_decalsystem_numqueued == MAX_DECALSYSTEM_QUEUE) + return; + + queue = &r_decalsystem_queue[r_decalsystem_numqueued++]; + VectorCopy(worldorigin, queue->worldorigin); + VectorCopy(worldnormal, queue->worldnormal); + Vector4Set(queue->color, r, g, b, a); + Vector4Set(queue->tcrange, s1, t1, s2, t2); + queue->worldsize = worldsize; + queue->decalsequence = cl.decalsequence++; +} + +static void R_DecalSystem_ApplySplatEntitiesQueue(void) +{ + int i; + r_decalsystem_splatqueue_t *queue; + + for (i = 0, queue = r_decalsystem_queue;i < r_decalsystem_numqueued;i++, queue++) + R_DecalSystem_ApplySplatEntities(queue->worldorigin, queue->worldnormal, queue->color[0], queue->color[1], queue->color[2], queue->color[3], queue->tcrange[0], queue->tcrange[1], queue->tcrange[2], queue->tcrange[3], queue->worldsize, queue->decalsequence); + r_decalsystem_numqueued = 0; +} + +extern cvar_t cl_decals_max; +static void R_DrawModelDecals_FadeEntity(entity_render_t *ent) +{ + int i; + decalsystem_t *decalsystem = &ent->decalsystem; + int numdecals; + int killsequence; + tridecal_t *decal; + float frametime; + float lifetime; + + if (!decalsystem->numdecals) + return; + + if (r_showsurfaces.integer) + return; + + if (ent->model != decalsystem->model || ent->alpha < 1 || (ent->flags & RENDER_ADDITIVE)) + { + R_DecalSystem_Reset(decalsystem); + return; + } + + killsequence = cl.decalsequence - max(1, cl_decals_max.integer); + lifetime = cl_decals_time.value + cl_decals_fadetime.value; + + if (decalsystem->lastupdatetime) + frametime = (r_refdef.scene.time - decalsystem->lastupdatetime); + else + frametime = 0; + decalsystem->lastupdatetime = r_refdef.scene.time; + numdecals = decalsystem->numdecals; + + for (i = 0, decal = decalsystem->decals;i < numdecals;i++, decal++) + { + if (decal->color4f[0][3]) + { + decal->lived += frametime; + if (killsequence - decal->decalsequence > 0 || decal->lived >= lifetime) + { + memset(decal, 0, sizeof(*decal)); + if (decalsystem->freedecal > i) + decalsystem->freedecal = i; + } + } + } + decal = decalsystem->decals; + while (numdecals > 0 && !decal[numdecals-1].color4f[0][3]) + numdecals--; + + // collapse the array by shuffling the tail decals into the gaps + for (;;) + { + while (decalsystem->freedecal < numdecals && decal[decalsystem->freedecal].color4f[0][3]) + decalsystem->freedecal++; + if (decalsystem->freedecal == numdecals) + break; + decal[decalsystem->freedecal] = decal[--numdecals]; + } + + decalsystem->numdecals = numdecals; + + if (numdecals <= 0) + { + // if there are no decals left, reset decalsystem + R_DecalSystem_Reset(decalsystem); + } +} + +extern skinframe_t *decalskinframe; +static void R_DrawModelDecals_Entity(entity_render_t *ent) +{ + int i; + decalsystem_t *decalsystem = &ent->decalsystem; + int numdecals; + tridecal_t *decal; + float faderate; + float alpha; + float *v3f; + float *c4f; + float *t2f; + const int *e; + const unsigned char *surfacevisible = ent == r_refdef.scene.worldentity ? r_refdef.viewcache.world_surfacevisible : NULL; + int numtris = 0; + + numdecals = decalsystem->numdecals; + if (!numdecals) + return; + + if (r_showsurfaces.integer) + return; + + if (ent->model != decalsystem->model || ent->alpha < 1 || (ent->flags & RENDER_ADDITIVE)) + { + R_DecalSystem_Reset(decalsystem); + return; + } + + // if the model is static it doesn't matter what value we give for + // wantnormals and wanttangents, so this logic uses only rules applicable + // to a model, knowing that they are meaningless otherwise + if (ent == r_refdef.scene.worldentity) + RSurf_ActiveWorldEntity(); + else + RSurf_ActiveModelEntity(ent, false, false, false); + + decalsystem->lastupdatetime = r_refdef.scene.time; + + faderate = 1.0f / max(0.001f, cl_decals_fadetime.value); + + // update vertex positions for animated models + v3f = decalsystem->vertex3f; + c4f = decalsystem->color4f; + t2f = decalsystem->texcoord2f; + for (i = 0, decal = decalsystem->decals;i < numdecals;i++, decal++) + { + if (!decal->color4f[0][3]) + continue; + + if (surfacevisible && !surfacevisible[decal->surfaceindex]) + continue; + + // skip backfaces + if (decal->triangleindex < 0 && DotProduct(r_refdef.view.origin, decal->plane) < decal->plane[3]) + continue; + + // update color values for fading decals + if (decal->lived >= cl_decals_time.value) + alpha = 1 - faderate * (decal->lived - cl_decals_time.value); + else + alpha = 1.0f; + + c4f[ 0] = decal->color4f[0][0] * alpha; + c4f[ 1] = decal->color4f[0][1] * alpha; + c4f[ 2] = decal->color4f[0][2] * alpha; + c4f[ 3] = 1; + c4f[ 4] = decal->color4f[1][0] * alpha; + c4f[ 5] = decal->color4f[1][1] * alpha; + c4f[ 6] = decal->color4f[1][2] * alpha; + c4f[ 7] = 1; + c4f[ 8] = decal->color4f[2][0] * alpha; + c4f[ 9] = decal->color4f[2][1] * alpha; + c4f[10] = decal->color4f[2][2] * alpha; + c4f[11] = 1; + + t2f[0] = decal->texcoord2f[0][0]; + t2f[1] = decal->texcoord2f[0][1]; + t2f[2] = decal->texcoord2f[1][0]; + t2f[3] = decal->texcoord2f[1][1]; + t2f[4] = decal->texcoord2f[2][0]; + t2f[5] = decal->texcoord2f[2][1]; + + // update vertex positions for animated models + if (decal->triangleindex >= 0 && decal->triangleindex < rsurface.modelnumtriangles) + { + e = rsurface.modelelement3i + 3*decal->triangleindex; + VectorCopy(rsurface.modelvertex3f + 3*e[0], v3f); + VectorCopy(rsurface.modelvertex3f + 3*e[1], v3f + 3); + VectorCopy(rsurface.modelvertex3f + 3*e[2], v3f + 6); + } + else + { + VectorCopy(decal->vertex3f[0], v3f); + VectorCopy(decal->vertex3f[1], v3f + 3); + VectorCopy(decal->vertex3f[2], v3f + 6); + } + + if (r_refdef.fogenabled) + { + alpha = RSurf_FogVertex(v3f); + VectorScale(c4f, alpha, c4f); + alpha = RSurf_FogVertex(v3f + 3); + VectorScale(c4f + 4, alpha, c4f + 4); + alpha = RSurf_FogVertex(v3f + 6); + VectorScale(c4f + 8, alpha, c4f + 8); + } + + v3f += 9; + c4f += 12; + t2f += 6; + numtris++; + } + + if (numtris > 0) + { + r_refdef.stats[r_stat_drawndecals] += numtris; + + // now render the decals all at once + // (this assumes they all use one particle font texture!) + RSurf_ActiveCustomEntity(&rsurface.matrix, &rsurface.inversematrix, rsurface.ent_flags, ent->shadertime, 1, 1, 1, 1, numdecals*3, decalsystem->vertex3f, decalsystem->texcoord2f, NULL, NULL, NULL, decalsystem->color4f, numtris, decalsystem->element3i, decalsystem->element3s, false, false); +// R_Mesh_ResetTextureState(); + R_Mesh_PrepareVertices_Generic_Arrays(numtris * 3, decalsystem->vertex3f, decalsystem->color4f, decalsystem->texcoord2f); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(rsurface.basepolygonfactor + r_polygonoffset_decals_factor.value, rsurface.basepolygonoffset + r_polygonoffset_decals_offset.value); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + R_SetupShader_Generic(decalskinframe->base, NULL, GL_MODULATE, 1, false, false, false); + R_Mesh_Draw(0, numtris * 3, 0, numtris, decalsystem->element3i, NULL, 0, decalsystem->element3s, NULL, 0); + } +} + +static void R_DrawModelDecals(void) +{ + int i, numdecals; + + // fade faster when there are too many decals + numdecals = r_refdef.scene.worldentity->decalsystem.numdecals; + for (i = 0;i < r_refdef.scene.numentities;i++) + numdecals += r_refdef.scene.entities[i]->decalsystem.numdecals; + + R_DrawModelDecals_FadeEntity(r_refdef.scene.worldentity); + for (i = 0;i < r_refdef.scene.numentities;i++) + if (r_refdef.scene.entities[i]->decalsystem.numdecals) + R_DrawModelDecals_FadeEntity(r_refdef.scene.entities[i]); + + R_DecalSystem_ApplySplatEntitiesQueue(); + + numdecals = r_refdef.scene.worldentity->decalsystem.numdecals; + for (i = 0;i < r_refdef.scene.numentities;i++) + numdecals += r_refdef.scene.entities[i]->decalsystem.numdecals; + + r_refdef.stats[r_stat_totaldecals] += numdecals; + + if (r_showsurfaces.integer) + return; + + R_DrawModelDecals_Entity(r_refdef.scene.worldentity); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + if (r_refdef.scene.entities[i]->decalsystem.numdecals) + R_DrawModelDecals_Entity(r_refdef.scene.entities[i]); + } +} + +extern cvar_t mod_collision_bih; +static void R_DrawDebugModel(void) +{ + entity_render_t *ent = rsurface.entity; + int i, j, k, l, flagsmask; + const msurface_t *surface; + dp_model_t *model = ent->model; + vec3_t v; + + if (!sv.active && !cls.demoplayback && ent != r_refdef.scene.worldentity) + return; + + if (r_showoverdraw.value > 0) + { + float c = r_refdef.view.colorscale * r_showoverdraw.value * 0.125f; + flagsmask = MATERIALFLAG_SKY | MATERIALFLAG_WALL; + R_SetupShader_Generic_NoTexture(false, false); + GL_DepthTest(false); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_BlendFunc(GL_ONE, GL_ONE); + for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) + { + if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) + continue; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, 1, &surface); + GL_CullFace((rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE) ? GL_NONE : r_refdef.view.cullface_back); + if (!rsurface.texture->currentlayers->depthmask) + GL_Color(c, 0, 0, 1.0f); + else if (ent == r_refdef.scene.worldentity) + GL_Color(c, c, c, 1.0f); + else + GL_Color(0, c, 0, 1.0f); + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); + RSurf_DrawBatch(); + } + } + rsurface.texture = NULL; + } + + flagsmask = MATERIALFLAG_SKY | MATERIALFLAG_WALL; + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic_NoTexture(false, false); + GL_DepthRange(0, 1); + GL_DepthTest(!r_showdisabledepthtest.integer); + GL_DepthMask(false); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (r_showcollisionbrushes.value > 0 && model->collision_bih.numleafs) + { + int triangleindex; + int bihleafindex; + qboolean cullbox = false; + const q3mbrush_t *brush; + const bih_t *bih = &model->collision_bih; + const bih_leaf_t *bihleaf; + float vertex3f[3][3]; + GL_PolygonOffset(r_refdef.polygonfactor + r_showcollisionbrushes_polygonfactor.value, r_refdef.polygonoffset + r_showcollisionbrushes_polygonoffset.value); + for (bihleafindex = 0, bihleaf = bih->leafs;bihleafindex < bih->numleafs;bihleafindex++, bihleaf++) + { + if (cullbox && R_CullBox(bihleaf->mins, bihleaf->maxs)) + continue; + switch (bihleaf->type) + { + case BIH_BRUSH: + brush = model->brush.data_brushes + bihleaf->itemindex; + if (brush->colbrushf && brush->colbrushf->numtriangles) + { + GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); + R_Mesh_PrepareVertices_Generic_Arrays(brush->colbrushf->numpoints, brush->colbrushf->points->v, NULL, NULL); + R_Mesh_Draw(0, brush->colbrushf->numpoints, 0, brush->colbrushf->numtriangles, brush->colbrushf->elements, NULL, 0, NULL, NULL, 0); + } + break; + case BIH_COLLISIONTRIANGLE: + triangleindex = bihleaf->itemindex; + VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+0], vertex3f[0]); + VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+1], vertex3f[1]); + VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+2], vertex3f[2]); + GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); + R_Mesh_PrepareVertices_Generic_Arrays(3, vertex3f[0], NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + break; + case BIH_RENDERTRIANGLE: + triangleindex = bihleaf->itemindex; + VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+0], vertex3f[0]); + VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+1], vertex3f[1]); + VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+2], vertex3f[2]); + GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); + R_Mesh_PrepareVertices_Generic_Arrays(3, vertex3f[0], NULL, NULL); + R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + break; + } + } + } + + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + +#ifndef USE_GLES2 + if (r_showtris.integer && qglPolygonMode) + { + if (r_showdisabledepthtest.integer) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + } + else + { + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); + } + qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);CHECKGLERROR + for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) + { + if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) + continue; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_NOGAPS, 1, &surface); + if (!rsurface.texture->currentlayers->depthmask) + GL_Color(r_refdef.view.colorscale, 0, 0, r_showtris.value); + else if (ent == r_refdef.scene.worldentity) + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, r_showtris.value); + else + GL_Color(0, r_refdef.view.colorscale, 0, r_showtris.value); + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); + RSurf_DrawBatch(); + } + } + qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);CHECKGLERROR + rsurface.texture = NULL; + } + + if (r_shownormals.value != 0 && qglBegin) + { + if (r_showdisabledepthtest.integer) + { + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + } + else + { + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); + } + for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) + { + if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) + continue; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) + { + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_NOGAPS, 1, &surface); + qglBegin(GL_LINES); + if (r_shownormals.value < 0 && rsurface.batchnormal3f) + { + for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) + { + VectorCopy(rsurface.batchvertex3f + l * 3, v); + GL_Color(0, 0, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + VectorMA(v, -r_shownormals.value, rsurface.batchnormal3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + } + } + if (r_shownormals.value > 0 && rsurface.batchsvector3f) + { + for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) + { + VectorCopy(rsurface.batchvertex3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, 0, 0, 1); + qglVertex3f(v[0], v[1], v[2]); + VectorMA(v, r_shownormals.value, rsurface.batchsvector3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + } + } + if (r_shownormals.value > 0 && rsurface.batchtvector3f) + { + for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) + { + VectorCopy(rsurface.batchvertex3f + l * 3, v); + GL_Color(0, r_refdef.view.colorscale, 0, 1); + qglVertex3f(v[0], v[1], v[2]); + VectorMA(v, r_shownormals.value, rsurface.batchtvector3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + } + } + if (r_shownormals.value > 0 && rsurface.batchnormal3f) + { + for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) + { + VectorCopy(rsurface.batchvertex3f + l * 3, v); + GL_Color(0, 0, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + VectorMA(v, r_shownormals.value, rsurface.batchnormal3f + l * 3, v); + GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); + qglVertex3f(v[0], v[1], v[2]); + } + } + qglEnd(); + CHECKGLERROR + } + } + rsurface.texture = NULL; + } +#endif +} + +int r_maxsurfacelist = 0; +const msurface_t **r_surfacelist = NULL; +void R_DrawWorldSurfaces(qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass) +{ + int i, j, endj, flagsmask; + dp_model_t *model = r_refdef.scene.worldmodel; + msurface_t *surfaces; + unsigned char *update; + int numsurfacelist = 0; + if (model == NULL) + return; + + if (r_maxsurfacelist < model->num_surfaces) + { + r_maxsurfacelist = model->num_surfaces; + if (r_surfacelist) + Mem_Free((msurface_t**)r_surfacelist); + r_surfacelist = (const msurface_t **) Mem_Alloc(r_main_mempool, r_maxsurfacelist * sizeof(*r_surfacelist)); + } + + RSurf_ActiveWorldEntity(); + + surfaces = model->data_surfaces; + update = model->brushq1.lightmapupdateflags; + + // update light styles on this submodel + if (!skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.lightmapintensity > 0) + { + model_brush_lightstyleinfo_t *style; + for (i = 0, style = model->brushq1.data_lightstyleinfo;i < model->brushq1.num_lightstyles;i++, style++) + { + if (style->value != r_refdef.scene.lightstylevalue[style->style]) + { + int *list = style->surfacelist; + style->value = r_refdef.scene.lightstylevalue[style->style]; + for (j = 0;j < style->numsurfaces;j++) + update[list[j]] = true; + } + } + } + + flagsmask = skysurfaces ? MATERIALFLAG_SKY : MATERIALFLAG_WALL; + + if (debug) + { + R_DrawDebugModel(); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + return; + } + + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + rsurface.texture = NULL; + rsurface.rtlight = NULL; + numsurfacelist = 0; + // add visible surfaces to draw list + for (i = 0;i < model->nummodelsurfaces;i++) + { + j = model->sortedmodelsurfaces[i]; + if (r_refdef.viewcache.world_surfacevisible[j]) + r_surfacelist[numsurfacelist++] = surfaces + j; + } + // update lightmaps if needed + if (model->brushq1.firstrender) + { + model->brushq1.firstrender = false; + for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) + if (update[j]) + R_BuildLightMap(r_refdef.scene.worldentity, surfaces + j); + } + else if (update) + { + for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) + if (r_refdef.viewcache.world_surfacevisible[j]) + if (update[j]) + R_BuildLightMap(r_refdef.scene.worldentity, surfaces + j); + } + // don't do anything if there were no surfaces + if (!numsurfacelist) + { + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + return; + } + R_QueueWorldSurfaceList(numsurfacelist, r_surfacelist, flagsmask, writedepth, depthonly, prepass); + + // add to stats if desired + if (r_speeds.integer && !skysurfaces && !depthonly) + { + r_refdef.stats[r_stat_world_surfaces] += numsurfacelist; + for (j = 0;j < numsurfacelist;j++) + r_refdef.stats[r_stat_world_triangles] += r_surfacelist[j]->num_triangles; + } + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_DrawModelSurfaces(entity_render_t *ent, qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass) +{ + int i, j, endj, flagsmask; + dp_model_t *model = ent->model; + msurface_t *surfaces; + unsigned char *update; + int numsurfacelist = 0; + if (model == NULL) + return; + + if (r_maxsurfacelist < model->num_surfaces) + { + r_maxsurfacelist = model->num_surfaces; + if (r_surfacelist) + Mem_Free((msurface_t **)r_surfacelist); + r_surfacelist = (const msurface_t **) Mem_Alloc(r_main_mempool, r_maxsurfacelist * sizeof(*r_surfacelist)); + } + + // if the model is static it doesn't matter what value we give for + // wantnormals and wanttangents, so this logic uses only rules applicable + // to a model, knowing that they are meaningless otherwise + if (ent == r_refdef.scene.worldentity) + RSurf_ActiveWorldEntity(); + else if (r_showsurfaces.integer && r_showsurfaces.integer != 3) + RSurf_ActiveModelEntity(ent, false, false, false); + else if (prepass) + RSurf_ActiveModelEntity(ent, true, true, true); + else if (depthonly) + { + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + RSurf_ActiveModelEntity(ent, model->wantnormals, model->wanttangents, false); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + RSurf_ActiveModelEntity(ent, model->wantnormals, false, false); + break; + } + } + else + { + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + RSurf_ActiveModelEntity(ent, true, true, false); + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + RSurf_ActiveModelEntity(ent, true, false, false); + break; + } + } + + surfaces = model->data_surfaces; + update = model->brushq1.lightmapupdateflags; + + // update light styles + if (!skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.lightmapintensity > 0) + { + model_brush_lightstyleinfo_t *style; + for (i = 0, style = model->brushq1.data_lightstyleinfo;i < model->brushq1.num_lightstyles;i++, style++) + { + if (style->value != r_refdef.scene.lightstylevalue[style->style]) + { + int *list = style->surfacelist; + style->value = r_refdef.scene.lightstylevalue[style->style]; + for (j = 0;j < style->numsurfaces;j++) + update[list[j]] = true; + } + } + } + + flagsmask = skysurfaces ? MATERIALFLAG_SKY : MATERIALFLAG_WALL; + + if (debug) + { + R_DrawDebugModel(); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + return; + } + + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + rsurface.texture = NULL; + rsurface.rtlight = NULL; + numsurfacelist = 0; + // add visible surfaces to draw list + for (i = 0;i < model->nummodelsurfaces;i++) + r_surfacelist[numsurfacelist++] = surfaces + model->sortedmodelsurfaces[i]; + // don't do anything if there were no surfaces + if (!numsurfacelist) + { + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + return; + } + // update lightmaps if needed + if (update) + { + int updated = 0; + for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) + { + if (update[j]) + { + updated++; + R_BuildLightMap(ent, surfaces + j); + } + } + } + + R_QueueModelSurfaceList(ent, numsurfacelist, r_surfacelist, flagsmask, writedepth, depthonly, prepass); + + // add to stats if desired + if (r_speeds.integer && !skysurfaces && !depthonly) + { + r_refdef.stats[r_stat_entities_surfaces] += numsurfacelist; + for (j = 0;j < numsurfacelist;j++) + r_refdef.stats[r_stat_entities_triangles] += r_surfacelist[j]->num_triangles; + } + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_DrawCustomSurface(skinframe_t *skinframe, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass) +{ + static texture_t texture; + static msurface_t surface; + const msurface_t *surfacelist = &surface; + + // fake enough texture and surface state to render this geometry + + texture.update_lastrenderframe = -1; // regenerate this texture + texture.basematerialflags = materialflags | MATERIALFLAG_CUSTOMSURFACE | MATERIALFLAG_WALL; + texture.currentskinframe = skinframe; + texture.currenttexmatrix = *texmatrix; // requires MATERIALFLAG_CUSTOMSURFACE + texture.offsetmapping = OFFSETMAPPING_OFF; + texture.offsetscale = 1; + texture.specularscalemod = 1; + texture.specularpowermod = 1; + texture.transparentsort = TRANSPARENTSORT_DISTANCE; + // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS + // JUST GREP FOR "specularscalemod = 1". + + surface.texture = &texture; + surface.num_triangles = numtriangles; + surface.num_firsttriangle = firsttriangle; + surface.num_vertices = numvertices; + surface.num_firstvertex = firstvertex; + + // now render it + rsurface.texture = R_GetCurrentTexture(surface.texture); + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + R_DrawModelTextureSurfaceList(1, &surfacelist, writedepth, prepass); +} + +void R_DrawCustomSurface_Texture(texture_t *texture, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass) +{ + static msurface_t surface; + const msurface_t *surfacelist = &surface; + + // fake enough texture and surface state to render this geometry + surface.texture = texture; + surface.num_triangles = numtriangles; + surface.num_firsttriangle = firsttriangle; + surface.num_vertices = numvertices; + surface.num_firstvertex = firstvertex; + + // now render it + rsurface.texture = R_GetCurrentTexture(surface.texture); + rsurface.lightmaptexture = NULL; + rsurface.deluxemaptexture = NULL; + rsurface.uselightmaptexture = false; + R_DrawModelTextureSurfaceList(1, &surfacelist, writedepth, prepass); +} diff --git a/app/jni/gl_rsurf.c b/app/jni/gl_rsurf.c new file mode 100644 index 0000000..17b183d --- /dev/null +++ b/app/jni/gl_rsurf.c @@ -0,0 +1,1645 @@ +/* +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. + +*/ +// r_surf.c: surface-related refresh code + +#include "quakedef.h" +#include "r_shadow.h" +#include "portals.h" +#include "csprogs.h" +#include "image.h" + +cvar_t r_ambient = {0, "r_ambient", "0", "brightens map, value is 0-128"}; +cvar_t r_lockpvs = {0, "r_lockpvs", "0", "disables pvs switching, allows you to walk around and inspect what is visible from a given location in the map (anything not visible from your current location will not be drawn)"}; +cvar_t r_lockvisibility = {0, "r_lockvisibility", "0", "disables visibility updates, allows you to walk around and inspect what is visible from a given viewpoint in the map (anything offscreen at the moment this is enabled will not be drawn)"}; +cvar_t r_useportalculling = {0, "r_useportalculling", "2", "improve framerate with r_novis 1 by using portal culling - still not as good as compiled visibility data in the map, but it helps (a value of 2 forces use of this even with vis data, which improves framerates in maps without too much complexity, but hurts in extremely complex maps, which is why 2 is not the default mode)"}; +cvar_t r_usesurfaceculling = {0, "r_usesurfaceculling", "1", "skip off-screen surfaces (1 = cull surfaces if the map is likely to benefit, 2 = always cull surfaces)"}; +cvar_t r_q3bsp_renderskydepth = {0, "r_q3bsp_renderskydepth", "0", "draws sky depth masking in q3 maps (as in q1 maps), this means for example that sky polygons can hide other things"}; + +/* +=============== +R_BuildLightMap + +Combine and scale multiple lightmaps into the 8.8 format in blocklights +=============== +*/ +void R_BuildLightMap (const entity_render_t *ent, msurface_t *surface) +{ + int smax, tmax, i, size, size3, maps, l; + int *bl, scale; + unsigned char *lightmap, *out, *stain; + dp_model_t *model = ent->model; + int *intblocklights; + unsigned char *templight; + + smax = (surface->lightmapinfo->extents[0]>>4)+1; + tmax = (surface->lightmapinfo->extents[1]>>4)+1; + size = smax*tmax; + size3 = size*3; + + r_refdef.stats[r_stat_lightmapupdatepixels] += size; + r_refdef.stats[r_stat_lightmapupdates]++; + + if (cl.buildlightmapmemorysize < size*sizeof(int[3])) + { + cl.buildlightmapmemorysize = size*sizeof(int[3]); + if (cl.buildlightmapmemory) + Mem_Free(cl.buildlightmapmemory); + cl.buildlightmapmemory = (unsigned char *) Mem_Alloc(cls.levelmempool, cl.buildlightmapmemorysize); + } + + // these both point at the same buffer, templight is only used for final + // processing and can replace the intblocklights data as it goes + intblocklights = (int *)cl.buildlightmapmemory; + templight = (unsigned char *)cl.buildlightmapmemory; + + // update cached lighting info + model->brushq1.lightmapupdateflags[surface - model->data_surfaces] = false; + + lightmap = surface->lightmapinfo->samples; + +// set to full bright if no light data + bl = intblocklights; + if (!model->brushq1.lightdata) + { + for (i = 0;i < size3;i++) + bl[i] = 128*256; + } + else + { +// clear to no light + memset(bl, 0, size3*sizeof(*bl)); + +// add all the lightmaps + if (lightmap) + for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++, lightmap += size3) + for (scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]], i = 0;i < size3;i++) + bl[i] += lightmap[i] * scale; + } + + stain = surface->lightmapinfo->stainsamples; + bl = intblocklights; + out = templight; + // the >> 16 shift adjusts down 8 bits to account for the stainmap + // scaling, and remaps the 0-65536 (2x overbright) to 0-256, it will + // be doubled during rendering to achieve 2x overbright + // (0 = 0.0, 128 = 1.0, 256 = 2.0) + if (stain) + { + for (i = 0;i < size;i++, bl += 3, stain += 3, out += 4) + { + l = (bl[0] * stain[0]) >> 16;out[2] = min(l, 255); + l = (bl[1] * stain[1]) >> 16;out[1] = min(l, 255); + l = (bl[2] * stain[2]) >> 16;out[0] = min(l, 255); + out[3] = 255; + } + } + else + { + for (i = 0;i < size;i++, bl += 3, out += 4) + { + l = bl[0] >> 8;out[2] = min(l, 255); + l = bl[1] >> 8;out[1] = min(l, 255); + l = bl[2] >> 8;out[0] = min(l, 255); + out[3] = 255; + } + } + + if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + Image_MakesRGBColorsFromLinear_Lightmap(templight, templight, size); + R_UpdateTexture(surface->lightmaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1); + + // update the surface's deluxemap if it has one + if (surface->deluxemaptexture != r_texture_blanknormalmap) + { + vec3_t n; + unsigned char *normalmap = surface->lightmapinfo->nmapsamples; + lightmap = surface->lightmapinfo->samples; + // clear to no normalmap + bl = intblocklights; + memset(bl, 0, size3*sizeof(*bl)); + // add all the normalmaps + if (lightmap && normalmap) + { + for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++, lightmap += size3, normalmap += size3) + { + for (scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]], i = 0;i < size;i++) + { + // add the normalmap with weighting proportional to the style's lightmap intensity + l = (int)(VectorLength(lightmap + i*3) * scale); + bl[i*3+0] += ((int)normalmap[i*3+0] - 128) * l; + bl[i*3+1] += ((int)normalmap[i*3+1] - 128) * l; + bl[i*3+2] += ((int)normalmap[i*3+2] - 128) * l; + } + } + } + bl = intblocklights; + out = templight; + // we simply renormalize the weighted normals to get a valid deluxemap + for (i = 0;i < size;i++, bl += 3, out += 4) + { + VectorCopy(bl, n); + VectorNormalize(n); + l = (int)(n[0] * 128 + 128);out[2] = bound(0, l, 255); + l = (int)(n[1] * 128 + 128);out[1] = bound(0, l, 255); + l = (int)(n[2] * 128 + 128);out[0] = bound(0, l, 255); + out[3] = 255; + } + R_UpdateTexture(surface->deluxemaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1); + } +} + +static void R_StainNode (mnode_t *node, dp_model_t *model, const vec3_t origin, float radius, const float fcolor[8]) +{ + float ndist, a, ratio, maxdist, maxdist2, maxdist3, invradius, sdtable[256], td, dist2; + msurface_t *surface, *endsurface; + int i, s, t, smax, tmax, smax3, impacts, impactt, stained; + unsigned char *bl; + vec3_t impact; + + maxdist = radius * radius; + invradius = 1.0f / radius; + +loc0: + if (!node->plane) + return; + ndist = PlaneDiff(origin, node->plane); + if (ndist > radius) + { + node = node->children[0]; + goto loc0; + } + if (ndist < -radius) + { + node = node->children[1]; + goto loc0; + } + + dist2 = ndist * ndist; + maxdist3 = maxdist - dist2; + + if (node->plane->type < 3) + { + VectorCopy(origin, impact); + impact[node->plane->type] -= ndist; + } + else + { + impact[0] = origin[0] - node->plane->normal[0] * ndist; + impact[1] = origin[1] - node->plane->normal[1] * ndist; + impact[2] = origin[2] - node->plane->normal[2] * ndist; + } + + for (surface = model->data_surfaces + node->firstsurface, endsurface = surface + node->numsurfaces;surface < endsurface;surface++) + { + if (surface->lightmapinfo->stainsamples) + { + smax = (surface->lightmapinfo->extents[0] >> 4) + 1; + tmax = (surface->lightmapinfo->extents[1] >> 4) + 1; + + impacts = (int)(DotProduct (impact, surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3] - surface->lightmapinfo->texturemins[0]); + impactt = (int)(DotProduct (impact, surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3] - surface->lightmapinfo->texturemins[1]); + + s = bound(0, impacts, smax * 16) - impacts; + t = bound(0, impactt, tmax * 16) - impactt; + i = (int)(s * s + t * t + dist2); + if ((i > maxdist) || (smax > (int)(sizeof(sdtable)/sizeof(sdtable[0])))) // smax overflow fix from Andreas Dehmel + continue; + + // reduce calculations + for (s = 0, i = impacts; s < smax; s++, i -= 16) + sdtable[s] = i * i + dist2; + + bl = surface->lightmapinfo->stainsamples; + smax3 = smax * 3; + stained = false; + + i = impactt; + for (t = 0;t < tmax;t++, i -= 16) + { + td = i * i; + // make sure some part of it is visible on this line + if (td < maxdist3) + { + maxdist2 = maxdist - td; + for (s = 0;s < smax;s++) + { + if (sdtable[s] < maxdist2) + { + ratio = lhrandom(0.0f, 1.0f); + a = (fcolor[3] + ratio * fcolor[7]) * (1.0f - sqrt(sdtable[s] + td) * invradius); + if (a >= (1.0f / 64.0f)) + { + if (a > 1) + a = 1; + bl[0] = (unsigned char) ((float) bl[0] + a * ((fcolor[0] + ratio * fcolor[4]) - (float) bl[0])); + bl[1] = (unsigned char) ((float) bl[1] + a * ((fcolor[1] + ratio * fcolor[5]) - (float) bl[1])); + bl[2] = (unsigned char) ((float) bl[2] + a * ((fcolor[2] + ratio * fcolor[6]) - (float) bl[2])); + stained = true; + } + } + bl += 3; + } + } + else // skip line + bl += smax3; + } + // force lightmap upload + if (stained) + model->brushq1.lightmapupdateflags[surface - model->data_surfaces] = true; + } + } + + if (node->children[0]->plane) + { + if (node->children[1]->plane) + { + R_StainNode(node->children[0], model, origin, radius, fcolor); + node = node->children[1]; + goto loc0; + } + else + { + node = node->children[0]; + goto loc0; + } + } + else if (node->children[1]->plane) + { + node = node->children[1]; + goto loc0; + } +} + +void R_Stain (const vec3_t origin, float radius, int cr1, int cg1, int cb1, int ca1, int cr2, int cg2, int cb2, int ca2) +{ + int n; + float fcolor[8]; + entity_render_t *ent; + dp_model_t *model; + vec3_t org; + if (r_refdef.scene.worldmodel == NULL || !r_refdef.scene.worldmodel->brush.data_nodes || !r_refdef.scene.worldmodel->brushq1.lightdata) + return; + fcolor[0] = cr1; + fcolor[1] = cg1; + fcolor[2] = cb1; + fcolor[3] = ca1 * (1.0f / 64.0f); + fcolor[4] = cr2 - cr1; + fcolor[5] = cg2 - cg1; + fcolor[6] = cb2 - cb1; + fcolor[7] = (ca2 - ca1) * (1.0f / 64.0f); + + R_StainNode(r_refdef.scene.worldmodel->brush.data_nodes + r_refdef.scene.worldmodel->brushq1.hulls[0].firstclipnode, r_refdef.scene.worldmodel, origin, radius, fcolor); + + // look for embedded bmodels + for (n = 0;n < cl.num_brushmodel_entities;n++) + { + ent = &cl.entities[cl.brushmodel_entities[n]].render; + model = ent->model; + if (model && model->name[0] == '*') + { + if (model->brush.data_nodes) + { + Matrix4x4_Transform(&ent->inversematrix, origin, org); + R_StainNode(model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode, model, org, radius, fcolor); + } + } + } +} + + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +static void R_DrawPortal_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + // due to the hacky nature of this function's parameters, this is never + // called with a batch, so numsurfaces is always 1, and the surfacelist + // contains only a leaf number for coloring purposes + const mportal_t *portal = (mportal_t *)ent; + qboolean isvis; + int i, numpoints; + float *v; + float vertex3f[POLYGONELEMENTS_MAXPOINTS*3]; + CHECKGLERROR + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + GL_DepthTest(true); + GL_CullFace(GL_NONE); + R_EntityMatrix(&identitymatrix); + + numpoints = min(portal->numpoints, POLYGONELEMENTS_MAXPOINTS); + +// R_Mesh_ResetTextureState(); + + isvis = (portal->here->clusterindex >= 0 && portal->past->clusterindex >= 0 && portal->here->clusterindex != portal->past->clusterindex); + + i = surfacelist[0] >> 1; + GL_Color(((i & 0x0007) >> 0) * (1.0f / 7.0f) * r_refdef.view.colorscale, + ((i & 0x0038) >> 3) * (1.0f / 7.0f) * r_refdef.view.colorscale, + ((i & 0x01C0) >> 6) * (1.0f / 7.0f) * r_refdef.view.colorscale, + isvis ? 0.125f : 0.03125f); + for (i = 0, v = vertex3f;i < numpoints;i++, v += 3) + VectorCopy(portal->points[i].position, v); + R_Mesh_PrepareVertices_Generic_Arrays(numpoints, vertex3f, NULL, NULL); + R_SetupShader_Generic_NoTexture(false, false); + R_Mesh_Draw(0, numpoints, 0, numpoints - 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); +} + +// LordHavoc: this is just a nice debugging tool, very slow +void R_DrawPortals(void) +{ + int i, leafnum; + mportal_t *portal; + float center[3], f; + dp_model_t *model = r_refdef.scene.worldmodel; + if (model == NULL) + return; + for (leafnum = 0;leafnum < r_refdef.scene.worldmodel->brush.num_leafs;leafnum++) + { + if (r_refdef.viewcache.world_leafvisible[leafnum]) + { + //for (portalnum = 0, portal = model->brush.data_portals;portalnum < model->brush.num_portals;portalnum++, portal++) + for (portal = r_refdef.scene.worldmodel->brush.data_leafs[leafnum].portals;portal;portal = portal->next) + { + if (portal->numpoints <= POLYGONELEMENTS_MAXPOINTS) + if (!R_CullBox(portal->mins, portal->maxs)) + { + VectorClear(center); + for (i = 0;i < portal->numpoints;i++) + VectorAdd(center, portal->points[i].position, center); + f = ixtable[portal->numpoints]; + VectorScale(center, f, center); + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, R_DrawPortal_Callback, (entity_render_t *)portal, leafnum, rsurface.rtlight); + } + } + } + } +} + +static void R_View_WorldVisibility_CullSurfaces(void) +{ + int surfaceindex; + int surfaceindexstart; + int surfaceindexend; + unsigned char *surfacevisible; + msurface_t *surfaces; + dp_model_t *model = r_refdef.scene.worldmodel; + if (!model) + return; + if (r_trippy.integer) + return; + if (r_usesurfaceculling.integer < 1) + return; + surfaceindexstart = model->firstmodelsurface; + surfaceindexend = surfaceindexstart + model->nummodelsurfaces; + surfaces = model->data_surfaces; + surfacevisible = r_refdef.viewcache.world_surfacevisible; + for (surfaceindex = surfaceindexstart;surfaceindex < surfaceindexend;surfaceindex++) + if (surfacevisible[surfaceindex] && R_CullBox(surfaces[surfaceindex].mins, surfaces[surfaceindex].maxs)) + surfacevisible[surfaceindex] = 0; +} + +void R_View_WorldVisibility(qboolean forcenovis) +{ + int i, j, *mark; + mleaf_t *leaf; + mleaf_t *viewleaf; + dp_model_t *model = r_refdef.scene.worldmodel; + + if (!model) + return; + + if (r_refdef.view.usecustompvs) + { + // clear the visible surface and leaf flags arrays + memset(r_refdef.viewcache.world_surfacevisible, 0, model->num_surfaces); + memset(r_refdef.viewcache.world_leafvisible, 0, model->brush.num_leafs); + r_refdef.viewcache.world_novis = false; + + // simply cull each marked leaf to the frustum (view pyramid) + for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) + { + // if leaf is in current pvs and on the screen, mark its surfaces + if (CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex) && !R_CullBox(leaf->mins, leaf->maxs)) + { + r_refdef.stats[r_stat_world_leafs]++; + r_refdef.viewcache.world_leafvisible[j] = true; + if (leaf->numleafsurfaces) + for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) + r_refdef.viewcache.world_surfacevisible[*mark] = true; + } + } + R_View_WorldVisibility_CullSurfaces(); + return; + } + + // if possible find the leaf the view origin is in + viewleaf = model->brush.PointInLeaf ? model->brush.PointInLeaf(model, r_refdef.view.origin) : NULL; + // if possible fetch the visible cluster bits + if (!r_lockpvs.integer && model->brush.FatPVS) + model->brush.FatPVS(model, r_refdef.view.origin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); + + if (!r_lockvisibility.integer) + { + // clear the visible surface and leaf flags arrays + memset(r_refdef.viewcache.world_surfacevisible, 0, model->num_surfaces); + memset(r_refdef.viewcache.world_leafvisible, 0, model->brush.num_leafs); + + r_refdef.viewcache.world_novis = false; + + // if floating around in the void (no pvs data available, and no + // portals available), simply use all on-screen leafs. + if (!viewleaf || viewleaf->clusterindex < 0 || forcenovis || r_trippy.integer) + { + // no visibility method: (used when floating around in the void) + // simply cull each leaf to the frustum (view pyramid) + // similar to quake's RecursiveWorldNode but without cache misses + r_refdef.viewcache.world_novis = true; + for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) + { + if (leaf->clusterindex < 0) + continue; + // if leaf is in current pvs and on the screen, mark its surfaces + if (!R_CullBox(leaf->mins, leaf->maxs)) + { + r_refdef.stats[r_stat_world_leafs]++; + r_refdef.viewcache.world_leafvisible[j] = true; + if (leaf->numleafsurfaces) + for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) + r_refdef.viewcache.world_surfacevisible[*mark] = true; + } + } + } + // just check if each leaf in the PVS is on screen + // (unless portal culling is enabled) + else if (!model->brush.data_portals || r_useportalculling.integer < 1 || (r_useportalculling.integer < 2 && !r_novis.integer)) + { + // pvs method: + // simply check if each leaf is in the Potentially Visible Set, + // and cull to frustum (view pyramid) + // similar to quake's RecursiveWorldNode but without cache misses + for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) + { + if (leaf->clusterindex < 0) + continue; + // if leaf is in current pvs and on the screen, mark its surfaces + if (CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex) && !R_CullBox(leaf->mins, leaf->maxs)) + { + r_refdef.stats[r_stat_world_leafs]++; + r_refdef.viewcache.world_leafvisible[j] = true; + if (leaf->numleafsurfaces) + for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) + r_refdef.viewcache.world_surfacevisible[*mark] = true; + } + } + } + // if desired use a recursive portal flow, culling each portal to + // frustum and checking if the leaf the portal leads to is in the pvs + else + { + int leafstackpos; + mportal_t *p; + mleaf_t *leafstack[8192]; + // simple-frustum portal method: + // follows portals leading outward from viewleaf, does not venture + // offscreen or into leafs that are not visible, faster than + // Quake's RecursiveWorldNode and vastly better in unvised maps, + // often culls some surfaces that pvs alone would miss + // (such as a room in pvs that is hidden behind a wall, but the + // passage leading to the room is off-screen) + leafstack[0] = viewleaf; + leafstackpos = 1; + while (leafstackpos) + { + leaf = leafstack[--leafstackpos]; + if (r_refdef.viewcache.world_leafvisible[leaf - model->brush.data_leafs]) + continue; + if (leaf->clusterindex < 0) + continue; + r_refdef.stats[r_stat_world_leafs]++; + r_refdef.viewcache.world_leafvisible[leaf - model->brush.data_leafs] = true; + // mark any surfaces bounding this leaf + if (leaf->numleafsurfaces) + for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) + r_refdef.viewcache.world_surfacevisible[*mark] = true; + // follow portals into other leafs + // the checks are: + // if viewer is behind portal (portal faces outward into the scene) + // and the portal polygon's bounding box is on the screen + // and the leaf has not been visited yet + // and the leaf is visible in the pvs + // (the first two checks won't cause as many cache misses as the leaf checks) + for (p = leaf->portals;p;p = p->next) + { + r_refdef.stats[r_stat_world_portals]++; + if (DotProduct(r_refdef.view.origin, p->plane.normal) < (p->plane.dist + 1) + && !r_refdef.viewcache.world_leafvisible[p->past - model->brush.data_leafs] + && CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, p->past->clusterindex) + && !R_CullBox(p->mins, p->maxs) + && leafstackpos < (int)(sizeof(leafstack) / sizeof(leafstack[0]))) + leafstack[leafstackpos++] = p->past; + } + } + } + } + + R_View_WorldVisibility_CullSurfaces(); +} + +void R_Q1BSP_DrawSky(entity_render_t *ent) +{ + if (ent->model == NULL) + return; + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(true, true, false, false, false); + else + R_DrawModelSurfaces(ent, true, true, false, false, false); +} + +void R_Q1BSP_DrawAddWaterPlanes(entity_render_t *ent) +{ + int i, j, n, flagsmask; + dp_model_t *model = ent->model; + msurface_t *surfaces; + if (model == NULL) + return; + + if (ent == r_refdef.scene.worldentity) + RSurf_ActiveWorldEntity(); + else + RSurf_ActiveModelEntity(ent, true, false, false); + + surfaces = model->data_surfaces; + flagsmask = MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA; + + // add visible surfaces to draw list + if (ent == r_refdef.scene.worldentity) + { + for (i = 0;i < model->nummodelsurfaces;i++) + { + j = model->sortedmodelsurfaces[i]; + if (r_refdef.viewcache.world_surfacevisible[j]) + if (surfaces[j].texture->basematerialflags & flagsmask) + R_Water_AddWaterPlane(surfaces + j, 0); + } + } + else + { + if(ent->entitynumber >= MAX_EDICTS) // && CL_VM_TransformView(ent->entitynumber - MAX_EDICTS, NULL, NULL, NULL)) + n = ent->entitynumber; + else + n = 0; + for (i = 0;i < model->nummodelsurfaces;i++) + { + j = model->sortedmodelsurfaces[i]; + if (surfaces[j].texture->basematerialflags & flagsmask) + R_Water_AddWaterPlane(surfaces + j, n); + } + } + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_Q1BSP_Draw(entity_render_t *ent) +{ + dp_model_t *model = ent->model; + if (model == NULL) + return; + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(false, true, false, false, false); + else + R_DrawModelSurfaces(ent, false, true, false, false, false); +} + +void R_Q1BSP_DrawDepth(entity_render_t *ent) +{ + dp_model_t *model = ent->model; + if (model == NULL || model->surfmesh.isanimated) + return; + GL_ColorMask(0,0,0,0); + GL_Color(1,1,1,1); + GL_DepthTest(true); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthMask(true); +// R_Mesh_ResetTextureState(); + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(false, false, true, false, false); + else + R_DrawModelSurfaces(ent, false, false, true, false, false); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); +} + +void R_Q1BSP_DrawDebug(entity_render_t *ent) +{ + if (ent->model == NULL) + return; + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(false, false, false, true, false); + else + R_DrawModelSurfaces(ent, false, false, false, true, false); +} + +void R_Q1BSP_DrawPrepass(entity_render_t *ent) +{ + dp_model_t *model = ent->model; + if (model == NULL) + return; + if (ent == r_refdef.scene.worldentity) + R_DrawWorldSurfaces(false, true, false, false, true); + else + R_DrawModelSurfaces(ent, false, true, false, false, true); +} + +typedef struct r_q1bsp_getlightinfo_s +{ + dp_model_t *model; + vec3_t relativelightorigin; + float lightradius; + int *outleaflist; + unsigned char *outleafpvs; + int outnumleafs; + unsigned char *visitingleafpvs; + int *outsurfacelist; + unsigned char *outsurfacepvs; + unsigned char *tempsurfacepvs; + unsigned char *outshadowtrispvs; + unsigned char *outlighttrispvs; + int outnumsurfaces; + vec3_t outmins; + vec3_t outmaxs; + vec3_t lightmins; + vec3_t lightmaxs; + const unsigned char *pvs; + qboolean svbsp_active; + qboolean svbsp_insertoccluder; + int numfrustumplanes; + const mplane_t *frustumplanes; +} +r_q1bsp_getlightinfo_t; + +#define GETLIGHTINFO_MAXNODESTACK 4096 + +static void R_Q1BSP_RecursiveGetLightInfo_BSP(r_q1bsp_getlightinfo_t *info, qboolean skipsurfaces) +{ + // nodestack + mnode_t *nodestack[GETLIGHTINFO_MAXNODESTACK]; + int nodestackpos = 0; + // node processing + mplane_t *plane; + mnode_t *node; + int sides; + // leaf processing + mleaf_t *leaf; + const msurface_t *surface; + const msurface_t *surfaces = info->model->data_surfaces; + int numleafsurfaces; + int leafsurfaceindex; + int surfaceindex; + int triangleindex, t; + int currentmaterialflags; + qboolean castshadow; + const int *e; + const vec_t *v[3]; + float v2[3][3]; + qboolean insidebox; + qboolean frontsidecasting = r_shadow_frontsidecasting.integer != 0; + qboolean svbspactive = info->svbsp_active; + qboolean svbspinsertoccluder = info->svbsp_insertoccluder; + const int *leafsurfaceindices; + qboolean addedtris; + int i; + mportal_t *portal; + static float points[128][3]; + // push the root node onto our nodestack + nodestack[nodestackpos++] = info->model->brush.data_nodes; + // we'll be done when the nodestack is empty + while (nodestackpos) + { + // get a node from the stack to process + node = nodestack[--nodestackpos]; + // is it a node or a leaf? + plane = node->plane; + if (plane) + { + // node +#if 0 + if (!BoxesOverlap(info->lightmins, info->lightmaxs, node->mins, node->maxs)) + continue; +#endif +#if 0 + if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(node->mins, node->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) + continue; +#endif + // axial planes can be processed much more quickly + if (plane->type < 3) + { + // axial plane + if (info->lightmins[plane->type] > plane->dist) + nodestack[nodestackpos++] = node->children[0]; + else if (info->lightmaxs[plane->type] < plane->dist) + nodestack[nodestackpos++] = node->children[1]; + else + { + // recurse front side first because the svbsp building prefers it + if (info->relativelightorigin[plane->type] >= plane->dist) + { + if (nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->children[0]; + nodestack[nodestackpos++] = node->children[1]; + } + else + { + if (nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->children[1]; + nodestack[nodestackpos++] = node->children[0]; + } + } + } + else + { + // sloped plane + sides = BoxOnPlaneSide(info->lightmins, info->lightmaxs, plane); + switch (sides) + { + default: + continue; // ERROR: NAN bounding box! + case 1: + nodestack[nodestackpos++] = node->children[0]; + break; + case 2: + nodestack[nodestackpos++] = node->children[1]; + break; + case 3: + // recurse front side first because the svbsp building prefers it + if (PlaneDist(info->relativelightorigin, plane) >= 0) + { + if (nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->children[0]; + nodestack[nodestackpos++] = node->children[1]; + } + else + { + if (nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->children[1]; + nodestack[nodestackpos++] = node->children[0]; + } + break; + } + } + } + else + { + // leaf + leaf = (mleaf_t *)node; +#if 1 + if (r_shadow_frontsidecasting.integer && info->pvs != NULL && !CHECKPVSBIT(info->pvs, leaf->clusterindex)) + continue; +#endif +#if 1 + if (!BoxesOverlap(info->lightmins, info->lightmaxs, leaf->mins, leaf->maxs)) + continue; +#endif +#if 1 + if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(leaf->mins, leaf->maxs, info->numfrustumplanes, info->frustumplanes)) + continue; +#endif + + if (svbspactive) + { + // we can occlusion test the leaf by checking if all of its portals + // are occluded (unless the light is in this leaf - but that was + // already handled by the caller) + for (portal = leaf->portals;portal;portal = portal->next) + { + for (i = 0;i < portal->numpoints;i++) + VectorCopy(portal->points[i].position, points[i]); + if (SVBSP_AddPolygon(&r_svbsp, portal->numpoints, points[0], false, NULL, NULL, 0) & 2) + break; + } + if (leaf->portals && portal == NULL) + continue; // no portals of this leaf visible + } + + // add this leaf to the reduced light bounds + info->outmins[0] = min(info->outmins[0], leaf->mins[0]); + info->outmins[1] = min(info->outmins[1], leaf->mins[1]); + info->outmins[2] = min(info->outmins[2], leaf->mins[2]); + info->outmaxs[0] = max(info->outmaxs[0], leaf->maxs[0]); + info->outmaxs[1] = max(info->outmaxs[1], leaf->maxs[1]); + info->outmaxs[2] = max(info->outmaxs[2], leaf->maxs[2]); + + // mark this leaf as being visible to the light + if (info->outleafpvs) + { + int leafindex = leaf - info->model->brush.data_leafs; + if (!CHECKPVSBIT(info->outleafpvs, leafindex)) + { + SETPVSBIT(info->outleafpvs, leafindex); + info->outleaflist[info->outnumleafs++] = leafindex; + } + } + + // when using BIH, we skip the surfaces here + if (skipsurfaces) + continue; + + // iterate the surfaces linked by this leaf and check their triangles + leafsurfaceindices = leaf->firstleafsurface; + numleafsurfaces = leaf->numleafsurfaces; + if (svbspinsertoccluder) + { + for (leafsurfaceindex = 0;leafsurfaceindex < numleafsurfaces;leafsurfaceindex++) + { + surfaceindex = leafsurfaceindices[leafsurfaceindex]; + if (CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) + continue; + SETPVSBIT(info->outsurfacepvs, surfaceindex); + surface = surfaces + surfaceindex; + if (!BoxesOverlap(info->lightmins, info->lightmaxs, surface->mins, surface->maxs)) + continue; + currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; + castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); + if (!castshadow) + continue; + insidebox = BoxInsideBox(surface->mins, surface->maxs, info->lightmins, info->lightmaxs); + for (triangleindex = 0, t = surface->num_firstshadowmeshtriangle, e = info->model->brush.shadowmesh->element3i + t * 3;triangleindex < surface->num_triangles;triangleindex++, t++, e += 3) + { + v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; + v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; + v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; + VectorCopy(v[0], v2[0]); + VectorCopy(v[1], v2[1]); + VectorCopy(v[2], v2[2]); + if (insidebox || TriangleBBoxOverlapsBox(v2[0], v2[1], v2[2], info->lightmins, info->lightmaxs)) + SVBSP_AddPolygon(&r_svbsp, 3, v2[0], true, NULL, NULL, 0); + } + } + } + else + { + for (leafsurfaceindex = 0;leafsurfaceindex < numleafsurfaces;leafsurfaceindex++) + { + surfaceindex = leafsurfaceindices[leafsurfaceindex]; + if (CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) + continue; + SETPVSBIT(info->outsurfacepvs, surfaceindex); + surface = surfaces + surfaceindex; + if (!BoxesOverlap(info->lightmins, info->lightmaxs, surface->mins, surface->maxs)) + continue; + addedtris = false; + currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; + castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); + insidebox = BoxInsideBox(surface->mins, surface->maxs, info->lightmins, info->lightmaxs); + for (triangleindex = 0, t = surface->num_firstshadowmeshtriangle, e = info->model->brush.shadowmesh->element3i + t * 3;triangleindex < surface->num_triangles;triangleindex++, t++, e += 3) + { + v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; + v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; + v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; + VectorCopy(v[0], v2[0]); + VectorCopy(v[1], v2[1]); + VectorCopy(v[2], v2[2]); + if (!insidebox && !TriangleBBoxOverlapsBox(v2[0], v2[1], v2[2], info->lightmins, info->lightmaxs)) + continue; + if (svbspactive && !(SVBSP_AddPolygon(&r_svbsp, 3, v2[0], false, NULL, NULL, 0) & 2)) + continue; + // we don't omit triangles from lighting even if they are + // backfacing, because when using shadowmapping they are often + // not fully occluded on the horizon of an edge + SETPVSBIT(info->outlighttrispvs, t); + addedtris = true; + if (castshadow) + { + if (currentmaterialflags & MATERIALFLAG_NOCULLFACE) + { + // if the material is double sided we + // can't cull by direction + SETPVSBIT(info->outshadowtrispvs, t); + } + else if (frontsidecasting) + { + // front side casting occludes backfaces, + // so they are completely useless as both + // casters and lit polygons + if (PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) + SETPVSBIT(info->outshadowtrispvs, t); + } + else + { + // back side casting does not occlude + // anything so we can't cull lit polygons + if (!PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) + SETPVSBIT(info->outshadowtrispvs, t); + } + } + } + if (addedtris) + info->outsurfacelist[info->outnumsurfaces++] = surfaceindex; + } + } + } + } +} + +static void R_Q1BSP_RecursiveGetLightInfo_BIH(r_q1bsp_getlightinfo_t *info, const bih_t *bih) +{ + bih_leaf_t *leaf; + bih_node_t *node; + int nodenum; + int axis; + int surfaceindex; + int t; + int nodeleafindex; + int currentmaterialflags; + qboolean castshadow; + msurface_t *surface; + const int *e; + const vec_t *v[3]; + float v2[3][3]; + int nodestack[GETLIGHTINFO_MAXNODESTACK]; + int nodestackpos = 0; + // note: because the BSP leafs are not in the BIH tree, the _BSP function + // must be called to mark leafs visible for entity culling... + // we start at the root node + nodestack[nodestackpos++] = bih->rootnode; + // we'll be done when the stack is empty + while (nodestackpos) + { + // pop one off the stack to process + nodenum = nodestack[--nodestackpos]; + // node + node = bih->nodes + nodenum; + if (node->type == BIH_UNORDERED) + { + for (nodeleafindex = 0;nodeleafindex < BIH_MAXUNORDEREDCHILDREN && node->children[nodeleafindex] >= 0;nodeleafindex++) + { + leaf = bih->leafs + node->children[nodeleafindex]; + if (leaf->type != BIH_RENDERTRIANGLE) + continue; +#if 1 + if (!BoxesOverlap(info->lightmins, info->lightmaxs, leaf->mins, leaf->maxs)) + continue; +#endif +#if 1 + if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(leaf->mins, leaf->maxs, info->numfrustumplanes, info->frustumplanes)) + continue; +#endif + surfaceindex = leaf->surfaceindex; + surface = info->model->data_surfaces + surfaceindex; + currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; + castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); + t = leaf->itemindex + surface->num_firstshadowmeshtriangle - surface->num_firsttriangle; + e = info->model->brush.shadowmesh->element3i + t * 3; + v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; + v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; + v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; + VectorCopy(v[0], v2[0]); + VectorCopy(v[1], v2[1]); + VectorCopy(v[2], v2[2]); + if (info->svbsp_insertoccluder) + { + if (castshadow) + SVBSP_AddPolygon(&r_svbsp, 3, v2[0], true, NULL, NULL, 0); + continue; + } + if (info->svbsp_active && !(SVBSP_AddPolygon(&r_svbsp, 3, v2[0], false, NULL, NULL, 0) & 2)) + continue; + // we don't occlude triangles from lighting even + // if they are backfacing, because when using + // shadowmapping they are often not fully occluded + // on the horizon of an edge + SETPVSBIT(info->outlighttrispvs, t); + if (castshadow) + { + if (currentmaterialflags & MATERIALFLAG_NOCULLFACE) + { + // if the material is double sided we + // can't cull by direction + SETPVSBIT(info->outshadowtrispvs, t); + } + else if (r_shadow_frontsidecasting.integer) + { + // front side casting occludes backfaces, + // so they are completely useless as both + // casters and lit polygons + if (PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) + SETPVSBIT(info->outshadowtrispvs, t); + } + else + { + // back side casting does not occlude + // anything so we can't cull lit polygons + if (!PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) + SETPVSBIT(info->outshadowtrispvs, t); + } + } + if (!CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) + { + SETPVSBIT(info->outsurfacepvs, surfaceindex); + info->outsurfacelist[info->outnumsurfaces++] = surfaceindex; + } + } + } + else + { + axis = node->type - BIH_SPLITX; +#if 0 + if (!BoxesOverlap(info->lightmins, info->lightmaxs, node->mins, node->maxs)) + continue; +#endif +#if 0 + if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(node->mins, node->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) + continue; +#endif + if (info->lightmins[axis] <= node->backmax) + { + if (info->lightmaxs[axis] >= node->frontmin && nodestackpos < GETLIGHTINFO_MAXNODESTACK) + nodestack[nodestackpos++] = node->front; + nodestack[nodestackpos++] = node->back; + continue; + } + else if (info->lightmaxs[axis] >= node->frontmin) + { + nodestack[nodestackpos++] = node->front; + continue; + } + else + continue; // light falls between children, nothing here + } + } +} + +static void R_Q1BSP_CallRecursiveGetLightInfo(r_q1bsp_getlightinfo_t *info, qboolean use_svbsp) +{ + extern cvar_t r_shadow_usebihculling; + if (use_svbsp) + { + float origin[3]; + VectorCopy(info->relativelightorigin, origin); + r_svbsp.maxnodes = max(r_svbsp.maxnodes, 1<<12); + r_svbsp.nodes = (svbsp_node_t*) R_FrameData_Alloc(r_svbsp.maxnodes * sizeof(svbsp_node_t)); + info->svbsp_active = true; + info->svbsp_insertoccluder = true; + for (;;) + { + SVBSP_Init(&r_svbsp, origin, r_svbsp.maxnodes, r_svbsp.nodes); + R_Q1BSP_RecursiveGetLightInfo_BSP(info, false); + // if that failed, retry with more nodes + if (r_svbsp.ranoutofnodes) + { + // an upper limit is imposed + if (r_svbsp.maxnodes >= 2<<22) + break; + r_svbsp.maxnodes *= 2; + r_svbsp.nodes = (svbsp_node_t*) R_FrameData_Alloc(r_svbsp.maxnodes * sizeof(svbsp_node_t)); + //Mem_Free(r_svbsp.nodes); + //r_svbsp.nodes = (svbsp_node_t*) Mem_Alloc(tempmempool, r_svbsp.maxnodes * sizeof(svbsp_node_t)); + } + else + break; + } + // now clear the visibility arrays because we need to redo it + info->outnumleafs = 0; + info->outnumsurfaces = 0; + memset(info->outleafpvs, 0, (info->model->brush.num_leafs + 7) >> 3); + memset(info->outsurfacepvs, 0, (info->model->nummodelsurfaces + 7) >> 3); + if (info->model->brush.shadowmesh) + memset(info->outshadowtrispvs, 0, (info->model->brush.shadowmesh->numtriangles + 7) >> 3); + else + memset(info->outshadowtrispvs, 0, (info->model->surfmesh.num_triangles + 7) >> 3); + memset(info->outlighttrispvs, 0, (info->model->surfmesh.num_triangles + 7) >> 3); + } + else + info->svbsp_active = false; + + // we HAVE to mark the leaf the light is in as lit, because portals are + // irrelevant to a leaf that the light source is inside of + // (and they are all facing away, too) + { + mnode_t *node = info->model->brush.data_nodes; + mleaf_t *leaf; + while (node->plane) + node = node->children[(node->plane->type < 3 ? info->relativelightorigin[node->plane->type] : DotProduct(info->relativelightorigin,node->plane->normal)) < node->plane->dist]; + leaf = (mleaf_t *)node; + info->outmins[0] = min(info->outmins[0], leaf->mins[0]); + info->outmins[1] = min(info->outmins[1], leaf->mins[1]); + info->outmins[2] = min(info->outmins[2], leaf->mins[2]); + info->outmaxs[0] = max(info->outmaxs[0], leaf->maxs[0]); + info->outmaxs[1] = max(info->outmaxs[1], leaf->maxs[1]); + info->outmaxs[2] = max(info->outmaxs[2], leaf->maxs[2]); + if (info->outleafpvs) + { + int leafindex = leaf - info->model->brush.data_leafs; + if (!CHECKPVSBIT(info->outleafpvs, leafindex)) + { + SETPVSBIT(info->outleafpvs, leafindex); + info->outleaflist[info->outnumleafs++] = leafindex; + } + } + } + + info->svbsp_insertoccluder = false; + // use BIH culling on single leaf maps (generally this only happens if running a model as a map), otherwise use BSP culling to make use of vis data + if (r_shadow_usebihculling.integer > 0 && (r_shadow_usebihculling.integer == 2 || info->model->brush.num_leafs == 1) && info->model->render_bih.leafs != NULL) + { + R_Q1BSP_RecursiveGetLightInfo_BSP(info, true); + R_Q1BSP_RecursiveGetLightInfo_BIH(info, &info->model->render_bih); + } + else + R_Q1BSP_RecursiveGetLightInfo_BSP(info, false); + // we're using temporary framedata memory, so this pointer will be invalid soon, clear it + r_svbsp.nodes = NULL; + if (developer_extra.integer && use_svbsp) + { + Con_DPrintf("GetLightInfo: svbsp built with %i nodes, polygon stats:\n", r_svbsp.numnodes); + Con_DPrintf("occluders: %i accepted, %i rejected, %i fragments accepted, %i fragments rejected.\n", r_svbsp.stat_occluders_accepted, r_svbsp.stat_occluders_rejected, r_svbsp.stat_occluders_fragments_accepted, r_svbsp.stat_occluders_fragments_rejected); + Con_DPrintf("queries : %i accepted, %i rejected, %i fragments accepted, %i fragments rejected.\n", r_svbsp.stat_queries_accepted, r_svbsp.stat_queries_rejected, r_svbsp.stat_queries_fragments_accepted, r_svbsp.stat_queries_fragments_rejected); + } +} + +static msurface_t *r_q1bsp_getlightinfo_surfaces; + +static int R_Q1BSP_GetLightInfo_comparefunc(const void *ap, const void *bp) +{ + int a = *(int*)ap; + int b = *(int*)bp; + const msurface_t *as = r_q1bsp_getlightinfo_surfaces + a; + const msurface_t *bs = r_q1bsp_getlightinfo_surfaces + b; + if (as->texture < bs->texture) + return -1; + if (as->texture > bs->texture) + return 1; + return a - b; +} + +extern cvar_t r_shadow_sortsurfaces; + +void R_Q1BSP_GetLightInfo(entity_render_t *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes) +{ + r_q1bsp_getlightinfo_t info; + VectorCopy(relativelightorigin, info.relativelightorigin); + info.lightradius = lightradius; + info.lightmins[0] = info.relativelightorigin[0] - info.lightradius; + info.lightmins[1] = info.relativelightorigin[1] - info.lightradius; + info.lightmins[2] = info.relativelightorigin[2] - info.lightradius; + info.lightmaxs[0] = info.relativelightorigin[0] + info.lightradius; + info.lightmaxs[1] = info.relativelightorigin[1] + info.lightradius; + info.lightmaxs[2] = info.relativelightorigin[2] + info.lightradius; + if (ent->model == NULL) + { + VectorCopy(info.lightmins, outmins); + VectorCopy(info.lightmaxs, outmaxs); + *outnumleafspointer = 0; + *outnumsurfacespointer = 0; + return; + } + info.model = ent->model; + info.outleaflist = outleaflist; + info.outleafpvs = outleafpvs; + info.outnumleafs = 0; + info.visitingleafpvs = visitingleafpvs; + info.outsurfacelist = outsurfacelist; + info.outsurfacepvs = outsurfacepvs; + info.outshadowtrispvs = outshadowtrispvs; + info.outlighttrispvs = outlighttrispvs; + info.outnumsurfaces = 0; + info.numfrustumplanes = numfrustumplanes; + info.frustumplanes = frustumplanes; + VectorCopy(info.relativelightorigin, info.outmins); + VectorCopy(info.relativelightorigin, info.outmaxs); + memset(visitingleafpvs, 0, (info.model->brush.num_leafs + 7) >> 3); + memset(outleafpvs, 0, (info.model->brush.num_leafs + 7) >> 3); + memset(outsurfacepvs, 0, (info.model->nummodelsurfaces + 7) >> 3); + if (info.model->brush.shadowmesh) + memset(outshadowtrispvs, 0, (info.model->brush.shadowmesh->numtriangles + 7) >> 3); + else + memset(outshadowtrispvs, 0, (info.model->surfmesh.num_triangles + 7) >> 3); + memset(outlighttrispvs, 0, (info.model->surfmesh.num_triangles + 7) >> 3); + if (info.model->brush.GetPVS && r_shadow_frontsidecasting.integer) + info.pvs = info.model->brush.GetPVS(info.model, info.relativelightorigin); + else + info.pvs = NULL; + RSurf_ActiveWorldEntity(); + + if (r_shadow_frontsidecasting.integer && r_shadow_compilingrtlight && r_shadow_realtime_world_compileportalculling.integer && info.model->brush.data_portals) + { + // use portal recursion for exact light volume culling, and exact surface checking + Portal_Visibility(info.model, info.relativelightorigin, info.outleaflist, info.outleafpvs, &info.outnumleafs, info.outsurfacelist, info.outsurfacepvs, &info.outnumsurfaces, NULL, 0, true, info.lightmins, info.lightmaxs, info.outmins, info.outmaxs, info.outshadowtrispvs, info.outlighttrispvs, info.visitingleafpvs); + } + else if (r_shadow_frontsidecasting.integer && r_shadow_realtime_dlight_portalculling.integer && info.model->brush.data_portals) + { + // use portal recursion for exact light volume culling, but not the expensive exact surface checking + Portal_Visibility(info.model, info.relativelightorigin, info.outleaflist, info.outleafpvs, &info.outnumleafs, info.outsurfacelist, info.outsurfacepvs, &info.outnumsurfaces, NULL, 0, r_shadow_realtime_dlight_portalculling.integer >= 2, info.lightmins, info.lightmaxs, info.outmins, info.outmaxs, info.outshadowtrispvs, info.outlighttrispvs, info.visitingleafpvs); + } + else + { + // recurse the bsp tree, checking leafs and surfaces for visibility + // optionally using svbsp for exact culling of compiled lights + // (or if the user enables dlight svbsp culling, which is mostly for + // debugging not actual use) + R_Q1BSP_CallRecursiveGetLightInfo(&info, (r_shadow_compilingrtlight ? r_shadow_realtime_world_compilesvbsp.integer : r_shadow_realtime_dlight_svbspculling.integer) != 0); + } + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + + // limit combined leaf box to light boundaries + outmins[0] = max(info.outmins[0] - 1, info.lightmins[0]); + outmins[1] = max(info.outmins[1] - 1, info.lightmins[1]); + outmins[2] = max(info.outmins[2] - 1, info.lightmins[2]); + outmaxs[0] = min(info.outmaxs[0] + 1, info.lightmaxs[0]); + outmaxs[1] = min(info.outmaxs[1] + 1, info.lightmaxs[1]); + outmaxs[2] = min(info.outmaxs[2] + 1, info.lightmaxs[2]); + + *outnumleafspointer = info.outnumleafs; + *outnumsurfacespointer = info.outnumsurfaces; + + // now sort surfaces by texture for faster rendering + r_q1bsp_getlightinfo_surfaces = info.model->data_surfaces; + if (r_shadow_sortsurfaces.integer) + qsort(info.outsurfacelist, info.outnumsurfaces, sizeof(*info.outsurfacelist), R_Q1BSP_GetLightInfo_comparefunc); +} + +void R_Q1BSP_CompileShadowVolume(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist) +{ + dp_model_t *model = ent->model; + msurface_t *surface; + int surfacelistindex; + float projectdistance = relativelightdirection ? lightradius : lightradius + model->radius*2 + r_shadow_projectdistance.value; + // if triangle neighbors are disabled, shadowvolumes are disabled + if (!model->brush.shadowmesh->neighbor3i) + return; + r_shadow_compilingrtlight->static_meshchain_shadow_zfail = Mod_ShadowMesh_Begin(r_main_mempool, 32768, 32768, NULL, NULL, NULL, false, false, true); + R_Shadow_PrepareShadowMark(model->brush.shadowmesh->numtriangles); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + surface = model->data_surfaces + surfacelist[surfacelistindex]; + if (surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW) + continue; + R_Shadow_MarkVolumeFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, relativelightorigin, relativelightdirection, r_shadow_compilingrtlight->cullmins, r_shadow_compilingrtlight->cullmaxs, surface->mins, surface->maxs); + } + R_Shadow_VolumeFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, model->brush.shadowmesh->neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); + r_shadow_compilingrtlight->static_meshchain_shadow_zfail = Mod_ShadowMesh_Finish(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zfail, false, false, true); +} + +extern cvar_t r_polygonoffset_submodel_factor; +extern cvar_t r_polygonoffset_submodel_offset; +void R_Q1BSP_DrawShadowVolume(entity_render_t *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const vec3_t lightmins, const vec3_t lightmaxs) +{ + dp_model_t *model = ent->model; + const msurface_t *surface; + int modelsurfacelistindex; + float projectdistance = relativelightdirection ? lightradius : lightradius + model->radius*2 + r_shadow_projectdistance.value; + // check the box in modelspace, it was already checked in worldspace + if (!BoxesOverlap(model->normalmins, model->normalmaxs, lightmins, lightmaxs)) + return; + R_FrameData_SetMark(); + if (ent->model->brush.submodel) + GL_PolygonOffset(r_refdef.shadowpolygonfactor + r_polygonoffset_submodel_factor.value, r_refdef.shadowpolygonoffset + r_polygonoffset_submodel_offset.value); + if (model->brush.shadowmesh) + { + // if triangle neighbors are disabled, shadowvolumes are disabled + if (!model->brush.shadowmesh->neighbor3i) + return; + R_Shadow_PrepareShadowMark(model->brush.shadowmesh->numtriangles); + for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) + { + surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; + if (R_GetCurrentTexture(surface->texture)->currentmaterialflags & MATERIALFLAG_NOSHADOW) + continue; + R_Shadow_MarkVolumeFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, relativelightorigin, relativelightdirection, lightmins, lightmaxs, surface->mins, surface->maxs); + } + R_Shadow_VolumeFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, model->brush.shadowmesh->neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); + } + else + { + // if triangle neighbors are disabled, shadowvolumes are disabled + if (!model->surfmesh.data_neighbor3i) + return; + projectdistance = lightradius + model->radius*2; + R_Shadow_PrepareShadowMark(model->surfmesh.num_triangles); + // identify lit faces within the bounding box + for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) + { + surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_NOSHADOW) + continue; + R_Shadow_MarkVolumeFromBox(surface->num_firsttriangle, surface->num_triangles, rsurface.modelvertex3f, rsurface.modelelement3i, relativelightorigin, relativelightdirection, lightmins, lightmaxs, surface->mins, surface->maxs); + } + R_Shadow_VolumeFromList(model->surfmesh.num_vertices, model->surfmesh.num_triangles, rsurface.modelvertex3f, model->surfmesh.data_element3i, model->surfmesh.data_neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); + } + if (ent->model->brush.submodel) + GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset); + R_FrameData_ReturnToMark(); +} + +void R_Q1BSP_CompileShadowMap(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist) +{ + dp_model_t *model = ent->model; + msurface_t *surface; + int surfacelistindex; + int sidetotals[6] = { 0, 0, 0, 0, 0, 0 }, sidemasks = 0; + int i; + if (!model->brush.shadowmesh) + return; + r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap = Mod_ShadowMesh_Begin(r_main_mempool, 32768, 32768, NULL, NULL, NULL, false, false, true); + R_Shadow_PrepareShadowSides(model->brush.shadowmesh->numtriangles); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + surface = model->data_surfaces + surfacelist[surfacelistindex]; + sidemasks |= R_Shadow_ChooseSidesFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, &r_shadow_compilingrtlight->matrix_worldtolight, relativelightorigin, relativelightdirection, r_shadow_compilingrtlight->cullmins, r_shadow_compilingrtlight->cullmaxs, surface->mins, surface->maxs, surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW ? NULL : sidetotals); + } + R_Shadow_ShadowMapFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, numshadowsides, sidetotals, shadowsides, shadowsideslist); + r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap = Mod_ShadowMesh_Finish(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap, false, false, true); + r_shadow_compilingrtlight->static_shadowmap_receivers &= sidemasks; + for(i = 0;i<6;i++) + if(!sidetotals[i]) + r_shadow_compilingrtlight->static_shadowmap_casters &= ~(1 << i); +} + +#define RSURF_MAX_BATCHSURFACES 8192 + +static const msurface_t *batchsurfacelist[RSURF_MAX_BATCHSURFACES]; + +void R_Q1BSP_DrawShadowMap(int side, entity_render_t *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs) +{ + dp_model_t *model = ent->model; + const msurface_t *surface; + int modelsurfacelistindex, batchnumsurfaces; + // check the box in modelspace, it was already checked in worldspace + if (!BoxesOverlap(model->normalmins, model->normalmaxs, lightmins, lightmaxs)) + return; + R_FrameData_SetMark(); + // identify lit faces within the bounding box + for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) + { + surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; + if (surfacesides && !(surfacesides[modelsurfacelistindex] && (1 << side))) + continue; + rsurface.texture = R_GetCurrentTexture(surface->texture); + if (rsurface.texture->currentmaterialflags & MATERIALFLAG_NOSHADOW) + continue; + if (!BoxesOverlap(lightmins, lightmaxs, surface->mins, surface->maxs)) + continue; + r_refdef.stats[r_stat_lights_dynamicshadowtriangles] += surface->num_triangles; + r_refdef.stats[r_stat_lights_shadowtriangles] += surface->num_triangles; + batchsurfacelist[0] = surface; + batchnumsurfaces = 1; + while(++modelsurfacelistindex < modelnumsurfaces && batchnumsurfaces < RSURF_MAX_BATCHSURFACES) + { + surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; + if (surfacesides && !(surfacesides[modelsurfacelistindex] & (1 << side))) + continue; + if (surface->texture != batchsurfacelist[0]->texture) + break; + if (!BoxesOverlap(lightmins, lightmaxs, surface->mins, surface->maxs)) + continue; + r_refdef.stats[r_stat_lights_dynamicshadowtriangles] += surface->num_triangles; + r_refdef.stats[r_stat_lights_shadowtriangles] += surface->num_triangles; + batchsurfacelist[batchnumsurfaces++] = surface; + } + --modelsurfacelistindex; + GL_CullFace(rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE ? GL_NONE : r_refdef.view.cullface_back); + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, batchnumsurfaces, batchsurfacelist); + R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + RSurf_DrawBatch(); + } + R_FrameData_ReturnToMark(); +} + +#define BATCHSIZE 1024 + +static void R_Q1BSP_DrawLight_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i, j, endsurface; + texture_t *t; + const msurface_t *surface; + R_FrameData_SetMark(); + // note: in practice this never actually receives batches + R_Shadow_RenderMode_Begin(); + R_Shadow_RenderMode_ActiveLight(rtlight); + R_Shadow_RenderMode_Lighting(false, true, false); + R_Shadow_SetupEntityLight(ent); + for (i = 0;i < numsurfaces;i = j) + { + j = i + 1; + surface = rsurface.modelsurfaces + surfacelist[i]; + t = surface->texture; + rsurface.texture = R_GetCurrentTexture(t); + endsurface = min(j + BATCHSIZE, numsurfaces); + for (j = i;j < endsurface;j++) + { + surface = rsurface.modelsurfaces + surfacelist[j]; + if (t != surface->texture) + break; + R_Shadow_RenderLighting(1, &surface); + } + } + R_Shadow_RenderMode_End(); + R_FrameData_ReturnToMark(); +} + +extern qboolean r_shadow_usingdeferredprepass; +void R_Q1BSP_DrawLight(entity_render_t *ent, int numsurfaces, const int *surfacelist, const unsigned char *lighttrispvs) +{ + dp_model_t *model = ent->model; + const msurface_t *surface; + int i, k, kend, l, endsurface, batchnumsurfaces, texturenumsurfaces; + const msurface_t **texturesurfacelist; + texture_t *tex; + CHECKGLERROR + R_FrameData_SetMark(); + // this is a double loop because non-visible surface skipping has to be + // fast, and even if this is not the world model (and hence no visibility + // checking) the input surface list and batch buffer are different formats + // so some processing is necessary. (luckily models have few surfaces) + for (i = 0;i < numsurfaces;) + { + batchnumsurfaces = 0; + endsurface = min(i + RSURF_MAX_BATCHSURFACES, numsurfaces); + if (ent == r_refdef.scene.worldentity) + { + for (;i < endsurface;i++) + if (r_refdef.viewcache.world_surfacevisible[surfacelist[i]]) + batchsurfacelist[batchnumsurfaces++] = model->data_surfaces + surfacelist[i]; + } + else + { + for (;i < endsurface;i++) + batchsurfacelist[batchnumsurfaces++] = model->data_surfaces + surfacelist[i]; + } + if (!batchnumsurfaces) + continue; + for (k = 0;k < batchnumsurfaces;k = kend) + { + surface = batchsurfacelist[k]; + tex = surface->texture; + rsurface.texture = R_GetCurrentTexture(tex); + // gather surfaces into a batch range + for (kend = k;kend < batchnumsurfaces && tex == batchsurfacelist[kend]->texture;kend++) + ; + // now figure out what to do with this particular range of surfaces + // VorteX: added MATERIALFLAG_NORTLIGHT + if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WALL | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NORTLIGHT)) != MATERIALFLAG_WALL) + continue; + if (r_fb.water.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA))) + continue; + if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) + { + vec3_t tempcenter, center; + for (l = k;l < kend;l++) + { + surface = batchsurfacelist[l]; + if (r_transparent_sortsurfacesbynearest.integer) + { + tempcenter[0] = bound(surface->mins[0], rsurface.localvieworigin[0], surface->maxs[0]); + tempcenter[1] = bound(surface->mins[1], rsurface.localvieworigin[1], surface->maxs[1]); + tempcenter[2] = bound(surface->mins[2], rsurface.localvieworigin[2], surface->maxs[2]); + } + else + { + tempcenter[0] = (surface->mins[0] + surface->maxs[0]) * 0.5f; + tempcenter[1] = (surface->mins[1] + surface->maxs[1]) * 0.5f; + tempcenter[2] = (surface->mins[2] + surface->maxs[2]) * 0.5f; + } + Matrix4x4_Transform(&rsurface.matrix, tempcenter, center); + if (ent->transparent_offset) // transparent offset + { + center[0] += r_refdef.view.forward[0]*ent->transparent_offset; + center[1] += r_refdef.view.forward[1]*ent->transparent_offset; + center[2] += r_refdef.view.forward[2]*ent->transparent_offset; + } + R_MeshQueue_AddTransparent((rsurface.entity->flags & RENDER_WORLDOBJECT) ? TRANSPARENTSORT_SKY : ((rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) ? TRANSPARENTSORT_HUD : rsurface.texture->transparentsort), center, R_Q1BSP_DrawLight_TransparentCallback, ent, surface - rsurface.modelsurfaces, rsurface.rtlight); + } + continue; + } + if (r_shadow_usingdeferredprepass) + continue; + texturenumsurfaces = kend - k; + texturesurfacelist = batchsurfacelist + k; + R_Shadow_RenderLighting(texturenumsurfaces, texturesurfacelist); + } + } + R_FrameData_ReturnToMark(); +} + +//Made by [515] +static void R_ReplaceWorldTexture (void) +{ + dp_model_t *m; + texture_t *t; + int i; + const char *r, *newt; + skinframe_t *skinframe; + if (!r_refdef.scene.worldmodel) + { + Con_Printf("There is no worldmodel\n"); + return; + } + m = r_refdef.scene.worldmodel; + + if(Cmd_Argc() < 2) + { + Con_Print("r_replacemaptexture - replaces texture\n"); + Con_Print("r_replacemaptexture - switch back to default texture\n"); + return; + } + if(!cl.islocalgame || !cl.worldmodel) + { + Con_Print("This command works only in singleplayer\n"); + return; + } + r = Cmd_Argv(1); + newt = Cmd_Argv(2); + if(!newt[0]) + newt = r; + for(i=0,t=m->data_textures;inum_textures;i++,t++) + { + if(/*t->width && !strcasecmp(t->name, r)*/ matchpattern( t->name, r, true ) ) + { + if ((skinframe = R_SkinFrame_LoadExternal(newt, TEXF_MIPMAP | TEXF_ALPHA | TEXF_PICMIP, true))) + { +// t->skinframes[0] = skinframe; + t->currentskinframe = skinframe; + Con_Printf("%s replaced with %s\n", r, newt); + } + else + { + Con_Printf("%s was not found\n", newt); + return; + } + } + } +} + +//Made by [515] +static void R_ListWorldTextures (void) +{ + dp_model_t *m; + texture_t *t; + int i; + if (!r_refdef.scene.worldmodel) + { + Con_Printf("There is no worldmodel\n"); + return; + } + m = r_refdef.scene.worldmodel; + + Con_Print("Worldmodel textures :\n"); + for(i=0,t=m->data_textures;inum_textures;i++,t++) + if (t->numskinframes) + Con_Printf("%s\n", t->name); +} + +#if 0 +static void gl_surf_start(void) +{ +} + +static void gl_surf_shutdown(void) +{ +} + +static void gl_surf_newmap(void) +{ +} +#endif + +void GL_Surf_Init(void) +{ + + Cvar_RegisterVariable(&r_ambient); + Cvar_RegisterVariable(&r_lockpvs); + Cvar_RegisterVariable(&r_lockvisibility); + Cvar_RegisterVariable(&r_useportalculling); + Cvar_RegisterVariable(&r_usesurfaceculling); + Cvar_RegisterVariable(&r_q3bsp_renderskydepth); + + Cmd_AddCommand ("r_replacemaptexture", R_ReplaceWorldTexture, "override a map texture for testing purposes"); + Cmd_AddCommand ("r_listmaptextures", R_ListWorldTextures, "list all textures used by the current map"); + + //R_RegisterModule("GL_Surf", gl_surf_start, gl_surf_shutdown, gl_surf_newmap); +} + diff --git a/app/jni/gl_textures.c b/app/jni/gl_textures.c new file mode 100644 index 0000000..8703bfe --- /dev/null +++ b/app/jni/gl_textures.c @@ -0,0 +1,2983 @@ + +#include "quakedef.h" +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +#endif +#include "image.h" +#include "jpeg.h" +#include "image_png.h" +#include "intoverflow.h" +#include "dpsoftrast.h" + +#ifndef GL_TEXTURE_3D +#define GL_TEXTURE_3D 0x806F +#endif + +cvar_t gl_max_size = {CVAR_SAVE, "gl_max_size", "2048", "maximum allowed texture size, can be used to reduce video memory usage, limited by hardware capabilities (typically 2048, 4096, or 8192)"}; +cvar_t gl_max_lightmapsize = {CVAR_SAVE, "gl_max_lightmapsize", "1024", "maximum allowed texture size for lightmap textures, use larger values to improve rendering speed, as long as there is enough video memory available (setting it too high for the hardware will cause very bad performance)"}; +cvar_t gl_picmip = {CVAR_SAVE, "gl_picmip", "0", "reduces resolution of textures by powers of 2, for example 1 will halve width/height, reducing texture memory usage by 75%"}; +cvar_t gl_picmip_world = {CVAR_SAVE, "gl_picmip_world", "0", "extra picmip level for world textures (may be negative, which will then reduce gl_picmip for these)"}; +cvar_t r_picmipworld = {CVAR_SAVE, "r_picmipworld", "1", "whether gl_picmip shall apply to world textures too (setting this to 0 is a shorthand for gl_picmip_world -9999999)"}; +cvar_t gl_picmip_sprites = {CVAR_SAVE, "gl_picmip_sprites", "0", "extra picmip level for sprite textures (may be negative, which will then reduce gl_picmip for these)"}; +cvar_t r_picmipsprites = {CVAR_SAVE, "r_picmipsprites", "1", "make gl_picmip affect sprites too (saves some graphics memory in sprite heavy games) (setting this to 0 is a shorthand for gl_picmip_sprites -9999999)"}; +cvar_t gl_picmip_other = {CVAR_SAVE, "gl_picmip_other", "0", "extra picmip level for other textures (may be negative, which will then reduce gl_picmip for these)"}; +cvar_t r_lerpimages = {CVAR_SAVE, "r_lerpimages", "1", "bilinear filters images when scaling them up to power of 2 size (mode 1), looks better than glquake (mode 0)"}; +cvar_t gl_texture_anisotropy = {CVAR_SAVE, "gl_texture_anisotropy", "1", "anisotropic filtering quality (if supported by hardware), 1 sample (no anisotropy) and 8 sample (8 tap anisotropy) are recommended values"}; +cvar_t gl_texturecompression = {CVAR_SAVE, "gl_texturecompression", "0", "whether to compress textures, a value of 0 disables compression (even if the individual cvars are 1), 1 enables fast (low quality) compression at startup, 2 enables slow (high quality) compression at startup"}; +cvar_t gl_texturecompression_color = {CVAR_SAVE, "gl_texturecompression_color", "1", "whether to compress colormap (diffuse) textures"}; +cvar_t gl_texturecompression_normal = {CVAR_SAVE, "gl_texturecompression_normal", "0", "whether to compress normalmap (normalmap) textures"}; +cvar_t gl_texturecompression_gloss = {CVAR_SAVE, "gl_texturecompression_gloss", "1", "whether to compress glossmap (specular) textures"}; +cvar_t gl_texturecompression_glow = {CVAR_SAVE, "gl_texturecompression_glow", "1", "whether to compress glowmap (luma) textures"}; +cvar_t gl_texturecompression_2d = {CVAR_SAVE, "gl_texturecompression_2d", "0", "whether to compress 2d (hud/menu) textures other than the font"}; +cvar_t gl_texturecompression_q3bsplightmaps = {CVAR_SAVE, "gl_texturecompression_q3bsplightmaps", "0", "whether to compress lightmaps in q3bsp format levels"}; +cvar_t gl_texturecompression_q3bspdeluxemaps = {CVAR_SAVE, "gl_texturecompression_q3bspdeluxemaps", "0", "whether to compress deluxemaps in q3bsp format levels (only levels compiled with q3map2 -deluxe have these)"}; +cvar_t gl_texturecompression_sky = {CVAR_SAVE, "gl_texturecompression_sky", "0", "whether to compress sky textures"}; +cvar_t gl_texturecompression_lightcubemaps = {CVAR_SAVE, "gl_texturecompression_lightcubemaps", "1", "whether to compress light cubemaps (spotlights and other light projection images)"}; +cvar_t gl_texturecompression_reflectmask = {CVAR_SAVE, "gl_texturecompression_reflectmask", "1", "whether to compress reflection cubemap masks (mask of which areas of the texture should reflect the generic shiny cubemap)"}; +cvar_t gl_texturecompression_sprites = {CVAR_SAVE, "gl_texturecompression_sprites", "1", "whether to compress sprites"}; +cvar_t gl_nopartialtextureupdates = {CVAR_SAVE, "gl_nopartialtextureupdates", "0", "use alternate path for dynamic lightmap updates that avoids a possibly slow code path in the driver"}; +cvar_t r_texture_dds_load_alphamode = {0, "r_texture_dds_load_alphamode", "1", "0: trust DDPF_ALPHAPIXELS flag, 1: texture format and brute force search if ambiguous, 2: texture format only"}; +cvar_t r_texture_dds_load_logfailure = {0, "r_texture_dds_load_logfailure", "0", "log missing DDS textures to ddstexturefailures.log, 0: done log, 1: log with no optional textures (_norm, glow etc.). 2: log all"}; +cvar_t r_texture_dds_swdecode = {0, "r_texture_dds_swdecode", "0", "0: don't software decode DDS, 1: software decode DDS if unsupported, 2: always software decode DDS"}; + +qboolean gl_filter_force = false; +int gl_filter_min = GL_LINEAR_MIPMAP_LINEAR; +int gl_filter_mag = GL_LINEAR; +DPSOFTRAST_TEXTURE_FILTER dpsoftrast_filter_mipmap = DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE; +DPSOFTRAST_TEXTURE_FILTER dpsoftrast_filter_nomipmap = DPSOFTRAST_TEXTURE_FILTER_LINEAR; + +#ifdef SUPPORTD3D +int d3d_filter_flatmin = D3DTEXF_LINEAR; +int d3d_filter_flatmag = D3DTEXF_LINEAR; +int d3d_filter_flatmix = D3DTEXF_POINT; +int d3d_filter_mipmin = D3DTEXF_LINEAR; +int d3d_filter_mipmag = D3DTEXF_LINEAR; +int d3d_filter_mipmix = D3DTEXF_LINEAR; +int d3d_filter_nomip = false; +#endif + + +static mempool_t *texturemempool; +static memexpandablearray_t texturearray; + +// note: this must not conflict with TEXF_ flags in r_textures.h +// bitmask for mismatch checking +#define GLTEXF_IMPORTANTBITS (0) +// dynamic texture (treat texnum == 0 differently) +#define GLTEXF_DYNAMIC 0x00080000 + +typedef struct textypeinfo_s +{ + const char *name; + textype_t textype; + int inputbytesperpixel; + int internalbytesperpixel; + float glinternalbytesperpixel; + int glinternalformat; + int glformat; + int gltype; +} +textypeinfo_t; + +#ifdef USE_GLES2 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83f2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83f3 +// framebuffer texture formats +// GLES2 devices rarely support depth textures, so we actually use a renderbuffer there +static textypeinfo_t textype_shadowmap16_comp = {"shadowmap16_comp", TEXTYPE_SHADOWMAP16_COMP , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT16, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_shadowmap16_raw = {"shadowmap16_raw", TEXTYPE_SHADOWMAP16_RAW , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT16, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_shadowmap24_comp = {"shadowmap24_comp", TEXTYPE_SHADOWMAP24_COMP , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_OES , GL_DEPTH_COMPONENT24_OES, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_shadowmap24_raw = {"shadowmap24_raw", TEXTYPE_SHADOWMAP24_RAW , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_OES , GL_DEPTH_COMPONENT24_OES, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_depth16 = {"depth16", TEXTYPE_DEPTHBUFFER16 , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT16, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_depth24 = {"depth24", TEXTYPE_DEPTHBUFFER24 , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_OES , GL_DEPTH_COMPONENT24_OES, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_depth24stencil8 = {"depth24stencil8", TEXTYPE_DEPTHBUFFER24STENCIL8, 2, 2, 2.0f, GL_DEPTH_COMPONENT , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_colorbuffer_32 = {"colorbuffer", TEXTYPE_COLORBUFFER , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE}; +static textypeinfo_t textype_colorbuffer16f_32 = {"colorbuffer16f", TEXTYPE_COLORBUFFER16F , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE}; +static textypeinfo_t textype_colorbuffer32f_32 = {"colorbuffer32f", TEXTYPE_COLORBUFFER32F , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE}; + +static textypeinfo_t textype_colorbuffer_16 = {"colorbuffer", TEXTYPE_COLORBUFFER , 2, 2, 2.0f, GL_RGB , GL_RGB , GL_UNSIGNED_SHORT_5_6_5}; +static textypeinfo_t textype_colorbuffer16f_16 = {"colorbuffer16f", TEXTYPE_COLORBUFFER16F , 2, 2, 2.0f, GL_RGB , GL_RGB , GL_UNSIGNED_SHORT_5_6_5}; +static textypeinfo_t textype_colorbuffer32f_16 = {"colorbuffer32f", TEXTYPE_COLORBUFFER32F , 2, 2, 2.0f, GL_RGB , GL_RGB , GL_UNSIGNED_SHORT_5_6_5}; + +// image formats: +static textypeinfo_t textype_alpha = {"alpha", TEXTYPE_ALPHA , 1, 4, 4.0f, GL_ALPHA , GL_ALPHA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_palette = {"palette", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_palette_alpha = {"palette_alpha", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba = {"rgba", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba_alpha = {"rgba_alpha", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra = {"bgra", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra_alpha = {"bgra_alpha", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_dxt1 = {"dxt1", TEXTYPE_DXT1 , 4, 0, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt1a = {"dxt1a", TEXTYPE_DXT1A , 4, 0, 0.5f, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt3 = {"dxt3", TEXTYPE_DXT3 , 4, 0, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt5 = {"dxt5", TEXTYPE_DXT5 , 4, 0, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , 0 , 0 }; +#else +// framebuffer texture formats +static textypeinfo_t textype_shadowmap16_comp = {"shadowmap16_comp", TEXTYPE_SHADOWMAP16_COMP , 2, 2, 2.0f, GL_DEPTH_COMPONENT16_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_shadowmap16_raw = {"shadowmap16_raw", TEXTYPE_SHADOWMAP16_RAW , 2, 2, 2.0f, GL_DEPTH_COMPONENT16_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_shadowmap24_comp = {"shadowmap24_comp", TEXTYPE_SHADOWMAP24_COMP , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; +static textypeinfo_t textype_shadowmap24_raw = {"shadowmap24_raw", TEXTYPE_SHADOWMAP24_RAW , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; +static textypeinfo_t textype_depth16 = {"depth16", TEXTYPE_DEPTHBUFFER16 , 2, 2, 2.0f, GL_DEPTH_COMPONENT16_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; +static textypeinfo_t textype_depth24 = {"depth24", TEXTYPE_DEPTHBUFFER24 , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; +static textypeinfo_t textype_depth24stencil8 = {"depth24stencil8", TEXTYPE_DEPTHBUFFER24STENCIL8, 4, 4, 4.0f, GL_DEPTH24_STENCIL8_EXT , GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT}; +static textypeinfo_t textype_colorbuffer = {"colorbuffer", TEXTYPE_COLORBUFFER , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_colorbuffer16f = {"colorbuffer16f", TEXTYPE_COLORBUFFER16F , 8, 8, 8.0f, GL_RGBA16F_ARB , GL_RGBA , GL_FLOAT }; +static textypeinfo_t textype_colorbuffer32f = {"colorbuffer32f", TEXTYPE_COLORBUFFER32F , 16, 16, 16.0f, GL_RGBA32F_ARB , GL_RGBA , GL_FLOAT }; + +// image formats: +static textypeinfo_t textype_alpha = {"alpha", TEXTYPE_ALPHA , 1, 4, 4.0f, GL_ALPHA , GL_ALPHA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_palette = {"palette", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGB , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_palette_alpha = {"palette_alpha", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba = {"rgba", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGB , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba_alpha = {"rgba_alpha", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba_compress = {"rgba_compress", TEXTYPE_RGBA , 4, 4, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_rgba_alpha_compress = {"rgba_alpha_compress", TEXTYPE_RGBA , 4, 4, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra = {"bgra", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGB , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra_alpha = {"bgra_alpha", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra_compress = {"bgra_compress", TEXTYPE_BGRA , 4, 4, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_bgra_alpha_compress = {"bgra_alpha_compress", TEXTYPE_BGRA , 4, 4, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_dxt1 = {"dxt1", TEXTYPE_DXT1 , 4, 0, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt1a = {"dxt1a", TEXTYPE_DXT1A , 4, 0, 0.5f, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt3 = {"dxt3", TEXTYPE_DXT3 , 4, 0, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT , 0 , 0 }; +static textypeinfo_t textype_dxt5 = {"dxt5", TEXTYPE_DXT5 , 4, 0, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , 0 , 0 }; +static textypeinfo_t textype_sRGB_palette = {"sRGB_palette", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_SRGB_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_palette_alpha = {"sRGB_palette_alpha", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_rgba = {"sRGB_rgba", TEXTYPE_RGBA , 4, 4, 4.0f, GL_SRGB_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_rgba_alpha = {"sRGB_rgba_alpha", TEXTYPE_RGBA , 4, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_rgba_compress = {"sRGB_rgba_compress", TEXTYPE_RGBA , 4, 4, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_rgba_alpha_compress = {"sRGB_rgba_alpha_compress", TEXTYPE_RGBA , 4, 4, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_bgra = {"sRGB_bgra", TEXTYPE_BGRA , 4, 4, 4.0f, GL_SRGB_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_bgra_alpha = {"sRGB_bgra_alpha", TEXTYPE_BGRA , 4, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_bgra_compress = {"sRGB_bgra_compress", TEXTYPE_BGRA , 4, 4, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_bgra_alpha_compress = {"sRGB_bgra_alpha_compress", TEXTYPE_BGRA , 4, 4, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_BGRA , GL_UNSIGNED_BYTE }; +static textypeinfo_t textype_sRGB_dxt1 = {"sRGB_dxt1", TEXTYPE_DXT1 , 4, 0, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , 0 , 0 }; +static textypeinfo_t textype_sRGB_dxt1a = {"sRGB_dxt1a", TEXTYPE_DXT1A , 4, 0, 0.5f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, 0 , 0 }; +static textypeinfo_t textype_sRGB_dxt3 = {"sRGB_dxt3", TEXTYPE_DXT3 , 4, 0, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, 0 , 0 }; +static textypeinfo_t textype_sRGB_dxt5 = {"sRGB_dxt5", TEXTYPE_DXT5 , 4, 0, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, 0 , 0 }; +#endif + +typedef enum gltexturetype_e +{ + GLTEXTURETYPE_2D, + GLTEXTURETYPE_3D, + GLTEXTURETYPE_CUBEMAP, + GLTEXTURETYPE_TOTAL +} +gltexturetype_t; + +static int gltexturetypeenums[GLTEXTURETYPE_TOTAL] = {GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP}; +static int gltexturetypedimensions[GLTEXTURETYPE_TOTAL] = {2, 3, 2}; +static int cubemapside[6] = +{ + GL_TEXTURE_CUBE_MAP_POSITIVE_X, + GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Z +}; + +typedef struct gltexture_s +{ + // this portion of the struct is exposed to the R_GetTexture macro for + // speed reasons, must be identical in rtexture_t! + int texnum; // GL texture slot number + int renderbuffernum; // GL renderbuffer slot number + qboolean dirty; // indicates that R_RealGetTexture should be called + qboolean glisdepthstencil; // indicates that FBO attachment has to be GL_DEPTH_STENCIL_ATTACHMENT + int gltexturetypeenum; // used by R_Mesh_TexBind + // d3d stuff the backend needs + void *d3dtexture; + void *d3dsurface; +#ifdef SUPPORTD3D + qboolean d3disrendertargetsurface; + qboolean d3disdepthstencilsurface; + int d3dformat; + int d3dusage; + int d3dpool; + int d3daddressu; + int d3daddressv; + int d3daddressw; + int d3dmagfilter; + int d3dminfilter; + int d3dmipfilter; + int d3dmaxmiplevelfilter; + int d3dmipmaplodbias; + int d3dmaxmiplevel; +#endif + + // dynamic texture stuff [11/22/2007 Black] + updatecallback_t updatecallback; + void *updatacallback_data; + // --- [11/22/2007 Black] + + // stores backup copy of texture for deferred texture updates (gl_nopartialtextureupdates cvar) + unsigned char *bufferpixels; + qboolean buffermodified; + + // pointer to texturepool (check this to see if the texture is allocated) + struct gltexturepool_s *pool; + // pointer to next texture in texturepool chain + struct gltexture_s *chain; + // name of the texture (this might be removed someday), no duplicates + char identifier[MAX_QPATH + 32]; + // original data size in *inputtexels + int inputwidth, inputheight, inputdepth; + // copy of the original texture(s) supplied to the upload function, for + // delayed uploads (non-precached) + unsigned char *inputtexels; + // original data size in *inputtexels + int inputdatasize; + // flags supplied to the LoadTexture function + // (might be altered to remove TEXF_ALPHA), and GLTEXF_ private flags + int flags; + // picmip level + int miplevel; + // pointer to one of the textype_ structs + textypeinfo_t *textype; + // one of the GLTEXTURETYPE_ values + int texturetype; + // palette if the texture is TEXTYPE_PALETTE + const unsigned int *palette; + // actual stored texture size after gl_picmip and gl_max_size are applied + // (power of 2 if vid.support.arb_texture_non_power_of_two is not supported) + int tilewidth, tileheight, tiledepth; + // 1 or 6 depending on texturetype + int sides; + // how many mipmap levels in this texture + int miplevels; + // bytes per pixel + int bytesperpixel; + // GL_RGB or GL_RGBA or GL_DEPTH_COMPONENT + int glformat; + // 3 or 4 + int glinternalformat; + // GL_UNSIGNED_BYTE or GL_UNSIGNED_INT or GL_UNSIGNED_SHORT or GL_FLOAT + int gltype; +} +gltexture_t; + +#define TEXTUREPOOL_SENTINEL 0xC0DEDBAD + +typedef struct gltexturepool_s +{ + unsigned int sentinel; + struct gltexture_s *gltchain; + struct gltexturepool_s *next; +} +gltexturepool_t; + +static gltexturepool_t *gltexturepoolchain = NULL; + +static unsigned char *resizebuffer = NULL, *colorconvertbuffer; +static int resizebuffersize = 0; +static const unsigned char *texturebuffer; + +extern int is32bit; + +static textypeinfo_t *R_GetTexTypeInfo(textype_t textype, int flags) +{ + switch(textype) + { + case TEXTYPE_DXT1: return &textype_dxt1; + case TEXTYPE_DXT1A: return &textype_dxt1a; + case TEXTYPE_DXT3: return &textype_dxt3; + case TEXTYPE_DXT5: return &textype_dxt5; + case TEXTYPE_PALETTE: return (flags & TEXF_ALPHA) ? &textype_palette_alpha : &textype_palette; + case TEXTYPE_RGBA: return /*((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_rgba_alpha_compress : &textype_rgba_compress) : */((flags & TEXF_ALPHA) ? &textype_rgba_alpha : &textype_rgba); + case TEXTYPE_BGRA: return /*((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_bgra_alpha_compress : &textype_bgra_compress) : */((flags & TEXF_ALPHA) ? &textype_bgra_alpha : &textype_bgra); + case TEXTYPE_ALPHA: return &textype_alpha; + case TEXTYPE_COLORBUFFER: return (is32bit) ? &textype_colorbuffer_32 : &textype_colorbuffer_16; + case TEXTYPE_COLORBUFFER16F: return (is32bit) ? &textype_colorbuffer16f_32 : &textype_colorbuffer16f_16; + case TEXTYPE_COLORBUFFER32F: return (is32bit) ? &textype_colorbuffer32f_32 : &textype_colorbuffer32f_16; + case TEXTYPE_DEPTHBUFFER16: return &textype_depth16; + case TEXTYPE_DEPTHBUFFER24: return &textype_depth24; + case TEXTYPE_DEPTHBUFFER24STENCIL8: return &textype_depth24stencil8; + case TEXTYPE_SHADOWMAP16_COMP: return &textype_shadowmap16_comp; + case TEXTYPE_SHADOWMAP16_RAW: return &textype_shadowmap16_raw; + case TEXTYPE_SHADOWMAP24_COMP: return &textype_shadowmap24_comp; + case TEXTYPE_SHADOWMAP24_RAW: return &textype_shadowmap24_raw; + /*case TEXTYPE_SRGB_DXT1: return &textype_sRGB_dxt1; + case TEXTYPE_SRGB_DXT1A: return &textype_sRGB_dxt1a; + case TEXTYPE_SRGB_DXT3: return &textype_sRGB_dxt3; + case TEXTYPE_SRGB_DXT5: return &textype_sRGB_dxt5; + case TEXTYPE_SRGB_PALETTE: return (flags & TEXF_ALPHA) ? &textype_sRGB_palette_alpha : &textype_sRGB_palette; + case TEXTYPE_SRGB_RGBA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_sRGB_rgba_alpha_compress : &textype_sRGB_rgba_compress) : ((flags & TEXF_ALPHA) ? &textype_sRGB_rgba_alpha : &textype_sRGB_rgba); + case TEXTYPE_SRGB_BGRA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_sRGB_bgra_alpha_compress : &textype_sRGB_bgra_compress) : ((flags & TEXF_ALPHA) ? &textype_sRGB_bgra_alpha : &textype_sRGB_bgra); + */ + default: + Host_Error("R_GetTexTypeInfo: unknown texture format"); + break; + } + return NULL; +} + +// dynamic texture code [11/22/2007 Black] +void R_MarkDirtyTexture(rtexture_t *rt) { + gltexture_t *glt = (gltexture_t*) rt; + if( !glt ) { + return; + } + + // dont do anything if the texture is already dirty (and make sure this *is* a dynamic texture after all!) + if (glt->flags & GLTEXF_DYNAMIC) + { + // mark it as dirty, so R_RealGetTexture gets called + glt->dirty = true; + } +} + +void R_MakeTextureDynamic(rtexture_t *rt, updatecallback_t updatecallback, void *data) { + gltexture_t *glt = (gltexture_t*) rt; + if( !glt ) { + return; + } + + glt->flags |= GLTEXF_DYNAMIC; + glt->updatecallback = updatecallback; + glt->updatacallback_data = data; +} + +static void R_UpdateDynamicTexture(gltexture_t *glt) { + glt->dirty = false; + if( glt->updatecallback ) { + glt->updatecallback( (rtexture_t*) glt, glt->updatacallback_data ); + } +} + +void R_PurgeTexture(rtexture_t *rt) +{ + if(rt && !(((gltexture_t*) rt)->flags & TEXF_PERSISTENT)) { + R_FreeTexture(rt); + } +} + +void R_FreeTexture(rtexture_t *rt) +{ + gltexture_t *glt, **gltpointer; + + glt = (gltexture_t *)rt; + if (glt == NULL) + Host_Error("R_FreeTexture: texture == NULL"); + + for (gltpointer = &glt->pool->gltchain;*gltpointer && *gltpointer != glt;gltpointer = &(*gltpointer)->chain); + if (*gltpointer == glt) + *gltpointer = glt->chain; + else + Host_Error("R_FreeTexture: texture \"%s\" not linked in pool", glt->identifier); + + R_Mesh_ClearBindingsForTexture(glt->texnum); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (glt->texnum) + { + CHECKGLERROR + qglDeleteTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR + } + if (glt->renderbuffernum) + { + CHECKGLERROR + qglDeleteRenderbuffers(1, (GLuint *)&glt->renderbuffernum);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (glt->d3dsurface) + IDirect3DSurface9_Release((IDirect3DSurface9 *)glt->d3dsurface); + else if (glt->tiledepth > 1) + IDirect3DVolumeTexture9_Release((IDirect3DVolumeTexture9 *)glt->d3dtexture); + else if (glt->sides == 6) + IDirect3DCubeTexture9_Release((IDirect3DCubeTexture9 *)glt->d3dtexture); + else + IDirect3DTexture9_Release((IDirect3DTexture9 *)glt->d3dtexture); + glt->d3dtexture = NULL; + glt->d3dsurface = NULL; +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (glt->texnum) + DPSOFTRAST_Texture_Free(glt->texnum); + break; + } + + if (glt->inputtexels) + Mem_Free(glt->inputtexels); + Mem_ExpandableArray_FreeRecord(&texturearray, glt); +} + +rtexturepool_t *R_AllocTexturePool(void) +{ + gltexturepool_t *pool; + if (texturemempool == NULL) + return NULL; + pool = (gltexturepool_t *)Mem_Alloc(texturemempool, sizeof(gltexturepool_t)); + if (pool == NULL) + return NULL; + pool->next = gltexturepoolchain; + gltexturepoolchain = pool; + pool->sentinel = TEXTUREPOOL_SENTINEL; + return (rtexturepool_t *)pool; +} + +void R_FreeTexturePool(rtexturepool_t **rtexturepool) +{ + gltexturepool_t *pool, **poolpointer; + if (rtexturepool == NULL) + return; + if (*rtexturepool == NULL) + return; + pool = (gltexturepool_t *)(*rtexturepool); + *rtexturepool = NULL; + if (pool->sentinel != TEXTUREPOOL_SENTINEL) + Host_Error("R_FreeTexturePool: pool already freed"); + for (poolpointer = &gltexturepoolchain;*poolpointer && *poolpointer != pool;poolpointer = &(*poolpointer)->next); + if (*poolpointer == pool) + *poolpointer = pool->next; + else + Host_Error("R_FreeTexturePool: pool not linked"); + while (pool->gltchain) + R_FreeTexture((rtexture_t *)pool->gltchain); + Mem_Free(pool); +} + + +typedef struct glmode_s +{ + const char *name; + int minification, magnification; + DPSOFTRAST_TEXTURE_FILTER dpsoftrastfilter_mipmap, dpsoftrastfilter_nomipmap; +} +glmode_t; + +static glmode_t modes[6] = +{ + {"GL_NEAREST", GL_NEAREST, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_LINEAR} +}; + +#ifdef SUPPORTD3D +typedef struct d3dmode_s +{ + const char *name; + int m1, m2; +} +d3dmode_t; + +static d3dmode_t d3dmodes[6] = +{ + {"GL_NEAREST", D3DTEXF_POINT, D3DTEXF_POINT}, + {"GL_LINEAR", D3DTEXF_LINEAR, D3DTEXF_POINT}, + {"GL_NEAREST_MIPMAP_NEAREST", D3DTEXF_POINT, D3DTEXF_POINT}, + {"GL_LINEAR_MIPMAP_NEAREST", D3DTEXF_LINEAR, D3DTEXF_POINT}, + {"GL_NEAREST_MIPMAP_LINEAR", D3DTEXF_POINT, D3DTEXF_LINEAR}, + {"GL_LINEAR_MIPMAP_LINEAR", D3DTEXF_LINEAR, D3DTEXF_LINEAR} +}; +#endif + +static void GL_TextureMode_f (void) +{ + int i; + GLint oldbindtexnum; + gltexture_t *glt; + gltexturepool_t *pool; + + if (Cmd_Argc() == 1) + { + Con_Printf("Texture mode is %sforced\n", gl_filter_force ? "" : "not "); + for (i = 0;i < 6;i++) + { + if (gl_filter_min == modes[i].minification) + { + Con_Printf("%s\n", modes[i].name); + return; + } + } + Con_Print("current filter is unknown???\n"); + return; + } + + for (i = 0;i < (int)(sizeof(modes)/sizeof(*modes));i++) + if (!strcasecmp (modes[i].name, Cmd_Argv(1) ) ) + break; + if (i == 6) + { + Con_Print("bad filter name\n"); + return; + } + + gl_filter_min = modes[i].minification; + gl_filter_mag = modes[i].magnification; + gl_filter_force = ((Cmd_Argc() > 2) && !strcasecmp(Cmd_Argv(2), "force")); + + dpsoftrast_filter_mipmap = modes[i].dpsoftrastfilter_mipmap; + dpsoftrast_filter_nomipmap = modes[i].dpsoftrastfilter_nomipmap; + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // change all the existing mipmap texture objects + // FIXME: force renderer(/client/something?) restart instead? + CHECKGLERROR + GL_ActiveTexture(0); + for (pool = gltexturepoolchain;pool;pool = pool->next) + { + for (glt = pool->gltchain;glt;glt = glt->chain) + { + // only update already uploaded images + if (glt->texnum && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) + { + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + if (glt->flags & TEXF_MIPMAP) + { + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MIN_FILTER, gl_filter_min);CHECKGLERROR + } + else + { + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MIN_FILTER, gl_filter_mag);CHECKGLERROR + } + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAG_FILTER, gl_filter_mag);CHECKGLERROR + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + } + } + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + d3d_filter_flatmin = d3dmodes[i].m1; + d3d_filter_flatmag = d3dmodes[i].m1; + d3d_filter_flatmix = D3DTEXF_POINT; + d3d_filter_mipmin = d3dmodes[i].m1; + d3d_filter_mipmag = d3dmodes[i].m1; + d3d_filter_mipmix = d3dmodes[i].m2; + d3d_filter_nomip = i < 2; + if (gl_texture_anisotropy.integer > 1 && i == 5) + d3d_filter_mipmin = d3d_filter_mipmag = D3DTEXF_ANISOTROPIC; + for (pool = gltexturepoolchain;pool;pool = pool->next) + { + for (glt = pool->gltchain;glt;glt = glt->chain) + { + // only update already uploaded images + if (glt->d3dtexture && !glt->d3dsurface && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) + { + if (glt->flags & TEXF_MIPMAP) + { + glt->d3dminfilter = d3d_filter_mipmin; + glt->d3dmagfilter = d3d_filter_mipmag; + glt->d3dmipfilter = d3d_filter_mipmix; + glt->d3dmaxmiplevelfilter = 0; + } + else + { + glt->d3dminfilter = d3d_filter_flatmin; + glt->d3dmagfilter = d3d_filter_flatmag; + glt->d3dmipfilter = d3d_filter_flatmix; + glt->d3dmaxmiplevelfilter = 0; + } + } + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + // change all the existing texture objects + for (pool = gltexturepoolchain;pool;pool = pool->next) + for (glt = pool->gltchain;glt;glt = glt->chain) + if (glt->texnum && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) + DPSOFTRAST_Texture_Filter(glt->texnum, (glt->flags & TEXF_MIPMAP) ? dpsoftrast_filter_mipmap : dpsoftrast_filter_nomipmap); + break; + } +} + +static void GL_Texture_CalcImageSize(int texturetype, int flags, int miplevel, int inwidth, int inheight, int indepth, int *outwidth, int *outheight, int *outdepth, int *outmiplevels) +{ + int picmip = 0, maxsize = 0, width2 = 1, height2 = 1, depth2 = 1, miplevels = 1; + + switch (texturetype) + { + default: + case GLTEXTURETYPE_2D: + maxsize = vid.maxtexturesize_2d; + if (flags & TEXF_PICMIP) + { + maxsize = bound(1, gl_max_size.integer, maxsize); + picmip = miplevel; + } + break; + case GLTEXTURETYPE_3D: + maxsize = vid.maxtexturesize_3d; + break; + case GLTEXTURETYPE_CUBEMAP: + maxsize = vid.maxtexturesize_cubemap; + break; + } + + if (vid.support.arb_texture_non_power_of_two) + { + width2 = min(inwidth >> picmip, maxsize); + height2 = min(inheight >> picmip, maxsize); + depth2 = min(indepth >> picmip, maxsize); + } + else + { + for (width2 = 1;width2 < inwidth;width2 <<= 1); + for (width2 >>= picmip;width2 > maxsize;width2 >>= 1); + for (height2 = 1;height2 < inheight;height2 <<= 1); + for (height2 >>= picmip;height2 > maxsize;height2 >>= 1); + for (depth2 = 1;depth2 < indepth;depth2 <<= 1); + for (depth2 >>= picmip;depth2 > maxsize;depth2 >>= 1); + } + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#if 0 + // for some reason the REF rasterizer (and hence the PIX debugger) does not like small textures... + if (texturetype == GLTEXTURETYPE_2D) + { + width2 = max(width2, 2); + height2 = max(height2, 2); + } +#endif + break; + } + + miplevels = 1; + if (flags & TEXF_MIPMAP) + { + int extent = max(width2, max(height2, depth2)); + while(extent >>= 1) + miplevels++; + } + + if (outwidth) + *outwidth = max(1, width2); + if (outheight) + *outheight = max(1, height2); + if (outdepth) + *outdepth = max(1, depth2); + if (outmiplevels) + *outmiplevels = miplevels; +} + + +static int R_CalcTexelDataSize (gltexture_t *glt) +{ + int width2, height2, depth2, size; + + GL_Texture_CalcImageSize(glt->texturetype, glt->flags, glt->miplevel, glt->inputwidth, glt->inputheight, glt->inputdepth, &width2, &height2, &depth2, NULL); + + size = width2 * height2 * depth2; + + if (glt->flags & TEXF_MIPMAP) + { + while (width2 > 1 || height2 > 1 || depth2 > 1) + { + if (width2 > 1) + width2 >>= 1; + if (height2 > 1) + height2 >>= 1; + if (depth2 > 1) + depth2 >>= 1; + size += width2 * height2 * depth2; + } + } + + return (int)(size * glt->textype->glinternalbytesperpixel) * glt->sides; +} + +void R_TextureStats_Print(qboolean printeach, qboolean printpool, qboolean printtotal) +{ + int glsize; + int isloaded; + int pooltotal = 0, pooltotalt = 0, pooltotalp = 0, poolloaded = 0, poolloadedt = 0, poolloadedp = 0; + int sumtotal = 0, sumtotalt = 0, sumtotalp = 0, sumloaded = 0, sumloadedt = 0, sumloadedp = 0; + gltexture_t *glt; + gltexturepool_t *pool; + if (printeach) + Con_Print("glsize input loaded mip alpha name\n"); + for (pool = gltexturepoolchain;pool;pool = pool->next) + { + pooltotal = 0; + pooltotalt = 0; + pooltotalp = 0; + poolloaded = 0; + poolloadedt = 0; + poolloadedp = 0; + for (glt = pool->gltchain;glt;glt = glt->chain) + { + glsize = R_CalcTexelDataSize(glt); + isloaded = glt->texnum != 0 || glt->renderbuffernum != 0 || glt->d3dtexture || glt->d3dsurface; + pooltotal++; + pooltotalt += glsize; + pooltotalp += glt->inputdatasize; + if (isloaded) + { + poolloaded++; + poolloadedt += glsize; + poolloadedp += glt->inputdatasize; + } + if (printeach) + Con_Printf("%c%4i%c%c%4i%c %-24s %s %s %s %s\n", isloaded ? '[' : ' ', (glsize + 1023) / 1024, isloaded ? ']' : ' ', glt->inputtexels ? '[' : ' ', (glt->inputdatasize + 1023) / 1024, glt->inputtexels ? ']' : ' ', glt->textype->name, isloaded ? "loaded" : " ", (glt->flags & TEXF_MIPMAP) ? "mip" : " ", (glt->flags & TEXF_ALPHA) ? "alpha" : " ", glt->identifier); + } + if (printpool) + Con_Printf("texturepool %10p total: %i (%.3fMB, %.3fMB original), uploaded %i (%.3fMB, %.3fMB original), upload on demand %i (%.3fMB, %.3fMB original)\n", (void *)pool, pooltotal, pooltotalt / 1048576.0, pooltotalp / 1048576.0, poolloaded, poolloadedt / 1048576.0, poolloadedp / 1048576.0, pooltotal - poolloaded, (pooltotalt - poolloadedt) / 1048576.0, (pooltotalp - poolloadedp) / 1048576.0); + sumtotal += pooltotal; + sumtotalt += pooltotalt; + sumtotalp += pooltotalp; + sumloaded += poolloaded; + sumloadedt += poolloadedt; + sumloadedp += poolloadedp; + } + if (printtotal) + Con_Printf("textures total: %i (%.3fMB, %.3fMB original), uploaded %i (%.3fMB, %.3fMB original), upload on demand %i (%.3fMB, %.3fMB original)\n", sumtotal, sumtotalt / 1048576.0, sumtotalp / 1048576.0, sumloaded, sumloadedt / 1048576.0, sumloadedp / 1048576.0, sumtotal - sumloaded, (sumtotalt - sumloadedt) / 1048576.0, (sumtotalp - sumloadedp) / 1048576.0); +} + +static void R_TextureStats_f(void) +{ + R_TextureStats_Print(true, true, true); +} + +static void r_textures_start(void) +{ + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + // LordHavoc: allow any alignment + CHECKGLERROR + qglPixelStorei(GL_UNPACK_ALIGNMENT, 1);CHECKGLERROR + qglPixelStorei(GL_PACK_ALIGNMENT, 1);CHECKGLERROR + break; + case RENDERPATH_D3D9: + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + + texturemempool = Mem_AllocPool("texture management", 0, NULL); + Mem_ExpandableArray_NewArray(&texturearray, texturemempool, sizeof(gltexture_t), 512); + + // Disable JPEG screenshots if the DLL isn't loaded + if (! JPEG_OpenLibrary ()) + Cvar_SetValueQuick (&scr_screenshot_jpeg, 0); + if (! PNG_OpenLibrary ()) + Cvar_SetValueQuick (&scr_screenshot_png, 0); +} + +static void r_textures_shutdown(void) +{ + rtexturepool_t *temp; + + JPEG_CloseLibrary (); + + while(gltexturepoolchain) + { + temp = (rtexturepool_t *) gltexturepoolchain; + R_FreeTexturePool(&temp); + } + + resizebuffersize = 0; + resizebuffer = NULL; + colorconvertbuffer = NULL; + texturebuffer = NULL; + Mem_ExpandableArray_FreeArray(&texturearray); + Mem_FreePool(&texturemempool); +} + +static void r_textures_newmap(void) +{ +} + +static void r_textures_devicelost(void) +{ + int i, endindex; + gltexture_t *glt; + endindex = Mem_ExpandableArray_IndexRange(&texturearray); + for (i = 0;i < endindex;i++) + { + glt = (gltexture_t *) Mem_ExpandableArray_RecordAtIndex(&texturearray, i); + if (!glt || !(glt->flags & TEXF_RENDERTARGET)) + continue; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (glt->d3dsurface) + IDirect3DSurface9_Release((IDirect3DSurface9 *)glt->d3dsurface); + else if (glt->tiledepth > 1) + IDirect3DVolumeTexture9_Release((IDirect3DVolumeTexture9 *)glt->d3dtexture); + else if (glt->sides == 6) + IDirect3DCubeTexture9_Release((IDirect3DCubeTexture9 *)glt->d3dtexture); + else + IDirect3DTexture9_Release((IDirect3DTexture9 *)glt->d3dtexture); + glt->d3dtexture = NULL; + glt->d3dsurface = NULL; +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + } +} + +static void r_textures_devicerestored(void) +{ + int i, endindex; + gltexture_t *glt; + endindex = Mem_ExpandableArray_IndexRange(&texturearray); + for (i = 0;i < endindex;i++) + { + glt = (gltexture_t *) Mem_ExpandableArray_RecordAtIndex(&texturearray, i); + if (!glt || !(glt->flags & TEXF_RENDERTARGET)) + continue; + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + HRESULT d3dresult; + if (glt->d3disrendertargetsurface) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dsurface, NULL))) + Sys_Error("IDirect3DDevice9_CreateRenderTarget failed!"); + } + else if (glt->d3disdepthstencilsurface) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateDepthStencilSurface(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dsurface, NULL))) + Sys_Error("IDirect3DDevice9_CreateDepthStencilSurface failed!"); + } + else if (glt->tiledepth > 1) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateVolumeTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->tiledepth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DVolumeTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateVolumeTexture failed!"); + } + else if (glt->sides == 6) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateCubeTexture(vid_d3d9dev, glt->tilewidth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DCubeTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateCubeTexture failed!"); + } + else + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateTexture failed!"); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + break; + } + } +} + + +void R_Textures_Init (void) +{ + Cmd_AddCommand("gl_texturemode", &GL_TextureMode_f, "set texture filtering mode (GL_NEAREST, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, etc); an additional argument 'force' forces the texture mode even in cases where it may not be appropriate"); + Cmd_AddCommand("r_texturestats", R_TextureStats_f, "print information about all loaded textures and some statistics"); + Cvar_RegisterVariable (&gl_max_size); + Cvar_RegisterVariable (&gl_picmip); + Cvar_RegisterVariable (&gl_picmip_world); + Cvar_RegisterVariable (&r_picmipworld); + Cvar_RegisterVariable (&gl_picmip_sprites); + Cvar_RegisterVariable (&r_picmipsprites); + Cvar_RegisterVariable (&gl_picmip_other); + Cvar_RegisterVariable (&gl_max_lightmapsize); + Cvar_RegisterVariable (&r_lerpimages); + Cvar_RegisterVariable (&gl_texture_anisotropy); + Cvar_RegisterVariable (&gl_texturecompression); + Cvar_RegisterVariable (&gl_texturecompression_color); + Cvar_RegisterVariable (&gl_texturecompression_normal); + Cvar_RegisterVariable (&gl_texturecompression_gloss); + Cvar_RegisterVariable (&gl_texturecompression_glow); + Cvar_RegisterVariable (&gl_texturecompression_2d); + Cvar_RegisterVariable (&gl_texturecompression_q3bsplightmaps); + Cvar_RegisterVariable (&gl_texturecompression_q3bspdeluxemaps); + Cvar_RegisterVariable (&gl_texturecompression_sky); + Cvar_RegisterVariable (&gl_texturecompression_lightcubemaps); + Cvar_RegisterVariable (&gl_texturecompression_reflectmask); + Cvar_RegisterVariable (&gl_texturecompression_sprites); + Cvar_RegisterVariable (&gl_nopartialtextureupdates); + Cvar_RegisterVariable (&r_texture_dds_load_alphamode); + Cvar_RegisterVariable (&r_texture_dds_load_logfailure); + Cvar_RegisterVariable (&r_texture_dds_swdecode); + + R_RegisterModule("R_Textures", r_textures_start, r_textures_shutdown, r_textures_newmap, r_textures_devicelost, r_textures_devicerestored); +} + +void R_Textures_Frame (void) +{ + static int old_aniso = 0; + + // could do procedural texture animation here, if we keep track of which + // textures were accessed this frame... + + // free the resize buffers + resizebuffersize = 0; + if (resizebuffer) + { + Mem_Free(resizebuffer); + resizebuffer = NULL; + } + if (colorconvertbuffer) + { + Mem_Free(colorconvertbuffer); + colorconvertbuffer = NULL; + } + + if (old_aniso != gl_texture_anisotropy.integer) + { + gltexture_t *glt; + gltexturepool_t *pool; + GLint oldbindtexnum; + + old_aniso = bound(1, gl_texture_anisotropy.integer, (int)vid.max_anisotropy); + + Cvar_SetValueQuick(&gl_texture_anisotropy, old_aniso); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + GL_ActiveTexture(0); + for (pool = gltexturepoolchain;pool;pool = pool->next) + { + for (glt = pool->gltchain;glt;glt = glt->chain) + { + // only update already uploaded images + if (glt->texnum && (glt->flags & TEXF_MIPMAP) == TEXF_MIPMAP) + { + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAX_ANISOTROPY_EXT, old_aniso); + CHECKGLERROR + + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + } + } + } + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + break; + } + } +} + +static void R_MakeResizeBufferBigger(int size) +{ + if (resizebuffersize < size) + { + resizebuffersize = size; + if (resizebuffer) + Mem_Free(resizebuffer); + if (colorconvertbuffer) + Mem_Free(colorconvertbuffer); + resizebuffer = (unsigned char *)Mem_Alloc(texturemempool, resizebuffersize); + colorconvertbuffer = (unsigned char *)Mem_Alloc(texturemempool, resizebuffersize); + if (!resizebuffer || !colorconvertbuffer) + Host_Error("R_Upload: out of memory"); + } +} + +static void GL_SetupTextureParameters(int flags, textype_t textype, int texturetype) +{ + int textureenum = gltexturetypeenums[texturetype]; + int wrapmode = (flags & TEXF_CLAMP) ? GL_CLAMP_TO_EDGE : GL_REPEAT; + + CHECKGLERROR + + if (vid.support.ext_texture_filter_anisotropic && (flags & TEXF_MIPMAP)) + { + int aniso = bound(1, gl_texture_anisotropy.integer, (int)vid.max_anisotropy); + if (gl_texture_anisotropy.integer != aniso) + Cvar_SetValueQuick(&gl_texture_anisotropy, aniso); + qglTexParameteri(textureenum, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_WRAP_S, wrapmode);CHECKGLERROR + qglTexParameteri(textureenum, GL_TEXTURE_WRAP_T, wrapmode);CHECKGLERROR +#ifdef GL_TEXTURE_WRAP_R + if (gltexturetypedimensions[texturetype] >= 3) + { + qglTexParameteri(textureenum, GL_TEXTURE_WRAP_R, wrapmode);CHECKGLERROR + } +#endif + + CHECKGLERROR + if (!gl_filter_force && flags & TEXF_FORCENEAREST) + { + if (flags & TEXF_MIPMAP) + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);CHECKGLERROR + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_NEAREST);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, GL_NEAREST);CHECKGLERROR + } + else if (!gl_filter_force && flags & TEXF_FORCELINEAR) + { + if (flags & TEXF_MIPMAP) + { + if (gl_filter_min == GL_NEAREST_MIPMAP_LINEAR || gl_filter_min == GL_LINEAR_MIPMAP_LINEAR) + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);CHECKGLERROR + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);CHECKGLERROR + } + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, GL_LINEAR);CHECKGLERROR + } + else + { + if (flags & TEXF_MIPMAP) + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, gl_filter_min);CHECKGLERROR + } + else + { + qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, gl_filter_mag);CHECKGLERROR + } + qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, gl_filter_mag);CHECKGLERROR + } + + switch(textype) + { + case TEXTYPE_SHADOWMAP16_COMP: + case TEXTYPE_SHADOWMAP24_COMP: + /*qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);CHECKGLERROR + qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);CHECKGLERROR + qglTexParameteri(textureenum, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);*/CHECKGLERROR + break; + case TEXTYPE_SHADOWMAP16_RAW: + case TEXTYPE_SHADOWMAP24_RAW: + /*qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE);CHECKGLERROR + qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);CHECKGLERROR + qglTexParameteri(textureenum, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);*/CHECKGLERROR + break; + default: + break; + } + + CHECKGLERROR +} + +static void R_UploadPartialTexture(gltexture_t *glt, const unsigned char *data, int fragx, int fragy, int fragz, int fragwidth, int fragheight, int fragdepth) +{ + if (data == NULL) + Sys_Error("R_UploadPartialTexture \"%s\": partial update with NULL pixels", glt->identifier); + + if (glt->texturetype != GLTEXTURETYPE_2D) + Sys_Error("R_UploadPartialTexture \"%s\": partial update of type other than 2D", glt->identifier); + + if (glt->textype->textype == TEXTYPE_PALETTE) + Sys_Error("R_UploadPartialTexture \"%s\": partial update of paletted texture", glt->identifier); + + if (glt->flags & (TEXF_MIPMAP | TEXF_PICMIP)) + Sys_Error("R_UploadPartialTexture \"%s\": partial update not supported with MIPMAP or PICMIP flags", glt->identifier); + + if (glt->inputwidth != glt->tilewidth || glt->inputheight != glt->tileheight || glt->tiledepth != 1) + Sys_Error("R_UploadPartialTexture \"%s\": partial update not supported with stretched or special textures", glt->identifier); + + // update a portion of the image + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + { + int oldbindtexnum; + CHECKGLERROR + // we need to restore the texture binding after finishing the upload + GL_ActiveTexture(0); + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + qglTexSubImage2D(GL_TEXTURE_2D, 0, fragx, fragy, fragwidth, fragheight, glt->glformat, glt->gltype, data);CHECKGLERROR + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + RECT d3drect; + D3DLOCKED_RECT d3dlockedrect; + int y; + memset(&d3drect, 0, sizeof(d3drect)); + d3drect.left = fragx; + d3drect.top = fragy; + d3drect.right = fragx+fragwidth; + d3drect.bottom = fragy+fragheight; + if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, 0, &d3dlockedrect, &d3drect, 0) == D3D_OK && d3dlockedrect.pBits) + { + for (y = 0;y < fragheight;y++) + memcpy((unsigned char *)d3dlockedrect.pBits + d3dlockedrect.Pitch * y, data + fragwidth*glt->bytesperpixel * y, fragwidth*glt->bytesperpixel); + IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, 0); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + DPSOFTRAST_Texture_UpdatePartial(glt->texnum, 0, data, fragx, fragy, fragwidth, fragheight); + break; + } +} + +static void R_UploadFullTexture(gltexture_t *glt, const unsigned char *data) +{ + int i, mip = 0, width, height, depth; + GLint oldbindtexnum = 0; + const unsigned char *prevbuffer; + prevbuffer = data; + + // error out if a stretch is needed on special texture types + if (glt->texturetype != GLTEXTURETYPE_2D && (glt->tilewidth != glt->inputwidth || glt->tileheight != glt->inputheight || glt->tiledepth != glt->inputdepth)) + Sys_Error("R_UploadFullTexture \"%s\": stretch uploads allowed only on 2D textures\n", glt->identifier); + + // when picmip or maxsize is applied, we scale up to a power of 2 multiple + // of the target size and then use the mipmap reduction function to get + // high quality supersampled results + for (width = glt->tilewidth;width < glt->inputwidth ;width <<= 1); + for (height = glt->tileheight;height < glt->inputheight;height <<= 1); + for (depth = glt->tiledepth;depth < glt->inputdepth ;depth <<= 1); + R_MakeResizeBufferBigger(width * height * depth * glt->sides * glt->bytesperpixel); + + if (prevbuffer == NULL) + { + width = glt->tilewidth; + height = glt->tileheight; + depth = glt->tiledepth; +// memset(resizebuffer, 0, width * height * depth * glt->sides * glt->bytesperpixel); +// prevbuffer = resizebuffer; + } + else + { + if (glt->textype->textype == TEXTYPE_PALETTE) + { + // promote paletted to BGRA, so we only have to worry about BGRA in the rest of this code + Image_Copy8bitBGRA(prevbuffer, colorconvertbuffer, glt->inputwidth * glt->inputheight * glt->inputdepth * glt->sides, glt->palette); + prevbuffer = colorconvertbuffer; + } + if (glt->flags & TEXF_RGBMULTIPLYBYALPHA) + { + // multiply RGB channels by A channel before uploading + int alpha; + for (i = 0;i < glt->inputwidth*glt->inputheight*glt->inputdepth*4;i += 4) + { + alpha = prevbuffer[i+3]; + colorconvertbuffer[i] = (prevbuffer[i] * alpha) >> 8; + colorconvertbuffer[i+1] = (prevbuffer[i+1] * alpha) >> 8; + colorconvertbuffer[i+2] = (prevbuffer[i+2] * alpha) >> 8; + colorconvertbuffer[i+3] = alpha; + } + prevbuffer = colorconvertbuffer; + } + // scale up to a power of 2 size (if appropriate) + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); + prevbuffer = resizebuffer; + } + // apply mipmap reduction algorithm to get down to picmip/max_size + while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); + prevbuffer = resizebuffer; + } + } + + // do the appropriate upload type... + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (glt->texnum) // not renderbuffers + { + CHECKGLERROR + + // we need to restore the texture binding after finishing the upload + GL_ActiveTexture(0); + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + +#ifdef GL_TEXTURE_COMPRESSION_HINT_ARB + if (qglGetCompressedTexImageARB) + { + if (gl_texturecompression.integer >= 2) + qglHint(GL_TEXTURE_COMPRESSION_HINT_ARB, GL_NICEST); + else + qglHint(GL_TEXTURE_COMPRESSION_HINT_ARB, GL_FASTEST); + CHECKGLERROR + } +#endif + switch(glt->texturetype) + { + case GLTEXTURETYPE_2D: + qglTexImage2D(GL_TEXTURE_2D, mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + qglTexImage2D(GL_TEXTURE_2D, mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + } + } + break; + case GLTEXTURETYPE_3D: +#ifndef USE_GLES2 + qglTexImage3D(GL_TEXTURE_3D, mip++, glt->glinternalformat, width, height, depth, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + qglTexImage3D(GL_TEXTURE_3D, mip++, glt->glinternalformat, width, height, depth, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + } + } +#endif + break; + case GLTEXTURETYPE_CUBEMAP: + // convert and upload each side in turn, + // from a continuous block of input texels + texturebuffer = (unsigned char *)prevbuffer; + for (i = 0;i < 6;i++) + { + prevbuffer = texturebuffer; + texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); + prevbuffer = resizebuffer; + } + // picmip/max_size + while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); + prevbuffer = resizebuffer; + } + mip = 0; + qglTexImage2D(cubemapside[i], mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + qglTexImage2D(cubemapside[i], mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR + } + } + } + break; + } + GL_SetupTextureParameters(glt->flags, glt->textype->textype, glt->texturetype); + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + if (!(glt->flags & TEXF_RENDERTARGET) && glt->d3dtexture && !glt->d3dsurface) + { + D3DLOCKED_RECT d3dlockedrect; + D3DLOCKED_BOX d3dlockedbox; + switch(glt->texturetype) + { + case GLTEXTURETYPE_2D: + if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + if (prevbuffer) + memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); + else + memset(d3dlockedrect.pBits, 255, width*height*glt->bytesperpixel); + IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); + } + mip++; + if ((glt->flags & TEXF_MIPMAP) && prevbuffer) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); + IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); + } + mip++; + } + } + break; + case GLTEXTURETYPE_3D: + if (IDirect3DVolumeTexture9_LockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip, &d3dlockedbox, NULL, 0) == D3D_OK && d3dlockedbox.pBits) + { + // we are not honoring the RowPitch or SlicePitch, hopefully this works with all sizes + memcpy(d3dlockedbox.pBits, prevbuffer, width*height*depth*glt->bytesperpixel); + IDirect3DVolumeTexture9_UnlockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip); + } + mip++; + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + if (IDirect3DVolumeTexture9_LockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip, &d3dlockedbox, NULL, 0) == D3D_OK && d3dlockedbox.pBits) + { + // we are not honoring the RowPitch or SlicePitch, hopefully this works with all sizes + memcpy(d3dlockedbox.pBits, prevbuffer, width*height*depth*glt->bytesperpixel); + IDirect3DVolumeTexture9_UnlockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip); + } + mip++; + } + } + break; + case GLTEXTURETYPE_CUBEMAP: + // convert and upload each side in turn, + // from a continuous block of input texels + texturebuffer = (unsigned char *)prevbuffer; + for (i = 0;i < 6;i++) + { + prevbuffer = texturebuffer; + texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); + prevbuffer = resizebuffer; + } + // picmip/max_size + while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); + prevbuffer = resizebuffer; + } + mip = 0; + if (IDirect3DCubeTexture9_LockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); + IDirect3DCubeTexture9_UnlockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip); + } + mip++; + if (glt->flags & TEXF_MIPMAP) + { + while (width > 1 || height > 1 || depth > 1) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); + prevbuffer = resizebuffer; + if (IDirect3DCubeTexture9_LockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); + IDirect3DCubeTexture9_UnlockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip); + } + mip++; + } + } + } + break; + } + } + glt->d3daddressw = 0; + if (glt->flags & TEXF_CLAMP) + { + glt->d3daddressu = D3DTADDRESS_CLAMP; + glt->d3daddressv = D3DTADDRESS_CLAMP; + if (glt->tiledepth > 1) + glt->d3daddressw = D3DTADDRESS_CLAMP; + } + else + { + glt->d3daddressu = D3DTADDRESS_WRAP; + glt->d3daddressv = D3DTADDRESS_WRAP; + if (glt->tiledepth > 1) + glt->d3daddressw = D3DTADDRESS_WRAP; + } + glt->d3dmipmaplodbias = 0; + glt->d3dmaxmiplevel = 0; + glt->d3dmaxmiplevelfilter = d3d_filter_nomip ? 0 : glt->d3dmaxmiplevel; + if (glt->flags & TEXF_FORCELINEAR) + { + glt->d3dminfilter = D3DTEXF_LINEAR; + glt->d3dmagfilter = D3DTEXF_LINEAR; + glt->d3dmipfilter = D3DTEXF_POINT; + } + else if (glt->flags & TEXF_FORCENEAREST) + { + glt->d3dminfilter = D3DTEXF_POINT; + glt->d3dmagfilter = D3DTEXF_POINT; + glt->d3dmipfilter = D3DTEXF_POINT; + } + else if (glt->flags & TEXF_MIPMAP) + { + glt->d3dminfilter = d3d_filter_mipmin; + glt->d3dmagfilter = d3d_filter_mipmag; + glt->d3dmipfilter = d3d_filter_mipmix; + } + else + { + glt->d3dminfilter = d3d_filter_flatmin; + glt->d3dmagfilter = d3d_filter_flatmag; + glt->d3dmipfilter = d3d_filter_flatmix; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + switch(glt->texturetype) + { + case GLTEXTURETYPE_2D: + DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); + break; + case GLTEXTURETYPE_3D: + DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); + break; + case GLTEXTURETYPE_CUBEMAP: + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + unsigned char *combinedbuffer = (unsigned char *)Mem_Alloc(tempmempool, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->sides*glt->bytesperpixel); + // convert and upload each side in turn, + // from a continuous block of input texels + // copy the results into combinedbuffer + texturebuffer = (unsigned char *)prevbuffer; + for (i = 0;i < 6;i++) + { + prevbuffer = texturebuffer; + texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; + if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) + { + Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); + prevbuffer = resizebuffer; + } + // picmip/max_size + while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) + { + Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); + prevbuffer = resizebuffer; + } + memcpy(combinedbuffer + i*glt->tilewidth*glt->tileheight*glt->tiledepth*glt->bytesperpixel, prevbuffer, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->bytesperpixel); + } + DPSOFTRAST_Texture_UpdateFull(glt->texnum, combinedbuffer); + Mem_Free(combinedbuffer); + } + else + DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); + break; + } + if (glt->flags & TEXF_FORCELINEAR) + DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_LINEAR); + else if (glt->flags & TEXF_FORCENEAREST) + DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_NEAREST); + else if (glt->flags & TEXF_MIPMAP) + DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_mipmap); + else + DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_nomipmap); + break; + } +} + +static rtexture_t *R_SetupTexture(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, int sides, int flags, int miplevel, textype_t textype, int texturetype, const unsigned char *data, const unsigned int *palette) +{ + int i, size; + gltexture_t *glt; + gltexturepool_t *pool = (gltexturepool_t *)rtexturepool; + textypeinfo_t *texinfo, *texinfo2; + unsigned char *temppixels = NULL; + qboolean swaprb; + + if (cls.state == ca_dedicated) + return NULL; + + // see if we need to swap red and blue (BGRA <-> RGBA conversion) + if (textype == TEXTYPE_PALETTE && vid.forcetextype == TEXTYPE_RGBA) + { + int numpixels = width * height * depth * sides; + size = numpixels * 4; + temppixels = (unsigned char *)Mem_Alloc(tempmempool, size); + if (data) + { + const unsigned char *p; + unsigned char *o = temppixels; + for (i = 0;i < numpixels;i++, o += 4) + { + p = (const unsigned char *)palette + 4*data[i]; + o[0] = p[2]; + o[1] = p[1]; + o[2] = p[0]; + o[3] = p[3]; + } + } + data = temppixels; + textype = TEXTYPE_RGBA; + } + swaprb = false; + switch(textype) + { + case TEXTYPE_RGBA: if (vid.forcetextype == TEXTYPE_BGRA) {swaprb = true;textype = TEXTYPE_BGRA;} break; + case TEXTYPE_BGRA: if (vid.forcetextype == TEXTYPE_RGBA) {swaprb = true;textype = TEXTYPE_RGBA;} break; + case TEXTYPE_SRGB_RGBA: if (vid.forcetextype == TEXTYPE_BGRA) {swaprb = true;textype = TEXTYPE_SRGB_BGRA;} break; + case TEXTYPE_SRGB_BGRA: if (vid.forcetextype == TEXTYPE_RGBA) {swaprb = true;textype = TEXTYPE_SRGB_RGBA;} break; + default: break; + } + if (swaprb) + { + // swap bytes + static int rgbaswapindices[4] = {2, 1, 0, 3}; + size = width * height * depth * sides * 4; + temppixels = (unsigned char *)Mem_Alloc(tempmempool, size); + if (data) + Image_CopyMux(temppixels, data, width, height*depth*sides, false, false, false, 4, 4, rgbaswapindices); + data = temppixels; + } + + // if sRGB texture formats are not supported, convert input to linear and upload as normal types + if (!vid.support.ext_texture_srgb) + { + qboolean convertsRGB = false; + switch(textype) + { + case TEXTYPE_SRGB_DXT1: textype = TEXTYPE_DXT1 ;convertsRGB = true;break; + case TEXTYPE_SRGB_DXT1A: textype = TEXTYPE_DXT1A ;convertsRGB = true;break; + case TEXTYPE_SRGB_DXT3: textype = TEXTYPE_DXT3 ;convertsRGB = true;break; + case TEXTYPE_SRGB_DXT5: textype = TEXTYPE_DXT5 ;convertsRGB = true;break; + case TEXTYPE_SRGB_PALETTE: textype = TEXTYPE_PALETTE;/*convertsRGB = true;*/break; + case TEXTYPE_SRGB_RGBA: textype = TEXTYPE_RGBA ;convertsRGB = true;break; + case TEXTYPE_SRGB_BGRA: textype = TEXTYPE_BGRA ;convertsRGB = true;break; + default: + break; + } + if (convertsRGB && data) + { + size = width * height * depth * sides * 4; + if (!temppixels) + { + temppixels = (unsigned char *)Mem_Alloc(tempmempool, size); + memcpy(temppixels, data, size); + data = temppixels; + } + Image_MakeLinearColorsFromsRGB(temppixels, temppixels, width*height*depth*sides); + } + } + + if (texturetype == GLTEXTURETYPE_CUBEMAP && !vid.support.arb_texture_cube_map) + { + Con_Printf ("R_LoadTexture: cubemap texture not supported by driver\n"); + return NULL; + } + if (texturetype == GLTEXTURETYPE_3D && !vid.support.ext_texture_3d) + { + Con_Printf ("R_LoadTexture: 3d texture not supported by driver\n"); + return NULL; + } + + texinfo = R_GetTexTypeInfo(textype, flags); + size = width * height * depth * sides * texinfo->inputbytesperpixel; + if (size < 1) + { + Con_Printf ("R_LoadTexture: bogus texture size (%dx%dx%dx%dbppx%dsides = %d bytes)\n", width, height, depth, texinfo->inputbytesperpixel * 8, sides, size); + return NULL; + } + + // clear the alpha flag if the texture has no transparent pixels + switch(textype) + { + case TEXTYPE_PALETTE: + case TEXTYPE_SRGB_PALETTE: + if (flags & TEXF_ALPHA) + { + flags &= ~TEXF_ALPHA; + if (data) + { + for (i = 0;i < size;i++) + { + if (((unsigned char *)&palette[data[i]])[3] < 255) + { + flags |= TEXF_ALPHA; + break; + } + } + } + } + break; + case TEXTYPE_RGBA: + case TEXTYPE_BGRA: + case TEXTYPE_SRGB_RGBA: + case TEXTYPE_SRGB_BGRA: + if (flags & TEXF_ALPHA) + { + flags &= ~TEXF_ALPHA; + if (data) + { + for (i = 3;i < size;i += 4) + { + if (data[i] < 255) + { + flags |= TEXF_ALPHA; + break; + } + } + } + } + break; + case TEXTYPE_SHADOWMAP16_COMP: + case TEXTYPE_SHADOWMAP16_RAW: + case TEXTYPE_SHADOWMAP24_COMP: + case TEXTYPE_SHADOWMAP24_RAW: + break; + case TEXTYPE_DXT1: + case TEXTYPE_SRGB_DXT1: + break; + case TEXTYPE_DXT1A: + case TEXTYPE_SRGB_DXT1A: + case TEXTYPE_DXT3: + case TEXTYPE_SRGB_DXT3: + case TEXTYPE_DXT5: + case TEXTYPE_SRGB_DXT5: + flags |= TEXF_ALPHA; + break; + case TEXTYPE_ALPHA: + flags |= TEXF_ALPHA; + break; + case TEXTYPE_COLORBUFFER: + case TEXTYPE_COLORBUFFER16F: + case TEXTYPE_COLORBUFFER32F: + flags |= TEXF_ALPHA; + break; + default: + Sys_Error("R_LoadTexture: unknown texture type"); + } + + texinfo2 = R_GetTexTypeInfo(textype, flags); + if(size == width * height * depth * sides * texinfo->inputbytesperpixel) + texinfo = texinfo2; + else + Con_Printf ("R_LoadTexture: input size changed after alpha fallback\n"); + + glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); + if (identifier) + strlcpy (glt->identifier, identifier, sizeof(glt->identifier)); + + glt->pool = pool; + glt->chain = pool->gltchain; + pool->gltchain = glt; + glt->inputwidth = width; + glt->inputheight = height; + glt->inputdepth = depth; + glt->flags = flags; + glt->miplevel = (miplevel < 0) ? R_PicmipForFlags(flags) : miplevel; // note: if miplevel is -1, we know the texture is in original size and we can picmip it normally + glt->textype = texinfo; + glt->texturetype = texturetype; + glt->inputdatasize = size; + glt->palette = palette; + glt->glinternalformat = texinfo->glinternalformat; + glt->glformat = texinfo->glformat; + glt->gltype = texinfo->gltype; + glt->bytesperpixel = texinfo->internalbytesperpixel; + glt->sides = glt->texturetype == GLTEXTURETYPE_CUBEMAP ? 6 : 1; + glt->texnum = 0; + glt->dirty = false; + glt->glisdepthstencil = false; + glt->gltexturetypeenum = gltexturetypeenums[glt->texturetype]; + // init the dynamic texture attributes, too [11/22/2007 Black] + glt->updatecallback = NULL; + glt->updatacallback_data = NULL; + + GL_Texture_CalcImageSize(glt->texturetype, glt->flags, glt->miplevel, glt->inputwidth, glt->inputheight, glt->inputdepth, &glt->tilewidth, &glt->tileheight, &glt->tiledepth, &glt->miplevels); + + // upload the texture + // data may be NULL (blank texture for dynamic rendering) + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglGenTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DFORMAT d3dformat; + D3DPOOL d3dpool; + DWORD d3dusage; + HRESULT d3dresult; + d3dusage = 0; + d3dpool = D3DPOOL_MANAGED; + if (flags & TEXF_RENDERTARGET) + { + d3dusage |= D3DUSAGE_RENDERTARGET; + d3dpool = D3DPOOL_DEFAULT; + } + switch(textype) + { + case TEXTYPE_PALETTE: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; + case TEXTYPE_RGBA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8B8G8R8 : D3DFMT_X8B8G8R8;break; + case TEXTYPE_BGRA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; + case TEXTYPE_COLORBUFFER: d3dformat = D3DFMT_A8R8G8B8;break; + case TEXTYPE_COLORBUFFER16F: d3dformat = D3DFMT_A16B16G16R16F;break; + case TEXTYPE_COLORBUFFER32F: d3dformat = D3DFMT_A32B32G32R32F;break; + case TEXTYPE_ALPHA: d3dformat = D3DFMT_A8;break; + default: d3dformat = D3DFMT_A8R8G8B8;Sys_Error("R_LoadTexture: unsupported texture type %i when picking D3DFMT", (int)textype);break; + } + glt->d3dformat = d3dformat; + glt->d3dusage = d3dusage; + glt->d3dpool = d3dpool; + glt->d3disrendertargetsurface = false; + glt->d3disdepthstencilsurface = false; + if (glt->tiledepth > 1) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateVolumeTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->tiledepth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DVolumeTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateVolumeTexture failed!"); + } + else if (glt->sides == 6) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateCubeTexture(vid_d3d9dev, glt->tilewidth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DCubeTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateCubeTexture failed!"); + } + else + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL))) + Sys_Error("IDirect3DDevice9_CreateTexture failed!"); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + { + int tflags = 0; + switch(textype) + { + case TEXTYPE_PALETTE: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; + case TEXTYPE_RGBA: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA8;break; + case TEXTYPE_BGRA: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; + case TEXTYPE_COLORBUFFER: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; + case TEXTYPE_COLORBUFFER16F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA16F;break; + case TEXTYPE_COLORBUFFER32F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA32F;break; + case TEXTYPE_SHADOWMAP16_COMP: + case TEXTYPE_SHADOWMAP16_RAW: + case TEXTYPE_SHADOWMAP24_COMP: + case TEXTYPE_SHADOWMAP24_RAW: tflags = DPSOFTRAST_TEXTURE_FORMAT_DEPTH;break; + case TEXTYPE_DEPTHBUFFER16: + case TEXTYPE_DEPTHBUFFER24: + case TEXTYPE_DEPTHBUFFER24STENCIL8: tflags = DPSOFTRAST_TEXTURE_FORMAT_DEPTH;break; + case TEXTYPE_ALPHA: tflags = DPSOFTRAST_TEXTURE_FORMAT_ALPHA8;break; + default: Sys_Error("R_LoadTexture: unsupported texture type %i when picking DPSOFTRAST_TEXTURE_FLAGS", (int)textype); + } + if (glt->miplevels > 1) tflags |= DPSOFTRAST_TEXTURE_FLAG_MIPMAP; + if (flags & TEXF_ALPHA) tflags |= DPSOFTRAST_TEXTURE_FLAG_USEALPHA; + if (glt->sides == 6) tflags |= DPSOFTRAST_TEXTURE_FLAG_CUBEMAP; + if (glt->flags & TEXF_CLAMP) tflags |= DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE; + glt->texnum = DPSOFTRAST_Texture_New(tflags, glt->tilewidth, glt->tileheight, glt->tiledepth); + } + break; + } + + R_UploadFullTexture(glt, data); + + if ((glt->flags & TEXF_ALLOWUPDATES) && gl_nopartialtextureupdates.integer) + glt->bufferpixels = (unsigned char *)Mem_Alloc(texturemempool, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->sides*glt->bytesperpixel); + + // free any temporary processing buffer we allocated... + if (temppixels) + Mem_Free(temppixels); + + // texture converting and uploading can take a while, so make sure we're sending keepalives + // FIXME: this causes rendering during R_Shadow_DrawLights +// CL_KeepaliveMessage(false); + + return (rtexture_t *)glt; +} + +rtexture_t *R_LoadTexture2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) +{ + return R_SetupTexture(rtexturepool, identifier, width, height, 1, 1, flags, miplevel, textype, GLTEXTURETYPE_2D, data, palette); +} + +rtexture_t *R_LoadTexture3D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) +{ + return R_SetupTexture(rtexturepool, identifier, width, height, depth, 1, flags, miplevel, textype, GLTEXTURETYPE_3D, data, palette); +} + +rtexture_t *R_LoadTextureCubeMap(rtexturepool_t *rtexturepool, const char *identifier, int width, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) +{ + return R_SetupTexture(rtexturepool, identifier, width, width, 1, 6, flags, miplevel, textype, GLTEXTURETYPE_CUBEMAP, data, palette); +} + +rtexture_t *R_LoadTextureShadowMap2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, textype_t textype, qboolean filter) +{ + return R_SetupTexture(rtexturepool, identifier, width, height, 1, 1, TEXF_RENDERTARGET | TEXF_CLAMP | (filter ? TEXF_FORCELINEAR : TEXF_FORCENEAREST), -1, textype, GLTEXTURETYPE_2D, NULL, NULL); +} + +rtexture_t *R_LoadTextureRenderBuffer(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, textype_t textype) +{ + gltexture_t *glt; + gltexturepool_t *pool = (gltexturepool_t *)rtexturepool; + textypeinfo_t *texinfo; + + if (cls.state == ca_dedicated) + return NULL; + + texinfo = R_GetTexTypeInfo(textype, TEXF_RENDERTARGET | TEXF_CLAMP); + + glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); + if (identifier) + strlcpy (glt->identifier, identifier, sizeof(glt->identifier)); + glt->pool = pool; + glt->chain = pool->gltchain; + pool->gltchain = glt; + glt->inputwidth = width; + glt->inputheight = height; + glt->inputdepth = 1; + glt->flags = TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_FORCENEAREST; + glt->miplevel = 0; + glt->textype = texinfo; + glt->texturetype = textype; + glt->inputdatasize = width*height*texinfo->internalbytesperpixel; + glt->palette = NULL; + glt->glinternalformat = texinfo->glinternalformat; + glt->glformat = texinfo->glformat; + glt->gltype = texinfo->gltype; + glt->bytesperpixel = texinfo->internalbytesperpixel; + glt->sides = glt->texturetype == GLTEXTURETYPE_CUBEMAP ? 6 : 1; + glt->texnum = 0; + glt->dirty = false; + glt->glisdepthstencil = glt->texturetype == TEXTYPE_DEPTHBUFFER24STENCIL8; + glt->gltexturetypeenum = gltexturetypeenums[glt->texturetype]; + // init the dynamic texture attributes, too [11/22/2007 Black] + glt->updatecallback = NULL; + glt->updatacallback_data = NULL; + + GL_Texture_CalcImageSize(glt->texturetype, glt->flags, glt->miplevel, glt->inputwidth, glt->inputheight, glt->inputdepth, &glt->tilewidth, &glt->tileheight, &glt->tiledepth, &glt->miplevels); + + // upload the texture + // data may be NULL (blank texture for dynamic rendering) + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + qglGenRenderbuffers(1, (GLuint *)&glt->renderbuffernum);CHECKGLERROR + qglBindRenderbuffer(GL_RENDERBUFFER, glt->renderbuffernum);CHECKGLERROR + qglRenderbufferStorage(GL_RENDERBUFFER, glt->glinternalformat, glt->tilewidth, glt->tileheight);CHECKGLERROR + // note we can query the renderbuffer for info with glGetRenderbufferParameteriv for GL_WIDTH, GL_HEIGHt, GL_RED_SIZE, GL_GREEN_SIZE, GL_BLUE_SIZE, GL_GL_ALPHA_SIZE, GL_DEPTH_SIZE, GL_STENCIL_SIZE, GL_INTERNAL_FORMAT + qglBindRenderbuffer(GL_RENDERBUFFER, 0);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DFORMAT d3dformat; + HRESULT d3dresult; + glt->d3disrendertargetsurface = false; + glt->d3disdepthstencilsurface = false; + switch(textype) + { + case TEXTYPE_COLORBUFFER: d3dformat = D3DFMT_A8R8G8B8;glt->d3disrendertargetsurface = true;break; + case TEXTYPE_COLORBUFFER16F: d3dformat = D3DFMT_A16B16G16R16F;glt->d3disrendertargetsurface = true;break; + case TEXTYPE_COLORBUFFER32F: d3dformat = D3DFMT_A32B32G32R32F;glt->d3disrendertargetsurface = true;break; + case TEXTYPE_DEPTHBUFFER16: d3dformat = D3DFMT_D16;glt->d3disdepthstencilsurface = true;break; + case TEXTYPE_DEPTHBUFFER24: d3dformat = D3DFMT_D24X8;glt->d3disdepthstencilsurface = true;break; + case TEXTYPE_DEPTHBUFFER24STENCIL8: d3dformat = D3DFMT_D24S8;glt->d3disdepthstencilsurface = true;break; + default: d3dformat = D3DFMT_A8R8G8B8;Sys_Error("R_LoadTextureRenderbuffer: unsupported texture type %i when picking D3DFMT", (int)textype);break; + } + glt->d3dformat = d3dformat; + glt->d3dusage = 0; + glt->d3dpool = 0; + if (glt->d3disrendertargetsurface) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dsurface, NULL))) + Sys_Error("IDirect3DDevice9_CreateRenderTarget failed!"); + } + else if (glt->d3disdepthstencilsurface) + { + if (FAILED(d3dresult = IDirect3DDevice9_CreateDepthStencilSurface(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dsurface, NULL))) + Sys_Error("IDirect3DDevice9_CreateDepthStencilSurface failed!"); + } + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + { + int tflags = 0; + switch(textype) + { + case TEXTYPE_COLORBUFFER: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8 | DPSOFTRAST_TEXTURE_FLAG_USEALPHA | DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE;break; + case TEXTYPE_COLORBUFFER16F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA16F | DPSOFTRAST_TEXTURE_FLAG_USEALPHA | DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE;break; + case TEXTYPE_COLORBUFFER32F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA32F | DPSOFTRAST_TEXTURE_FLAG_USEALPHA | DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE;break; + case TEXTYPE_DEPTHBUFFER16: + case TEXTYPE_DEPTHBUFFER24: + case TEXTYPE_DEPTHBUFFER24STENCIL8: tflags = DPSOFTRAST_TEXTURE_FORMAT_DEPTH | DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE;break; + default: Sys_Error("R_LoadTextureRenderbuffer: unsupported texture type %i when picking DPSOFTRAST_TEXTURE_FLAGS", (int)textype); + } + glt->texnum = DPSOFTRAST_Texture_New(tflags, glt->tilewidth, glt->tileheight, glt->tiledepth); + } + break; + } + + return (rtexture_t *)glt; +} + +int R_SaveTextureDDSFile(rtexture_t *rt, const char *filename, qboolean skipuncompressed, qboolean hasalpha) +{ +#ifdef USE_GLES2 + return -1; // unsupported on this platform +#else + gltexture_t *glt = (gltexture_t *)rt; + unsigned char *dds; + int oldbindtexnum; + int bytesperpixel = 0; + int bytesperblock = 0; + int dds_flags; + int dds_format_flags; + int dds_caps1; + int dds_caps2; + int ret; + int mip; + int mipmaps; + int mipinfo[16][4]; + int ddssize = 128; + GLint internalformat; + const char *ddsfourcc; + if (!rt) + return -1; // NULL pointer + if (!strcmp(gl_version, "2.0.5885 WinXP Release")) + return -2; // broken driver - crashes on reading internal format + if (!qglGetTexLevelParameteriv) + return -2; + GL_ActiveTexture(0); + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + qglGetTexLevelParameteriv(gltexturetypeenums[glt->texturetype], 0, GL_TEXTURE_INTERNAL_FORMAT, &internalformat); + switch(internalformat) + { + default: ddsfourcc = NULL;bytesperpixel = 4;break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: ddsfourcc = "DXT1";bytesperblock = 8;break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: ddsfourcc = "DXT3";bytesperblock = 16;break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: ddsfourcc = "DXT5";bytesperblock = 16;break; + } + // if premultiplied alpha, say so in the DDS file + if(glt->flags & TEXF_RGBMULTIPLYBYALPHA) + { + switch(internalformat) + { + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: ddsfourcc = "DXT2";break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: ddsfourcc = "DXT4";break; + } + } + if (!bytesperblock && skipuncompressed) + return -3; // skipped + memset(mipinfo, 0, sizeof(mipinfo)); + mipinfo[0][0] = glt->tilewidth; + mipinfo[0][1] = glt->tileheight; + mipmaps = 1; + if ((glt->flags & TEXF_MIPMAP) && !(glt->tilewidth == 1 && glt->tileheight == 1)) + { + for (mip = 1;mip < 16;mip++) + { + mipinfo[mip][0] = mipinfo[mip-1][0] > 1 ? mipinfo[mip-1][0] >> 1 : 1; + mipinfo[mip][1] = mipinfo[mip-1][1] > 1 ? mipinfo[mip-1][1] >> 1 : 1; + if (mipinfo[mip][0] == 1 && mipinfo[mip][1] == 1) + { + mip++; + break; + } + } + mipmaps = mip; + } + for (mip = 0;mip < mipmaps;mip++) + { + mipinfo[mip][2] = bytesperblock ? ((mipinfo[mip][0]+3)/4)*((mipinfo[mip][1]+3)/4)*bytesperblock : mipinfo[mip][0]*mipinfo[mip][1]*bytesperpixel; + mipinfo[mip][3] = ddssize; + ddssize += mipinfo[mip][2]; + } + dds = (unsigned char *)Mem_Alloc(tempmempool, ddssize); + if (!dds) + return -4; + dds_caps1 = 0x1000; // DDSCAPS_TEXTURE + dds_caps2 = 0; + if (bytesperblock) + { + dds_flags = 0x81007; // DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_LINEARSIZE + dds_format_flags = 0x4; // DDPF_FOURCC + } + else + { + dds_flags = 0x100F; // DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH + dds_format_flags = 0x40; // DDPF_RGB + } + if (mipmaps) + { + dds_flags |= 0x20000; // DDSD_MIPMAPCOUNT + dds_caps1 |= 0x400008; // DDSCAPS_MIPMAP | DDSCAPS_COMPLEX + } + if(hasalpha) + dds_format_flags |= 0x1; // DDPF_ALPHAPIXELS + memcpy(dds, "DDS ", 4); + StoreLittleLong(dds+4, 124); // http://msdn.microsoft.com/en-us/library/bb943982%28v=vs.85%29.aspx says so + StoreLittleLong(dds+8, dds_flags); + StoreLittleLong(dds+12, mipinfo[0][1]); // height + StoreLittleLong(dds+16, mipinfo[0][0]); // width + StoreLittleLong(dds+24, 0); // depth + StoreLittleLong(dds+28, mipmaps); // mipmaps + StoreLittleLong(dds+76, 32); // format size + StoreLittleLong(dds+80, dds_format_flags); + StoreLittleLong(dds+108, dds_caps1); + StoreLittleLong(dds+112, dds_caps2); + if (bytesperblock) + { + StoreLittleLong(dds+20, mipinfo[0][2]); // linear size + memcpy(dds+84, ddsfourcc, 4); + for (mip = 0;mip < mipmaps;mip++) + { + qglGetCompressedTexImageARB(gltexturetypeenums[glt->texturetype], mip, dds + mipinfo[mip][3]);CHECKGLERROR + } + } + else + { + StoreLittleLong(dds+20, mipinfo[0][0]*bytesperpixel); // pitch + StoreLittleLong(dds+88, bytesperpixel*8); // bits per pixel + dds[94] = dds[97] = dds[100] = dds[107] = 255; // bgra byte order masks + for (mip = 0;mip < mipmaps;mip++) + { + qglGetTexImage(gltexturetypeenums[glt->texturetype], mip, GL_BGRA, GL_UNSIGNED_BYTE, dds + mipinfo[mip][3]);CHECKGLERROR + } + } + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + ret = FS_WriteFile(filename, dds, ddssize); + Mem_Free(dds); + return ret ? ddssize : -5; +#endif +} + +rtexture_t *R_LoadTextureDDSFile(rtexturepool_t *rtexturepool, const char *filename, qboolean srgb, int flags, qboolean *hasalphaflag, float *avgcolor, int miplevel, qboolean optionaltexture) // DDS textures are opaque, so miplevel isn't a pointer but just seen as a hint +{ + int i, size, dds_format_flags, dds_miplevels, dds_width, dds_height; + //int dds_flags; + textype_t textype; + int bytesperblock, bytesperpixel; + int mipcomplete; + gltexture_t *glt; + gltexturepool_t *pool = (gltexturepool_t *)rtexturepool; + textypeinfo_t *texinfo; + int mip, mipwidth, mipheight, mipsize, mipsize_total; + unsigned int c, r, g, b; + GLint oldbindtexnum = 0; + unsigned char *mippixels; + unsigned char *mippixels_start; + unsigned char *ddspixels; + unsigned char *dds; + fs_offset_t ddsfilesize; + unsigned int ddssize; + qboolean force_swdecode, npothack; + + if (cls.state == ca_dedicated) + return NULL; + + dds = FS_LoadFile(filename, tempmempool, true, &ddsfilesize); + ddssize = ddsfilesize; + + if (!dds) + { + if (r_texture_dds_load_logfailure.integer && (r_texture_dds_load_logfailure.integer >= 2 || !optionaltexture)) + Log_Printf("ddstexturefailures.log", "%s\n", filename); + return NULL; // not found + } + + if (ddsfilesize <= 128 || memcmp(dds, "DDS ", 4) || ddssize < (unsigned int)BuffLittleLong(dds+4) || BuffLittleLong(dds+76) != 32) + { + Mem_Free(dds); + Con_Printf("^1%s: not a DDS image\n", filename); + return NULL; + } + + //dds_flags = BuffLittleLong(dds+8); + dds_format_flags = BuffLittleLong(dds+80); + dds_miplevels = (BuffLittleLong(dds+108) & 0x400000) ? BuffLittleLong(dds+28) : 1; + dds_width = BuffLittleLong(dds+16); + dds_height = BuffLittleLong(dds+12); + ddspixels = dds + 128; + + if(r_texture_dds_load_alphamode.integer == 0) + if(!(dds_format_flags & 0x1)) // DDPF_ALPHAPIXELS + flags &= ~TEXF_ALPHA; + + //flags &= ~TEXF_ALPHA; // disabled, as we DISABLE TEXF_ALPHA in the alpha detection, not enable it! + if ((dds_format_flags & 0x40) && BuffLittleLong(dds+88) == 32) + { + // very sloppy BGRA 32bit identification + textype = TEXTYPE_BGRA; + flags &= ~TEXF_COMPRESS; // don't let the textype be wrong + bytesperblock = 0; + bytesperpixel = 4; + size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(dds_width, dds_height), bytesperpixel); + if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) + { + Mem_Free(dds); + Con_Printf("^1%s: invalid BGRA DDS image\n", filename); + return NULL; + } + if((r_texture_dds_load_alphamode.integer == 1) && (flags & TEXF_ALPHA)) + { + // check alpha + for (i = 3;i < size;i += 4) + if (ddspixels[i] < 255) + break; + if (i >= size) + flags &= ~TEXF_ALPHA; + } + } + else if (!memcmp(dds+84, "DXT1", 4)) + { + // we need to find out if this is DXT1 (opaque) or DXT1A (transparent) + // LordHavoc: it is my belief that this does not infringe on the + // patent because it is not decoding pixels... + textype = TEXTYPE_DXT1; + bytesperblock = 8; + bytesperpixel = 0; + //size = ((dds_width+3)/4)*((dds_height+3)/4)*bytesperblock; + size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); + if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) + { + Mem_Free(dds); + Con_Printf("^1%s: invalid DXT1 DDS image\n", filename); + return NULL; + } + if (flags & TEXF_ALPHA) + { + if (r_texture_dds_load_alphamode.integer == 1) + { + // check alpha + for (i = 0;i < size;i += bytesperblock) + if (ddspixels[i+0] + ddspixels[i+1] * 256 <= ddspixels[i+2] + ddspixels[i+3] * 256) + { + // NOTE: this assumes sizeof(unsigned int) == 4 + unsigned int data = * (unsigned int *) &(ddspixels[i+4]); + // check if data, in base 4, contains a digit 3 (DXT1: transparent pixel) + if(data & (data<<1) & 0xAAAAAAAA)//rgh + break; + } + if (i < size) + textype = TEXTYPE_DXT1A; + else + flags &= ~TEXF_ALPHA; + } + else if (r_texture_dds_load_alphamode.integer == 0) + textype = TEXTYPE_DXT1A; + else + { + flags &= ~TEXF_ALPHA; + } + } + } + else if (!memcmp(dds+84, "DXT3", 4) || !memcmp(dds+84, "DXT2", 4)) + { + if(!memcmp(dds+84, "DXT2", 4)) + { + if(!(flags & TEXF_RGBMULTIPLYBYALPHA)) + { + Con_Printf("^1%s: expecting DXT3 image without premultiplied alpha, got DXT2 image with premultiplied alpha\n", filename); + } + } + else + { + if(flags & TEXF_RGBMULTIPLYBYALPHA) + { + Con_Printf("^1%s: expecting DXT2 image without premultiplied alpha, got DXT3 image without premultiplied alpha\n", filename); + } + } + textype = TEXTYPE_DXT3; + bytesperblock = 16; + bytesperpixel = 0; + size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); + if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) + { + Mem_Free(dds); + Con_Printf("^1%s: invalid DXT3 DDS image\n", filename); + return NULL; + } + // we currently always assume alpha + } + else if (!memcmp(dds+84, "DXT5", 4) || !memcmp(dds+84, "DXT4", 4)) + { + if(!memcmp(dds+84, "DXT4", 4)) + { + if(!(flags & TEXF_RGBMULTIPLYBYALPHA)) + { + Con_Printf("^1%s: expecting DXT5 image without premultiplied alpha, got DXT4 image with premultiplied alpha\n", filename); + } + } + else + { + if(flags & TEXF_RGBMULTIPLYBYALPHA) + { + Con_Printf("^1%s: expecting DXT4 image without premultiplied alpha, got DXT5 image without premultiplied alpha\n", filename); + } + } + textype = TEXTYPE_DXT5; + bytesperblock = 16; + bytesperpixel = 0; + size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); + if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) + { + Mem_Free(dds); + Con_Printf("^1%s: invalid DXT5 DDS image\n", filename); + return NULL; + } + // we currently always assume alpha + } + else + { + Mem_Free(dds); + Con_Printf("^1%s: unrecognized/unsupported DDS format\n", filename); + return NULL; + } + + // when requesting a non-alpha texture and we have DXT3/5, convert to DXT1 + if(!(flags & TEXF_ALPHA) && (textype == TEXTYPE_DXT3 || textype == TEXTYPE_DXT5)) + { + textype = TEXTYPE_DXT1; + bytesperblock = 8; + ddssize -= 128; + ddssize /= 2; + for (i = 0;i < (int)ddssize;i += bytesperblock) + memcpy(&ddspixels[i], &ddspixels[(i<<1)+8], 8); + ddssize += 128; + } + + force_swdecode = false; + npothack = + (!vid.support.arb_texture_non_power_of_two && + ( + (dds_width & (dds_width - 1)) + || + (dds_height & (dds_height - 1)) + ) + ); + if(bytesperblock) + { + if(vid.support.arb_texture_compression && vid.support.ext_texture_compression_s3tc && !npothack) + { + if(r_texture_dds_swdecode.integer > 1) + force_swdecode = true; + } + else + { + if(r_texture_dds_swdecode.integer < 1) + { + // unsupported + Mem_Free(dds); + return NULL; + } + force_swdecode = true; + } + } + + // return whether this texture is transparent + if (hasalphaflag) + *hasalphaflag = (flags & TEXF_ALPHA) != 0; + + // if we SW decode, choose 2 sizes bigger + if(force_swdecode) + { + // this is quarter res, so do not scale down more than we have to + miplevel -= 2; + + if(miplevel < 0) + Con_DPrintf("WARNING: fake software decoding of compressed texture %s degraded quality\n", filename); + } + + // this is where we apply gl_picmip + mippixels_start = ddspixels; + mipwidth = dds_width; + mipheight = dds_height; + while(miplevel >= 1 && dds_miplevels >= 1) + { + if (mipwidth <= 1 && mipheight <= 1) + break; + mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; + mippixels_start += mipsize; // just skip + --dds_miplevels; + --miplevel; + if (mipwidth > 1) + mipwidth >>= 1; + if (mipheight > 1) + mipheight >>= 1; + } + mipsize_total = ddssize - 128 - (mippixels_start - ddspixels); + mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; + + // from here on, we do not need the ddspixels and ddssize any more (apart from the statistics entry in glt) + + // fake decode S3TC if needed + if(force_swdecode) + { + int mipsize_new = mipsize_total / bytesperblock * 4; + unsigned char *mipnewpixels = (unsigned char *) Mem_Alloc(tempmempool, mipsize_new); + unsigned char *p = mipnewpixels; + for (i = bytesperblock == 16 ? 8 : 0;i < (int)mipsize_total;i += bytesperblock, p += 4) + { + c = mippixels_start[i] + 256*mippixels_start[i+1] + 65536*mippixels_start[i+2] + 16777216*mippixels_start[i+3]; + p[2] = (((c >> 11) & 0x1F) + ((c >> 27) & 0x1F)) * (0.5f / 31.0f * 255.0f); + p[1] = (((c >> 5) & 0x3F) + ((c >> 21) & 0x3F)) * (0.5f / 63.0f * 255.0f); + p[0] = (((c ) & 0x1F) + ((c >> 16) & 0x1F)) * (0.5f / 31.0f * 255.0f); + if(textype == TEXTYPE_DXT5) + p[3] = (0.5 * mippixels_start[i-8] + 0.5 * mippixels_start[i-7]); + else if(textype == TEXTYPE_DXT3) + p[3] = ( + (mippixels_start[i-8] & 0x0F) + + (mippixels_start[i-8] >> 4) + + (mippixels_start[i-7] & 0x0F) + + (mippixels_start[i-7] >> 4) + + (mippixels_start[i-6] & 0x0F) + + (mippixels_start[i-6] >> 4) + + (mippixels_start[i-5] & 0x0F) + + (mippixels_start[i-5] >> 4) + ) * (0.125f / 15.0f * 255.0f); + else + p[3] = 255; + } + + textype = TEXTYPE_BGRA; + bytesperblock = 0; + bytesperpixel = 4; + + // as each block becomes a pixel, we must use pixel count for this + mipwidth = (mipwidth + 3) / 4; + mipheight = (mipheight + 3) / 4; + mipsize = bytesperpixel * mipwidth * mipheight; + mippixels_start = mipnewpixels; + mipsize_total = mipsize_new; + } + + // start mip counting + mippixels = mippixels_start; + + // calculate average color if requested + if (avgcolor) + { + float f; + Vector4Clear(avgcolor); + if (bytesperblock) + { + for (i = bytesperblock == 16 ? 8 : 0;i < mipsize;i += bytesperblock) + { + c = mippixels[i] + 256*mippixels[i+1] + 65536*mippixels[i+2] + 16777216*mippixels[i+3]; + avgcolor[0] += ((c >> 11) & 0x1F) + ((c >> 27) & 0x1F); + avgcolor[1] += ((c >> 5) & 0x3F) + ((c >> 21) & 0x3F); + avgcolor[2] += ((c ) & 0x1F) + ((c >> 16) & 0x1F); + if(textype == TEXTYPE_DXT5) + avgcolor[3] += (mippixels[i-8] + (int) mippixels[i-7]) * (0.5f / 255.0f); + else if(textype == TEXTYPE_DXT3) + avgcolor[3] += ( + (mippixels_start[i-8] & 0x0F) + + (mippixels_start[i-8] >> 4) + + (mippixels_start[i-7] & 0x0F) + + (mippixels_start[i-7] >> 4) + + (mippixels_start[i-6] & 0x0F) + + (mippixels_start[i-6] >> 4) + + (mippixels_start[i-5] & 0x0F) + + (mippixels_start[i-5] >> 4) + ) * (0.125f / 15.0f); + else + avgcolor[3] += 1.0f; + } + f = (float)bytesperblock / mipsize; + avgcolor[0] *= (0.5f / 31.0f) * f; + avgcolor[1] *= (0.5f / 63.0f) * f; + avgcolor[2] *= (0.5f / 31.0f) * f; + avgcolor[3] *= f; + } + else + { + for (i = 0;i < mipsize;i += 4) + { + avgcolor[0] += mippixels[i+2]; + avgcolor[1] += mippixels[i+1]; + avgcolor[2] += mippixels[i]; + avgcolor[3] += mippixels[i+3]; + } + f = (1.0f / 255.0f) * bytesperpixel / mipsize; + avgcolor[0] *= f; + avgcolor[1] *= f; + avgcolor[2] *= f; + avgcolor[3] *= f; + } + } + + // if we want sRGB, convert now + if(srgb) + { + if (vid.support.ext_texture_srgb) + { + switch(textype) + { + case TEXTYPE_DXT1: textype = TEXTYPE_SRGB_DXT1 ;break; + case TEXTYPE_DXT1A: textype = TEXTYPE_SRGB_DXT1A ;break; + case TEXTYPE_DXT3: textype = TEXTYPE_SRGB_DXT3 ;break; + case TEXTYPE_DXT5: textype = TEXTYPE_SRGB_DXT5 ;break; + case TEXTYPE_RGBA: textype = TEXTYPE_SRGB_RGBA ;break; + default: + break; + } + } + else + { + switch(textype) + { + case TEXTYPE_DXT1: + case TEXTYPE_DXT1A: + case TEXTYPE_DXT3: + case TEXTYPE_DXT5: + { + for (i = bytesperblock == 16 ? 8 : 0;i < mipsize_total;i += bytesperblock) + { + int c0, c1, c0new, c1new; + c0 = mippixels_start[i] + 256*mippixels_start[i+1]; + r = ((c0 >> 11) & 0x1F); + g = ((c0 >> 5) & 0x3F); + b = ((c0 ) & 0x1F); + r = floor(Image_LinearFloatFromsRGB(r * (255.0f / 31.0f)) * 31.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB + g = floor(Image_LinearFloatFromsRGB(g * (255.0f / 63.0f)) * 63.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB + b = floor(Image_LinearFloatFromsRGB(b * (255.0f / 31.0f)) * 31.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB + c0new = (r << 11) | (g << 5) | b; + c1 = mippixels_start[i+2] + 256*mippixels_start[i+3]; + r = ((c1 >> 11) & 0x1F); + g = ((c1 >> 5) & 0x3F); + b = ((c1 ) & 0x1F); + r = floor(Image_LinearFloatFromsRGB(r * (255.0f / 31.0f)) * 31.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB + g = floor(Image_LinearFloatFromsRGB(g * (255.0f / 63.0f)) * 63.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB + b = floor(Image_LinearFloatFromsRGB(b * (255.0f / 31.0f)) * 31.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB + c1new = (r << 11) | (g << 5) | b; + // swap the colors if needed to fix order + if(c0 > c1) // thirds + { + if(c0new < c1new) + { + c = c0new; + c0new = c1new; + c1new = c; + if(c0new == c1new) + mippixels_start[i+4] ^= 0x55; + mippixels_start[i+5] ^= 0x55; + mippixels_start[i+6] ^= 0x55; + mippixels_start[i+7] ^= 0x55; + } + else if(c0new == c1new) + { + mippixels_start[i+4] = 0x00; + mippixels_start[i+5] = 0x00; + mippixels_start[i+6] = 0x00; + mippixels_start[i+7] = 0x00; + } + } + else // half + transparent + { + if(c0new > c1new) + { + c = c0new; + c0new = c1new; + c1new = c; + mippixels_start[i+4] ^= (~mippixels_start[i+4] >> 1) & 0x55; + mippixels_start[i+5] ^= (~mippixels_start[i+5] >> 1) & 0x55; + mippixels_start[i+6] ^= (~mippixels_start[i+6] >> 1) & 0x55; + mippixels_start[i+7] ^= (~mippixels_start[i+7] >> 1) & 0x55; + } + } + mippixels_start[i] = c0new & 255; + mippixels_start[i+1] = c0new >> 8; + mippixels_start[i+2] = c1new & 255; + mippixels_start[i+3] = c1new >> 8; + } + } + break; + case TEXTYPE_RGBA: + Image_MakeLinearColorsFromsRGB(mippixels, mippixels, mipsize_total / bytesperblock); + break; + default: + break; + } + } + } + + // when not requesting mipmaps, do not load them + if(!(flags & TEXF_MIPMAP)) + dds_miplevels = 0; + + if (dds_miplevels >= 1) + flags |= TEXF_MIPMAP; + else + flags &= ~TEXF_MIPMAP; + + texinfo = R_GetTexTypeInfo(textype, flags); + + glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); + strlcpy (glt->identifier, filename, sizeof(glt->identifier)); + glt->pool = pool; + glt->chain = pool->gltchain; + pool->gltchain = glt; + glt->inputwidth = mipwidth; + glt->inputheight = mipheight; + glt->inputdepth = 1; + glt->flags = flags; + glt->textype = texinfo; + glt->texturetype = GLTEXTURETYPE_2D; + glt->inputdatasize = ddssize; + glt->glinternalformat = texinfo->glinternalformat; + glt->glformat = texinfo->glformat; + glt->gltype = texinfo->gltype; + glt->bytesperpixel = texinfo->internalbytesperpixel; + glt->sides = 1; + glt->gltexturetypeenum = gltexturetypeenums[glt->texturetype]; + glt->tilewidth = mipwidth; + glt->tileheight = mipheight; + glt->tiledepth = 1; + glt->miplevels = dds_miplevels; + + if(npothack) + { + for (glt->tilewidth = 1;glt->tilewidth < mipwidth;glt->tilewidth <<= 1); + for (glt->tileheight = 1;glt->tileheight < mipheight;glt->tileheight <<= 1); + } + + // texture uploading can take a while, so make sure we're sending keepalives + CL_KeepaliveMessage(false); + + // create the texture object + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + CHECKGLERROR + GL_ActiveTexture(0); + oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); + qglGenTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR + qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DFORMAT d3dformat; + D3DPOOL d3dpool; + DWORD d3dusage; + switch(textype) + { + case TEXTYPE_BGRA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; + case TEXTYPE_DXT1: case TEXTYPE_DXT1A: d3dformat = D3DFMT_DXT1;break; + case TEXTYPE_DXT3: d3dformat = D3DFMT_DXT3;break; + case TEXTYPE_DXT5: d3dformat = D3DFMT_DXT5;break; + default: d3dformat = D3DFMT_A8R8G8B8;Host_Error("R_LoadTextureDDSFile: unsupported texture type %i when picking D3DFMT", (int)textype);break; + } + d3dusage = 0; + d3dpool = D3DPOOL_MANAGED; + IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, d3dusage, d3dformat, d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL); + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + glt->texnum = DPSOFTRAST_Texture_New(((glt->flags & TEXF_CLAMP) ? DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE : 0) | (dds_miplevels > 1 ? DPSOFTRAST_TEXTURE_FLAG_MIPMAP : 0), glt->tilewidth, glt->tileheight, glt->tiledepth); + break; + } + + // upload the texture + // we need to restore the texture binding after finishing the upload + mipcomplete = false; + + for (mip = 0;mip <= dds_miplevels;mip++) // <= to include the not-counted "largest" miplevel + { + unsigned char *upload_mippixels = mippixels; + int upload_mipwidth = mipwidth; + int upload_mipheight = mipheight; + mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; + if (mippixels + mipsize > mippixels_start + mipsize_total) + break; + if(npothack) + { + upload_mipwidth = (glt->tilewidth >> mip); + upload_mipheight = (glt->tileheight >> mip); + if(upload_mipwidth != mipwidth || upload_mipheight != mipheight) + // I _think_ they always mismatch, but I was too lazy + // to properly check, and this test here is really + // harmless + { + upload_mippixels = (unsigned char *) Mem_Alloc(tempmempool, 4 * upload_mipwidth * upload_mipheight); + Image_Resample32(mippixels, mipwidth, mipheight, 1, upload_mippixels, upload_mipwidth, upload_mipheight, 1, r_lerpimages.integer); + } + } + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + if (bytesperblock) + { + qglCompressedTexImage2DARB(GL_TEXTURE_2D, mip, glt->glinternalformat, upload_mipwidth, upload_mipheight, 0, mipsize, upload_mippixels);CHECKGLERROR + } + else + { + qglTexImage2D(GL_TEXTURE_2D, mip, glt->glinternalformat, upload_mipwidth, upload_mipheight, 0, glt->glformat, glt->gltype, upload_mippixels);CHECKGLERROR + } + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + { + D3DLOCKED_RECT d3dlockedrect; + if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) + { + memcpy(d3dlockedrect.pBits, upload_mippixels, mipsize); + IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); + } + break; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (bytesperblock) + Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + else + DPSOFTRAST_Texture_UpdateFull(glt->texnum, upload_mippixels); + // DPSOFTRAST calculates its own mipmaps + mip = dds_miplevels; + break; + } + if(upload_mippixels != mippixels) + Mem_Free(upload_mippixels); + mippixels += mipsize; + if (mipwidth <= 1 && mipheight <= 1) + { + mipcomplete = true; + break; + } + if (mipwidth > 1) + mipwidth >>= 1; + if (mipheight > 1) + mipheight >>= 1; + } + + // after upload we have to set some parameters... + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: +#ifdef GL_TEXTURE_MAX_LEVEL + if (dds_miplevels >= 1 && !mipcomplete) + { + // need to set GL_TEXTURE_MAX_LEVEL + qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAX_LEVEL, dds_miplevels - 1);CHECKGLERROR + } +#endif + GL_SetupTextureParameters(glt->flags, glt->textype->textype, glt->texturetype); + qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR + break; + case RENDERPATH_D3D9: +#ifdef SUPPORTD3D + glt->d3daddressw = 0; + if (glt->flags & TEXF_CLAMP) + { + glt->d3daddressu = D3DTADDRESS_CLAMP; + glt->d3daddressv = D3DTADDRESS_CLAMP; + if (glt->tiledepth > 1) + glt->d3daddressw = D3DTADDRESS_CLAMP; + } + else + { + glt->d3daddressu = D3DTADDRESS_WRAP; + glt->d3daddressv = D3DTADDRESS_WRAP; + if (glt->tiledepth > 1) + glt->d3daddressw = D3DTADDRESS_WRAP; + } + glt->d3dmipmaplodbias = 0; + glt->d3dmaxmiplevel = 0; + glt->d3dmaxmiplevelfilter = 0; + if (glt->flags & TEXF_MIPMAP) + { + glt->d3dminfilter = d3d_filter_mipmin; + glt->d3dmagfilter = d3d_filter_mipmag; + glt->d3dmipfilter = d3d_filter_mipmix; + } + else + { + glt->d3dminfilter = d3d_filter_flatmin; + glt->d3dmagfilter = d3d_filter_flatmag; + glt->d3dmipfilter = d3d_filter_flatmix; + } +#endif + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + if (glt->flags & TEXF_FORCELINEAR) + DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_LINEAR); + else if (glt->flags & TEXF_FORCENEAREST) + DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_NEAREST); + else if (glt->flags & TEXF_MIPMAP) + DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_mipmap); + else + DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_nomipmap); + break; + } + + Mem_Free(dds); + if(force_swdecode) + Mem_Free((unsigned char *) mippixels_start); + return (rtexture_t *)glt; +} + +int R_TextureWidth(rtexture_t *rt) +{ + return rt ? ((gltexture_t *)rt)->inputwidth : 0; +} + +int R_TextureHeight(rtexture_t *rt) +{ + return rt ? ((gltexture_t *)rt)->inputheight : 0; +} + +int R_TextureFlags(rtexture_t *rt) +{ + return rt ? ((gltexture_t *)rt)->flags : 0; +} + +void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth) +{ + gltexture_t *glt = (gltexture_t *)rt; + if (data == NULL) + Host_Error("R_UpdateTexture: no data supplied"); + if (glt == NULL) + Host_Error("R_UpdateTexture: no texture supplied"); + if (!glt->texnum && !glt->d3dtexture) + { + Con_DPrintf("R_UpdateTexture: texture %p \"%s\" in pool %p has not been uploaded yet\n", (void *)glt, glt->identifier, (void *)glt->pool); + return; + } + // update part of the texture + if (glt->bufferpixels) + { + int j; + int bpp = glt->bytesperpixel; + int inputskip = width*bpp; + int outputskip = glt->tilewidth*bpp; + const unsigned char *input = data; + unsigned char *output = glt->bufferpixels; + if (glt->inputdepth != 1 || glt->sides != 1) + Sys_Error("R_UpdateTexture on buffered texture that is not 2D\n"); + if (x < 0) + { + width += x; + input -= x*bpp; + x = 0; + } + if (y < 0) + { + height += y; + input -= y*inputskip; + y = 0; + } + if (width > glt->tilewidth - x) + width = glt->tilewidth - x; + if (height > glt->tileheight - y) + height = glt->tileheight - y; + if (width < 1 || height < 1) + return; + glt->dirty = true; + glt->buffermodified = true; + output += y*outputskip + x*bpp; + for (j = 0;j < height;j++, output += outputskip, input += inputskip) + memcpy(output, input, width*bpp); + } + else if (x || y || z || width != glt->inputwidth || height != glt->inputheight || depth != glt->inputdepth) + R_UploadPartialTexture(glt, data, x, y, z, width, height, depth); + else + R_UploadFullTexture(glt, data); +} + +int R_RealGetTexture(rtexture_t *rt) +{ + if (rt) + { + gltexture_t *glt; + glt = (gltexture_t *)rt; + if (glt->flags & GLTEXF_DYNAMIC) + R_UpdateDynamicTexture(glt); + if (glt->buffermodified && glt->bufferpixels) + { + glt->buffermodified = false; + R_UploadFullTexture(glt, glt->bufferpixels); + } + glt->dirty = false; + return glt->texnum; + } + else + return 0; +} + +void R_ClearTexture (rtexture_t *rt) +{ + gltexture_t *glt = (gltexture_t *)rt; + + R_UploadFullTexture(glt, NULL); +} + +int R_PicmipForFlags(int flags) +{ + int miplevel = 0; + if(flags & TEXF_PICMIP) + { + miplevel += gl_picmip.integer; + if (flags & TEXF_ISWORLD) + { + if (r_picmipworld.integer) + miplevel += gl_picmip_world.integer; + else + miplevel = 0; + } + else if (flags & TEXF_ISSPRITE) + { + if (r_picmipsprites.integer) + miplevel += gl_picmip_sprites.integer; + else + miplevel = 0; + } + else + miplevel += gl_picmip_other.integer; + } + return max(0, miplevel); +} diff --git a/app/jni/glquake.h b/app/jni/glquake.h new file mode 100644 index 0000000..4b68d27 --- /dev/null +++ b/app/jni/glquake.h @@ -0,0 +1,1347 @@ +/* +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. + +*/ + +#ifndef GLQUAKE_H +#define GLQUAKE_H + +#ifdef USE_GLES2 +#ifdef __IPHONEOS__ +#include +#else +#include +#include +//Hack to avoid compile errors and don't break the code +#define GL_STEREO 0x0000 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_BGRA 0x000080e1 +//#include +#endif +// used in R_SetupShader_Generic calls, not actually passed to GL +#ifndef GL_MODULATE +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 +#define GL_ADD 0x0104 +#endif +#endif + +// disable data conversion warnings + +#ifdef _MSC_VER +#pragma warning(disable : 4310) // LordHavoc: MSVC++ 2008 x86: cast truncates constant value +#pragma warning(disable : 4245) // LordHavoc: MSVC++ 2008 x86: 'initializing' : conversion from 'int' to 'unsigned char', signed/unsigned mismatch +#pragma warning(disable : 4204) // LordHavoc: MSVC++ 2008 x86: nonstandard extension used : non-constant aggregate initializer +#pragma warning(disable : 4267) // LordHavoc: MSVC++ 2008 x64, conversion from 'size_t' to 'int', possible loss of data +//#pragma warning(disable : 4244) // LordHavoc: MSVC++ 4 x86, double/float +//#pragma warning(disable : 4305) // LordHavoc: MSVC++ 6 x86, double/float +//#pragma warning(disable : 4706) // LordHavoc: MSVC++ 2008 x86, assignment within conditional expression +//#pragma warning(disable : 4127) // LordHavoc: MSVC++ 2008 x86, conditional expression is constant +//#pragma warning(disable : 4100) // LordHavoc: MSVC++ 2008 x86, unreferenced formal parameter +//#pragma warning(disable : 4055) // LordHavoc: MSVC++ 2008 x86, 'type cast' from data pointer to function pointer +//#pragma warning(disable : 4054) // LordHavoc: MSVC++ 2008 x86, 'type cast' from function pointer to data pointer +#endif + + +//==================================================== + +#ifndef USE_GLES2 +// wgl uses APIENTRY +#ifndef APIENTRY +#define APIENTRY +#endif + +// for platforms (wgl) that do not use GLAPIENTRY +#ifndef GLAPIENTRY +#define GLAPIENTRY APIENTRY +#endif + +#ifndef GL_PROJECTION +#include + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +// 1-byte signed +typedef signed char GLbyte; +// 2-byte signed +typedef short GLshort; +// 4-byte signed +typedef int GLint; +// 1-byte unsigned +typedef unsigned char GLubyte; +// 2-byte unsigned +typedef unsigned short GLushort; +// 4-byte unsigned +typedef unsigned int GLuint; +// 4-byte signed +typedef int GLsizei; +// single precision float +typedef float GLfloat; +// single precision float in [0,1] +typedef float GLclampf; +// double precision float +typedef double GLdouble; +// double precision float in [0,1] +typedef double GLclampd; +// int whose size is the same as a pointer (?) +typedef ptrdiff_t GLintptrARB; +// int whose size is the same as a pointer (?) +typedef ptrdiff_t GLsizeiptrARB; + +#define GL_STEREO 0x0C33 + +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 + +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +#define GL_DEPTH_TEST 0x0B71 + +#define GL_CULL_FACE 0x0B44 + +#define GL_BLEND 0x0BE2 +#define GL_ALPHA_TEST 0x0BC0 + +#define GL_ZERO 0x0 +#define GL_ONE 0x1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 + +#define GL_TEXTURE_ENV 0x2300 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D + +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 + +#define GL_ADD 0x0104 +#define GL_DECAL 0x2101 +#define GL_MODULATE 0x2100 + +#define GL_REPEAT 0x2901 +#define GL_CLAMP 0x2900 + +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +#define GL_FALSE 0x0 +#define GL_TRUE 0x1 + +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 + +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +//#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +//#define GL_EDGE_FLAG_ARRAY 0x8079 + +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C + +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +#define GL_NO_ERROR 0x0 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +#define GL_DITHER 0x0BD0 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 + +#define GL_MAX_TEXTURE_SIZE 0x0D33 + +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 + +#define GL_RED_SCALE 0x0D14 +#define GL_GREEN_SCALE 0x0D18 +#define GL_BLUE_SCALE 0x0D1A +#define GL_ALPHA_SCALE 0x0D1C + +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 + +#define GL_STENCIL_TEST 0x0B90 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 + +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +#define GL_POINT_SMOOTH 0x0B10 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_POLYGON_SMOOTH 0x0B41 + +#define GL_POLYGON_STIPPLE 0x0B42 + +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 + +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_VIEWPORT 0x0BA2 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_LUMINANCE 0x1909 +#define GL_INTENSITY 0x8049 + +#endif + +//GL_EXT_texture_filter_anisotropic +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +// GL_ARB_depth_texture +#ifndef GL_DEPTH_COMPONENT32_ARB +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +// GL_ARB_shadow +#ifndef GL_TEXTURE_COMPARE_MODE_ARB +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +// GL_ARB_multitexture +extern void qglMultiTexCoord1f(GLenum, GLfloat); +extern void qglMultiTexCoord2f(GLenum, GLfloat, GLfloat); +extern void qglMultiTexCoord3f(GLenum, GLfloat, GLfloat, GLfloat); +extern void qglMultiTexCoord4f(GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +extern void qglClientActiveTexture(GLenum); +#ifndef GL_ACTIVE_TEXTURE +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#endif + +// GL_ARB_texture_env_combine +#ifndef GL_COMBINE +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#endif + +#ifndef GL_MAX_ELEMENTS_VERTICES +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#endif +#ifndef GL_MAX_ELEMENTS_INDICES +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#endif + + +#ifndef GL_TEXTURE_3D +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_TEXTURE_BINDING_3D 0x806A +extern void qglTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void qglTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +extern void qglCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_TEXTURE_CUBE_MAP_POSITIVE_X +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#endif + +#ifndef GL_DEPTH_COMPONENT16_ARB +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +#ifndef GL_SCISSOR_TEST +#define GL_SCISSOR_TEST 0x0C11 +#define GL_SCISSOR_BOX 0x0C10 +#endif + +// GL_SGIS_texture_edge_clamp or GL_EXT_texture_edge_clamp +#ifndef GL_CLAMP_TO_EDGE +#define GL_CLAMP_TO_EDGE 0x812F +#endif + +//GL_ATI_separate_stencil +#ifndef GL_STENCIL_BACK_FUNC +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#endif +extern void qglStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); +extern void qglStencilFuncSeparate(GLenum, GLenum, GLint, GLuint); + +//GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +extern void qglActiveStencilFaceEXT(GLenum); + +//GL_EXT_blend_minmax +#ifndef GL_FUNC_ADD +#define GL_FUNC_ADD 0x8006 // also supplied by GL_blend_subtract +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 // also supplied by GL_blend_subtract +extern void qglBlendEquationEXT(GLenum); // also supplied by GL_blend_subtract +#endif + +//GL_EXT_blend_subtract +#ifndef GL_FUNC_SUBTRACT +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +extern void qglBlendEquationEXT(GLenum); // also supplied by GL_blend_subtract +#endif + +//GL_ARB_texture_non_power_of_two + +//GL_ARB_vertex_buffer_object +#ifndef GL_ARRAY_BUFFER +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#endif +extern void qglBindBufferARB(GLenum target, GLuint buffer); +extern void qglDeleteBuffersARB(GLsizei n, const GLuint *buffers); +extern void qglGenBuffersARB(GLsizei n, GLuint *buffers); +extern GLboolean qglIsBufferARB(GLuint buffer); +extern GLvoid qglMapBufferARB(GLenum target, GLenum access); +extern GLboolean qglUnmapBufferARB(GLenum target); +extern void qglBufferDataARB(GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +extern void qglBufferSubDataARB(GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); + +//GL_ARB_framebuffer_object +// (slight differences from GL_EXT_framebuffer_object as this integrates GL_EXT_packed_depth_stencil) +#ifndef GL_FRAMEBUFFER +#define GL_FRAMEBUFFER 0x8D40 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_RENDERBUFFER 0x8D41 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_SRGB 0x8C40 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_INDEX 0x8222 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_SAMPLES 0x8D57 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 // alias DRAW_FRAMEBUFFER_BINDING +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#endif +extern GLboolean qglIsRenderbuffer(GLuint renderbuffer); +extern GLvoid qglBindRenderbuffer(GLenum target, GLuint renderbuffer); +extern GLvoid qglDeleteRenderbuffers(GLsizei n, const GLuint *renderbuffers); +extern GLvoid qglGenRenderbuffers(GLsizei n, GLuint *renderbuffers); +extern GLvoid qglRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +extern GLvoid qglRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +extern GLvoid qglGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint *params); +extern GLboolean qglIsFramebuffer(GLuint framebuffer); +extern GLvoid qglBindFramebuffer(GLenum target, GLuint framebuffer); +extern GLvoid qglDeleteFramebuffers(GLsizei n, const GLuint *framebuffers); +extern GLvoid qglGenFramebuffers(GLsizei n, GLuint *framebuffers); +extern GLenum qglCheckFramebufferStatus(GLenum target); +extern GLvoid qglFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +extern GLvoid qglFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +extern GLvoid qglFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer); +extern GLvoid qglFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +extern GLvoid qglFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +extern GLvoid qglGetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint *params); +extern GLvoid qglBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +extern GLvoid qglGenerateMipmap(GLenum target); + +// GL_ARB_draw_buffers +#ifndef GL_MAX_DRAW_BUFFERS_ARB +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +#endif +extern void qglDrawBuffersARB(GLsizei n, const GLenum *bufs); + +// GL_ARB_texture_float +#ifndef GL_RGBA32F_ARB +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif + +// GL_EXT_texture_sRGB +#ifndef GL_SRGB_EXT +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif + +// GL_ARB_uniform_buffer_object +#ifndef GL_UNIFORM_BUFFER +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 +#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 +#define GL_INVALID_INDEX 0xFFFFFFFFu +#endif +extern void qglGetUniformIndices(GLuint program, GLsizei uniformCount, const char** uniformNames, GLuint* uniformIndices); +extern void qglGetActiveUniformsiv(GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params); +extern void qglGetActiveUniformName(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length, char* uniformName); +extern GLuint qglGetUniformBlockIndex(GLuint program, const char* uniformBlockName); +extern void qglGetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params); +extern void qglGetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, char* uniformBlockName); +extern void qglBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptrARB offset, GLsizeiptrARB size); +extern void qglBindBufferBase(GLenum target, GLuint index, GLuint buffer); +extern void qglGetIntegeri_v(GLenum target, GLuint index, GLint* data); +extern void qglUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); + +extern void qglScissor(GLint x, GLint y, GLsizei width, GLsizei height); + +extern void qglClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + +extern void qglClear(GLbitfield mask); + +extern void qglAlphaFunc(GLenum func, GLclampf ref); +extern void qglBlendFunc(GLenum sfactor, GLenum dfactor); +extern void qglCullFace(GLenum mode); + +extern void qglDrawBuffer(GLenum mode); +extern void qglReadBuffer(GLenum mode); +extern void qglEnable(GLenum cap); +extern void qglDisable(GLenum cap); +extern GLboolean qglIsEnabled(GLenum cap); + +extern void qglEnableClientState(GLenum cap); +extern void qglDisableClientState(GLenum cap); + +extern void qglGetBooleanv(GLenum pname, GLboolean *params); +extern void qglGetDoublev(GLenum pname, GLdouble *params); +extern void qglGetFloatv(GLenum pname, GLfloat *params); +extern void qglGetIntegerv(GLenum pname, GLint *params); + +extern GLenum qglGetError(void); +extern const GLubyte* qglGetString(GLenum name); +extern void qglFinish(void); +extern void qglFlush(void); + +extern void qglClearDepth(GLclampd depth); +extern void qglDepthFunc(GLenum func); +extern void qglDepthMask(GLboolean flag); +extern void qglDepthRange(GLclampd near_val, GLclampd far_val); +extern void qglDepthRangef(GLclampf near_val, GLclampf far_val); +extern void qglColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); + +extern void qglDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +extern void qglDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern void qglDrawArrays(GLenum mode, GLint first, GLsizei count); +extern void qglVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +extern void qglNormalPointer(GLenum type, GLsizei stride, const GLvoid *ptr); +extern void qglColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +extern void qglTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +extern void qglArrayElement(GLint i); + +extern void qglColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void qglColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void qglTexCoord1f(GLfloat s); +extern void qglTexCoord2f(GLfloat s, GLfloat t); +extern void qglTexCoord3f(GLfloat s, GLfloat t, GLfloat r); +extern void qglTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void qglVertex2f(GLfloat x, GLfloat y); +extern void qglVertex3f(GLfloat x, GLfloat y, GLfloat z); +extern void qglVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void qglBegin(GLenum mode); +extern void qglEnd(void); + +extern void qglMatrixMode(GLenum mode); +//extern void qglOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); +//extern void qglFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); +extern void qglViewport(GLint x, GLint y, GLsizei width, GLsizei height); +//extern void qglPushMatrix(void); +//extern void qglPopMatrix(void); +extern void qglLoadIdentity(void); +//extern void qglLoadMatrixd)(const GLdouble *m); +extern void qglLoadMatrixf(const GLfloat *m); +//extern void qglMultMatrixd)(const GLdouble *m); +//extern void qglMultMatrixf)(const GLfloat *m); +//extern void qglRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +//extern void qglRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +//extern void qglScaled(GLdouble x, GLdouble y, GLdouble z); +//extern void qglScalef(GLfloat x, GLfloat y, GLfloat z); +//extern void qglTranslated(GLdouble x, GLdouble y, GLdouble z); +//extern void qglTranslatef(GLfloat x, GLfloat y, GLfloat z); + +extern void qglReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); + +extern void qglStencilFunc(GLenum func, GLint ref, GLuint mask); +extern void qglStencilMask(GLuint mask); +extern void qglStencilOp(GLenum fail, GLenum zfail, GLenum zpass); +extern void qglClearStencil(GLint s); + +extern void qglTexEnvf(GLenum target, GLenum pname, GLfloat param); +extern void qglTexEnvfv(GLenum target, GLenum pname, const GLfloat *params); +extern void qglTexEnvi(GLenum target, GLenum pname, GLint param); +extern void qglTexParameterf(GLenum target, GLenum pname, GLfloat param); +extern void qglTexParameterfv(GLenum target, GLenum pname, GLfloat *params); +extern void qglTexParameteri(GLenum target, GLenum pname, GLint param); +extern void qglGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params); +extern void qglGetTexParameteriv(GLenum target, GLenum pname, GLint *params); +extern void qglGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern void qglGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params); +extern void qglGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern void qglHint(GLenum target, GLenum mode); + +extern void qglGenTextures(GLsizei n, GLuint *textures); +extern void qglDeleteTextures(GLsizei n, const GLuint *textures); +extern void qglBindTexture(GLenum target, GLuint texture); +//extern void qglPrioritizeTextures(GLsizei n, const GLuint *textures, const GLclampf *priorities); +//extern GLboolean qglAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean *residences); +//extern GLboolean qglIsTexture(GLuint texture); +//extern void qglPixelStoref(GLenum pname, GLfloat param); +extern void qglPixelStorei(GLenum pname, GLint param); + +//extern void qglTexImage1D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void qglTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +//extern void qglTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern void qglTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +//extern void qglCopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +extern void qglCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +//extern void qglCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void qglCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + +extern void qglPolygonOffset(GLfloat factor, GLfloat units); +extern void qglPolygonMode(GLenum face, GLenum mode); + +//extern void qglClipPlane(GLenum plane, const GLdouble *equation); +//extern void qglGetClipPlane(GLenum plane, GLdouble *equation); + +//[515]: added on 29.07.2005 +extern void qglLineWidth(GLfloat width); +extern void qglPointSize(GLfloat size); + +// GL 2.0 shader objects +#ifndef GL_PROGRAM_OBJECT +// 1-byte character string +typedef char GLchar; +#endif +extern void qglDeleteShader(GLuint obj); +extern void qglDeleteProgram(GLuint obj); +//extern GLuint qglGetHandle(GLenum pname); +extern void qglDetachShader(GLuint containerObj, GLuint attachedObj); +extern GLuint qglCreateShader(GLenum shaderType); +extern void qglShaderSource(GLuint shaderObj, GLsizei count, const GLchar **string, const GLint *length); +extern void qglCompileShader(GLuint shaderObj); +extern GLuint qglCreateProgram(void); +extern void qglAttachShader(GLuint containerObj, GLuint obj); +extern void qglLinkProgram(GLuint programObj); +extern void qglUseProgram(GLuint programObj); +extern void qglValidateProgram(GLuint programObj); +extern void qglUniform1f(GLint location, GLfloat v0); +extern void qglUniform2f(GLint location, GLfloat v0, GLfloat v1); +extern void qglUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +extern void qglUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +extern void qglUniform1i(GLint location, GLint v0); +extern void qglUniform2i(GLint location, GLint v0, GLint v1); +extern void qglUniform3i(GLint location, GLint v0, GLint v1, GLint v2); +extern void qglUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +extern void qglUniform1fv(GLint location, GLsizei count, const GLfloat *value); +extern void qglUniform2fv(GLint location, GLsizei count, const GLfloat *value); +extern void qglUniform3fv(GLint location, GLsizei count, const GLfloat *value); +extern void qglUniform4fv(GLint location, GLsizei count, const GLfloat *value); +extern void qglUniform1iv(GLint location, GLsizei count, const GLint *value); +extern void qglUniform2iv(GLint location, GLsizei count, const GLint *value); +extern void qglUniform3iv(GLint location, GLsizei count, const GLint *value); +extern void qglUniform4iv(GLint location, GLsizei count, const GLint *value); +extern void qglUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +extern void qglUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +extern void qglUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +extern void qglGetShaderiv(GLuint obj, GLenum pname, GLint *params); +extern void qglGetProgramiv(GLuint obj, GLenum pname, GLint *params); +extern void qglGetShaderInfoLog(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); +extern void qglGetProgramInfoLog(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); +extern void qglGetAttachedShaders(GLuint containerObj, GLsizei maxCount, GLsizei *count, GLuint *obj); +extern GLint qglGetUniformLocation(GLuint programObj, const GLchar *name); +extern void qglGetActiveUniform(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +extern void qglGetUniformfv(GLuint programObj, GLint location, GLfloat *params); +extern void qglGetUniformiv(GLuint programObj, GLint location, GLint *params); +extern void qglGetShaderSource(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *source); +extern void qglPolygonStipple(const GLubyte *mask); +#ifndef GL_PROGRAM_OBJECT +#define GL_PROGRAM_OBJECT 0x8B40 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_SHADER_OBJECT 0x8B48 +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT 0x1406 +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT 0x1404 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#endif + +// GL 2.0 vertex shader +extern void qglVertexAttrib1f(GLuint index, GLfloat v0); +extern void qglVertexAttrib1s(GLuint index, GLshort v0); +extern void qglVertexAttrib1d(GLuint index, GLdouble v0); +extern void qglVertexAttrib2f(GLuint index, GLfloat v0, GLfloat v1); +extern void qglVertexAttrib2s(GLuint index, GLshort v0, GLshort v1); +extern void qglVertexAttrib2d(GLuint index, GLdouble v0, GLdouble v1); +extern void qglVertexAttrib3f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); +extern void qglVertexAttrib3s(GLuint index, GLshort v0, GLshort v1, GLshort v2); +extern void qglVertexAttrib3d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2); +extern void qglVertexAttrib4f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +extern void qglVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3); +extern void qglVertexAttrib4d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +extern void qglVertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +extern void qglVertexAttrib1fv(GLuint index, const GLfloat *v); +extern void qglVertexAttrib1sv(GLuint index, const GLshort *v); +extern void qglVertexAttrib1dv(GLuint index, const GLdouble *v); +extern void qglVertexAttrib2fv(GLuint index, const GLfloat *v); +extern void qglVertexAttrib2sv(GLuint index, const GLshort *v); +extern void qglVertexAttrib2dv(GLuint index, const GLdouble *v); +extern void qglVertexAttrib3fv(GLuint index, const GLfloat *v); +extern void qglVertexAttrib3sv(GLuint index, const GLshort *v); +extern void qglVertexAttrib3dv(GLuint index, const GLdouble *v); +extern void qglVertexAttrib4fv(GLuint index, const GLfloat *v); +extern void qglVertexAttrib4sv(GLuint index, const GLshort *v); +extern void qglVertexAttrib4dv(GLuint index, const GLdouble *v); +extern void qglVertexAttrib4iv(GLuint index, const GLint *v); +extern void qglVertexAttrib4bv(GLuint index, const GLbyte *v); +extern void qglVertexAttrib4ubv(GLuint index, const GLubyte *v); +extern void qglVertexAttrib4usv(GLuint index, const GLushort *v); +extern void qglVertexAttrib4uiv(GLuint index, const GLuint *v); +extern void qglVertexAttrib4Nbv(GLuint index, const GLbyte *v); +extern void qglVertexAttrib4Nsv(GLuint index, const GLshort *v); +extern void qglVertexAttrib4Niv(GLuint index, const GLint *v); +extern void qglVertexAttrib4Nubv(GLuint index, const GLubyte *v); +extern void qglVertexAttrib4Nusv(GLuint index, const GLushort *v); +extern void qglVertexAttrib4Nuiv(GLuint index, const GLuint *v); +extern void qglVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +extern void qglEnableVertexAttribArray(GLuint index); +extern void qglDisableVertexAttribArray(GLuint index); +extern void qglBindAttribLocation(GLuint programObj, GLuint index, const GLchar *name); +extern void qglBindFragDataLocation(GLuint programObj, GLuint index, const GLchar *name); +extern void qglGetActiveAttrib(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +extern GLint qglGetAttribLocation(GLuint programObj, const GLchar *name); +extern void qglGetVertexAttribdv(GLuint index, GLenum pname, GLdouble *params); +extern void qglGetVertexAttribfv(GLuint index, GLenum pname, GLfloat *params); +extern void qglGetVertexAttribiv(GLuint index, GLenum pname, GLint *params); +extern void qglGetVertexAttribPointerv(GLuint index, GLenum pname, GLvoid **pointer); +#ifndef GL_VERTEX_SHADER +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_FLOAT 0x1406 +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#endif + +// GL 2.0 fragment shader +#ifndef GL_FRAGMENT_SHADER +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#endif + +// GL 2.0 shading language 100 +#ifndef GL_SHADING_LANGUAGE_VERSION +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +// GL_ARB_texture_compression +extern void qglCompressedTexImage3DARB(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +extern void qglCompressedTexImage2DARB(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +//extern void qglCompressedTexImage1DARB(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +extern void qglCompressedTexSubImage3DARB(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +extern void qglCompressedTexSubImage2DARB(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +//extern void qglCompressedTexSubImage1DARB(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +extern void qglGetCompressedTexImageARB(GLenum target, GLint lod, void *img); +#ifndef GL_COMPRESSED_RGB_ARB +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +// GL_EXT_texture_compression_s3tc +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +// GL_ARB_occlusion_query +extern void qglGenQueriesARB(GLsizei n, GLuint *ids); +extern void qglDeleteQueriesARB(GLsizei n, const GLuint *ids); +extern GLboolean qglIsQueryARB(GLuint qid); +extern void qglBeginQueryARB(GLenum target, GLuint qid); +extern void qglEndQueryARB(GLenum target); +extern void qglGetQueryivARB(GLenum target, GLenum pname, GLint *params); +extern void qglGetQueryObjectivARB(GLuint qid, GLenum pname, GLint *params); +extern void qglGetQueryObjectuivARB(GLuint qid, GLenum pname, GLuint *params); +#ifndef GL_SAMPLES_PASSED_ARB +#define GL_SAMPLES_PASSED_ARB 0x8914 +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#endif + +// GL_EXT_bgr +#define GL_BGR 0x80E0 + +// GL_EXT_bgra +#define GL_BGRA 0x80E1 + +//GL_AMD_texture_texture4 + +//GL_ARB_texture_gather + +//GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +extern void qglSampleCoverageARB(GLclampf value, GLboolean invert); + +extern void qglPointSize(GLfloat size); + +//GL_EXT_packed_depth_stencil +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#define GL_DEPTH24_STENCIL8_EXT 0x88F0 + +#endif + +#define DEBUGGL + +#ifdef DEBUGGL +#ifdef USE_GLES2 +#define CHECKGLERROR {if (gl_paranoid.integer){if (gl_printcheckerror.integer) Con_Printf("CHECKGLERROR at %s:%d\n", __FILE__, __LINE__);errornumber = glGetError();if (errornumber) GL_PrintError(errornumber, __FILE__, __LINE__);}} +#else +#define CHECKGLERROR {if (gl_paranoid.integer){if (gl_printcheckerror.integer) Con_Printf("CHECKGLERROR at %s:%d\n", __FILE__, __LINE__);errornumber = qglGetError ? qglGetError() : 0;if (errornumber) GL_PrintError(errornumber, __FILE__, __LINE__);}} +#endif +extern int errornumber; +void GL_PrintError(int errornumber, const char *filename, int linenumber); +#else +#define CHECKGLERROR +#endif + +#ifdef USE_GLES2 +#define qglIsBufferARB glIsBuffer +#define qglIsEnabled glIsEnabled +#define qglIsFramebufferEXT glIsFramebuffer +//#define qglIsQueryARB glIsQuery +#define qglIsRenderbufferEXT glIsRenderbuffer +//#define qglUnmapBufferARB glUnmapBuffer +#define qglCheckFramebufferStatusEXT glCheckFramebufferStatus +#define qglGetError glGetError +#define qglCreateProgram glCreateProgram +#define qglCreateShader glCreateShader +//#define qglGetHandleARB glGetHandle +#define qglGetAttribLocation glGetAttribLocation +#define qglGetUniformLocation glGetUniformLocation +//#define qglMapBufferARB glMapBuffer +#define qglGetString glGetString +//#define qglActiveStencilFaceEXT glActiveStencilFace +#define qglActiveTexture glActiveTexture +#define qglAlphaFunc glAlphaFunc +#define qglArrayElement glArrayElement +#define qglAttachShader glAttachShader +//#define qglBegin glBegin +//#define qglBeginQueryARB glBeginQuery +#define qglBindAttribLocation glBindAttribLocation +//#define qglBindFragDataLocation glBindFragDataLocation +#define qglBindBufferARB glBindBuffer +#define qglBindFramebufferEXT glBindFramebuffer +#define qglBindRenderbufferEXT glBindRenderbuffer +#define qglBindTexture glBindTexture +#define qglBlendEquationEXT glBlendEquation +#define qglBlendFunc glBlendFunc +#define qglBufferDataARB glBufferData +#define qglBufferSubDataARB glBufferSubData +#define qglClear glClear +#define qglClearColor glClearColor +#define qglClearDepthf glClearDepthf +#define qglClearStencil glClearStencil +#define qglClientActiveTexture glClientActiveTexture +#define qglColor4f glColor4f +#define qglColor4ub glColor4ub +#define qglColorMask glColorMask +#define qglColorPointer glColorPointer +#define qglCompileShader glCompileShader +#define qglCompressedTexImage2DARB glCompressedTexImage2D +#define qglCompressedTexImage3DARB glCompressedTexImage3D +#define qglCompressedTexSubImage2DARB glCompressedTexSubImage2D +#define qglCompressedTexSubImage3DARB glCompressedTexSubImage3D +#define qglCopyTexImage2D glCopyTexImage2D +#define qglCopyTexSubImage2D glCopyTexSubImage2D +#define qglCopyTexSubImage3D glCopyTexSubImage3D +#define qglCullFace glCullFace +#define qglDeleteBuffersARB glDeleteBuffers +#define qglDeleteFramebuffersEXT glDeleteFramebuffers +#define qglDeleteProgram glDeleteProgram +#define qglDeleteShader glDeleteShader +//#define qglDeleteQueriesARB glDeleteQueries +#define qglDeleteRenderbuffersEXT glDeleteRenderbuffers +#define qglDeleteTextures glDeleteTextures +#define qglDepthFunc glDepthFunc +#define qglDepthMask glDepthMask +#define qglDepthRangef glDepthRangef +#define qglDetachShader glDetachShader +#define qglDisable glDisable +#define qglDisableClientState glDisableClientState +#define qglDisableVertexAttribArray glDisableVertexAttribArray +#define qglDrawArrays glDrawArrays +//#define qglDrawBuffer glDrawBuffer +//#define qglDrawBuffersARB glDrawBuffers +#define qglDrawElements glDrawElements +//#define qglDrawRangeElements glDrawRangeElements +#define qglEnable glEnable +#define qglEnableClientState glEnableClientState +#define qglEnableVertexAttribArray glEnableVertexAttribArray +//#define qglEnd glEnd +//#define qglEndQueryARB glEndQuery +#define qglFinish glFinish +#define qglFlush glFlush +#define qglFramebufferRenderbuffer glFramebufferRenderbuffer +#define qglFramebufferTexture2D glFramebufferTexture2D +#define qglFramebufferTexture3DEXT glFramebufferTexture3D +#define qglGenBuffersARB glGenBuffers +#define qglGenFramebuffersEXT glGenFramebuffers +//#define qglGenQueriesARB glGenQueries +#define qglGenRenderbuffersEXT glGenRenderbuffers +#define qglGenTextures glGenTextures +#define qglGenerateMipmapEXT glGenerateMipmap +#define qglGetActiveAttrib glGetActiveAttrib +#define qglGetActiveUniform glGetActiveUniform +#define qglGetAttachedShaders glGetAttachedShaders +#define qglGetBooleanv glGetBooleanv +//#define qglGetCompressedTexImageARB glGetCompressedTexImage +#define qglGetDoublev glGetDoublev +#define qglGetFloatv glGetFloatv +#define qglGetFramebufferAttachmentParameterivEXT glGetFramebufferAttachmentParameteriv +#define qglGetProgramInfoLog glGetProgramInfoLog +#define qglGetShaderInfoLog glGetShaderInfoLog +#define qglGetIntegerv glGetIntegerv +#define qglGetShaderiv glGetShaderiv +#define qglGetProgramiv glGetProgramiv +//#define qglGetQueryObjectivARB glGetQueryObjectiv +//#define qglGetQueryObjectuivARB glGetQueryObjectuiv +//#define qglGetQueryivARB glGetQueryiv +#define qglGetRenderbufferParameterivEXT glGetRenderbufferParameteriv +#define qglGetShaderSource glGetShaderSource +#define qglGetTexImage glGetTexImage +#define qglGetTexLevelParameterfv glGetTexLevelParameterfv +#define qglGetTexLevelParameteriv glGetTexLevelParameteriv +#define qglGetTexParameterfv glGetTexParameterfv +#define qglGetTexParameteriv glGetTexParameteriv +#define qglGetUniformfv glGetUniformfv +#define qglGetUniformiv glGetUniformiv +#define qglHint glHint +#define qglLineWidth glLineWidth +#define qglLinkProgram glLinkProgram +#define qglLoadIdentity glLoadIdentity +#define qglLoadMatrixf glLoadMatrixf +#define qglMatrixMode glMatrixMode +#define qglMultiTexCoord1f glMultiTexCoord1f +#define qglMultiTexCoord2f glMultiTexCoord2f +#define qglMultiTexCoord3f glMultiTexCoord3f +#define qglMultiTexCoord4f glMultiTexCoord4f +#define qglNormalPointer glNormalPointer +#define qglPixelStorei glPixelStorei +#define qglPointSize glPointSize +//#define qglPolygonMode glPolygonMode +#define qglPolygonOffset glPolygonOffset +//#define qglPolygonStipple glPolygonStipple +#define qglReadBuffer glReadBuffer +#define qglReadPixels glReadPixels +#define qglRenderbufferStorageEXT glRenderbufferStorage +#define qglScissor glScissor +#define qglShaderSource glShaderSource +#define qglStencilFunc glStencilFunc +#define qglStencilFuncSeparate glStencilFuncSeparate +#define qglStencilMask glStencilMask +#define qglStencilOp glStencilOp +#define qglStencilOpSeparate glStencilOpSeparate +#define qglTexCoord1f glTexCoord1f +#define qglTexCoord2f glTexCoord2f +#define qglTexCoord3f glTexCoord3f +#define qglTexCoord4f glTexCoord4f +#define qglTexCoordPointer glTexCoordPointer +#define qglTexEnvf glTexEnvf +#define qglTexEnvfv glTexEnvfv +#define qglTexEnvi glTexEnvi +#define qglTexImage2D glTexImage2D +#define qglTexImage3D glTexImage3D +#define qglTexParameterf glTexParameterf +#define qglTexParameterfv glTexParameterfv +#define qglTexParameteri glTexParameteri +#define qglTexSubImage2D glTexSubImage2D +#define qglTexSubImage3D glTexSubImage3D +#define qglUniform1f glUniform1f +#define qglUniform1fv glUniform1fv +#define qglUniform1i glUniform1i +#define qglUniform1iv glUniform1iv +#define qglUniform2f glUniform2f +#define qglUniform2fv glUniform2fv +#define qglUniform2i glUniform2i +#define qglUniform2iv glUniform2iv +#define qglUniform3f glUniform3f +#define qglUniform3fv glUniform3fv +#define qglUniform3i glUniform3i +#define qglUniform3iv glUniform3iv +#define qglUniform4f glUniform4f +#define qglUniform4fv glUniform4fv +#define qglUniform4i glUniform4i +#define qglUniform4iv glUniform4iv +#define qglUniformMatrix2fv glUniformMatrix2fv +#define qglUniformMatrix3fv glUniformMatrix3fv +#define qglUniformMatrix4fv glUniformMatrix4fv +#define qglUseProgram glUseProgram +#define qglValidateProgram glValidateProgram +#define qglVertex2f glVertex2f +#define qglVertex3f glVertex3f +#define qglVertex4f glVertex4f +#define qglVertexAttribPointer glVertexAttribPointer +#define qglVertexPointer glVertexPointer +#define qglViewport glViewport +#define qglVertexAttrib1f glVertexAttrib1f +//#define qglVertexAttrib1s glVertexAttrib1s +//#define qglVertexAttrib1d glVertexAttrib1d +#define qglVertexAttrib2f glVertexAttrib2f +//#define qglVertexAttrib2s glVertexAttrib2s +//#define qglVertexAttrib2d glVertexAttrib2d +#define qglVertexAttrib3f glVertexAttrib3f +//#define qglVertexAttrib3s glVertexAttrib3s +//#define qglVertexAttrib3d glVertexAttrib3d +#define qglVertexAttrib4f glVertexAttrib4f +//#define qglVertexAttrib4s glVertexAttrib4s +//#define qglVertexAttrib4d glVertexAttrib4d +//#define qglVertexAttrib4Nub glVertexAttrib4Nub +#define qglVertexAttrib1fv glVertexAttrib1fv +//#define qglVertexAttrib1sv glVertexAttrib1sv +//#define qglVertexAttrib1dv glVertexAttrib1dv +#define qglVertexAttrib2fv glVertexAttrib2fv +//#define qglVertexAttrib2sv glVertexAttrib2sv +//#define qglVertexAttrib2dv glVertexAttrib2dv +#define qglVertexAttrib3fv glVertexAttrib3fv +//#define qglVertexAttrib3sv glVertexAttrib3sv +//#define qglVertexAttrib3dv glVertexAttrib3dv +#define qglVertexAttrib4fv glVertexAttrib4fv +//#define qglVertexAttrib4sv glVertexAttrib4sv +//#define qglVertexAttrib4dv glVertexAttrib4dv +//#define qglVertexAttrib4iv glVertexAttrib4iv +//#define qglVertexAttrib4bv glVertexAttrib4bv +//#define qglVertexAttrib4ubv glVertexAttrib4ubv +//#define qglVertexAttrib4usv glVertexAttrib4usv +//#define qglVertexAttrib4uiv glVertexAttrib4uiv +//#define qglVertexAttrib4Nbv glVertexAttrib4Nbv +//#define qglVertexAttrib4Nsv glVertexAttrib4Nsv +//#define qglVertexAttrib4Niv glVertexAttrib4Niv +//#define qglVertexAttrib4Nubv glVertexAttrib4Nubv +//#define qglVertexAttrib4Nusv glVertexAttrib4Nusv +//#define qglVertexAttrib4Nuiv glVertexAttrib4Nuiv +//#define qglGetVertexAttribdv glGetVertexAttribdv +#define qglGetVertexAttribfv glGetVertexAttribfv +#define qglGetVertexAttribiv glGetVertexAttribiv +#define qglGetVertexAttribPointerv glGetVertexAttribPointerv +#endif + +#endif + diff --git a/app/jni/hmac.c b/app/jni/hmac.c new file mode 100644 index 0000000..e740632 --- /dev/null +++ b/app/jni/hmac.c @@ -0,0 +1,61 @@ +#include "quakedef.h" +#include "hmac.h" + +qboolean hmac( + hashfunc_t hfunc, int hlen, int hblock, + unsigned char *out, + const unsigned char *in, int n, + const unsigned char *key, int k +) +{ + unsigned char hashbuf[32]; + unsigned char k_xor_ipad[128]; + unsigned char k_xor_opad[128]; + unsigned char *catbuf; + int i; + + if(sizeof(hashbuf) < (size_t) hlen) + return false; + if(sizeof(k_xor_ipad) < (size_t) hblock) + return false; + if(sizeof(k_xor_ipad) < (size_t) hlen) + return false; + + catbuf = (unsigned char *)Mem_Alloc(tempmempool, (size_t) hblock + max((size_t) hlen, (size_t) n)); + + if(k > hblock) + { + // hash the key if it is too long + hfunc(k_xor_opad, key, k); + key = k_xor_opad; + k = hlen; + } + + if(k < hblock) + { + // zero pad the key if it is too short + if(key != k_xor_opad) + memcpy(k_xor_opad, key, k); + for(i = k; i < hblock; ++i) + k_xor_opad[i] = 0; + key = k_xor_opad; + k = hblock; + } + + for(i = 0; i < hblock; ++i) + { + k_xor_ipad[i] = key[i] ^ 0x36; + k_xor_opad[i] = key[i] ^ 0x5c; + } + + memcpy(catbuf, k_xor_ipad, hblock); + memcpy(catbuf + hblock, in, n); + hfunc(hashbuf, catbuf, hblock + n); + memcpy(catbuf, k_xor_opad, hblock); + memcpy(catbuf + hblock, hashbuf, hlen); + hfunc(out, catbuf, hblock + hlen); + + Mem_Free(catbuf); + + return true; +} diff --git a/app/jni/hmac.h b/app/jni/hmac.h new file mode 100644 index 0000000..4493900 --- /dev/null +++ b/app/jni/hmac.h @@ -0,0 +1,15 @@ +#ifndef HMAC_H +#define HMAC_H + +typedef void (*hashfunc_t) (unsigned char *out, const unsigned char *in, int n); +qboolean hmac( + hashfunc_t hfunc, int hlen, int hblock, + unsigned char *out, + const unsigned char *in, int n, + const unsigned char *key, int k +); + +#define HMAC_MDFOUR_16BYTES(out, in, n, key, k) hmac(mdfour, 16, 64, out, in, n, key, k) +#define HMAC_SHA256_32BYTES(out, in, n, key, k) hmac(sha256, 32, 64, out, in, n, key, k) + +#endif diff --git a/app/jni/host.c b/app/jni/host.c new file mode 100644 index 0000000..d578379 --- /dev/null +++ b/app/jni/host.c @@ -0,0 +1,1460 @@ +/* +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. + +*/ +// host.c -- coordinates spawning and killing of local servers + +#include "quakedef.h" + +#include +#include "libcurl.h" +#include "cdaudio.h" +#include "cl_video.h" +#include "progsvm.h" +#include "csprogs.h" +#include "sv_demo.h" +#include "snd_main.h" +#include "thread.h" +#include "utf8lib.h" + +/* + +A server can always be started, even if the system started out as a client +to a remote system. + +A client can NOT be started if the system started as a dedicated server. + +Memory is cleared / released when a server or client begins, not when they end. + +*/ + +// how many frames have occurred +// (checked by Host_Error and Host_SaveConfig_f) +int host_framecount = 0; +// LordHavoc: set when quit is executed +qboolean host_shuttingdown = false; + +// the accumulated mainloop time since application started (with filtering), without any slowmo or clamping +double realtime; +// the main loop wall time for this frame +double host_dirtytime; + +// current client +client_t *host_client; + +jmp_buf host_abortframe; + +extern int r_stereo_side; + +// pretend frames take this amount of time (in seconds), 0 = realtime +cvar_t host_framerate = {0, "host_framerate","0", "locks frame timing to this value in seconds, 0.05 is 20fps for example, note that this can easily run too fast, use cl_maxfps if you want to limit your framerate instead, or sys_ticrate to limit server speed"}; +cvar_t cl_maxphysicsframesperserverframe = {0, "cl_maxphysicsframesperserverframe","10", "maximum number of physics frames per server frame"}; +// shows time used by certain subsystems +cvar_t host_speeds = {0, "host_speeds","0", "reports how much time is used in server/graphics/sound"}; +cvar_t host_maxwait = {0, "host_maxwait","1000", "maximum sleep time requested from the operating system in millisecond. Larger sleeps will be done using multiple host_maxwait length sleeps. Lowering this value will increase CPU load, but may help working around problems with accuracy of sleep times."}; +cvar_t cl_minfps = {CVAR_SAVE, "cl_minfps", "40", "minimum fps target - while the rendering performance is below this, it will drift toward lower quality"}; +cvar_t cl_minfps_fade = {CVAR_SAVE, "cl_minfps_fade", "1", "how fast the quality adapts to varying framerate"}; +cvar_t cl_minfps_qualitymax = {CVAR_SAVE, "cl_minfps_qualitymax", "1", "highest allowed drawdistance multiplier"}; +cvar_t cl_minfps_qualitymin = {CVAR_SAVE, "cl_minfps_qualitymin", "0.25", "lowest allowed drawdistance multiplier"}; +cvar_t cl_minfps_qualitymultiply = {CVAR_SAVE, "cl_minfps_qualitymultiply", "0.2", "multiplier for quality changes in quality change per second render time (1 assumes linearity of quality and render time)"}; +cvar_t cl_minfps_qualityhysteresis = {CVAR_SAVE, "cl_minfps_qualityhysteresis", "0.05", "reduce all quality increments by this to reduce flickering"}; +cvar_t cl_minfps_qualitystepmax = {CVAR_SAVE, "cl_minfps_qualitystepmax", "0.1", "maximum quality change in a single frame"}; +cvar_t cl_minfps_force = {0, "cl_minfps_force", "0", "also apply quality reductions in timedemo/capturevideo"}; +cvar_t cl_maxfps = {CVAR_SAVE, "cl_maxfps", "0", "maximum fps cap, 0 = unlimited, if game is running faster than this it will wait before running another frame (useful to make cpu time available to other programs)"}; +cvar_t cl_maxfps_alwayssleep = {0, "cl_maxfps_alwayssleep","1", "gives up some processing time to other applications each frame, value in milliseconds, disabled if cl_maxfps is 0"}; +cvar_t cl_maxidlefps = {CVAR_SAVE, "cl_maxidlefps", "20", "maximum fps cap when the game is not the active window (makes cpu time available to other programs"}; + +cvar_t developer = {CVAR_SAVE, "developer","0", "shows debugging messages and information (recommended for all developers and level designers); the value -1 also suppresses buffering and logging these messages"}; +cvar_t developer_extra = {0, "developer_extra", "0", "prints additional debugging messages, often very verbose!"}; +cvar_t developer_insane = {0, "developer_insane", "0", "prints huge streams of information about internal workings, entire contents of files being read/written, etc. Not recommended!"}; +cvar_t developer_loadfile = {0, "developer_loadfile","0", "prints name and size of every file loaded via the FS_LoadFile function (which is almost everything)"}; +cvar_t developer_loading = {0, "developer_loading","0", "prints information about files as they are loaded or unloaded successfully"}; +cvar_t developer_entityparsing = {0, "developer_entityparsing", "0", "prints detailed network entities information each time a packet is received"}; + +cvar_t timestamps = {CVAR_SAVE, "timestamps", "0", "prints timestamps on console messages"}; +cvar_t timeformat = {CVAR_SAVE, "timeformat", "[%Y-%m-%d %H:%M:%S] ", "time format to use on timestamped console messages"}; + +cvar_t sessionid = {CVAR_READONLY, "sessionid", "", "ID of the current session (use the -sessionid parameter to set it); this is always either empty or begins with a dot (.)"}; +cvar_t locksession = {0, "locksession", "0", "Lock the session? 0 = no, 1 = yes and abort on failure, 2 = yes and continue on failure"}; + +/* +================ +Host_AbortCurrentFrame + +aborts the current host frame and goes on with the next one +================ +*/ +void Host_AbortCurrentFrame(void) DP_FUNC_NORETURN; +void Host_AbortCurrentFrame(void) +{ + // in case we were previously nice, make us mean again + Sys_MakeProcessMean(); + + longjmp (host_abortframe, 1); +} + +/* +================ +Host_Error + +This shuts down both the client and server +================ +*/ +void Host_Error (const char *error, ...) +{ + static char hosterrorstring1[MAX_INPUTLINE]; // THREAD UNSAFE + static char hosterrorstring2[MAX_INPUTLINE]; // THREAD UNSAFE + static qboolean hosterror = false; + va_list argptr; + + // turn off rcon redirect if it was active when the crash occurred + // to prevent loops when it is a networking problem + Con_Rcon_Redirect_Abort(); + + va_start (argptr,error); + dpvsnprintf (hosterrorstring1,sizeof(hosterrorstring1),error,argptr); + va_end (argptr); + + Con_Printf("Host_Error: %s\n", hosterrorstring1); + + // LordHavoc: if crashing very early, or currently shutting down, do + // Sys_Error instead + if (host_framecount < 3 || host_shuttingdown) + Sys_Error ("Host_Error: %s", hosterrorstring1); + + if (hosterror) + Sys_Error ("Host_Error: recursively entered (original error was: %s new error is: %s)", hosterrorstring2, hosterrorstring1); + hosterror = true; + + strlcpy(hosterrorstring2, hosterrorstring1, sizeof(hosterrorstring2)); + + CL_Parse_DumpPacket(); + + CL_Parse_ErrorCleanUp(); + + //PR_Crash(); + + // print out where the crash happened, if it was caused by QC (and do a cleanup) + PRVM_Crash(SVVM_prog); + PRVM_Crash(CLVM_prog); + PRVM_Crash(MVM_prog); + + cl.csqc_loaded = false; + Cvar_SetValueQuick(&csqc_progcrc, -1); + Cvar_SetValueQuick(&csqc_progsize, -1); + + SV_LockThreadMutex(); + Host_ShutdownServer (); + SV_UnlockThreadMutex(); + + if (cls.state == ca_dedicated) + Sys_Error ("Host_Error: %s",hosterrorstring2); // dedicated servers exit + + CL_Disconnect (); + cls.demonum = -1; + + hosterror = false; + + Host_AbortCurrentFrame(); +} + +static void Host_ServerOptions (void) +{ + int i; + + // general default + svs.maxclients = 8; + +// COMMANDLINEOPTION: Server: -dedicated [playerlimit] starts a dedicated server (with a command console), default playerlimit is 8 +// COMMANDLINEOPTION: Server: -listen [playerlimit] starts a multiplayer server with graphical client, like singleplayer but other players can connect, default playerlimit is 8 + // if no client is in the executable or -dedicated is specified on + // commandline, start a dedicated server + i = COM_CheckParm ("-dedicated"); + if (i || !cl_available) + { + cls.state = ca_dedicated; + // check for -dedicated specifying how many players + if (i && i + 1 < com_argc && atoi (com_argv[i+1]) >= 1) + svs.maxclients = atoi (com_argv[i+1]); + if (COM_CheckParm ("-listen")) + Con_Printf ("Only one of -dedicated or -listen can be specified\n"); + // default sv_public on for dedicated servers (often hosted by serious administrators), off for listen servers (often hosted by clueless users) + Cvar_SetValue("sv_public", 1); + } + else if (cl_available) + { + // client exists and not dedicated, check if -listen is specified + cls.state = ca_disconnected; + i = COM_CheckParm ("-listen"); + if (i) + { + // default players unless specified + if (i + 1 < com_argc && atoi (com_argv[i+1]) >= 1) + svs.maxclients = atoi (com_argv[i+1]); + } + else + { + // default players in some games, singleplayer in most + if (gamemode != GAME_GOODVSBAD2 && gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC && gamemode != GAME_BATTLEMECH) + svs.maxclients = 1; + } + } + + svs.maxclients = svs.maxclients_next = bound(1, svs.maxclients, MAX_SCOREBOARD); + + svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients); + + if (svs.maxclients > 1 && !deathmatch.integer && !coop.integer) + Cvar_SetValueQuick(&deathmatch, 1); +} + +/* +======================= +Host_InitLocal +====================== +*/ +void Host_SaveConfig_f(void); +void Host_LoadConfig_f(void); +extern cvar_t sv_writepicture_quality; +extern cvar_t r_texture_jpeg_fastpicmip; +static void Host_InitLocal (void) +{ + Cmd_AddCommand("saveconfig", Host_SaveConfig_f, "save settings to config.cfg (or a specified filename) immediately (also automatic when quitting)"); + Cmd_AddCommand("loadconfig", Host_LoadConfig_f, "reset everything and reload configs"); + + Cvar_RegisterVariable (&cl_maxphysicsframesperserverframe); + Cvar_RegisterVariable (&host_framerate); + Cvar_RegisterVariable (&host_speeds); + Cvar_RegisterVariable (&host_maxwait); + Cvar_RegisterVariable (&cl_minfps); + Cvar_RegisterVariable (&cl_minfps_fade); + Cvar_RegisterVariable (&cl_minfps_qualitymax); + Cvar_RegisterVariable (&cl_minfps_qualitymin); + Cvar_RegisterVariable (&cl_minfps_qualitystepmax); + Cvar_RegisterVariable (&cl_minfps_qualityhysteresis); + Cvar_RegisterVariable (&cl_minfps_qualitymultiply); + Cvar_RegisterVariable (&cl_minfps_force); + Cvar_RegisterVariable (&cl_maxfps); + Cvar_RegisterVariable (&cl_maxfps_alwayssleep); + Cvar_RegisterVariable (&cl_maxidlefps); + + Cvar_RegisterVariable (&developer); + Cvar_RegisterVariable (&developer_extra); + Cvar_RegisterVariable (&developer_insane); + Cvar_RegisterVariable (&developer_loadfile); + Cvar_RegisterVariable (&developer_loading); + Cvar_RegisterVariable (&developer_entityparsing); + + Cvar_RegisterVariable (×tamps); + Cvar_RegisterVariable (&timeformat); + + Cvar_RegisterVariable (&sv_writepicture_quality); + Cvar_RegisterVariable (&r_texture_jpeg_fastpicmip); +} + + +/* +=============== +Host_SaveConfig_f + +Writes key bindings and archived cvars to config.cfg +=============== +*/ +static void Host_SaveConfig_to(const char *file) +{ + qfile_t *f; + +// dedicated servers initialize the host but don't parse and set the +// config.cfg cvars + // LordHavoc: don't save a config if it crashed in startup + if (host_framecount >= 3 && cls.state != ca_dedicated && !COM_CheckParm("-benchmark") && !COM_CheckParm("-capturedemo")) + { + f = FS_OpenRealFile(file, "wb", false); + if (!f) + { + Con_Printf("Couldn't write %s.\n", file); + return; + } + + Key_WriteBindings (f); + Cvar_WriteVariables (f); + + FS_Close (f); + } +} +void Host_SaveConfig(void) +{ + Host_SaveConfig_to(CONFIGFILENAME); +} +void Host_SaveConfig_f(void) +{ + const char *file = CONFIGFILENAME; + + if(Cmd_Argc() >= 2) { + file = Cmd_Argv(1); + Con_Printf("Saving to %s\n", file); + } + + Host_SaveConfig_to(file); +} + +static void Host_AddConfigText(void) +{ + // set up the default startmap_sp and startmap_dm aliases (mods can + // override these) and then execute the quake.rc startup script + if (gamemode == GAME_NEHAHRA) + Cbuf_InsertText("alias startmap_sp \"map nehstart\"\nalias startmap_dm \"map nehstart\"\nexec " STARTCONFIGFILENAME "\n"); + else if (gamemode == GAME_TRANSFUSION) + Cbuf_InsertText("alias startmap_sp \"map e1m1\"\n""alias startmap_dm \"map bb1\"\nexec " STARTCONFIGFILENAME "\n"); + else if (gamemode == GAME_TEU) + Cbuf_InsertText("alias startmap_sp \"map start\"\nalias startmap_dm \"map start\"\nexec teu.rc\n"); + else + Cbuf_InsertText("alias startmap_sp \"map start\"\nalias startmap_dm \"map start\"\nexec " STARTCONFIGFILENAME "\n"); +} + +/* +=============== +Host_LoadConfig_f + +Resets key bindings and cvars to defaults and then reloads scripts +=============== +*/ +void Host_LoadConfig_f(void) +{ + // reset all cvars, commands and aliases to init values + Cmd_RestoreInitState(); + // prepend a menu restart command to execute after the config + Cbuf_InsertText("\nmenu_restart\n"); + // reset cvars to their defaults, and then exec startup scripts again + Host_AddConfigText(); +} + +/* +================= +SV_ClientPrint + +Sends text across to be displayed +FIXME: make this just a stuffed echo? +================= +*/ +void SV_ClientPrint(const char *msg) +{ + if (host_client->netconnection) + { + MSG_WriteByte(&host_client->netconnection->message, svc_print); + MSG_WriteString(&host_client->netconnection->message, msg); + } +} + +/* +================= +SV_ClientPrintf + +Sends text across to be displayed +FIXME: make this just a stuffed echo? +================= +*/ +void SV_ClientPrintf(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + SV_ClientPrint(msg); +} + +/* +================= +SV_BroadcastPrint + +Sends text to all active clients +================= +*/ +void SV_BroadcastPrint(const char *msg) +{ + int i; + client_t *client; + + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (client->active && client->netconnection) + { + MSG_WriteByte(&client->netconnection->message, svc_print); + MSG_WriteString(&client->netconnection->message, msg); + } + } + + if (sv_echobprint.integer && cls.state == ca_dedicated) + Con_Print(msg); +} + +/* +================= +SV_BroadcastPrintf + +Sends text to all active clients +================= +*/ +void SV_BroadcastPrintf(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + SV_BroadcastPrint(msg); +} + +/* +================= +Host_ClientCommands + +Send text over to the client to be executed +================= +*/ +void Host_ClientCommands(const char *fmt, ...) +{ + va_list argptr; + char string[MAX_INPUTLINE]; + + if (!host_client->netconnection) + return; + + va_start(argptr,fmt); + dpvsnprintf(string, sizeof(string), fmt, argptr); + va_end(argptr); + + MSG_WriteByte(&host_client->netconnection->message, svc_stufftext); + MSG_WriteString(&host_client->netconnection->message, string); +} + +/* +===================== +SV_DropClient + +Called when the player is getting totally kicked off the host +if (crash = true), don't bother sending signofs +===================== +*/ +void SV_DropClient(qboolean crash) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + Con_Printf("Client \"%s\" dropped\n", host_client->name); + + SV_StopDemoRecording(host_client); + + // make sure edict is not corrupt (from a level change for example) + host_client->edict = PRVM_EDICT_NUM(host_client - svs.clients + 1); + + if (host_client->netconnection) + { + // tell the client to be gone + if (!crash) + { + // LordHavoc: no opportunity for resending, so use unreliable 3 times + unsigned char bufdata[8]; + sizebuf_t buf; + memset(&buf, 0, sizeof(buf)); + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + MSG_WriteByte(&buf, svc_disconnect); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, false); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, false); + NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, false); + } + } + + // call qc ClientDisconnect function + // LordHavoc: don't call QC if server is dead (avoids recursive + // Host_Error in some mods when they run out of edicts) + if (host_client->clientconnectcalled && sv.active && host_client->edict) + { + // call the prog function for removing a client + // this will set the body to a dead frame, among other things + int saveSelf = PRVM_serverglobaledict(self); + host_client->clientconnectcalled = false; + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(ClientDisconnect), "QC function ClientDisconnect is missing"); + PRVM_serverglobaledict(self) = saveSelf; + } + + if (host_client->netconnection) + { + // break the net connection + NetConn_Close(host_client->netconnection); + host_client->netconnection = NULL; + } + + // if a download is active, close it + if (host_client->download_file) + { + Con_DPrintf("Download of %s aborted when %s dropped\n", host_client->download_name, host_client->name); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + host_client->download_name[0] = 0; + host_client->download_expectedposition = 0; + host_client->download_started = false; + } + + // remove leaving player from scoreboard + host_client->name[0] = 0; + host_client->colors = 0; + host_client->frags = 0; + // send notification to all clients + // get number of client manually just to make sure we get it right... + i = host_client - svs.clients; + MSG_WriteByte (&sv.reliable_datagram, svc_updatename); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteString (&sv.reliable_datagram, host_client->name); + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteByte (&sv.reliable_datagram, host_client->colors); + MSG_WriteByte (&sv.reliable_datagram, svc_updatefrags); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteShort (&sv.reliable_datagram, host_client->frags); + + // free the client now + if (host_client->entitydatabase) + EntityFrame_FreeDatabase(host_client->entitydatabase); + if (host_client->entitydatabase4) + EntityFrame4_FreeDatabase(host_client->entitydatabase4); + if (host_client->entitydatabase5) + EntityFrame5_FreeDatabase(host_client->entitydatabase5); + + if (sv.active) + { + // clear a fields that matter to DP_SV_CLIENTNAME and DP_SV_CLIENTCOLORS, and also frags + PRVM_ED_ClearEdict(prog, host_client->edict); + } + + // clear the client struct (this sets active to false) + memset(host_client, 0, sizeof(*host_client)); + + // update server listing on the master because player count changed + // (which the master uses for filtering empty/full servers) + NetConn_Heartbeat(1); + + if (sv.loadgame) + { + for (i = 0;i < svs.maxclients;i++) + if (svs.clients[i].active && !svs.clients[i].spawned) + break; + if (i == svs.maxclients) + { + Con_Printf("Loaded game, everyone rejoined - unpausing\n"); + sv.paused = sv.loadgame = false; // we're basically done with loading now + } + } +} + +/* +================== +Host_ShutdownServer + +This only happens at the end of a game, not between levels +================== +*/ +void Host_ShutdownServer(void) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + + Con_DPrintf("Host_ShutdownServer\n"); + + if (!sv.active) + return; + + NetConn_Heartbeat(2); + NetConn_Heartbeat(2); + +// make sure all the clients know we're disconnecting + World_End(&sv.world); + if(prog->loaded) + { + if(PRVM_serverfunction(SV_Shutdown)) + { + func_t s = PRVM_serverfunction(SV_Shutdown); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again + prog->ExecuteProgram(prog, s,"SV_Shutdown() required"); + } + } + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if (host_client->active) + SV_DropClient(false); // server shutdown + + NetConn_CloseServerPorts(); + + sv.active = false; +// +// clear structures +// + memset(&sv, 0, sizeof(sv)); + memset(svs.clients, 0, svs.maxclients*sizeof(client_t)); + + cl.islocalgame = false; +} + + +//============================================================================ + +/* +=================== +Host_GetConsoleCommands + +Add them exactly as if they had been typed at the console +=================== +*/ +static void Host_GetConsoleCommands (void) +{ + char *cmd; + + while (1) + { + cmd = Sys_ConsoleInput (); + if (!cmd) + break; + Cbuf_AddText (cmd); + } +} + +/* +================== +Host_TimeReport + +Returns a time report string, for example for +================== +*/ +const char *Host_TimingReport(char *buf, size_t buflen) +{ + return va(buf, buflen, "%.1f%% CPU, %.2f%% lost, offset avg %.1fms, max %.1fms, sdev %.1fms", svs.perf_cpuload * 100, svs.perf_lost * 100, svs.perf_offset_avg * 1000, svs.perf_offset_max * 1000, svs.perf_offset_sdev * 1000); +} + +/* +================== +Host_Frame + +Runs all active servers +================== +*/ +static void Host_Init(void); + +double time1 = 0; +double time2 = 0; +double time3 = 0; +double cl_timer = 0, sv_timer = 0; +double clframetime, deltacleantime, olddirtytime, dirtytime; +double wait; +int pass1, pass2, pass3, i; +char vabuf[1024]; +qboolean playing; +void Host_BeginFrame(void) +{ + if (setjmp(host_abortframe)) + { + SCR_ClearLoadingScreen(false); + return; + } + + olddirtytime = host_dirtytime; + dirtytime = Sys_DirtyTime(); + deltacleantime = dirtytime - olddirtytime; + if (deltacleantime < 0) + { + // warn if it's significant + if (deltacleantime < -0.01) + Con_Printf("Host_Mingled: time stepped backwards (went from %f to %f, difference %f)\n", olddirtytime, dirtytime, deltacleantime); + deltacleantime = 0; + } + else if (deltacleantime >= 1800) + { + Con_Printf("Host_Mingled: time stepped forward (went from %f to %f, difference %f)\n", olddirtytime, dirtytime, deltacleantime); + deltacleantime = 0; + } + realtime += deltacleantime; + host_dirtytime = dirtytime; + + cl_timer += deltacleantime; + sv_timer += deltacleantime; + + if (!svs.threaded) + { + svs.perf_acc_realtime += deltacleantime; + + // Look for clients who have spawned + playing = false; + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if(host_client->begun) + if(host_client->netconnection) + playing = true; + if(sv.time < 10) + { + // don't accumulate time for the first 10 seconds of a match + // so things can settle + svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; + } + else if(svs.perf_acc_realtime > 5) + { + svs.perf_cpuload = 1 - svs.perf_acc_sleeptime / svs.perf_acc_realtime; + svs.perf_lost = svs.perf_acc_lost / svs.perf_acc_realtime; + if(svs.perf_acc_offset_samples > 0) + { + svs.perf_offset_max = svs.perf_acc_offset_max; + svs.perf_offset_avg = svs.perf_acc_offset / svs.perf_acc_offset_samples; + svs.perf_offset_sdev = sqrt(svs.perf_acc_offset_squared / svs.perf_acc_offset_samples - svs.perf_offset_avg * svs.perf_offset_avg); + } + if(svs.perf_lost > 0 && developer_extra.integer) + if(playing) // only complain if anyone is looking + Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); + svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; + } + } + + if (slowmo.value < 0.00001 && slowmo.value != 0) + Cvar_SetValue("slowmo", 0); + if (host_framerate.value < 0.00001 && host_framerate.value != 0) + Cvar_SetValue("host_framerate", 0); + + // keep the random time dependent, but not when playing demos/benchmarking + if(!*sv_random_seed.string && !cls.demoplayback) + rand(); + + // get new key events + Key_EventQueue_Unblock(); + SndSys_SendKeyEvents(); + Sys_SendKeyEvents(); + + NetConn_UpdateSockets(); + + Log_DestBuffer_Flush(); + + // receive packets on each main loop iteration, as the main loop may + // be undersleeping due to select() detecting a new packet + if (sv.active && !svs.threaded) + NetConn_ServerFrame(); + + Curl_Run(); + + // check for commands typed to the host + Host_GetConsoleCommands(); + + // when a server is running we only execute console commands on server frames + // (this mainly allows frikbot .way config files to work properly by staying in sync with the server qc) + // otherwise we execute them on client frames + if (sv.active ? sv_timer > 0 : cl_timer > 0) + { + // process console commands +// R_TimeReport("preconsole"); + CL_VM_PreventInformationLeaks(); + Cbuf_Frame(); +// R_TimeReport("console"); + } + + //Con_Printf("%6.0f %6.0f\n", cl_timer * 1000000.0, sv_timer * 1000000.0); + + // if the accumulators haven't become positive yet, wait a while + if (cls.state == ca_dedicated) + wait = sv_timer * -1000000.0; + else if (!sv.active || svs.threaded) + wait = cl_timer * -1000000.0; + else + wait = max(cl_timer, sv_timer) * -1000000.0; + + if (!cls.timedemo && wait >= 1) + { + double time0, delta; + + if(host_maxwait.value <= 0) + wait = min(wait, 1000000.0); + else + wait = min(wait, host_maxwait.value * 1000.0); + if(wait < 1) + wait = 1; // because we cast to int + + time0 = Sys_DirtyTime(); + if (sv_checkforpacketsduringsleep.integer && !sys_usenoclockbutbenchmark.integer && !svs.threaded) + NetConn_SleepMicroseconds((int)wait); + else + Sys_Sleep((int)wait); + delta = Sys_DirtyTime() - time0; + if (delta < 0 || delta >= 1800) delta = 0; + if (!svs.threaded) + svs.perf_acc_sleeptime += delta; +// R_TimeReport("sleep"); + return; + } + + // limit the frametime steps to no more than 100ms each + if (cl_timer > 0.1) + cl_timer = 0.1; + if (sv_timer > 0.1) + { + if (!svs.threaded) + svs.perf_acc_lost += (sv_timer - 0.1); + sv_timer = 0.1; + } + + R_TimeReport("---"); + + //------------------- + // + // server operations + // + //------------------- + + // limit the frametime steps to no more than 100ms each + if (sv.active && sv_timer > 0 && !svs.threaded) + { + // execute one or more server frames, with an upper limit on how much + // execution time to spend on server frames to avoid freezing the game if + // the server is overloaded, this execution time limit means the game will + // slow down if the server is taking too long. + int framecount, framelimit = 1; + double advancetime, aborttime = 0; + float offset; + prvm_prog_t *prog = SVVM_prog; + + // run the world state + // don't allow simulation to run too fast or too slow or logic glitches can occur + + // stop running server frames if the wall time reaches this value + if (sys_ticrate.value <= 0) + advancetime = sv_timer; + else if (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) + { + // synchronize to the client frametime, but no less than 10ms and no more than 100ms + advancetime = bound(0.01, cl_timer, 0.1); + } + else + { + advancetime = sys_ticrate.value; + // listen servers can run multiple server frames per client frame + framelimit = cl_maxphysicsframesperserverframe.integer; + aborttime = Sys_DirtyTime() + 0.1; + } + if(slowmo.value > 0 && slowmo.value < 1) + advancetime = min(advancetime, 0.1 / slowmo.value); + else + advancetime = min(advancetime, 0.1); + + if(advancetime > 0) + { + offset = Sys_DirtyTime() - dirtytime;if (offset < 0 || offset >= 1800) offset = 0; + offset += sv_timer; + ++svs.perf_acc_offset_samples; + svs.perf_acc_offset += offset; + svs.perf_acc_offset_squared += offset * offset; + if(svs.perf_acc_offset_max < offset) + svs.perf_acc_offset_max = offset; + } + + // only advance time if not paused + // the game also pauses in singleplayer when menu or console is used + sv.frametime = advancetime * slowmo.value; + if (host_framerate.value) + sv.frametime = host_framerate.value; + if (sv.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) + sv.frametime = 0; + + for (framecount = 0;framecount < framelimit && sv_timer > 0;framecount++) + { + sv_timer -= advancetime; + + // move things around and think unless paused + if (sv.frametime) + SV_Physics(); + + // if this server frame took too long, break out of the loop + if (framelimit > 1 && Sys_DirtyTime() >= aborttime) + break; + } + R_TimeReport("serverphysics"); + + // send all messages to the clients + SV_SendClientMessages(); + + if (sv.paused == 1 && realtime > sv.pausedstart && sv.pausedstart > 0) { + prog->globals.fp[OFS_PARM0] = realtime - sv.pausedstart; + PRVM_serverglobalfloat(time) = sv.time; + prog->ExecuteProgram(prog, PRVM_serverfunction(SV_PausedTic), "QC function SV_PausedTic is missing"); + } + + // send an heartbeat if enough time has passed since the last one + NetConn_Heartbeat(0); + R_TimeReport("servernetwork"); + } + else if (!svs.threaded) + { + // don't let r_speeds display jump around + R_TimeReport("serverphysics"); + R_TimeReport("servernetwork"); + } + + //------------------- + // + // client operations + // + //------------------- + + if (cls.state != ca_dedicated && (cl_timer > 0 || cls.timedemo || ((vid_activewindow ? cl_maxfps : cl_maxidlefps).value < 1))) + { + R_TimeReport("---"); + Collision_Cache_NewFrame(); + R_TimeReport("photoncache"); + // decide the simulation time + if (cls.capturevideo.active) + { + //*** + if (cls.capturevideo.realtime) + clframetime = cl.realframetime = max(cl_timer, 1.0 / cls.capturevideo.framerate); + else + { + clframetime = 1.0 / cls.capturevideo.framerate; + cl.realframetime = max(cl_timer, clframetime); + } + } + else if (vid_activewindow && cl_maxfps.value >= 1 && !cls.timedemo) + { + clframetime = cl.realframetime = max(cl_timer, 1.0 / cl_maxfps.value); + // when running slow, we need to sleep to keep input responsive + wait = bound(0, cl_maxfps_alwayssleep.value * 1000, 100000); + if (wait > 0) + Sys_Sleep((int)wait); + } + else if (!vid_activewindow && cl_maxidlefps.value >= 1 && !cls.timedemo) + clframetime = cl.realframetime = max(cl_timer, 1.0 / cl_maxidlefps.value); + else + clframetime = cl.realframetime = cl_timer; + + // apply slowmo scaling + clframetime *= cl.movevars_timescale; + // scale playback speed of demos by slowmo cvar + if (cls.demoplayback) + { + clframetime *= slowmo.value; + // if demo playback is paused, don't advance time at all + if (cls.demopaused) + clframetime = 0; + } + else + { + // host_framerate overrides all else + if (host_framerate.value) + clframetime = host_framerate.value; + + if (cl.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) + clframetime = 0; + } + + if (cls.timedemo) + clframetime = cl.realframetime = cl_timer; + + // deduct the frame time from the accumulator + cl_timer -= cl.realframetime; + + cl.oldtime = cl.time; + cl.time += clframetime; + + // update video + if (host_speeds.integer) + time1 = Sys_DirtyTime(); + R_TimeReport("pre-input"); + + // Collect input into cmd + CL_Input(); + + R_TimeReport("input"); + + // check for new packets + NetConn_ClientFrame(); + + // read a new frame from a demo if needed + CL_ReadDemoMessage(); + R_TimeReport("clientnetwork"); + + // now that packets have been read, send input to server + CL_SendMove(); + R_TimeReport("sendmove"); + + // update client world (interpolate entities, create trails, etc) + CL_UpdateWorld(); + R_TimeReport("lerpworld"); + + CL_Video_Frame(); + + R_TimeReport("client"); + } + + CL_BeginUpdateScreen(); +} + +void Host_Frame(int eye, int x, int y) +{ + r_stereo_side = eye; + + SCR_DrawScreen(x, y); + + R_TimeReport("render"); +} + +void Host_EndFrame(void) +{ + CL_EndUpdateScreen(); + + if (cls.state != ca_dedicated && (cl_timer > 0 || cls.timedemo || ((vid_activewindow ? cl_maxfps : cl_maxidlefps).value < 1))) + { + if (host_speeds.integer) + time2 = Sys_DirtyTime(); + + // update audio + if(cl.csqc_usecsqclistener) + { + S_Update(&cl.csqc_listenermatrix); + cl.csqc_usecsqclistener = false; + } + else + S_Update(&r_refdef.view.matrix); + + CDAudio_Update(); + R_TimeReport("audio"); + + // reset gathering of mouse input + in_mouse_x = in_mouse_y = 0; + + if (host_speeds.integer) + { + pass1 = (int)((time1 - time3)*1000000); + time3 = Sys_DirtyTime(); + pass2 = (int)((time2 - time1)*1000000); + pass3 = (int)((time3 - time2)*1000000); + Con_Printf("%6ius total %6ius server %6ius gfx %6ius snd\n", + pass1+pass2+pass3, pass1, pass2, pass3); + } + } + +#if MEMPARANOIA + Mem_CheckSentinelsGlobal(); +#else + if (developer_memorydebug.integer) + Mem_CheckSentinelsGlobal(); +#endif + + // if there is some time remaining from this frame, reset the timers + if (cl_timer >= 0) + cl_timer = 0; + if (sv_timer >= 0) + { + if (!svs.threaded) + svs.perf_acc_lost += sv_timer; + sv_timer = 0; + } + + host_framecount++; +} + +void Host_Main(void) +{ + Host_Init(); + + realtime = 0; +} + +//============================================================================ + +qboolean vid_opened = false; +void Host_StartVideo(void) +{ + if (!vid_opened && cls.state != ca_dedicated) + { + vid_opened = true; + // make sure we open sockets before opening video because the Windows Firewall "unblock?" dialog can screw up the graphics context on some graphics drivers + NetConn_UpdateSockets(); + VID_Start(); + CDAudio_Startup(); + } +} + +char engineversion[128]; + +qboolean sys_nostdout = false; + +extern qboolean host_stuffcmdsrun; + +static qfile_t *locksession_fh = NULL; +static qboolean locksession_run = false; +static void Host_InitSession(void) +{ + int i; + Cvar_RegisterVariable(&sessionid); + Cvar_RegisterVariable(&locksession); + + // load the session ID into the read-only cvar + if ((i = COM_CheckParm("-sessionid")) && (i + 1 < com_argc)) + { + char vabuf[1024]; + if(com_argv[i+1][0] == '.') + Cvar_SetQuick(&sessionid, com_argv[i+1]); + else + Cvar_SetQuick(&sessionid, va(vabuf, sizeof(vabuf), ".%s", com_argv[i+1])); + } +} +void Host_LockSession(void) +{ + if(locksession_run) + return; + locksession_run = true; + if(locksession.integer != 0 && !COM_CheckParm("-readonly")) + { + char vabuf[1024]; + char *p = va(vabuf, sizeof(vabuf), "%slock%s", *fs_userdir ? fs_userdir : fs_basedir, sessionid.string); + FS_CreatePath(p); + locksession_fh = FS_SysOpen(p, "wl", false); + // TODO maybe write the pid into the lockfile, while we are at it? may help server management tools + if(!locksession_fh) + { + if(locksession.integer == 2) + { + Con_Printf("WARNING: session lock %s could not be acquired. Please run with -sessionid and an unique session name. Continuing anyway.\n", p); + } + else + { + Sys_Error("session lock %s could not be acquired. Please run with -sessionid and an unique session name.\n", p); + } + } + } +} +void Host_UnlockSession(void) +{ + if(!locksession_run) + return; + locksession_run = false; + + if(locksession_fh) + { + FS_Close(locksession_fh); + // NOTE: we can NOT unlink the lock here, as doing so would + // create a race condition if another process created it + // between our close and our unlink + locksession_fh = NULL; + } +} + +/* +==================== +Host_Init +==================== +*/ +static void Host_Init (void) +{ + int i; + const char* os; + char vabuf[1024]; + + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(false); + + // LordHavoc: quake never seeded the random number generator before... heh + if (COM_CheckParm("-benchmark")) + srand(0); // predictable random sequence for -benchmark + else + srand((unsigned int)time(NULL)); + + // FIXME: this is evil, but possibly temporary + // LordHavoc: doesn't seem very temporary... + // LordHavoc: made this a saved cvar +// COMMANDLINEOPTION: Console: -developer enables warnings and other notices (RECOMMENDED for mod developers) + if (COM_CheckParm("-developer")) + { + developer.value = developer.integer = 1; + developer.string = "1"; + } + + if (COM_CheckParm("-developer2") || COM_CheckParm("-developer3")) + { + developer.value = developer.integer = 1; + developer.string = "1"; + developer_extra.value = developer_extra.integer = 1; + developer_extra.string = "1"; + developer_insane.value = developer_insane.integer = 1; + developer_insane.string = "1"; + developer_memory.value = developer_memory.integer = 1; + developer_memory.string = "1"; + developer_memorydebug.value = developer_memorydebug.integer = 1; + developer_memorydebug.string = "1"; + } + + if (COM_CheckParm("-developer3")) + { + gl_paranoid.integer = 1;gl_paranoid.string = "1"; + gl_printcheckerror.integer = 1;gl_printcheckerror.string = "1"; + } + +// COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from + if (COM_CheckParm("-nostdout")) + sys_nostdout = 1; + + // used by everything + Memory_Init(); + + // initialize console command/cvar/alias/command execution systems + Cmd_Init(); + + // initialize memory subsystem cvars/commands + Memory_Init_Commands(); + + // initialize console and logging and its cvars/commands + Con_Init(); + + // initialize various cvars that could not be initialized earlier + u8_Init(); + Curl_Init_Commands(); + Cmd_Init_Commands(); + Sys_Init_Commands(); + COM_Init_Commands(); + FS_Init_Commands(); + + // initialize console window (only used by sys_win.c) + Sys_InitConsole(); + + // initialize the self-pack (must be before COM_InitGameType as it may add command line options) + FS_Init_SelfPack(); + + // detect gamemode from commandline options or executable name + COM_InitGameType(); + + // construct a version string for the corner of the console + os = DP_OS_NAME; + dpsnprintf (engineversion, sizeof (engineversion), "%s %s %s", gamename, os, buildstring); + Con_Printf("%s\n", engineversion); + + // initialize process nice level + Sys_InitProcessNice(); + + // initialize ixtable + Mathlib_Init(); + + // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name) + FS_Init(); + + // register the cvars for session locking + Host_InitSession(); + + // must be after FS_Init + Crypto_Init(); + Crypto_Init_Commands(); + + NetConn_Init(); + Curl_Init(); + //PR_Init(); + //PR_Cmd_Init(); + PRVM_Init(); + Mod_Init(); + World_Init(); + SV_Init(); + V_Init(); // some cvars needed by server player physics (cl_rollangle etc) + Host_InitCommands(); + Host_InitLocal(); + Host_ServerOptions(); + + Thread_Init(); + + if (cls.state == ca_dedicated) + Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server (or disconnect all clients if running a server)"); + else + { + Con_DPrintf("Initializing client\n"); + + R_Modules_Init(); + Palette_Init(); + MR_Init_Commands(); + VID_Shared_Init(); + VID_Init(); + Render_Init(); + S_Init(); + CDAudio_Init(); + Key_Init(); + CL_Init(); + } + + // save off current state of aliases, commands and cvars for later restore if FS_GameDir_f is called + // NOTE: menu commands are freed by Cmd_RestoreInitState + Cmd_SaveInitState(); + + // FIXME: put this into some neat design, but the menu should be allowed to crash + // without crashing the whole game, so this should just be a short-time solution + + // here comes the not so critical stuff + if (setjmp(host_abortframe)) { + return; + } + + Host_AddConfigText(); + Cbuf_Execute(); + + // if stuffcmds wasn't run, then quake.rc is probably missing, use default + if (!host_stuffcmdsrun) + { + Cbuf_AddText("exec default.cfg\nexec " CONFIGFILENAME "\nexec autoexec.cfg\nstuffcmds\n"); + Cbuf_Execute(); + } + + // put up the loading image so the user doesn't stare at a black screen... + SCR_BeginLoadingPlaque(true); + + if (cls.state != ca_dedicated) + { + MR_Init(); + } + + // check for special benchmark mode +// COMMANDLINEOPTION: Client: -benchmark runs a timedemo and quits, results of any timedemo can be found in gamedir/benchmark.log (for example id1/benchmark.log) + i = COM_CheckParm("-benchmark"); + if (i && i + 1 < com_argc) + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", com_argv[i + 1])); + Cbuf_Execute(); + } + + // check for special demo mode +// COMMANDLINEOPTION: Client: -demo runs a playdemo and quits + i = COM_CheckParm("-demo"); + if (i && i + 1 < com_argc) + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText(va(vabuf, sizeof(vabuf), "playdemo %s\n", com_argv[i + 1])); + Cbuf_Execute(); + } + +// COMMANDLINEOPTION: Client: -capturedemo captures a playdemo and quits + i = COM_CheckParm("-capturedemo"); + if (i && i + 1 < com_argc) + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText(va(vabuf, sizeof(vabuf), "playdemo %s\ncl_capturevideo 1\n", com_argv[i + 1])); + Cbuf_Execute(); + } + + if (cls.state == ca_dedicated || COM_CheckParm("-listen")) + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText("startmap_dm\n"); + Cbuf_Execute(); + } + + if (!sv.active && !cls.demoplayback && !cls.connect_trying) + { + Cbuf_AddText("togglemenu 1\n"); + Cbuf_Execute(); + } + + Con_DPrint("========Initialized=========\n"); + + //Host_StartVideo(); + + if (cls.state != ca_dedicated) + SV_StartThread(); +} + + +/* +=============== +Host_Shutdown + +FIXME: this is a callback from Sys_Quit and Sys_Error. It would be better +to run quit through here before the final handoff to the sys code. +=============== +*/ +void Host_Shutdown(void) +{ + static qboolean isdown = false; + + if (isdown) + { + Con_Print("recursive shutdown\n"); + return; + } + if (setjmp(host_abortframe)) + { + Con_Print("aborted the quitting frame?!?\n"); + return; + } + isdown = true; + + // be quiet while shutting down + S_StopAllSounds(); + + // end the server thread + if (svs.threaded) + SV_StopThread(); + + // disconnect client from server if active + CL_Disconnect(); + + // shut down local server if active + SV_LockThreadMutex(); + Host_ShutdownServer (); + SV_UnlockThreadMutex(); + + // Shutdown menu + if(MR_Shutdown) + MR_Shutdown(); + + // AK shutdown PRVM + // AK hmm, no PRVM_Shutdown(); yet + + //Don't need to do this, we don't own the video side of things + //CL_Video_Shutdown(); + + Host_SaveConfig(); + + //CDAudio_Shutdown (); + S_Terminate (); + Curl_Shutdown (); + NetConn_Shutdown (); + //PR_Shutdown (); + + if (cls.state != ca_dedicated) + { + R_Modules_Shutdown(); + VID_Shutdown(); + } + + SV_StopThread(); + Thread_Shutdown(); + Cmd_Shutdown(); + Key_Shutdown(); + CL_Shutdown(); + Sys_Shutdown(); + Log_Close(); + Crypto_Shutdown(); + + Host_UnlockSession(); + + S_Shutdown(); + Con_Shutdown(); + Memory_Shutdown(); +} + diff --git a/app/jni/host_cmd.c b/app/jni/host_cmd.c new file mode 100644 index 0000000..6a0b991 --- /dev/null +++ b/app/jni/host_cmd.c @@ -0,0 +1,3051 @@ +/* +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 "sv_demo.h" +#include "image.h" + +#include "utf8lib.h" + +// for secure rcon authentication +#include "hmac.h" +#include "mdfour.h" +#include + +int current_skill; +cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"}; +cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"}; +cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"}; +cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."}; +cvar_t sv_namechangetimer = {CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"}; +cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; +cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"}; +cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"}; +cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"}; +cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"}; +cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"}; +cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"}; +cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"}; +qboolean allowcheats = false; + +extern qboolean host_shuttingdown; +extern cvar_t developer_entityparsing; + +/* +================== +Host_Quit_f +================== +*/ + +void Host_Quit_f (void) +{ + if(host_shuttingdown) + Con_Printf("shutting down already!\n"); + else + Sys_Quit (0); +} + +/* +================== +Host_Status_f +================== +*/ +static void Host_Status_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + char qcstatus[256]; + client_t *client; + int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0; + void (*print) (const char *fmt, ...); + char ip[48]; // can contain a full length v6 address with [] and a port + int frags; + char vabuf[1024]; + + if (cmd_source == src_command) + { + // if running a client, try to send over network so the client's status report parser will see the report + if (cls.state == ca_connected) + { + Cmd_ForwardToServer (); + return; + } + print = Con_Printf; + } + else + print = SV_ClientPrintf; + + if (!sv.active) + return; + + in = 0; + if (Cmd_Argc() == 2) + { + if (strcmp(Cmd_Argv(1), "1") == 0) + in = 1; + else if (strcmp(Cmd_Argv(1), "2") == 0) + in = 2; + } + + for (players = 0, i = 0;i < svs.maxclients;i++) + if (svs.clients[i].active) + players++; + print ("host: %s\n", Cvar_VariableString ("hostname")); + print ("version: %s build %s\n", gamename, buildstring); + print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol)); + print ("map: %s\n", sv.name); + print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); + print ("players: %i active (%i max)\n\n", players, svs.maxclients); + + if (in == 1) + print ("^2IP %%pl ping time frags no name\n"); + else if (in == 2) + print ("^5IP no name\n"); + + for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (!client->active) + continue; + + ++k; + + if (in == 0 || in == 1) + { + seconds = (int)(realtime - client->connecttime); + minutes = seconds / 60; + if (minutes) + { + seconds -= (minutes * 60); + hours = minutes / 60; + if (hours) + minutes -= (hours * 60); + } + else + hours = 0; + + packetloss = 0; + if (client->netconnection) + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) + packetloss++; + packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; + ping = bound(0, (int)floor(client->ping*1000+0.5), 9999); + } + + if(sv_status_privacy.integer && cmd_source != src_command) + strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48); + else + strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48); + + frags = client->frags; + + if(sv_status_show_qcstatus.integer) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1); + const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus)); + if(str && *str) + { + char *p; + const char *q; + p = qcstatus; + for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q) + if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q)) + *p++ = *q; + *p = 0; + if(*qcstatus) + frags = atoi(qcstatus); + } + } + + if (in == 0) // default layout + { + if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99) + { + // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output + print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds); + print (" %s\n", ip); + } + else + { + // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway... + print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds); + print (" %s\n", ip); + } + } + else if (in == 1) // extended layout + { + print ("%s%-47s %2i %4i %2i:%02i:%02i %4i #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name); + } + else if (in == 2) // reduced layout + { + print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name); + } + } +} + + +/* +================== +Host_God_f + +Sets client to godmode +================== +*/ +static void Host_God_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE; + if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) ) + SV_ClientPrint("godmode OFF\n"); + else + SV_ClientPrint("godmode ON\n"); +} + +static void Host_Notarget_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET; + if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) ) + SV_ClientPrint("notarget OFF\n"); + else + SV_ClientPrint("notarget ON\n"); +} + +qboolean noclip_anglehack; + +static void Host_Noclip_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP) + { + noclip_anglehack = true; + PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP; + SV_ClientPrint("noclip ON\n"); + } + else + { + noclip_anglehack = false; + PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK; + SV_ClientPrint("noclip OFF\n"); + } +} + +/* +================== +Host_Fly_f + +Sets client to flymode +================== +*/ +static void Host_Fly_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY) + { + PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY; + SV_ClientPrint("flymode ON\n"); + } + else + { + PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK; + SV_ClientPrint("flymode OFF\n"); + } +} + + +/* +================== +Host_Ping_f + +================== +*/ +void Host_Pings_f (void); // called by Host_Ping_f +static void Host_Ping_f (void) +{ + int i; + client_t *client; + void (*print) (const char *fmt, ...); + + if (cmd_source == src_command) + { + // if running a client, try to send over network so the client's ping report parser will see the report + if (cls.state == ca_connected) + { + Cmd_ForwardToServer (); + return; + } + print = Con_Printf; + } + else + print = SV_ClientPrintf; + + if (!sv.active) + return; + + print("Client ping times:\n"); + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (!client->active) + continue; + print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name); + } + + // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report) + // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console) + //Host_Pings_f(); +} + +/* +=============================================================================== + +SERVER TRANSITIONS + +=============================================================================== +*/ + +/* +====================== +Host_Map_f + +handle a +map +command from the console. Active clients are kicked off. +====================== +*/ +static void Host_Map_f (void) +{ + char level[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Con_Print("map : start a new game (kicks off all players)\n"); + return; + } + + // GAME_DELUXEQUAKE - clear warpmark (used by QC) + if (gamemode == GAME_DELUXEQUAKE) + Cvar_Set("warpmark", ""); + + cls.demonum = -1; // stop demo loop in case this fails + + CL_Disconnect (); + Host_ShutdownServer(); + + if(svs.maxclients != svs.maxclients_next) + { + svs.maxclients = svs.maxclients_next; + if (svs.clients) + Mem_Free(svs.clients); + svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients); + } + + // remove menu + if (key_dest == key_menu || key_dest == key_menu_grabbed) + MR_ToggleMenu(0); + key_dest = key_game; + + svs.serverflags = 0; // haven't completed an episode yet + allowcheats = sv_cheats.integer != 0; + strlcpy(level, Cmd_Argv(1), sizeof(level)); + SV_SpawnServer(level); + if (sv.active && cls.state == ca_disconnected) + CL_EstablishConnection("local:1", -2); +} + +/* +================== +Host_Changelevel_f + +Goes to a new map, taking all clients along +================== +*/ +static void Host_Changelevel_f (void) +{ + char level[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Con_Print("changelevel : continue game on a new level\n"); + return; + } + // HACKHACKHACK + if (!sv.active) { + Host_Map_f(); + return; + } + + // remove menu + if (key_dest == key_menu || key_dest == key_menu_grabbed) + MR_ToggleMenu(0); + key_dest = key_game; + + SV_SaveSpawnparms (); + allowcheats = sv_cheats.integer != 0; + strlcpy(level, Cmd_Argv(1), sizeof(level)); + SV_SpawnServer(level); + if (sv.active && cls.state == ca_disconnected) + CL_EstablishConnection("local:1", -2); +} + +/* +================== +Host_Restart_f + +Restarts the current server for a dead player +================== +*/ +static void Host_Restart_f (void) +{ + char mapname[MAX_QPATH]; + + if (Cmd_Argc() != 1) + { + Con_Print("restart : restart current level\n"); + return; + } + if (!sv.active) + { + Con_Print("Only the server may restart\n"); + return; + } + + // remove menu + if (key_dest == key_menu || key_dest == key_menu_grabbed) + MR_ToggleMenu(0); + key_dest = key_game; + + allowcheats = sv_cheats.integer != 0; + strlcpy(mapname, sv.name, sizeof(mapname)); + SV_SpawnServer(mapname); + if (sv.active && cls.state == ca_disconnected) + CL_EstablishConnection("local:1", -2); +} + +/* +================== +Host_Reconnect_f + +This command causes the client to wait for the signon messages again. +This is sent just before a server changes levels +================== +*/ +void Host_Reconnect_f (void) +{ + char temp[128]; + // if not connected, reconnect to the most recent server + if (!cls.netcon) + { + // if we have connected to a server recently, the userinfo + // will still contain its IP address, so get the address... + InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp)); + if (temp[0]) + CL_EstablishConnection(temp, -1); + else + Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n"); + return; + } + // if connected, do something based on protocol + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + // quakeworld can just re-login + if (cls.qw_downloadmemory) // don't change when downloading + return; + + S_StopAllSounds(); + + if (cls.state == ca_connected && cls.signon < SIGNONS) + { + Con_Printf("reconnecting...\n"); + MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "new"); + } + } + else + { + // netquake uses reconnect on level changes (silly) + if (Cmd_Argc() != 1) + { + Con_Print("reconnect : wait for signon messages again\n"); + return; + } + if (!cls.signon) + { + Con_Print("reconnect: no signon, ignoring reconnect\n"); + return; + } + cls.signon = 0; // need new connection messages + } +} + +/* +===================== +Host_Connect_f + +User command to connect to server +===================== +*/ +static void Host_Connect_f (void) +{ + if (Cmd_Argc() < 2) + { + Con_Print("connect [ ...]: connect to a multiplayer game\n"); + return; + } + // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command + if(rcon_secure.integer <= 0) + Cvar_SetQuick(&rcon_password, ""); + CL_EstablishConnection(Cmd_Argv(1), 2); +} + + +/* +=============================================================================== + +LOAD / SAVE GAME + +=============================================================================== +*/ + +#define SAVEGAME_VERSION 5 + +void Host_Savegame_to(prvm_prog_t *prog, const char *name) +{ + qfile_t *f; + int i, k, l, numbuffers, lightstyles = 64; + char comment[SAVEGAME_COMMENT_LENGTH+1]; + char line[MAX_INPUTLINE]; + qboolean isserver; + char *s; + + // first we have to figure out if this can be saved in 64 lightstyles + // (for Quake compatibility) + for (i=64 ; iedicts, message)), (int)PRVM_serverglobalfloat(killed_monsters), (int)PRVM_serverglobalfloat(total_monsters)); + else + dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name); + // convert space to _ to make stdio happy + // LordHavoc: convert control characters to _ as well + for (i=0 ; inum_edicts ; i++) + { + FS_Printf(f,"// edict %d\n", i); + //Con_Printf("edict %d...\n", i); + PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i)); + } + +#if 1 + FS_Printf(f,"/*\n"); + FS_Printf(f,"// DarkPlaces extended savegame\n"); + // darkplaces extension - extra lightstyles, support for color lightstyles + for (i=0 ; istringbuffersarray); + for (i = 0; i < numbuffers; i++) + { + prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); + if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED)) + { + FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS); + for(k = 0; k < stringbuffer->num_strings; k++) + { + if (!stringbuffer->strings[k]) + continue; + // Parse the string a bit to turn special characters + // (like newline, specifically) into escape codes + s = stringbuffer->strings[k]; + for (l = 0;l < (int)sizeof(line) - 2 && *s;) + { + if (*s == '\n') + { + line[l++] = '\\'; + line[l++] = 'n'; + } + else if (*s == '\r') + { + line[l++] = '\\'; + line[l++] = 'r'; + } + else if (*s == '\\') + { + line[l++] = '\\'; + line[l++] = '\\'; + } + else if (*s == '"') + { + line[l++] = '\\'; + line[l++] = '"'; + } + else + line[l++] = *s; + s++; + } + line[l] = '\0'; + FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line); + } + } + } + FS_Printf(f,"*/\n"); +#endif + + FS_Close (f); + Con_Print("done.\n"); +} + +/* +=============== +Host_Savegame_f +=============== +*/ +static void Host_Savegame_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + char name[MAX_QPATH]; + qboolean deadflag = false; + + if (!sv.active) + { + Con_Print("Can't save - no server running.\n"); + return; + } + + deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag); + + if (cl.islocalgame) + { + // singleplayer checks + if (cl.intermission) + { + Con_Print("Can't save in intermission.\n"); + return; + } + + if (deadflag) + { + Con_Print("Can't savegame with a dead player\n"); + return; + } + } + else + Con_Print("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n"); + + if (Cmd_Argc() != 2) + { + Con_Print("save : save a game\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Print("Relative pathnames are not allowed.\n"); + return; + } + + strlcpy (name, Cmd_Argv(1), sizeof (name)); + FS_DefaultExtension (name, ".sav", sizeof (name)); + + Host_Savegame_to(prog, name); +} + + +/* +=============== +Host_Loadgame_f +=============== +*/ + +prvm_stringbuffer_t *BufStr_FindCreateReplace (prvm_prog_t *prog, int bufindex, int flags, char *format); +void BufStr_Set(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex, const char *str); +void BufStr_Del(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer); +void BufStr_Flush(prvm_prog_t *prog); + +static void Host_Loadgame_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + char filename[MAX_QPATH]; + char mapname[MAX_QPATH]; + float time; + const char *start; + const char *end; + const char *t; + char *text; + prvm_edict_t *ent; + int i, k, numbuffers; + int entnum; + int version; + float spawn_parms[NUM_SPAWN_PARMS]; + prvm_stringbuffer_t *stringbuffer; + + if (Cmd_Argc() != 2) + { + Con_Print("load : load a game\n"); + return; + } + + strlcpy (filename, Cmd_Argv(1), sizeof(filename)); + FS_DefaultExtension (filename, ".sav", sizeof (filename)); + + Con_Printf("Loading game from %s...\n", filename); + + // stop playing demos + if (cls.demoplayback) + CL_Disconnect (); + + // remove menu + if (key_dest == key_menu || key_dest == key_menu_grabbed) + MR_ToggleMenu(0); + key_dest = key_game; + + cls.demonum = -1; // stop demo loop in case this fails + + t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL); + if (!text) + { + Con_Print("ERROR: couldn't open.\n"); + return; + } + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading version\n"); + + // version + COM_ParseToken_Simple(&t, false, false, true); + version = atoi(com_token); + if (version != SAVEGAME_VERSION) + { + Mem_Free(text); + Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION); + return; + } + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading description\n"); + + // description + COM_ParseToken_Simple(&t, false, false, true); + + for (i = 0;i < NUM_SPAWN_PARMS;i++) + { + COM_ParseToken_Simple(&t, false, false, true); + spawn_parms[i] = atof(com_token); + } + // skill + COM_ParseToken_Simple(&t, false, false, true); +// this silliness is so we can load 1.06 save files, which have float skill values + current_skill = (int)(atof(com_token) + 0.5); + Cvar_SetValue ("skill", (float)current_skill); + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading mapname\n"); + + // mapname + COM_ParseToken_Simple(&t, false, false, true); + strlcpy (mapname, com_token, sizeof(mapname)); + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading time\n"); + + // time + COM_ParseToken_Simple(&t, false, false, true); + time = atof(com_token); + + allowcheats = sv_cheats.integer != 0; + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: spawning server\n"); + + SV_SpawnServer (mapname); + if (!sv.active) + { + Mem_Free(text); + Con_Print("Couldn't load map\n"); + return; + } + sv.paused = true; // pause until all clients connect + sv.loadgame = true; + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading light styles\n"); + +// load the light styles + + // -1 is the globals + entnum = -1; + + for (i = 0;i < MAX_LIGHTSTYLES;i++) + { + // light style + start = t; + COM_ParseToken_Simple(&t, false, false, true); + // if this is a 64 lightstyle savegame produced by Quake, stop now + // we have to check this because darkplaces may save more than 64 + if (com_token[0] == '{') + { + t = start; + break; + } + strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i])); + } + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: skipping until globals\n"); + + // now skip everything before the first opening brace + // (this is for forward compatibility, so that older versions (at + // least ones with this fix) can load savegames with extra data before the + // first brace, as might be produced by a later engine version) + for (;;) + { + start = t; + if (!COM_ParseToken_Simple(&t, false, false, true)) + break; + if (com_token[0] == '{') + { + t = start; + break; + } + } + + // unlink all entities + World_UnlinkAll(&sv.world); + +// load the edicts out of the savegame file + end = t; + for (;;) + { + start = t; + while (COM_ParseToken_Simple(&t, false, false, true)) + if (!strcmp(com_token, "}")) + break; + if (!COM_ParseToken_Simple(&start, false, false, true)) + { + // end of file + break; + } + if (strcmp(com_token,"{")) + { + Mem_Free(text); + Host_Error ("First token isn't a brace"); + } + + if (entnum == -1) + { + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading globals\n"); + + // parse the global vars + PRVM_ED_ParseGlobals (prog, start); + + // restore the autocvar globals + Cvar_UpdateAllAutoCvars(); + } + else + { + // parse an edict + if (entnum >= MAX_EDICTS) + { + Mem_Free(text); + Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS); + } + while (entnum >= prog->max_edicts) + PRVM_MEM_IncreaseEdicts(prog); + ent = PRVM_EDICT_NUM(entnum); + memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); + ent->priv.server->free = false; + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum); + + PRVM_ED_ParseEdict (prog, start, ent); + + // link it into the bsp tree + if (!ent->priv.server->free) + SV_LinkEdict(ent); + } + + end = t; + entnum++; + } + + prog->num_edicts = entnum; + sv.time = time; + + for (i = 0;i < NUM_SPAWN_PARMS;i++) + svs.clients[0].spawn_parms[i] = spawn_parms[i]; + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: skipping until extended data\n"); + + // read extended data if present + // the extended data is stored inside a /* */ comment block, which the + // parser intentionally skips, so we have to check for it manually here + if(end) + { + while (*end == '\r' || *end == '\n') + end++; + if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n')) + { + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: loading extended data\n"); + + Con_Printf("Loading extended DarkPlaces savegame\n"); + t = end + 2; + memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles)); + memset(sv.model_precache[0], 0, sizeof(sv.model_precache)); + memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache)); + BufStr_Flush(prog); + + while (COM_ParseToken_Simple(&t, false, false, true)) + { + if (!strcmp(com_token, "sv.lightstyles")) + { + COM_ParseToken_Simple(&t, false, false, true); + i = atoi(com_token); + COM_ParseToken_Simple(&t, false, false, true); + if (i >= 0 && i < MAX_LIGHTSTYLES) + strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i])); + else + Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token); + } + else if (!strcmp(com_token, "sv.model_precache")) + { + COM_ParseToken_Simple(&t, false, false, true); + i = atoi(com_token); + COM_ParseToken_Simple(&t, false, false, true); + if (i >= 0 && i < MAX_MODELS) + { + strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i])); + sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL); + } + else + Con_Printf("unsupported model %i \"%s\"\n", i, com_token); + } + else if (!strcmp(com_token, "sv.sound_precache")) + { + COM_ParseToken_Simple(&t, false, false, true); + i = atoi(com_token); + COM_ParseToken_Simple(&t, false, false, true); + if (i >= 0 && i < MAX_SOUNDS) + strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i])); + else + Con_Printf("unsupported sound %i \"%s\"\n", i, com_token); + } + else if (!strcmp(com_token, "sv.buffer")) + { + if (COM_ParseToken_Simple(&t, false, false, true)) + { + i = atoi(com_token); + if (i >= 0) + { + k = STRINGBUFFER_SAVED; + if (COM_ParseToken_Simple(&t, false, false, true)) + k |= atoi(com_token); + if (!BufStr_FindCreateReplace(prog, i, k, "string")) + Con_Printf("failed to create stringbuffer %i\n", i); + } + else + Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token); + } + else + Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n"); + } + else if (!strcmp(com_token, "sv.bufstr")) + { + if (!COM_ParseToken_Simple(&t, false, false, true)) + Con_Printf("unexpected end of line when parsing sv.bufstr\n"); + else + { + i = atoi(com_token); + stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string"); + if (stringbuffer) + { + if (COM_ParseToken_Simple(&t, false, false, true)) + { + k = atoi(com_token); + if (COM_ParseToken_Simple(&t, false, false, true)) + BufStr_Set(prog, stringbuffer, k, com_token); + else + Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n"); + } + else + Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n"); + } + else + Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token); + } + } + // skip any trailing text or unrecognized commands + while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n")) + ; + } + } + } + Mem_Free(text); + + // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace) + numbuffers = Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); + for (i = 0; i < numbuffers; i++) + { + if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) ) + if (stringbuffer->flags & STRINGBUFFER_TEMP) + BufStr_Del(prog, stringbuffer); + } + + if(developer_entityparsing.integer) + Con_Printf("Host_Loadgame_f: finished\n"); + + // make sure we're connected to loopback + if (sv.active && cls.state == ca_disconnected) + CL_EstablishConnection("local:1", -2); +} + +//============================================================================ + +/* +====================== +Host_Name_f +====================== +*/ +cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"}; +static void Host_Name_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i, j; + qboolean valid_colors; + const char *newNameSource; + char newName[sizeof(host_client->name)]; + + if (Cmd_Argc () == 1) + { + Con_Printf("name: %s\n", cl_name.string); + return; + } + + if (Cmd_Argc () == 2) + newNameSource = Cmd_Argv(1); + else + newNameSource = Cmd_Args(); + + strlcpy(newName, newNameSource, sizeof(newName)); + + if (cmd_source == src_command) + { + Cvar_Set ("_cl_name", newName); + if (strlen(newNameSource) >= sizeof(newName)) // overflowed + { + Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1)); + Con_Printf("name: %s\n", cl_name.string); + } + return; + } + + if (realtime < host_client->nametime) + { + SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value)); + return; + } + + host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value); + + // point the string back at updateclient->name to keep it safe + strlcpy (host_client->name, newName, sizeof (host_client->name)); + + for (i = 0, j = 0;host_client->name[i];i++) + if (host_client->name[i] != '\r' && host_client->name[i] != '\n') + host_client->name[j++] = host_client->name[i]; + host_client->name[j] = 0; + + if(host_client->name[0] == 1 || host_client->name[0] == 2) + // may interfere with chat area, and will needlessly beep; so let's add a ^7 + { + memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2); + host_client->name[sizeof(host_client->name) - 1] = 0; + host_client->name[0] = STRING_COLOR_TAG; + host_client->name[1] = '0' + STRING_COLOR_DEFAULT; + } + + u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors); + if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string + { + size_t l; + l = strlen(host_client->name); + if(l < sizeof(host_client->name) - 1) + { + // duplicate the color tag to escape it + host_client->name[i] = STRING_COLOR_TAG; + host_client->name[i+1] = 0; + //Con_DPrintf("abuse detected, adding another trailing color tag\n"); + } + else + { + // remove the last character to fix the color code + host_client->name[l-1] = 0; + //Con_DPrintf("abuse detected, removing a trailing color tag\n"); + } + } + + // find the last color tag offset and decide if we need to add a reset tag + for (i = 0, j = -1;host_client->name[i];i++) + { + if (host_client->name[i] == STRING_COLOR_TAG) + { + if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9') + { + j = i; + // if this happens to be a reset tag then we don't need one + if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT) + j = -1; + i++; + continue; + } + if (host_client->name[i+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(host_client->name[i+2]) && isxdigit(host_client->name[i+3]) && isxdigit(host_client->name[i+4])) + { + j = i; + i += 4; + continue; + } + if (host_client->name[i+1] == STRING_COLOR_TAG) + { + i++; + continue; + } + } + } + // does not end in the default color string, so add it + if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2) + memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1); + + PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name); + if (strcmp(host_client->old_name, host_client->name)) + { + if (host_client->begun) + SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name); + strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name)); + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatename); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteString (&sv.reliable_datagram, host_client->name); + SV_WriteNetnameIntoDemo(host_client); + } +} + +/* +====================== +Host_Playermodel_f +====================== +*/ +cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"}; +// the old cl_playermodel in cl_main has been renamed to __cl_playermodel +static void Host_Playermodel_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i, j; + char newPath[sizeof(host_client->playermodel)]; + + if (Cmd_Argc () == 1) + { + Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string); + return; + } + + if (Cmd_Argc () == 2) + strlcpy (newPath, Cmd_Argv(1), sizeof (newPath)); + else + strlcpy (newPath, Cmd_Args(), sizeof (newPath)); + + for (i = 0, j = 0;newPath[i];i++) + if (newPath[i] != '\r' && newPath[i] != '\n') + newPath[j++] = newPath[i]; + newPath[j] = 0; + + if (cmd_source == src_command) + { + Cvar_Set ("_cl_playermodel", newPath); + return; + } + + /* + if (realtime < host_client->nametime) + { + SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n"); + return; + } + + host_client->nametime = realtime + 5; + */ + + // point the string back at updateclient->name to keep it safe + strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel)); + PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel); + if (strcmp(host_client->old_model, host_client->playermodel)) + { + strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model)); + /*// send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/ + } +} + +/* +====================== +Host_Playerskin_f +====================== +*/ +cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"}; +static void Host_Playerskin_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i, j; + char newPath[sizeof(host_client->playerskin)]; + + if (Cmd_Argc () == 1) + { + Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string); + return; + } + + if (Cmd_Argc () == 2) + strlcpy (newPath, Cmd_Argv(1), sizeof (newPath)); + else + strlcpy (newPath, Cmd_Args(), sizeof (newPath)); + + for (i = 0, j = 0;newPath[i];i++) + if (newPath[i] != '\r' && newPath[i] != '\n') + newPath[j++] = newPath[i]; + newPath[j] = 0; + + if (cmd_source == src_command) + { + Cvar_Set ("_cl_playerskin", newPath); + return; + } + + /* + if (realtime < host_client->nametime) + { + SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n"); + return; + } + + host_client->nametime = realtime + 5; + */ + + // point the string back at updateclient->name to keep it safe + strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin)); + PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin); + if (strcmp(host_client->old_skin, host_client->playerskin)) + { + //if (host_client->begun) + // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin); + strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin)); + /*// send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/ + } +} + +static void Host_Version_f (void) +{ + Con_Printf("Version: %s build %s\n", gamename, buildstring); +} + +static void Host_Say(qboolean teamonly) +{ + prvm_prog_t *prog = SVVM_prog; + client_t *save; + int j, quoted; + const char *p1; + char *p2; + // LordHavoc: long say messages + char text[1024]; + qboolean fromServer = false; + + if (cmd_source == src_command) + { + if (cls.state == ca_dedicated) + { + fromServer = true; + teamonly = false; + } + else + { + Cmd_ForwardToServer (); + return; + } + } + + if (Cmd_Argc () < 2) + return; + + if (!teamplay.integer) + teamonly = false; + + p1 = Cmd_Args(); + quoted = false; + if (*p1 == '\"') + { + quoted = true; + p1++; + } + // note this uses the chat prefix \001 + if (!fromServer && !teamonly) + dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1); + else if (!fromServer && teamonly) + dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1); + else if(*(sv_adminnick.string)) + dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1); + else + dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1); + p2 = text + strlen(text); + while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted))) + { + if (p2[-1] == '\"' && quoted) + quoted = false; + p2[-1] = 0; + p2--; + } + strlcat(text, "\n", sizeof(text)); + + // note: save is not a valid edict if fromServer is true + save = host_client; + for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++) + if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team))) + SV_ClientPrint(text); + host_client = save; + + if (cls.state == ca_dedicated) + Con_Print(&text[1]); +} + + +static void Host_Say_f(void) +{ + Host_Say(false); +} + + +static void Host_Say_Team_f(void) +{ + Host_Say(true); +} + + +static void Host_Tell_f(void) +{ + const char *playername_start = NULL; + size_t playername_length = 0; + int playernumber = 0; + client_t *save; + int j; + const char *p1, *p2; + char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64) + qboolean fromServer = false; + + if (cmd_source == src_command) + { + if (cls.state == ca_dedicated) + fromServer = true; + else + { + Cmd_ForwardToServer (); + return; + } + } + + if (Cmd_Argc () < 2) + return; + + // note this uses the chat prefix \001 + if (!fromServer) + dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name); + else if(*(sv_adminnick.string)) + dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string); + else + dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string); + + p1 = Cmd_Args(); + p2 = p1 + strlen(p1); + // remove the target name + while (p1 < p2 && *p1 == ' ') + p1++; + if(*p1 == '#') + { + ++p1; + while (p1 < p2 && *p1 == ' ') + p1++; + while (p1 < p2 && isdigit(*p1)) + { + playernumber = playernumber * 10 + (*p1 - '0'); + p1++; + } + --playernumber; + } + else if(*p1 == '"') + { + ++p1; + playername_start = p1; + while (p1 < p2 && *p1 != '"') + p1++; + playername_length = p1 - playername_start; + if(p1 < p2) + p1++; + } + else + { + playername_start = p1; + while (p1 < p2 && *p1 != ' ') + p1++; + playername_length = p1 - playername_start; + } + while (p1 < p2 && *p1 == ' ') + p1++; + if(playername_start) + { + // set playernumber to the right client + char namebuf[128]; + if(playername_length >= sizeof(namebuf)) + { + if (fromServer) + Con_Print("Host_Tell: too long player name/ID\n"); + else + SV_ClientPrint("Host_Tell: too long player name/ID\n"); + return; + } + memcpy(namebuf, playername_start, playername_length); + namebuf[playername_length] = 0; + for (playernumber = 0; playernumber < svs.maxclients; playernumber++) + { + if (!svs.clients[playernumber].active) + continue; + if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0) + break; + } + } + if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active)) + { + if (fromServer) + Con_Print("Host_Tell: invalid player name/ID\n"); + else + SV_ClientPrint("Host_Tell: invalid player name/ID\n"); + return; + } + // remove trailing newlines + while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r')) + p2--; + // remove quotes if present + if (*p1 == '"') + { + p1++; + if (p2[-1] == '"') + p2--; + else if (fromServer) + Con_Print("Host_Tell: missing end quote\n"); + else + SV_ClientPrint("Host_Tell: missing end quote\n"); + } + while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r')) + p2--; + if(p1 == p2) + return; // empty say + for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;) + text[j++] = *p1++; + text[j++] = '\n'; + text[j++] = 0; + + save = host_client; + host_client = svs.clients + playernumber; + SV_ClientPrint(text); + host_client = save; +} + + +/* +================== +Host_Color_f +================== +*/ +cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"}; +static void Host_Color(int changetop, int changebottom) +{ + prvm_prog_t *prog = SVVM_prog; + int top, bottom, playercolor; + + // get top and bottom either from the provided values or the current values + // (allows changing only top or bottom, or both at once) + top = changetop >= 0 ? changetop : (cl_color.integer >> 4); + bottom = changebottom >= 0 ? changebottom : cl_color.integer; + + top &= 15; + bottom &= 15; + // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out + //if (top > 13) + // top = 13; + //if (bottom > 13) + // bottom = 13; + + playercolor = top*16 + bottom; + + if (cmd_source == src_command) + { + Cvar_SetValueQuick(&cl_color, playercolor); + return; + } + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + return; + + if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam)) + { + Con_DPrint("Calling SV_ChangeTeam\n"); + prog->globals.fp[OFS_PARM0] = playercolor; + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing"); + } + else + { + if (host_client->edict) + { + PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor; + PRVM_serveredictfloat(host_client->edict, team) = bottom + 1; + } + host_client->colors = playercolor; + if (host_client->old_colors != host_client->colors) + { + host_client->old_colors = host_client->colors; + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteByte (&sv.reliable_datagram, host_client->colors); + } + } +} + +static void Host_Color_f(void) +{ + int top, bottom; + + if (Cmd_Argc() == 1) + { + Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15); + Con_Print("color <0-15> [0-15]\n"); + return; + } + + if (Cmd_Argc() == 2) + top = bottom = atoi(Cmd_Argv(1)); + else + { + top = atoi(Cmd_Argv(1)); + bottom = atoi(Cmd_Argv(2)); + } + Host_Color(top, bottom); +} + +static void Host_TopColor_f(void) +{ + if (Cmd_Argc() == 1) + { + Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15); + Con_Print("topcolor <0-15>\n"); + return; + } + + Host_Color(atoi(Cmd_Argv(1)), -1); +} + +static void Host_BottomColor_f(void) +{ + if (Cmd_Argc() == 1) + { + Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15); + Con_Print("bottomcolor <0-15>\n"); + return; + } + + Host_Color(-1, atoi(Cmd_Argv(1))); +} + +cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"}; +static void Host_Rate_f(void) +{ + int rate; + + if (Cmd_Argc() != 2) + { + Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer); + Con_Print("rate \n"); + return; + } + + rate = atoi(Cmd_Argv(1)); + + if (cmd_source == src_command) + { + Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate)); + return; + } + + host_client->rate = rate; +} + +/* +================== +Host_Kill_f +================== +*/ +static void Host_Kill_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + if (PRVM_serveredictfloat(host_client->edict, health) <= 0) + { + SV_ClientPrint("Can't suicide -- already dead!\n"); + return; + } + + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing"); +} + + +/* +================== +Host_Pause_f +================== +*/ +static void Host_Pause_f (void) +{ + void (*print) (const char *fmt, ...); + if (cmd_source == src_command) + { + // if running a client, try to send over network so the pause is handled by the server + if (cls.state == ca_connected) + { + Cmd_ForwardToServer (); + return; + } + print = Con_Printf; + } + else + print = SV_ClientPrintf; + + if (!pausable.integer) + { + if (cmd_source == src_client) + { + if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin + { + print("Pause not allowed.\n"); + return; + } + } + } + + sv.paused ^= 1; + SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un"); + // send notification to all clients + MSG_WriteByte(&sv.reliable_datagram, svc_setpause); + MSG_WriteByte(&sv.reliable_datagram, sv.paused); +} + +/* +====================== +Host_PModel_f +LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen. +LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility. +====================== +*/ +cvar_t cl_pmodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"}; +static void Host_PModel_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + + if (Cmd_Argc () == 1) + { + Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string); + return; + } + i = atoi(Cmd_Argv(1)); + + if (cmd_source == src_command) + { + if (cl_pmodel.integer == i) + return; + Cvar_SetValue ("_cl_pmodel", i); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + return; + } + + PRVM_serveredictfloat(host_client->edict, pmodel) = i; +} + +//=========================================================================== + + +/* +================== +Host_PreSpawn_f +================== +*/ +static void Host_PreSpawn_f (void) +{ + if (host_client->prespawned) + { + Con_Print("prespawn not valid -- already prespawned\n"); + return; + } + host_client->prespawned = true; + + if (host_client->netconnection) + { + SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize); + MSG_WriteByte (&host_client->netconnection->message, svc_signonnum); + MSG_WriteByte (&host_client->netconnection->message, 2); + host_client->sendsignon = 0; // enable unlimited sends again + } + + // reset the name change timer because the client will send name soon + host_client->nametime = 0; +} + +/* +================== +Host_Spawn_f +================== +*/ +static void Host_Spawn_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + client_t *client; + int stats[MAX_CL_STATS]; + + if (!host_client->prespawned) + { + Con_Print("Spawn not valid -- not yet prespawned\n"); + return; + } + if (host_client->spawned) + { + Con_Print("Spawn not valid -- already spawned\n"); + return; + } + host_client->spawned = true; + + // reset name change timer again because they might want to change name + // again in the first 5 seconds after connecting + host_client->nametime = 0; + + // LordHavoc: moved this above the QC calls at FrikaC's request + // LordHavoc: commented this out + //if (host_client->netconnection) + // SZ_Clear (&host_client->netconnection->message); + + // run the entrance script + if (sv.loadgame) + { + // loaded games are fully initialized already + if (PRVM_serverfunction(RestoreGame)) + { + Con_DPrint("Calling RestoreGame\n"); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing"); + } + } + else + { + //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), host_client->name); + + // copy spawn parms out of the client_t + for (i=0 ; i< NUM_SPAWN_PARMS ; i++) + (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i]; + + // call the spawn function + host_client->clientconnectcalled = true; + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing"); + + if (cls.state == ca_dedicated) + Con_Printf("%s connected\n", host_client->name); + + PRVM_serverglobalfloat(time) = sv.time; + prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing"); + } + + if (!host_client->netconnection) + return; + + // send time of update + MSG_WriteByte (&host_client->netconnection->message, svc_time); + MSG_WriteFloat (&host_client->netconnection->message, sv.time); + + // send all current names, colors, and frag counts + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (!client->active) + continue; + MSG_WriteByte (&host_client->netconnection->message, svc_updatename); + MSG_WriteByte (&host_client->netconnection->message, i); + MSG_WriteString (&host_client->netconnection->message, client->name); + MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags); + MSG_WriteByte (&host_client->netconnection->message, i); + MSG_WriteShort (&host_client->netconnection->message, client->frags); + MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors); + MSG_WriteByte (&host_client->netconnection->message, i); + MSG_WriteByte (&host_client->netconnection->message, client->colors); + } + + // send all current light styles + for (i=0 ; inetconnection->message, svc_lightstyle); + MSG_WriteByte (&host_client->netconnection->message, (char)i); + MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]); + } + } + + // send some stats + MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS); + MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets)); + + MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS); + MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters)); + + MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS); + MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets)); + + MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS); + MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters)); + + // send a fixangle + // Never send a roll angle, because savegames can catch the server + // in a state where it is expecting the client to correct the angle + // and it won't happen if the game was just loaded, so you wind up + // with a permanent head tilt + if (sv.loadgame) + { + MSG_WriteByte (&host_client->netconnection->message, svc_setangle); + MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol); + MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol); + MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol); + } + else + { + MSG_WriteByte (&host_client->netconnection->message, svc_setangle); + MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol); + MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol); + MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol); + } + + SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats); + + MSG_WriteByte (&host_client->netconnection->message, svc_signonnum); + MSG_WriteByte (&host_client->netconnection->message, 3); +} + +/* +================== +Host_Begin_f +================== +*/ +static void Host_Begin_f (void) +{ + if (!host_client->spawned) + { + Con_Print("Begin not valid -- not yet spawned\n"); + return; + } + if (host_client->begun) + { + Con_Print("Begin not valid -- already begun\n"); + return; + } + host_client->begun = true; + + // LordHavoc: note: this code also exists in SV_DropClient + if (sv.loadgame) + { + int i; + for (i = 0;i < svs.maxclients;i++) + if (svs.clients[i].active && !svs.clients[i].spawned) + break; + if (i == svs.maxclients) + { + Con_Printf("Loaded game, everyone rejoined - unpausing\n"); + sv.paused = sv.loadgame = false; // we're basically done with loading now + } + } +} + +//=========================================================================== + + +/* +================== +Host_Kick_f + +Kicks a user off of the server +================== +*/ +static void Host_Kick_f (void) +{ + const char *who; + const char *message = NULL; + client_t *save; + int i; + qboolean byNumber = false; + + if (!sv.active) + return; + + save = host_client; + + if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0) + { + i = (int)(atof(Cmd_Argv(2)) - 1); + if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active) + return; + byNumber = true; + } + else + { + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + if (!host_client->active) + continue; + if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0) + break; + } + } + + if (i < svs.maxclients) + { + if (cmd_source == src_command) + { + if (cls.state == ca_dedicated) + who = "Console"; + else + who = cl_name.string; + } + else + who = save->name; + + // can't kick yourself! + if (host_client == save) + return; + + if (Cmd_Argc() > 2) + { + message = Cmd_Args(); + COM_ParseToken_Simple(&message, false, false, true); + if (byNumber) + { + message++; // skip the # + while (*message == ' ') // skip white space + message++; + message += strlen(Cmd_Argv(2)); // skip the number + } + while (*message && *message == ' ') + message++; + } + if (message) + SV_ClientPrintf("Kicked by %s: %s\n", who, message); + else + SV_ClientPrintf("Kicked by %s\n", who); + SV_DropClient (false); // kicked + } + + host_client = save; +} + +/* +=============================================================================== + +DEBUGGING TOOLS + +=============================================================================== +*/ + +/* +================== +Host_Give_f +================== +*/ +static void Host_Give_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + const char *t; + int v; + + if (!allowcheats) + { + SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); + return; + } + + t = Cmd_Argv(1); + v = atoi (Cmd_Argv(2)); + + switch (t[0]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // MED 01/04/97 added hipnotic give stuff + if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) + { + if (t[0] == '6') + { + if (t[1] == 'a') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN; + else + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER; + } + else if (t[0] == '9') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON; + else if (t[0] == '0') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR; + else if (t[0] >= '2') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2')); + } + else + { + if (t[0] >= '2') + PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2')); + } + break; + + case 's': + if (gamemode == GAME_ROGUE) + PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v; + + PRVM_serveredictfloat(host_client->edict, ammo_shells) = v; + break; + case 'n': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; + } + else + { + PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; + } + break; + case 'l': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; + } + break; + case 'r': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; + } + else + { + PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; + } + break; + case 'm': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; + } + break; + case 'h': + PRVM_serveredictfloat(host_client->edict, health) = v; + break; + case 'c': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; + } + else + { + PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; + } + break; + case 'p': + if (gamemode == GAME_ROGUE) + { + PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v; + if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) + PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; + } + break; + } +} + +static prvm_edict_t *FindViewthing(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *e; + + for (i=0 ; inum_edicts ; i++) + { + e = PRVM_EDICT_NUM(i); + if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing")) + return e; + } + Con_Print("No viewthing on map\n"); + return NULL; +} + +/* +================== +Host_Viewmodel_f +================== +*/ +static void Host_Viewmodel_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + prvm_edict_t *e; + dp_model_t *m; + + if (!sv.active) + return; + + e = FindViewthing(prog); + if (e) + { + m = Mod_ForName (Cmd_Argv(1), false, true, NULL); + if (m && m->loaded && m->Draw) + { + PRVM_serveredictfloat(e, frame) = 0; + cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m; + } + else + Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1)); + } +} + +/* +================== +Host_Viewframe_f +================== +*/ +static void Host_Viewframe_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + prvm_edict_t *e; + int f; + dp_model_t *m; + + if (!sv.active) + return; + + e = FindViewthing(prog); + if (e) + { + m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; + + f = atoi(Cmd_Argv(1)); + if (f >= m->numframes) + f = m->numframes-1; + + PRVM_serveredictfloat(e, frame) = f; + } +} + + +static void PrintFrameName (dp_model_t *m, int frame) +{ + if (m->animscenes) + Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name); + else + Con_Printf("frame %i\n", frame); +} + +/* +================== +Host_Viewnext_f +================== +*/ +static void Host_Viewnext_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + prvm_edict_t *e; + dp_model_t *m; + + if (!sv.active) + return; + + e = FindViewthing(prog); + if (e) + { + m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; + + PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1; + if (PRVM_serveredictfloat(e, frame) >= m->numframes) + PRVM_serveredictfloat(e, frame) = m->numframes - 1; + + PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame)); + } +} + +/* +================== +Host_Viewprev_f +================== +*/ +static void Host_Viewprev_f (void) +{ + prvm_prog_t *prog = SVVM_prog; + prvm_edict_t *e; + dp_model_t *m; + + if (!sv.active) + return; + + e = FindViewthing(prog); + if (e) + { + m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; + + PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1; + if (PRVM_serveredictfloat(e, frame) < 0) + PRVM_serveredictfloat(e, frame) = 0; + + PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame)); + } +} + +/* +=============================================================================== + +DEMO LOOP CONTROL + +=============================================================================== +*/ + + +/* +================== +Host_Startdemos_f +================== +*/ +static void Host_Startdemos_f (void) +{ + int i, c; + + if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo")) + return; + + c = Cmd_Argc() - 1; + if (c > MAX_DEMOS) + { + Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS); + c = MAX_DEMOS; + } + Con_DPrintf("%i demo(s) in loop\n", c); + + for (i=1 ; iflags & CVAR_PRIVATE)) + Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname)); + else + Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string)); + return; + } + if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand)) + return; + + old = host_client; + if (cls.state != ca_dedicated) + i = 1; + else + i = 0; + for(;i 0) + { + Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n"); + return; + } + + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); + + if (cls.netcon) + { + InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address)); + } + else + { + if (!rcon_address.string[0]) + { + Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); + return; + } + strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1); + } + LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer); + mysocket = NetConn_ChooseClientSocketForAddress(&to); + if (mysocket) + { + sizebuf_t buf; + unsigned char bufdata[64]; + buf.data = bufdata; + SZ_Clear(&buf); + MSG_WriteLong(&buf, 0); + MSG_WriteByte(&buf, CCREQ_RCON); + SZ_Write(&buf, (const unsigned char*)rcon_password.string, n); + MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string + MSG_WriteString(&buf, Cmd_Args()); + StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, buf.data, buf.cursize, &to); + SZ_Clear(&buf); + } +} + +//============================================================================= + +// QuakeWorld commands + +/* +===================== +Host_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +static void Host_Rcon_f (void) // credit: taken from QuakeWorld +{ + int i, n; + const char *e; + lhnetaddress_t to; + lhnetsocket_t *mysocket; + char vabuf[1024]; + + if (Cmd_Argc() == 1) + { + Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0)); + return; + } + + if (!rcon_password.string || !rcon_password.string[0]) + { + Con_Printf ("You must set rcon_password before issuing an rcon command.\n"); + return; + } + + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); + + if (cls.netcon) + to = cls.netcon->peeraddress; + else + { + if (!rcon_address.string[0]) + { + Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); + return; + } + LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer); + } + mysocket = NetConn_ChooseClientSocketForAddress(&to); + if (mysocket && Cmd_Args()[0]) + { + // simply put together the rcon packet and send it + if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1) + { + if(cls.rcon_commands[cls.rcon_ringpos][0]) + { + char s[128]; + LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true); + Con_Printf("rcon to %s (for command %s) failed: too many buffered commands (possibly increase MAX_RCONS)\n", s, cls.rcon_commands[cls.rcon_ringpos]); + cls.rcon_commands[cls.rcon_ringpos][0] = 0; + --cls.rcon_trying; + } + for (i = 0;i < MAX_RCONS;i++) + if(cls.rcon_commands[i][0]) + if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i])) + break; + ++cls.rcon_trying; + if(i >= MAX_RCONS) + NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later + strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos])); + cls.rcon_addresses[cls.rcon_ringpos] = to; + cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value; + cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS; + } + else if(rcon_secure.integer > 0) + { + char buf[1500]; + char argbuf[1500]; + dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args()); + memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24); + if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n)) + { + buf[40] = ' '; + strlcpy(buf + 41, argbuf, sizeof(buf) - 41); + NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to); + } + } + else + { + NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to); + } + } +} + +/* +==================== +Host_User_f + +user + +Dump userdata / masterdata for a user +==================== +*/ +static void Host_User_f (void) // credit: taken from QuakeWorld +{ + int uid; + int i; + + if (Cmd_Argc() != 2) + { + Con_Printf ("Usage: user \n"); + return; + } + + uid = atoi(Cmd_Argv(1)); + + for (i = 0;i < cl.maxclients;i++) + { + if (!cl.scores[i].name[0]) + continue; + if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1))) + { + InfoString_Print(cl.scores[i].qw_userinfo); + return; + } + } + Con_Printf ("User not in server.\n"); +} + +/* +==================== +Host_Users_f + +Dump userids for all current players +==================== +*/ +static void Host_Users_f (void) // credit: taken from QuakeWorld +{ + int i; + int c; + + c = 0; + Con_Printf ("userid frags name\n"); + Con_Printf ("------ ----- ----\n"); + for (i = 0;i < cl.maxclients;i++) + { + if (cl.scores[i].name[0]) + { + Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name); + c++; + } + } + + Con_Printf ("%i total users\n", c); +} + +/* +================== +Host_FullServerinfo_f + +Sent by server when serverinfo changes +================== +*/ +// TODO: shouldn't this be a cvar instead? +static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld +{ + char temp[512]; + if (Cmd_Argc() != 2) + { + Con_Printf ("usage: fullserverinfo \n"); + return; + } + + strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo)); + InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp)); + cl.qw_teamplay = atoi(temp); +} + +/* +================== +Host_FullInfo_f + +Allow clients to change userinfo +================== +Casey was here :) +*/ +static void Host_FullInfo_f (void) // credit: taken from QuakeWorld +{ + char key[512]; + char value[512]; + char *o; + const char *s; + + if (Cmd_Argc() != 2) + { + Con_Printf ("fullinfo \n"); + return; + } + + s = Cmd_Argv(1); + if (*s == '\\') + s++; + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (!*s) + { + Con_Printf ("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + + CL_SetInfo(key, value, false, false, false, false); + } +} + +/* +================== +CL_SetInfo_f + +Allow clients to change userinfo +================== +*/ +static void Host_SetInfo_f (void) // credit: taken from QuakeWorld +{ + if (Cmd_Argc() == 1) + { + InfoString_Print(cls.userinfo); + return; + } + if (Cmd_Argc() != 3) + { + Con_Printf ("usage: setinfo [ ]\n"); + return; + } + CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false); +} + +/* +==================== +Host_Packet_f + +packet + +Contents allows \n escape character +==================== +*/ +static void Host_Packet_f (void) // credit: taken from QuakeWorld +{ + char send[2048]; + int i, l; + const char *in; + char *out; + lhnetaddress_t address; + lhnetsocket_t *mysocket; + + if (Cmd_Argc() != 3) + { + Con_Printf ("packet \n"); + return; + } + + if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer)) + { + Con_Printf ("Bad address\n"); + return; + } + + in = Cmd_Argv(2); + out = send+4; + send[0] = send[1] = send[2] = send[3] = -1; + + l = (int)strlen (in); + for (i=0 ; i= send + sizeof(send) - 1) + break; + if (in[i] == '\\' && in[i+1] == 'n') + { + *out++ = '\n'; + i++; + } + else if (in[i] == '\\' && in[i+1] == '0') + { + *out++ = '\0'; + i++; + } + else if (in[i] == '\\' && in[i+1] == 't') + { + *out++ = '\t'; + i++; + } + else if (in[i] == '\\' && in[i+1] == 'r') + { + *out++ = '\r'; + i++; + } + else if (in[i] == '\\' && in[i+1] == '"') + { + *out++ = '\"'; + i++; + } + else + *out++ = in[i]; + } + + mysocket = NetConn_ChooseClientSocketForAddress(&address); + if (!mysocket) + mysocket = NetConn_ChooseServerSocketForAddress(&address); + if (mysocket) + NetConn_Write(mysocket, send, out - send, &address); +} + +/* +==================== +Host_Pings_f + +Send back ping and packet loss update for all current players to this player +==================== +*/ +void Host_Pings_f (void) +{ + int i, j, ping, packetloss, movementloss; + char temp[128]; + + if (!host_client->netconnection) + return; + + if (sv.protocol != PROTOCOL_QUAKEWORLD) + { + MSG_WriteByte(&host_client->netconnection->message, svc_stufftext); + MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport"); + } + for (i = 0;i < svs.maxclients;i++) + { + packetloss = 0; + movementloss = 0; + if (svs.clients[i].netconnection) + { + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) + packetloss++; + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (svs.clients[i].movement_count[j] < 0) + movementloss++; + } + packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; + movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; + ping = (int)floor(svs.clients[i].ping*1000+0.5); + ping = bound(0, ping, 9999); + if (sv.protocol == PROTOCOL_QUAKEWORLD) + { + // send qw_svc_updateping and qw_svc_updatepl messages + MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping); + MSG_WriteShort(&host_client->netconnection->message, ping); + MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl); + MSG_WriteByte(&host_client->netconnection->message, packetloss); + } + else + { + // write the string into the packet as multiple unterminated strings to avoid needing a local buffer + if(movementloss) + dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss); + else + dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss); + MSG_WriteUnterminatedString(&host_client->netconnection->message, temp); + } + } + if (sv.protocol != PROTOCOL_QUAKEWORLD) + MSG_WriteString(&host_client->netconnection->message, "\n"); +} + +static void Host_PingPLReport_f(void) +{ + char *errbyte; + int i; + int l = Cmd_Argc(); + if (l > cl.maxclients) + l = cl.maxclients; + for (i = 0;i < l;i++) + { + cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2)); + cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0); + if(errbyte && *errbyte == ',') + cl.scores[i].qw_movementloss = atoi(errbyte + 1); + else + cl.scores[i].qw_movementloss = 0; + } +} + +//============================================================================= + +/* +================== +Host_InitCommands +================== +*/ +void Host_InitCommands (void) +{ + dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp"); + + Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information"); + Cmd_AddCommand ("quit", Host_Quit_f, "quit the game"); + Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)"); + Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)"); + Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)"); + Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)"); + Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory"); + Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level"); + Cmd_AddCommand ("restart", Host_Restart_f, "restart current level"); + Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients"); + Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname"); + Cmd_AddCommand ("reconnect", Host_Reconnect_f, "reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server)"); + Cmd_AddCommand ("version", Host_Version_f, "print engine version"); + Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server"); + Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server"); + Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server"); + Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly"); + Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)"); + Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name"); + Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server"); + Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file"); + Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file"); + + Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)"); + Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command"); + Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos"); + + Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level"); + Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level"); + Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level"); + Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level"); + + Cvar_RegisterVariable (&cl_name); + Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name"); + Cvar_RegisterVariable (&cl_color); + Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors"); + Cvar_RegisterVariable (&cl_rate); + Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed"); + Cvar_RegisterVariable (&cl_pmodel); + Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice"); + + // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first) + Cvar_RegisterVariable (&cl_playermodel); + Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model"); + Cvar_RegisterVariable (&cl_playerskin); + Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number"); + + Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)"); + Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)"); + Cmd_AddCommand_WithClientCommand ("begin", NULL, Host_Begin_f, "signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)"); + Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once"); + + Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC"); + + Cvar_RegisterVariable (&rcon_password); + Cvar_RegisterVariable (&rcon_address); + Cvar_RegisterVariable (&rcon_secure); + Cvar_RegisterVariable (&rcon_secure_challengetimeout); + Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP"); + Cmd_AddCommand ("srcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP"); + Cmd_AddCommand ("pqrcon", Host_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)"); + Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard"); + Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard"); + Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string"); + Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo"); + Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo"); + Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string"); + Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color"); + Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color"); + + Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)"); + Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)"); + + Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)"); + Cvar_RegisterVariable (&r_fixtrans_auto); + + Cvar_RegisterVariable (&team); + Cvar_RegisterVariable (&skin); + Cvar_RegisterVariable (&noaim); + + Cvar_RegisterVariable(&sv_cheats); + Cvar_RegisterVariable(&sv_adminnick); + Cvar_RegisterVariable(&sv_status_privacy); + Cvar_RegisterVariable(&sv_status_show_qcstatus); + Cvar_RegisterVariable(&sv_namechangetimer); +} + +void Host_NoOperation_f(void) +{ +} diff --git a/app/jni/image.c b/app/jni/image.c new file mode 100644 index 0000000..366e80d --- /dev/null +++ b/app/jni/image.c @@ -0,0 +1,1588 @@ + +#include "quakedef.h" +#include "image.h" +#include "jpeg.h" +#include "image_png.h" +#include "r_shadow.h" + +int image_width; +int image_height; + +static void Image_CopyAlphaFromBlueBGRA(unsigned char *outpixels, const unsigned char *inpixels, int w, int h) +{ + int i, n; + n = w * h; + for(i = 0; i < n; ++i) + outpixels[4*i+3] = inpixels[4*i]; // blue channel +} + +#if 1 +// written by LordHavoc in a readable way, optimized by Vic, further optimized by LordHavoc (the non-special index case), readable version preserved below this +void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices) +{ + int index, c, x, y; + const unsigned char *in, *line; + int row_inc = (inputflipy ? -inputwidth : inputwidth) * numinputcomponents, col_inc = (inputflipx ? -1 : 1) * numinputcomponents; + int row_ofs = (inputflipy ? (inputheight - 1) * inputwidth * numinputcomponents : 0), col_ofs = (inputflipx ? (inputwidth - 1) * numinputcomponents : 0); + + for (c = 0; c < numoutputcomponents; c++) + if (outputinputcomponentindices[c] & 0x80000000) + break; + if (c < numoutputcomponents) + { + // special indices used + if (inputflipdiagonal) + { + for (x = 0, line = inpixels + col_ofs; x < inputwidth; x++, line += col_inc) + for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numoutputcomponents) + for (c = 0; c < numoutputcomponents; c++) + outpixels[c] = ((index = outputinputcomponentindices[c]) & 0x80000000) ? index : in[index]; + } + else + { + for (y = 0, line = inpixels + row_ofs; y < inputheight; y++, line += row_inc) + for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numoutputcomponents) + for (c = 0; c < numoutputcomponents; c++) + outpixels[c] = ((index = outputinputcomponentindices[c]) & 0x80000000) ? index : in[index]; + } + } + else + { + // special indices not used + if (inputflipdiagonal) + { + for (x = 0, line = inpixels + col_ofs; x < inputwidth; x++, line += col_inc) + for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numoutputcomponents) + for (c = 0; c < numoutputcomponents; c++) + outpixels[c] = in[outputinputcomponentindices[c]]; + } + else + { + for (y = 0, line = inpixels + row_ofs; y < inputheight; y++, line += row_inc) + for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numoutputcomponents) + for (c = 0; c < numoutputcomponents; c++) + outpixels[c] = in[outputinputcomponentindices[c]]; + } + } +} +#else +// intentionally readable version +void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices) +{ + int index, c, x, y; + const unsigned char *in, *inrow, *incolumn; + if (inputflipdiagonal) + { + for (x = 0;x < inputwidth;x++) + { + for (y = 0;y < inputheight;y++) + { + in = inpixels + ((inputflipy ? inputheight - 1 - y : y) * inputwidth + (inputflipx ? inputwidth - 1 - x : x)) * numinputcomponents; + for (c = 0;c < numoutputcomponents;c++) + { + index = outputinputcomponentindices[c]; + if (index & 0x80000000) + *outpixels++ = index; + else + *outpixels++ = in[index]; + } + } + } + } + else + { + for (y = 0;y < inputheight;y++) + { + for (x = 0;x < inputwidth;x++) + { + in = inpixels + ((inputflipy ? inputheight - 1 - y : y) * inputwidth + (inputflipx ? inputwidth - 1 - x : x)) * numinputcomponents; + for (c = 0;c < numoutputcomponents;c++) + { + index = outputinputcomponentindices[c]; + if (index & 0x80000000) + *outpixels++ = index; + else + *outpixels++ = in[index]; + } + } + } + } +} +#endif + +void Image_GammaRemapRGB(const unsigned char *in, unsigned char *out, int pixels, const unsigned char *gammar, const unsigned char *gammag, const unsigned char *gammab) +{ + while (pixels--) + { + out[0] = gammar[in[0]]; + out[1] = gammag[in[1]]; + out[2] = gammab[in[2]]; + in += 3; + out += 3; + } +} + +// note: pal must be 32bit color +void Image_Copy8bitBGRA(const unsigned char *in, unsigned char *out, int pixels, const unsigned int *pal) +{ + int *iout = (int *)out; + while (pixels >= 8) + { + iout[0] = pal[in[0]]; + iout[1] = pal[in[1]]; + iout[2] = pal[in[2]]; + iout[3] = pal[in[3]]; + iout[4] = pal[in[4]]; + iout[5] = pal[in[5]]; + iout[6] = pal[in[6]]; + iout[7] = pal[in[7]]; + in += 8; + iout += 8; + pixels -= 8; + } + if (pixels & 4) + { + iout[0] = pal[in[0]]; + iout[1] = pal[in[1]]; + iout[2] = pal[in[2]]; + iout[3] = pal[in[3]]; + in += 4; + iout += 4; + } + if (pixels & 2) + { + iout[0] = pal[in[0]]; + iout[1] = pal[in[1]]; + in += 2; + iout += 2; + } + if (pixels & 1) + iout[0] = pal[in[0]]; +} + +/* +================================================================= + + PCX Loading + +================================================================= +*/ + +typedef struct pcx_s +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; +} pcx_t; + +/* +============ +LoadPCX +============ +*/ +static unsigned char* LoadPCX_BGRA (const unsigned char *f, int filesize, int *miplevel) +{ + pcx_t pcx; + unsigned char *a, *b, *image_buffer, *pbuf; + const unsigned char *palette, *fin, *enddata; + int x, y, x2, dataByte; + + if (filesize < (int)sizeof(pcx) + 768) + { + Con_Print("Bad pcx file\n"); + return NULL; + } + + fin = f; + + memcpy(&pcx, fin, sizeof(pcx)); + fin += sizeof(pcx); + + // LordHavoc: big-endian support ported from QF newtree + pcx.xmax = LittleShort (pcx.xmax); + pcx.xmin = LittleShort (pcx.xmin); + pcx.ymax = LittleShort (pcx.ymax); + pcx.ymin = LittleShort (pcx.ymin); + pcx.hres = LittleShort (pcx.hres); + pcx.vres = LittleShort (pcx.vres); + pcx.bytes_per_line = LittleShort (pcx.bytes_per_line); + pcx.palette_type = LittleShort (pcx.palette_type); + + image_width = pcx.xmax + 1 - pcx.xmin; + image_height = pcx.ymax + 1 - pcx.ymin; + if (pcx.manufacturer != 0x0a || pcx.version != 5 || pcx.encoding != 1 || pcx.bits_per_pixel != 8 || image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) + { + Con_Print("Bad pcx file\n"); + return NULL; + } + + palette = f + filesize - 768; + + image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width*image_height*4); + if (!image_buffer) + { + Con_Printf("LoadPCX: not enough memory for %i by %i image\n", image_width, image_height); + return NULL; + } + pbuf = image_buffer + image_width*image_height*3; + enddata = palette; + + for (y = 0;y < image_height && fin < enddata;y++) + { + a = pbuf + y * image_width; + for (x = 0;x < image_width && fin < enddata;) + { + dataByte = *fin++; + if(dataByte >= 0xC0) + { + if (fin >= enddata) + break; + x2 = x + (dataByte & 0x3F); + dataByte = *fin++; + if (x2 > image_width) + x2 = image_width; // technically an error + while(x < x2) + a[x++] = dataByte; + } + else + a[x++] = dataByte; + } + while(x < image_width) + a[x++] = 0; + } + + a = image_buffer; + b = pbuf; + + for(x = 0;x < image_width*image_height;x++) + { + y = *b++ * 3; + *a++ = palette[y+2]; + *a++ = palette[y+1]; + *a++ = palette[y]; + *a++ = 255; + } + + return image_buffer; +} + +/* +============ +LoadPCX +============ +*/ +qboolean LoadPCX_QWSkin(const unsigned char *f, int filesize, unsigned char *pixels, int outwidth, int outheight) +{ + pcx_t pcx; + unsigned char *a; + const unsigned char *fin, *enddata; + int x, y, x2, dataByte, pcxwidth, pcxheight; + + if (filesize < (int)sizeof(pcx) + 768) + return false; + + image_width = outwidth; + image_height = outheight; + fin = f; + + memcpy(&pcx, fin, sizeof(pcx)); + fin += sizeof(pcx); + + // LordHavoc: big-endian support ported from QF newtree + pcx.xmax = LittleShort (pcx.xmax); + pcx.xmin = LittleShort (pcx.xmin); + pcx.ymax = LittleShort (pcx.ymax); + pcx.ymin = LittleShort (pcx.ymin); + pcx.hres = LittleShort (pcx.hres); + pcx.vres = LittleShort (pcx.vres); + pcx.bytes_per_line = LittleShort (pcx.bytes_per_line); + pcx.palette_type = LittleShort (pcx.palette_type); + + pcxwidth = pcx.xmax + 1 - pcx.xmin; + pcxheight = pcx.ymax + 1 - pcx.ymin; + if (pcx.manufacturer != 0x0a || pcx.version != 5 || pcx.encoding != 1 || pcx.bits_per_pixel != 8 || pcxwidth > 4096 || pcxheight > 4096 || pcxwidth <= 0 || pcxheight <= 0) + return false; + + enddata = f + filesize - 768; + + for (y = 0;y < outheight && fin < enddata;y++) + { + a = pixels + y * outwidth; + // pad the output with blank lines if needed + if (y >= pcxheight) + { + memset(a, 0, outwidth); + continue; + } + for (x = 0;x < pcxwidth;) + { + if (fin >= enddata) + return false; + dataByte = *fin++; + if(dataByte >= 0xC0) + { + x2 = x + (dataByte & 0x3F); + if (fin >= enddata) + return false; + if (x2 > pcxwidth) + return false; + dataByte = *fin++; + for (;x < x2;x++) + if (x < outwidth) + a[x] = dataByte; + } + else + { + if (x < outwidth) // truncate to destination width + a[x] = dataByte; + x++; + } + } + while(x < outwidth) + a[x++] = 0; + } + + return true; +} + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ + +typedef struct _TargaHeader +{ + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} +TargaHeader; + +static void PrintTargaHeader(TargaHeader *t) +{ + Con_Printf("TargaHeader:\nuint8 id_length = %i;\nuint8 colormap_type = %i;\nuint8 image_type = %i;\nuint16 colormap_index = %i;\nuint16 colormap_length = %i;\nuint8 colormap_size = %i;\nuint16 x_origin = %i;\nuint16 y_origin = %i;\nuint16 width = %i;\nuint16 height = %i;\nuint8 pixel_size = %i;\nuint8 attributes = %i;\n", t->id_length, t->colormap_type, t->image_type, t->colormap_index, t->colormap_length, t->colormap_size, t->x_origin, t->y_origin, t->width, t->height, t->pixel_size, t->attributes); +} + +/* +============= +LoadTGA +============= +*/ +unsigned char *LoadTGA_BGRA (const unsigned char *f, int filesize, int *miplevel) +{ + int x, y, pix_inc, row_inci, runlen, alphabits; + unsigned char *image_buffer; + unsigned int *pixbufi; + const unsigned char *fin, *enddata; + TargaHeader targa_header; + unsigned int palettei[256]; + union + { + unsigned int i; + unsigned char b[4]; + } + bgra; + + if (filesize < 19) + return NULL; + + enddata = f + filesize; + + targa_header.id_length = f[0]; + targa_header.colormap_type = f[1]; + targa_header.image_type = f[2]; + + targa_header.colormap_index = f[3] + f[4] * 256; + targa_header.colormap_length = f[5] + f[6] * 256; + targa_header.colormap_size = f[7]; + targa_header.x_origin = f[8] + f[9] * 256; + targa_header.y_origin = f[10] + f[11] * 256; + targa_header.width = image_width = f[12] + f[13] * 256; + targa_header.height = image_height = f[14] + f[15] * 256; + targa_header.pixel_size = f[16]; + targa_header.attributes = f[17]; + + if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) + { + Con_Print("LoadTGA: invalid size\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + + // advance to end of header + fin = f + 18; + + // skip TARGA image comment (usually 0 bytes) + fin += targa_header.id_length; + + // read/skip the colormap if present (note: according to the TARGA spec it + // can be present even on truecolor or greyscale images, just not used by + // the image data) + if (targa_header.colormap_type) + { + if (targa_header.colormap_length > 256) + { + Con_Print("LoadTGA: only up to 256 colormap_length supported\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + if (targa_header.colormap_index) + { + Con_Print("LoadTGA: colormap_index not supported\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + if (targa_header.colormap_size == 24) + { + for (x = 0;x < targa_header.colormap_length;x++) + { + bgra.b[0] = *fin++; + bgra.b[1] = *fin++; + bgra.b[2] = *fin++; + bgra.b[3] = 255; + palettei[x] = bgra.i; + } + } + else if (targa_header.colormap_size == 32) + { + memcpy(palettei, fin, targa_header.colormap_length*4); + fin += targa_header.colormap_length * 4; + } + else + { + Con_Print("LoadTGA: Only 32 and 24 bit colormap_size supported\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + } + + // check our pixel_size restrictions according to image_type + switch (targa_header.image_type & ~8) + { + case 2: + if (targa_header.pixel_size != 24 && targa_header.pixel_size != 32) + { + Con_Print("LoadTGA: only 24bit and 32bit pixel sizes supported for type 2 and type 10 images\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + break; + case 3: + // set up a palette to make the loader easier + for (x = 0;x < 256;x++) + { + bgra.b[0] = bgra.b[1] = bgra.b[2] = x; + bgra.b[3] = 255; + palettei[x] = bgra.i; + } + // fall through to colormap case + case 1: + if (targa_header.pixel_size != 8) + { + Con_Print("LoadTGA: only 8bit pixel size for type 1, 3, 9, and 11 images supported\n"); + PrintTargaHeader(&targa_header); + return NULL; + } + break; + default: + Con_Printf("LoadTGA: Only type 1, 2, 3, 9, 10, and 11 targa RGB images supported, image_type = %i\n", targa_header.image_type); + PrintTargaHeader(&targa_header); + return NULL; + } + + if (targa_header.attributes & 0x10) + { + Con_Print("LoadTGA: origin must be in top left or bottom left, top right and bottom right are not supported\n"); + return NULL; + } + + // number of attribute bits per pixel, we only support 0 or 8 + alphabits = targa_header.attributes & 0x0F; + if (alphabits != 8 && alphabits != 0) + { + Con_Print("LoadTGA: only 0 or 8 attribute (alpha) bits supported\n"); + return NULL; + } + + image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + if (!image_buffer) + { + Con_Printf("LoadTGA: not enough memory for %i by %i image\n", image_width, image_height); + return NULL; + } + + // If bit 5 of attributes isn't set, the image has been stored from bottom to top + if ((targa_header.attributes & 0x20) == 0) + { + pixbufi = (unsigned int*)image_buffer + (image_height - 1)*image_width; + row_inci = -image_width*2; + } + else + { + pixbufi = (unsigned int*)image_buffer; + row_inci = 0; + } + + pix_inc = 1; + if ((targa_header.image_type & ~8) == 2) + pix_inc = (targa_header.pixel_size + 7) / 8; + switch (targa_header.image_type) + { + case 1: // colormapped, uncompressed + case 3: // greyscale, uncompressed + if (fin + image_width * image_height * pix_inc > enddata) + break; + for (y = 0;y < image_height;y++, pixbufi += row_inci) + for (x = 0;x < image_width;x++) + *pixbufi++ = palettei[*fin++]; + break; + case 2: + // BGR or BGRA, uncompressed + if (fin + image_width * image_height * pix_inc > enddata) + break; + if (targa_header.pixel_size == 32 && alphabits) + { + for (y = 0;y < image_height;y++) + memcpy(pixbufi + y * (image_width + row_inci), fin + y * image_width * pix_inc, image_width*4); + } + else + { + for (y = 0;y < image_height;y++, pixbufi += row_inci) + { + for (x = 0;x < image_width;x++, fin += pix_inc) + { + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = 255; + *pixbufi++ = bgra.i; + } + } + } + break; + case 9: // colormapped, RLE + case 11: // greyscale, RLE + for (y = 0;y < image_height;y++, pixbufi += row_inci) + { + for (x = 0;x < image_width;) + { + if (fin >= enddata) + break; // error - truncated file + runlen = *fin++; + if (runlen & 0x80) + { + // RLE - all pixels the same color + runlen += 1 - 0x80; + if (fin + pix_inc > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + bgra.i = palettei[*fin++]; + for (;runlen--;x++) + *pixbufi++ = bgra.i; + } + else + { + // uncompressed - all pixels different color + runlen++; + if (fin + pix_inc * runlen > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + for (;runlen--;x++) + *pixbufi++ = palettei[*fin++]; + } + } + + if (x != image_width) + { + // pixbufi is useless now + Con_Printf("LoadTGA: corrupt file\n"); + break; + } + } + break; + case 10: + // BGR or BGRA, RLE + if (targa_header.pixel_size == 32 && alphabits) + { + for (y = 0;y < image_height;y++, pixbufi += row_inci) + { + for (x = 0;x < image_width;) + { + if (fin >= enddata) + break; // error - truncated file + runlen = *fin++; + if (runlen & 0x80) + { + // RLE - all pixels the same color + runlen += 1 - 0x80; + if (fin + pix_inc > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = fin[3]; + fin += pix_inc; + for (;runlen--;x++) + *pixbufi++ = bgra.i; + } + else + { + // uncompressed - all pixels different color + runlen++; + if (fin + pix_inc * runlen > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + for (;runlen--;x++) + { + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = fin[3]; + fin += pix_inc; + *pixbufi++ = bgra.i; + } + } + } + + if (x != image_width) + { + // pixbufi is useless now + Con_Printf("LoadTGA: corrupt file\n"); + break; + } + } + } + else + { + for (y = 0;y < image_height;y++, pixbufi += row_inci) + { + for (x = 0;x < image_width;) + { + if (fin >= enddata) + break; // error - truncated file + runlen = *fin++; + if (runlen & 0x80) + { + // RLE - all pixels the same color + runlen += 1 - 0x80; + if (fin + pix_inc > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = 255; + fin += pix_inc; + for (;runlen--;x++) + *pixbufi++ = bgra.i; + } + else + { + // uncompressed - all pixels different color + runlen++; + if (fin + pix_inc * runlen > enddata) + break; // error - truncated file + if (x + runlen > image_width) + break; // error - line exceeds width + for (;runlen--;x++) + { + bgra.b[0] = fin[0]; + bgra.b[1] = fin[1]; + bgra.b[2] = fin[2]; + bgra.b[3] = 255; + fin += pix_inc; + *pixbufi++ = bgra.i; + } + } + } + + if (x != image_width) + { + // pixbufi is useless now + Con_Printf("LoadTGA: corrupt file\n"); + break; + } + } + } + break; + default: + // unknown image_type + break; + } + + return image_buffer; +} + +typedef struct q2wal_s +{ + char name[32]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} q2wal_t; + +static unsigned char *LoadWAL_BGRA (const unsigned char *f, int filesize, int *miplevel) +{ + unsigned char *image_buffer; + const q2wal_t *inwal = (const q2wal_t *)f; + + if (filesize < (int) sizeof(q2wal_t)) + { + Con_Print("LoadWAL: invalid WAL file\n"); + return NULL; + } + + image_width = LittleLong(inwal->width); + image_height = LittleLong(inwal->height); + if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) + { + Con_Printf("LoadWAL: invalid size %ix%i\n", image_width, image_height); + return NULL; + } + + if (filesize < (int) sizeof(q2wal_t) + (int) LittleLong(inwal->offsets[0]) + image_width * image_height) + { + Con_Print("LoadWAL: invalid WAL file\n"); + return NULL; + } + + image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + if (!image_buffer) + { + Con_Printf("LoadWAL: not enough memory for %i by %i image\n", image_width, image_height); + return NULL; + } + Image_Copy8bitBGRA(f + LittleLong(inwal->offsets[0]), image_buffer, image_width * image_height, palette_bgra_complete); + return image_buffer; +} + + +void Image_StripImageExtension (const char *in, char *out, size_t size_out) +{ + const char *ext; + + if (size_out == 0) + return; + + ext = FS_FileExtension(in); + if (ext && (!strcmp(ext, "tga") || !strcmp(ext, "pcx") || !strcmp(ext, "lmp") || !strcmp(ext, "png") || !strcmp(ext, "jpg"))) + FS_StripExtension(in, out, size_out); + else + strlcpy(out, in, size_out); +} + +static unsigned char image_linearfromsrgb[256]; +static unsigned char image_srgbfromlinear_lightmap[256]; + +void Image_MakeLinearColorsFromsRGB(unsigned char *pout, const unsigned char *pin, int numpixels) +{ + int i; + // this math from http://www.opengl.org/registry/specs/EXT/texture_sRGB.txt + if (!image_linearfromsrgb[255]) + for (i = 0;i < 256;i++) + image_linearfromsrgb[i] = (unsigned char)floor(Image_LinearFloatFromsRGB(i) * 255.0f + 0.5f); + for (i = 0;i < numpixels;i++) + { + pout[i*4+0] = image_linearfromsrgb[pin[i*4+0]]; + pout[i*4+1] = image_linearfromsrgb[pin[i*4+1]]; + pout[i*4+2] = image_linearfromsrgb[pin[i*4+2]]; + pout[i*4+3] = pin[i*4+3]; + } +} + +void Image_MakesRGBColorsFromLinear_Lightmap(unsigned char *pout, const unsigned char *pin, int numpixels) +{ + int i; + // this math from http://www.opengl.org/registry/specs/EXT/texture_sRGB.txt + if (!image_srgbfromlinear_lightmap[255]) + for (i = 0;i < 256;i++) + image_srgbfromlinear_lightmap[i] = (unsigned char)floor(bound(0.0f, Image_sRGBFloatFromLinear_Lightmap(i), 1.0f) * 255.0f + 0.5f); + for (i = 0;i < numpixels;i++) + { + pout[i*4+0] = image_srgbfromlinear_lightmap[pin[i*4+0]]; + pout[i*4+1] = image_srgbfromlinear_lightmap[pin[i*4+1]]; + pout[i*4+2] = image_srgbfromlinear_lightmap[pin[i*4+2]]; + pout[i*4+3] = pin[i*4+3]; + } +} + +typedef struct imageformat_s +{ + const char *formatstring; + unsigned char *(*loadfunc)(const unsigned char *f, int filesize, int *miplevel); +} +imageformat_t; + +// GAME_TENEBRAE only +imageformat_t imageformats_tenebrae[] = +{ + {"override/%s.tga", LoadTGA_BGRA}, + {"override/%s.png", PNG_LoadImage_BGRA}, + {"override/%s.jpg", JPEG_LoadImage_BGRA}, + {"override/%s.pcx", LoadPCX_BGRA}, + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {NULL, NULL} +}; + +imageformat_t imageformats_nopath[] = +{ + {"override/%s.tga", LoadTGA_BGRA}, + {"override/%s.png", PNG_LoadImage_BGRA}, + {"override/%s.jpg", JPEG_LoadImage_BGRA}, + {"textures/%s.tga", LoadTGA_BGRA}, + {"textures/%s.png", PNG_LoadImage_BGRA}, + {"textures/%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {NULL, NULL} +}; + +// GAME_DELUXEQUAKE only +// VorteX: the point why i use such messy texture paths is +// that GtkRadiant can't detect normal/gloss textures +// and exclude them from texture browser +// so i just use additional folder to store this textures +imageformat_t imageformats_dq[] = +{ + {"%s.tga", LoadTGA_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"texturemaps/%s.tga", LoadTGA_BGRA}, + {"texturemaps/%s.jpg", JPEG_LoadImage_BGRA}, + {NULL, NULL} +}; + +imageformat_t imageformats_textures[] = +{ + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {"%s.wal", LoadWAL_BGRA}, + {NULL, NULL} +}; + +imageformat_t imageformats_gfx[] = +{ + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {NULL, NULL} +}; + +imageformat_t imageformats_other[] = +{ + {"%s.tga", LoadTGA_BGRA}, + {"%s.png", PNG_LoadImage_BGRA}, + {"%s.jpg", JPEG_LoadImage_BGRA}, + {"%s.pcx", LoadPCX_BGRA}, + {NULL, NULL} +}; + +int fixtransparentpixels(unsigned char *data, int w, int h); +unsigned char *loadimagepixelsbgra (const char *filename, qboolean complain, qboolean allowFixtrans, qboolean convertsRGB, int *miplevel) +{ + fs_offset_t filesize; + imageformat_t *firstformat, *format; + unsigned char *f, *data = NULL, *data2 = NULL; + char basename[MAX_QPATH], name[MAX_QPATH], name2[MAX_QPATH], *c; + char vabuf[1024]; + //if (developer_memorydebug.integer) + // Mem_CheckSentinelsGlobal(); + if (developer_texturelogging.integer) + Log_Printf("textures.log", "%s\n", filename); + Image_StripImageExtension(filename, basename, sizeof(basename)); // strip filename extensions to allow replacement by other types + // replace *'s with #, so commandline utils don't get confused when dealing with the external files + for (c = basename;*c;c++) + if (*c == '*') + *c = '#'; + name[0] = 0; + if (strchr(basename, '/')) + { + int i; + for (i = 0;i < (int)sizeof(name)-1 && basename[i] != '/';i++) + name[i] = basename[i]; + name[i] = 0; + } + if (gamemode == GAME_TENEBRAE) + firstformat = imageformats_tenebrae; + else if (gamemode == GAME_DELUXEQUAKE) + firstformat = imageformats_dq; + else if (!strcasecmp(name, "textures")) + firstformat = imageformats_textures; + else if (!strcasecmp(name, "gfx")) + firstformat = imageformats_gfx; + else if (!strchr(basename, '/')) + firstformat = imageformats_nopath; + else + firstformat = imageformats_other; + // now try all the formats in the selected list + for (format = firstformat;format->formatstring;format++) + { + dpsnprintf (name, sizeof(name), format->formatstring, basename); + f = FS_LoadFile(name, tempmempool, true, &filesize); + if (f) + { + int mymiplevel = miplevel ? *miplevel : 0; + data = format->loadfunc(f, (int)filesize, &mymiplevel); + Mem_Free(f); + if (data) + { + if(format->loadfunc == JPEG_LoadImage_BGRA) // jpeg can't do alpha, so let's simulate it by loading another jpeg + { + dpsnprintf (name2, sizeof(name2), format->formatstring, va(vabuf, sizeof(vabuf), "%s_alpha", basename)); + f = FS_LoadFile(name2, tempmempool, true, &filesize); + if(f) + { + int mymiplevel2 = miplevel ? *miplevel : 0; + data2 = format->loadfunc(f, (int)filesize, &mymiplevel2); + if(data2 && mymiplevel == mymiplevel2) + Image_CopyAlphaFromBlueBGRA(data, data2, image_width, image_height); + else + Con_Printf("loadimagepixelsrgba: corrupt or invalid alpha image %s_alpha\n", basename); + if(data2) + Mem_Free(data2); + Mem_Free(f); + } + } + if (developer_loading.integer) + Con_DPrintf("loaded image %s (%dx%d)\n", name, image_width, image_height); + if(miplevel) + *miplevel = mymiplevel; + //if (developer_memorydebug.integer) + // Mem_CheckSentinelsGlobal(); + if(allowFixtrans && r_fixtrans_auto.integer) + { + int n = fixtransparentpixels(data, image_width, image_height); + if(n) + { + Con_Printf("- had to fix %s (%d pixels changed)\n", name, n); + if(r_fixtrans_auto.integer >= 2) + { + char outfilename[MAX_QPATH], buf[MAX_QPATH]; + Image_StripImageExtension(name, buf, sizeof(buf)); + dpsnprintf(outfilename, sizeof(outfilename), "fixtrans/%s.tga", buf); + Image_WriteTGABGRA(outfilename, image_width, image_height, data); + Con_Printf("- %s written.\n", outfilename); + } + } + } + if (convertsRGB) + Image_MakeLinearColorsFromsRGB(data, data, image_width * image_height); + return data; + } + else + Con_DPrintf("Error loading image %s (file loaded but decode failed)\n", name); + } + } + if (complain) + { + Con_Printf("Couldn't load %s using ", filename); + for (format = firstformat;format->formatstring;format++) + { + dpsnprintf (name, sizeof(name), format->formatstring, basename); + Con_Printf(format == firstformat ? "\"%s\"" : (format[1].formatstring ? ", \"%s\"" : " or \"%s\".\n"), format->formatstring); + } + } + + // texture loading can take a while, so make sure we're sending keepalives + CL_KeepaliveMessage(false); + + //if (developer_memorydebug.integer) + // Mem_CheckSentinelsGlobal(); + return NULL; +} + +extern cvar_t gl_picmip; +rtexture_t *loadtextureimage (rtexturepool_t *pool, const char *filename, qboolean complain, int flags, qboolean allowFixtrans, qboolean sRGB) +{ + unsigned char *data; + rtexture_t *rt; + int miplevel = R_PicmipForFlags(flags); + if (!(data = loadimagepixelsbgra (filename, complain, allowFixtrans, false, &miplevel))) + return 0; + rt = R_LoadTexture2D(pool, filename, image_width, image_height, data, sRGB ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, flags, miplevel, NULL); + Mem_Free(data); + return rt; +} + +int fixtransparentpixels(unsigned char *data, int w, int h) +{ + int const FIXTRANS_NEEDED = 1; + int const FIXTRANS_HAS_L = 2; + int const FIXTRANS_HAS_R = 4; + int const FIXTRANS_HAS_U = 8; + int const FIXTRANS_HAS_D = 16; + int const FIXTRANS_FIXED = 32; + unsigned char *fixMask = (unsigned char *) Mem_Alloc(tempmempool, w * h); + int fixPixels = 0; + int changedPixels = 0; + int x, y; + +#define FIXTRANS_PIXEL (y*w+x) +#define FIXTRANS_PIXEL_U (((y+h-1)%h)*w+x) +#define FIXTRANS_PIXEL_D (((y+1)%h)*w+x) +#define FIXTRANS_PIXEL_L (y*w+((x+w-1)%w)) +#define FIXTRANS_PIXEL_R (y*w+((x+1)%w)) + + memset(fixMask, 0, w * h); + for(y = 0; y < h; ++y) + for(x = 0; x < w; ++x) + { + if(data[FIXTRANS_PIXEL * 4 + 3] == 0) + { + fixMask[FIXTRANS_PIXEL] |= FIXTRANS_NEEDED; + ++fixPixels; + } + else + { + fixMask[FIXTRANS_PIXEL_D] |= FIXTRANS_HAS_U; + fixMask[FIXTRANS_PIXEL_U] |= FIXTRANS_HAS_D; + fixMask[FIXTRANS_PIXEL_R] |= FIXTRANS_HAS_L; + fixMask[FIXTRANS_PIXEL_L] |= FIXTRANS_HAS_R; + } + } + if(fixPixels == w * h) + return 0; // sorry, can't do anything about this + while(fixPixels) + { + for(y = 0; y < h; ++y) + for(x = 0; x < w; ++x) + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_NEEDED) + { + unsigned int sumR = 0, sumG = 0, sumB = 0, sumA = 0, sumRA = 0, sumGA = 0, sumBA = 0, cnt = 0; + unsigned char r, g, b, a, r0, g0, b0; + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_U) + { + r = data[FIXTRANS_PIXEL_U * 4 + 2]; + g = data[FIXTRANS_PIXEL_U * 4 + 1]; + b = data[FIXTRANS_PIXEL_U * 4 + 0]; + a = data[FIXTRANS_PIXEL_U * 4 + 3]; + sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; + } + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_D) + { + r = data[FIXTRANS_PIXEL_D * 4 + 2]; + g = data[FIXTRANS_PIXEL_D * 4 + 1]; + b = data[FIXTRANS_PIXEL_D * 4 + 0]; + a = data[FIXTRANS_PIXEL_D * 4 + 3]; + sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; + } + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_L) + { + r = data[FIXTRANS_PIXEL_L * 4 + 2]; + g = data[FIXTRANS_PIXEL_L * 4 + 1]; + b = data[FIXTRANS_PIXEL_L * 4 + 0]; + a = data[FIXTRANS_PIXEL_L * 4 + 3]; + sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; + } + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_R) + { + r = data[FIXTRANS_PIXEL_R * 4 + 2]; + g = data[FIXTRANS_PIXEL_R * 4 + 1]; + b = data[FIXTRANS_PIXEL_R * 4 + 0]; + a = data[FIXTRANS_PIXEL_R * 4 + 3]; + sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; + } + if(!cnt) + continue; + r0 = data[FIXTRANS_PIXEL * 4 + 2]; + g0 = data[FIXTRANS_PIXEL * 4 + 1]; + b0 = data[FIXTRANS_PIXEL * 4 + 0]; + if(sumA) + { + // there is a surrounding non-alpha pixel + r = (sumRA + sumA / 2) / sumA; + g = (sumGA + sumA / 2) / sumA; + b = (sumBA + sumA / 2) / sumA; + } + else + { + // need to use a "regular" average + r = (sumR + cnt / 2) / cnt; + g = (sumG + cnt / 2) / cnt; + b = (sumB + cnt / 2) / cnt; + } + if(r != r0 || g != g0 || b != b0) + ++changedPixels; + data[FIXTRANS_PIXEL * 4 + 2] = r; + data[FIXTRANS_PIXEL * 4 + 1] = g; + data[FIXTRANS_PIXEL * 4 + 0] = b; + fixMask[FIXTRANS_PIXEL] |= FIXTRANS_FIXED; + } + for(y = 0; y < h; ++y) + for(x = 0; x < w; ++x) + if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_FIXED) + { + fixMask[FIXTRANS_PIXEL] &= ~(FIXTRANS_NEEDED | FIXTRANS_FIXED); + fixMask[FIXTRANS_PIXEL_D] |= FIXTRANS_HAS_U; + fixMask[FIXTRANS_PIXEL_U] |= FIXTRANS_HAS_D; + fixMask[FIXTRANS_PIXEL_R] |= FIXTRANS_HAS_L; + fixMask[FIXTRANS_PIXEL_L] |= FIXTRANS_HAS_R; + --fixPixels; + } + } + return changedPixels; +} + +void Image_FixTransparentPixels_f(void) +{ + const char *filename, *filename_pattern; + fssearch_t *search; + int i, n; + char outfilename[MAX_QPATH], buf[MAX_QPATH]; + unsigned char *data; + if(Cmd_Argc() != 2) + { + Con_Printf("Usage: %s imagefile\n", Cmd_Argv(0)); + return; + } + filename_pattern = Cmd_Argv(1); + search = FS_Search(filename_pattern, true, true); + if(!search) + return; + for(i = 0; i < search->numfilenames; ++i) + { + filename = search->filenames[i]; + Con_Printf("Processing %s... ", filename); + Image_StripImageExtension(filename, buf, sizeof(buf)); + dpsnprintf(outfilename, sizeof(outfilename), "fixtrans/%s.tga", buf); + if(!(data = loadimagepixelsbgra(filename, true, false, false, NULL))) + return; + if((n = fixtransparentpixels(data, image_width, image_height))) + { + Image_WriteTGABGRA(outfilename, image_width, image_height, data); + Con_Printf("%s written (%d pixels changed).\n", outfilename, n); + } + else + Con_Printf("unchanged.\n"); + Mem_Free(data); + } + FS_FreeSearch(search); +} + +qboolean Image_WriteTGABGR_preflipped (const char *filename, int width, int height, const unsigned char *data) +{ + qboolean ret; + unsigned char buffer[18]; + const void *buffers[2]; + fs_offset_t sizes[2]; + + memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = (width >> 0) & 0xFF; + buffer[13] = (width >> 8) & 0xFF; + buffer[14] = (height >> 0) & 0xFF; + buffer[15] = (height >> 8) & 0xFF; + buffer[16] = 24; // pixel size + + buffers[0] = buffer; + sizes[0] = 18; + buffers[1] = data; + sizes[1] = width*height*3; + ret = FS_WriteFileInBlocks(filename, buffers, sizes, 2); + + return ret; +} + +qboolean Image_WriteTGABGRA (const char *filename, int width, int height, const unsigned char *data) +{ + int y; + unsigned char *buffer, *out; + const unsigned char *in, *end; + qboolean ret; + + buffer = (unsigned char *)Mem_Alloc(tempmempool, width*height*4 + 18); + + memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = (width >> 0) & 0xFF; + buffer[13] = (width >> 8) & 0xFF; + buffer[14] = (height >> 0) & 0xFF; + buffer[15] = (height >> 8) & 0xFF; + + for (y = 3;y < width*height*4;y += 4) + if (data[y] < 255) + break; + + if (y < width*height*4) + { + // save the alpha channel + buffer[16] = 32; // pixel size + buffer[17] = 8; // 8 bits of alpha + + // flip upside down + out = buffer + 18; + for (y = height - 1;y >= 0;y--) + { + memcpy(out, data + y * width * 4, width * 4); + out += width*4; + } + } + else + { + // save only the color channels + buffer[16] = 24; // pixel size + buffer[17] = 0; // 8 bits of alpha + + // truncate bgra to bgr and flip upside down + out = buffer + 18; + for (y = height - 1;y >= 0;y--) + { + in = data + y * width * 4; + end = in + width * 4; + for (;in < end;in += 4) + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + } + } + } + ret = FS_WriteFile (filename, buffer, out - buffer); + + Mem_Free(buffer); + + return ret; +} + +static void Image_Resample32LerpLine (const unsigned char *in, unsigned char *out, int inwidth, int outwidth) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; + fstep = (int) (inwidth*65536.0f/outwidth); + endx = (inwidth-1); + for (j = 0,f = 0;j < outwidth;j++, f += fstep) + { + xi = f >> 16; + if (xi != oldx) + { + in += (xi - oldx) * 4; + oldx = xi; + } + if (xi < endx) + { + lerp = f & 0xFFFF; + *out++ = (unsigned char) ((((in[4] - in[0]) * lerp) >> 16) + in[0]); + *out++ = (unsigned char) ((((in[5] - in[1]) * lerp) >> 16) + in[1]); + *out++ = (unsigned char) ((((in[6] - in[2]) * lerp) >> 16) + in[2]); + *out++ = (unsigned char) ((((in[7] - in[3]) * lerp) >> 16) + in[3]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + *out++ = in[3]; + } + } +} + +#define LERPBYTE(i) r = resamplerow1[i];out[i] = (unsigned char) ((((resamplerow2[i] - r) * lerp) >> 16) + r) +static void Image_Resample32Lerp(const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight) +{ + int i, j, r, yi, oldy, f, fstep, lerp, endy = (inheight-1), inwidth4 = inwidth*4, outwidth4 = outwidth*4; + unsigned char *out; + const unsigned char *inrow; + unsigned char *resamplerow1; + unsigned char *resamplerow2; + out = (unsigned char *)outdata; + fstep = (int) (inheight*65536.0f/outheight); + + resamplerow1 = (unsigned char *)Mem_Alloc(tempmempool, outwidth*4*2); + resamplerow2 = resamplerow1 + outwidth*4; + + inrow = (const unsigned char *)indata; + oldy = 0; + Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); + Image_Resample32LerpLine (inrow + inwidth4, resamplerow2, inwidth, outwidth); + for (i = 0, f = 0;i < outheight;i++,f += fstep) + { + yi = f >> 16; + if (yi < endy) + { + lerp = f & 0xFFFF; + if (yi != oldy) + { + inrow = (unsigned char *)indata + inwidth4*yi; + if (yi == oldy+1) + memcpy(resamplerow1, resamplerow2, outwidth4); + else + Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); + Image_Resample32LerpLine (inrow + inwidth4, resamplerow2, inwidth, outwidth); + oldy = yi; + } + j = outwidth - 4; + while(j >= 0) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + LERPBYTE(12); + LERPBYTE(13); + LERPBYTE(14); + LERPBYTE(15); + out += 16; + resamplerow1 += 16; + resamplerow2 += 16; + j -= 4; + } + if (j & 2) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + out += 8; + resamplerow1 += 8; + resamplerow2 += 8; + } + if (j & 1) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + out += 4; + resamplerow1 += 4; + resamplerow2 += 4; + } + resamplerow1 -= outwidth4; + resamplerow2 -= outwidth4; + } + else + { + if (yi != oldy) + { + inrow = (unsigned char *)indata + inwidth4*yi; + if (yi == oldy+1) + memcpy(resamplerow1, resamplerow2, outwidth4); + else + Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); + oldy = yi; + } + memcpy(out, resamplerow1, outwidth4); + } + } + + Mem_Free(resamplerow1); + resamplerow1 = NULL; + resamplerow2 = NULL; +} + +static void Image_Resample32Nolerp(const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight) +{ + int i, j; + unsigned frac, fracstep; + // relies on int being 4 bytes + int *inrow, *out; + out = (int *)outdata; + + fracstep = inwidth*0x10000/outwidth; + for (i = 0;i < outheight;i++) + { + inrow = (int *)indata + inwidth*(i*inheight/outheight); + frac = fracstep >> 1; + j = outwidth - 4; + while (j >= 0) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out[1] = inrow[frac >> 16];frac += fracstep; + out[2] = inrow[frac >> 16];frac += fracstep; + out[3] = inrow[frac >> 16];frac += fracstep; + out += 4; + j -= 4; + } + if (j & 2) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out[1] = inrow[frac >> 16];frac += fracstep; + out += 2; + } + if (j & 1) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out += 1; + } + } +} + +/* +================ +Image_Resample +================ +*/ +void Image_Resample32(const void *indata, int inwidth, int inheight, int indepth, void *outdata, int outwidth, int outheight, int outdepth, int quality) +{ + if (indepth != 1 || outdepth != 1) + { + Con_Printf ("Image_Resample: 3D resampling not supported\n"); + return; + } + if (quality) + Image_Resample32Lerp(indata, inwidth, inheight, outdata, outwidth, outheight); + else + Image_Resample32Nolerp(indata, inwidth, inheight, outdata, outwidth, outheight); +} + +// in can be the same as out +void Image_MipReduce32(const unsigned char *in, unsigned char *out, int *width, int *height, int *depth, int destwidth, int destheight, int destdepth) +{ + const unsigned char *inrow; + int x, y, nextrow; + if (*depth != 1 || destdepth != 1) + { + Con_Printf ("Image_Resample: 3D resampling not supported\n"); + if (*width > destwidth) + *width >>= 1; + if (*height > destheight) + *height >>= 1; + if (*depth > destdepth) + *depth >>= 1; + return; + } + // note: if given odd width/height this discards the last row/column of + // pixels, rather than doing a proper box-filter scale down + inrow = in; + nextrow = *width * 4; + if (*width > destwidth) + { + *width >>= 1; + if (*height > destheight) + { + // reduce both + *height >>= 1; + for (y = 0;y < *height;y++, inrow += nextrow * 2) + { + for (in = inrow, x = 0;x < *width;x++) + { + out[0] = (unsigned char) ((in[0] + in[4] + in[nextrow ] + in[nextrow+4]) >> 2); + out[1] = (unsigned char) ((in[1] + in[5] + in[nextrow+1] + in[nextrow+5]) >> 2); + out[2] = (unsigned char) ((in[2] + in[6] + in[nextrow+2] + in[nextrow+6]) >> 2); + out[3] = (unsigned char) ((in[3] + in[7] + in[nextrow+3] + in[nextrow+7]) >> 2); + out += 4; + in += 8; + } + } + } + else + { + // reduce width + for (y = 0;y < *height;y++, inrow += nextrow) + { + for (in = inrow, x = 0;x < *width;x++) + { + out[0] = (unsigned char) ((in[0] + in[4]) >> 1); + out[1] = (unsigned char) ((in[1] + in[5]) >> 1); + out[2] = (unsigned char) ((in[2] + in[6]) >> 1); + out[3] = (unsigned char) ((in[3] + in[7]) >> 1); + out += 4; + in += 8; + } + } + } + } + else + { + if (*height > destheight) + { + // reduce height + *height >>= 1; + for (y = 0;y < *height;y++, inrow += nextrow * 2) + { + for (in = inrow, x = 0;x < *width;x++) + { + out[0] = (unsigned char) ((in[0] + in[nextrow ]) >> 1); + out[1] = (unsigned char) ((in[1] + in[nextrow+1]) >> 1); + out[2] = (unsigned char) ((in[2] + in[nextrow+2]) >> 1); + out[3] = (unsigned char) ((in[3] + in[nextrow+3]) >> 1); + out += 4; + in += 4; + } + } + } + else + Con_Printf ("Image_MipReduce: desired size already achieved\n"); + } +} + +void Image_HeightmapToNormalmap_BGRA(const unsigned char *inpixels, unsigned char *outpixels, int width, int height, int clamp, float bumpscale) +{ + int x, y, x1, x2, y1, y2; + const unsigned char *b, *row[3]; + int p[5]; + unsigned char *out; + float ibumpscale, n[3]; + ibumpscale = (255.0f * 6.0f) / bumpscale; + out = outpixels; + for (y = 0, y1 = height-1;y < height;y1 = y, y++) + { + y2 = y + 1;if (y2 >= height) y2 = 0; + row[0] = inpixels + (y1 * width) * 4; + row[1] = inpixels + (y * width) * 4; + row[2] = inpixels + (y2 * width) * 4; + for (x = 0, x1 = width-1;x < width;x1 = x, x++) + { + x2 = x + 1;if (x2 >= width) x2 = 0; + // left, right + b = row[1] + x1 * 4;p[0] = (b[0] + b[1] + b[2]); + b = row[1] + x2 * 4;p[1] = (b[0] + b[1] + b[2]); + // above, below + b = row[0] + x * 4;p[2] = (b[0] + b[1] + b[2]); + b = row[2] + x * 4;p[3] = (b[0] + b[1] + b[2]); + // center + b = row[1] + x * 4;p[4] = (b[0] + b[1] + b[2]); + // calculate a normal from the slopes + n[0] = p[0] - p[1]; + n[1] = p[3] - p[2]; + n[2] = ibumpscale; + VectorNormalize(n); + // turn it into a dot3 rgb vector texture + out[2] = (int)(128.0f + n[0] * 127.0f); + out[1] = (int)(128.0f + n[1] * 127.0f); + out[0] = (int)(128.0f + n[2] * 127.0f); + out[3] = (p[4]) / 3; + out += 4; + } + } +} diff --git a/app/jni/image.h b/app/jni/image.h new file mode 100644 index 0000000..9153577 --- /dev/null +++ b/app/jni/image.h @@ -0,0 +1,62 @@ + +#ifndef IMAGE_H +#define IMAGE_H + +extern int image_width, image_height; + + +// swizzle components (even converting number of components) and flip images +// (warning: input must be different than output due to non-linear read/write) +// (tip: component indices can contain values | 0x80000000 to tell it to +// store them directly into output, so 255 | 0x80000000 would write 255) +void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices); + +// applies gamma correction to RGB pixels, in can be the same as out +void Image_GammaRemapRGB(const unsigned char *in, unsigned char *out, int pixels, const unsigned char *gammar, const unsigned char *gammag, const unsigned char *gammab); + +// converts 8bit image data to BGRA, in can not be the same as out +void Image_Copy8bitBGRA(const unsigned char *in, unsigned char *out, int pixels, const unsigned int *pal); + +void Image_StripImageExtension (const char *in, char *out, size_t size_out); + +// called by conchars.tga loader in gl_draw.c, otherwise private +unsigned char *LoadTGA_BGRA (const unsigned char *f, int filesize, int *miplevel); + +// loads a texture, as pixel data +unsigned char *loadimagepixelsbgra (const char *filename, qboolean complain, qboolean allowFixtrans, qboolean convertsRGB, int *miplevel); + +// loads an 8bit pcx image into a 296x194x8bit buffer, with cropping as needed +qboolean LoadPCX_QWSkin(const unsigned char *f, int filesize, unsigned char *pixels, int outwidth, int outheight); + +// loads a texture, as a texture +rtexture_t *loadtextureimage (rtexturepool_t *pool, const char *filename, qboolean complain, int flags, qboolean allowFixtrans, qboolean sRGB); + +// writes an upside down BGR image into a TGA +qboolean Image_WriteTGABGR_preflipped (const char *filename, int width, int height, const unsigned char *data); + +// writes a BGRA image into a TGA file +qboolean Image_WriteTGABGRA (const char *filename, int width, int height, const unsigned char *data); + +// resizes the image (in can not be the same as out) +void Image_Resample32(const void *indata, int inwidth, int inheight, int indepth, void *outdata, int outwidth, int outheight, int outdepth, int quality); + +// scales the image down by a power of 2 (in can be the same as out) +void Image_MipReduce32(const unsigned char *in, unsigned char *out, int *width, int *height, int *depth, int destwidth, int destheight, int destdepth); + +void Image_HeightmapToNormalmap_BGRA(const unsigned char *inpixels, unsigned char *outpixels, int width, int height, int clamp, float bumpscale); + +// console command to fix the colors of transparent pixels (to prevent weird borders) +void Image_FixTransparentPixels_f(void); +extern cvar_t r_fixtrans_auto; + +#define Image_LinearFloatFromsRGBFloat(c) (((c) <= 0.04045f) ? (c) * (1.0f / 12.92f) : (float)pow(((c) + 0.055f)*(1.0f/1.055f), 2.4f)) +#define Image_sRGBFloatFromLinearFloat(c) (((c) < 0.0031308f) ? (c) * 12.92f : 1.055f * (float)pow((c), 1.0f/2.4f) - 0.055f) +#define Image_LinearFloatFromsRGB(c) Image_LinearFloatFromsRGBFloat((c) * (1.0f / 255.0f)) +#define Image_sRGBFloatFromLinear(c) Image_sRGBFloatFromLinearFloat((c) * (1.0f / 255.0f)) +#define Image_sRGBFloatFromLinear_Lightmap(c) Image_sRGBFloatFromLinearFloat((c) * (2.0f / 255.0f)) * 0.5f + +void Image_MakeLinearColorsFromsRGB(unsigned char *pout, const unsigned char *pin, int numpixels); +void Image_MakesRGBColorsFromLinear_Lightmap(unsigned char *pout, const unsigned char *pin, int numpixels); + +#endif + diff --git a/app/jni/image_png.c b/app/jni/image_png.c new file mode 100644 index 0000000..04419bd --- /dev/null +++ b/app/jni/image_png.c @@ -0,0 +1,559 @@ +/* + Copyright (C) 2006 Serge "(515)" Ziryukin, Forest "LordHavoc" Hale + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +//[515]: png implemented into DP ONLY FOR TESTING 2d stuff with csqc +// so delete this bullshit :D +// +//LordHavoc: rewrote most of this. + +#include "quakedef.h" +#include "image.h" +#include "image_png.h" + + +static void (*qpng_set_sig_bytes) (void*, int); +static int (*qpng_sig_cmp) (const unsigned char*, size_t, size_t); +static void* (*qpng_create_read_struct) (const char*, void*, void(*)(void *png, const char *message), void(*)(void *png, const char *message)); +static void* (*qpng_create_write_struct) (const char*, void*, void(*)(void *png, const char *message), void(*)(void *png, const char *message)); +static void* (*qpng_create_info_struct) (void*); +static void (*qpng_read_info) (void*, void*); +static void (*qpng_set_compression_level) (void*, int); +static void (*qpng_set_filter) (void*, int, int); +static void (*qpng_set_expand) (void*); +static void (*qpng_set_palette_to_rgb) (void*); +static void (*qpng_set_tRNS_to_alpha) (void*); +static void (*qpng_set_gray_to_rgb) (void*); +static void (*qpng_set_filler) (void*, unsigned int, int); +static void (*qpng_set_IHDR) (void*, void*, unsigned long, unsigned long, int, int, int, int, int); +static void (*qpng_set_packing) (void*); +static void (*qpng_set_bgr) (void*); +static int (*qpng_set_interlace_handling) (void*); +static void (*qpng_read_update_info) (void*, void*); +static void (*qpng_read_image) (void*, unsigned char**); +static void (*qpng_read_end) (void*, void*); +static void (*qpng_destroy_read_struct) (void**, void**, void**); +static void (*qpng_destroy_write_struct) (void**, void**); +static void (*qpng_set_read_fn) (void*, void*, void(*)(void *png, unsigned char *data, size_t length)); +static void (*qpng_set_write_fn) (void*, void*, void(*)(void *png, unsigned char *data, size_t length), void(*)(void *png)); +static unsigned int (*qpng_get_valid) (void*, void*, unsigned int); +static unsigned int (*qpng_get_rowbytes) (void*, void*); +static unsigned char (*qpng_get_channels) (void*, void*); +static unsigned char (*qpng_get_bit_depth) (void*, void*); +static unsigned int (*qpng_get_IHDR) (void*, void*, unsigned long*, unsigned long*, int *, int *, int *, int *, int *); +static unsigned int (*qpng_access_version_number) (void); // FIXME is this return type right? It is a png_uint_32 in libpng +static void (*qpng_write_info) (void*, void*); +static void (*qpng_write_row) (void*, unsigned char*); +static void (*qpng_write_end) (void*, void*); + +// libpng 1.4+ longjmp hack +typedef void (*qpng_longjmp_ptr) (jmp_buf, int); +static jmp_buf* (*qpng_set_longjmp_fn) (void *, qpng_longjmp_ptr, size_t); +#define qpng_jmpbuf_14(png_ptr) (*qpng_set_longjmp_fn((png_ptr), longjmp, sizeof (jmp_buf))) + +// libpng 1.2 longjmp hack +#define qpng_jmpbuf_12(png_ptr) (*((jmp_buf *) png_ptr)) + +// all version support +#define qpng_jmpbuf(png_ptr) \ + (qpng_set_longjmp_fn ? qpng_jmpbuf_14(png_ptr) : qpng_jmpbuf_12(png_ptr)) + +static dllfunction_t pngfuncs[] = +{ + {"png_set_sig_bytes", (void **) &qpng_set_sig_bytes}, + {"png_sig_cmp", (void **) &qpng_sig_cmp}, + {"png_create_read_struct", (void **) &qpng_create_read_struct}, + {"png_create_write_struct", (void **) &qpng_create_write_struct}, + {"png_create_info_struct", (void **) &qpng_create_info_struct}, + {"png_read_info", (void **) &qpng_read_info}, + {"png_set_compression_level", (void **) &qpng_set_compression_level}, + {"png_set_filter", (void **) &qpng_set_filter}, + {"png_set_expand", (void **) &qpng_set_expand}, + {"png_set_palette_to_rgb", (void **) &qpng_set_palette_to_rgb}, + {"png_set_tRNS_to_alpha", (void **) &qpng_set_tRNS_to_alpha}, + {"png_set_gray_to_rgb", (void **) &qpng_set_gray_to_rgb}, + {"png_set_filler", (void **) &qpng_set_filler}, + {"png_set_IHDR", (void **) &qpng_set_IHDR}, + {"png_set_packing", (void **) &qpng_set_packing}, + {"png_set_bgr", (void **) &qpng_set_bgr}, + {"png_set_interlace_handling", (void **) &qpng_set_interlace_handling}, + {"png_read_update_info", (void **) &qpng_read_update_info}, + {"png_read_image", (void **) &qpng_read_image}, + {"png_read_end", (void **) &qpng_read_end}, + {"png_destroy_read_struct", (void **) &qpng_destroy_read_struct}, + {"png_destroy_write_struct", (void **) &qpng_destroy_write_struct}, + {"png_set_read_fn", (void **) &qpng_set_read_fn}, + {"png_set_write_fn", (void **) &qpng_set_write_fn}, + {"png_get_valid", (void **) &qpng_get_valid}, + {"png_get_rowbytes", (void **) &qpng_get_rowbytes}, + {"png_get_channels", (void **) &qpng_get_channels}, + {"png_get_bit_depth", (void **) &qpng_get_bit_depth}, + {"png_get_IHDR", (void **) &qpng_get_IHDR}, + {"png_access_version_number", (void **) &qpng_access_version_number}, + {"png_write_info", (void **) &qpng_write_info}, + {"png_write_row", (void **) &qpng_write_row}, + {"png_write_end", (void **) &qpng_write_end}, + {NULL, NULL} +}; +static dllfunction_t png14funcs[] = +{ + {"png_set_longjmp_fn", (void **) &qpng_set_longjmp_fn}, + {NULL, NULL} +}; + +// Handle for PNG DLL +dllhandle_t png_dll = NULL; +dllhandle_t png14_dll = NULL; + + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +PNG_OpenLibrary + +Try to load the PNG DLL +==================== +*/ +qboolean PNG_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if WIN32 + "libpng16.dll", + "libpng16-16.dll", + "libpng15-15.dll", + "libpng15.dll", + "libpng14-14.dll", + "libpng14.dll", + "libpng12.dll", +#elif defined(MACOSX) + "libpng16.16.dylib", + "libpng15.15.dylib", + "libpng14.14.dylib", + "libpng12.0.dylib", +#else + "libpng16.so.16", + "libpng15.so.15", // WTF libtool guidelines anyone? + "libpng14.so.14", // WTF libtool guidelines anyone? + "libpng12.so.0", + "libpng.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (png_dll) + return true; + + // Load the DLL + if(!Sys_LoadLibrary (dllnames, &png_dll, pngfuncs)) + return false; + if(qpng_access_version_number() / 100 >= 104) + if(!Sys_LoadLibrary (dllnames, &png14_dll, png14funcs)) + { + Sys_UnloadLibrary (&png_dll); + return false; + } + return true; +} + + +/* +==================== +PNG_CloseLibrary + +Unload the PNG DLL +==================== +*/ +void PNG_CloseLibrary (void) +{ + Sys_UnloadLibrary (&png14_dll); + Sys_UnloadLibrary (&png_dll); +} + +/* +================================================================= + + PNG decompression + +================================================================= +*/ + +#define PNG_LIBPNG_VER_STRING_12 "1.2.4" +#define PNG_LIBPNG_VER_STRING_14 "1.4.0" +#define PNG_LIBPNG_VER_STRING_15 "1.5.0" +#define PNG_LIBPNG_VER_STRING_16 "1.6.0" + +#define PNG_COLOR_MASK_PALETTE 1 +#define PNG_COLOR_MASK_COLOR 2 +#define PNG_COLOR_MASK_ALPHA 4 + +#define PNG_COLOR_TYPE_GRAY 0 +#define PNG_COLOR_TYPE_PALETTE (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE) +#define PNG_COLOR_TYPE_RGB (PNG_COLOR_MASK_COLOR) +#define PNG_COLOR_TYPE_RGB_ALPHA (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA) +#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA) + +#define PNG_COLOR_TYPE_RGBA PNG_COLOR_TYPE_RGB_ALPHA +#define PNG_COLOR_TYPE_GA PNG_COLOR_TYPE_GRAY_ALPHA + +#define PNG_INFO_tRNS 0x0010 + +// this struct is only used for status information during loading +static struct +{ + const unsigned char *tmpBuf; + int tmpBuflength; + int tmpi; + //int FBgColor; + //int FTransparent; + unsigned int FRowBytes; + //double FGamma; + //double FScreenGamma; + unsigned char **FRowPtrs; + unsigned char *Data; + //char *Title; + //char *Author; + //char *Description; + int BitDepth; + int BytesPerPixel; + int ColorType; + unsigned long Height; // retarded libpng 1.2 pngconf.h uses long (64bit/32bit depending on arch) + unsigned long Width; // retarded libpng 1.2 pngconf.h uses long (64bit/32bit depending on arch) + int Interlace; + int Compression; + int Filter; + //double LastModified; + //int Transparent; + qfile_t *outfile; +} my_png; + +//LordHavoc: removed __cdecl prefix, added overrun protection, and rewrote this to be more efficient +static void PNG_fReadData(void *png, unsigned char *data, size_t length) +{ + size_t l; + l = my_png.tmpBuflength - my_png.tmpi; + if (l < length) + { + Con_Printf("PNG_fReadData: overrun by %i bytes\n", (int)(length - l)); + // a read going past the end of the file, fill in the remaining bytes + // with 0 just to be consistent + memset(data + l, 0, length - l); + length = l; + } + memcpy(data, my_png.tmpBuf + my_png.tmpi, length); + my_png.tmpi += (int)length; + //Com_HexDumpToConsole(data, (int)length); +} + +static void PNG_fWriteData(void *png, unsigned char *data, size_t length) +{ + FS_Write(my_png.outfile, data, length); +} + +static void PNG_fFlushData(void *png) +{ +} + +static void PNG_error_fn(void *png, const char *message) +{ + Con_Printf("PNG_LoadImage: error: %s\n", message); +} + +static void PNG_warning_fn(void *png, const char *message) +{ + Con_Printf("PNG_LoadImage: warning: %s\n", message); +} + +unsigned char *PNG_LoadImage_BGRA (const unsigned char *raw, int filesize, int *miplevel) +{ + unsigned int c; + unsigned int y; + void *png, *pnginfo; + unsigned char *imagedata = NULL; + unsigned char ioBuffer[8192]; + + // FIXME: register an error handler so that abort() won't be called on error + + // No DLL = no PNGs + if (!png_dll) + return NULL; + + if(qpng_sig_cmp(raw, 0, filesize)) + return NULL; + png = (void *)qpng_create_read_struct( + (qpng_access_version_number() / 100 == 102) ? PNG_LIBPNG_VER_STRING_12 : + (qpng_access_version_number() / 100 == 104) ? PNG_LIBPNG_VER_STRING_14 : + (qpng_access_version_number() / 100 == 105) ? PNG_LIBPNG_VER_STRING_15 : + PNG_LIBPNG_VER_STRING_16, // nasty hack... whatever + 0, PNG_error_fn, PNG_warning_fn + ); + if(!png) + return NULL; + + // this must be memset before the setjmp error handler, because it relies + // on the fields in this struct for cleanup + memset(&my_png, 0, sizeof(my_png)); + + // NOTE: this relies on jmp_buf being the first thing in the png structure + // created by libpng! (this is correct for libpng 1.2.x) + if (setjmp(qpng_jmpbuf(png))) + { + if (my_png.Data) + Mem_Free(my_png.Data); + my_png.Data = NULL; + if (my_png.FRowPtrs) + Mem_Free(my_png.FRowPtrs); + my_png.FRowPtrs = NULL; + qpng_destroy_read_struct(&png, &pnginfo, 0); + return NULL; + } + // + + pnginfo = qpng_create_info_struct(png); + if(!pnginfo) + { + qpng_destroy_read_struct(&png, &pnginfo, 0); + return NULL; + } + qpng_set_sig_bytes(png, 0); + + my_png.tmpBuf = raw; + my_png.tmpBuflength = filesize; + my_png.tmpi = 0; + //my_png.Data = NULL; + //my_png.FRowPtrs = NULL; + //my_png.Height = 0; + //my_png.Width = 0; + my_png.ColorType = PNG_COLOR_TYPE_RGB; + //my_png.Interlace = 0; + //my_png.Compression = 0; + //my_png.Filter = 0; + qpng_set_read_fn(png, ioBuffer, PNG_fReadData); + qpng_read_info(png, pnginfo); + qpng_get_IHDR(png, pnginfo, &my_png.Width, &my_png.Height,&my_png.BitDepth, &my_png.ColorType, &my_png.Interlace, &my_png.Compression, &my_png.Filter); + + // this check guards against pngconf.h with unsigned int *width/height parameters on big endian systems by detecting the strange values and shifting them down 32bits + // (if it's little endian the unwritten bytes are the most significant + // ones and we don't worry about that) + // + // this is only necessary because of retarded 64bit png_uint_32 types in libpng 1.2, which can (conceivably) vary by platform +#if LONG_MAX > 4000000000 + if (my_png.Width > LONG_MAX || my_png.Height > LONG_MAX) + { + my_png.Width >>= 32; + my_png.Height >>= 32; + } +#endif + + if (my_png.ColorType == PNG_COLOR_TYPE_PALETTE) + qpng_set_palette_to_rgb(png); + if (my_png.ColorType == PNG_COLOR_TYPE_GRAY || my_png.ColorType == PNG_COLOR_TYPE_GRAY_ALPHA) + qpng_set_gray_to_rgb(png); + if (qpng_get_valid(png, pnginfo, PNG_INFO_tRNS)) + qpng_set_tRNS_to_alpha(png); + if (my_png.BitDepth == 8 && !(my_png.ColorType & PNG_COLOR_MASK_ALPHA)) + qpng_set_filler(png, 255, 1); + if (( my_png.ColorType == PNG_COLOR_TYPE_GRAY) || (my_png.ColorType == PNG_COLOR_TYPE_GRAY_ALPHA )) + qpng_set_gray_to_rgb(png); + if (my_png.BitDepth < 8) + qpng_set_expand(png); + + qpng_read_update_info(png, pnginfo); + + my_png.FRowBytes = qpng_get_rowbytes(png, pnginfo); + my_png.BytesPerPixel = qpng_get_channels(png, pnginfo); + + my_png.FRowPtrs = (unsigned char **)Mem_Alloc(tempmempool, my_png.Height * sizeof(*my_png.FRowPtrs)); + if (my_png.FRowPtrs) + { + imagedata = (unsigned char *)Mem_Alloc(tempmempool, my_png.Height * my_png.FRowBytes); + if(imagedata) + { + my_png.Data = imagedata; + for(y = 0;y < my_png.Height;y++) + my_png.FRowPtrs[y] = my_png.Data + y * my_png.FRowBytes; + qpng_read_image(png, my_png.FRowPtrs); + } + else + { + Con_Printf("PNG_LoadImage : not enough memory\n"); + qpng_destroy_read_struct(&png, &pnginfo, 0); + Mem_Free(my_png.FRowPtrs); + return NULL; + } + Mem_Free(my_png.FRowPtrs); + my_png.FRowPtrs = NULL; + } + else + { + Con_Printf("PNG_LoadImage : not enough memory\n"); + qpng_destroy_read_struct(&png, &pnginfo, 0); + return NULL; + } + + qpng_read_end(png, pnginfo); + qpng_destroy_read_struct(&png, &pnginfo, 0); + + image_width = (int)my_png.Width; + image_height = (int)my_png.Height; + + if (my_png.BitDepth != 8) + { + Con_Printf ("PNG_LoadImage : bad color depth\n"); + Mem_Free(imagedata); + return NULL; + } + + // swizzle RGBA to BGRA + for (y = 0;y < (unsigned int)(image_width*image_height*4);y += 4) + { + c = imagedata[y+0]; + imagedata[y+0] = imagedata[y+2]; + imagedata[y+2] = c; + } + + return imagedata; +} + +/* +================================================================= + + PNG compression + +================================================================= +*/ + +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define PNG_INTERLACE_NONE 0 +#define PNG_INTERLACE_ADAM7 1 +#define PNG_FILTER_TYPE_BASE 0 +#define PNG_FILTER_TYPE_DEFAULT PNG_FILTER_TYPE_BASE +#define PNG_COMPRESSION_TYPE_BASE 0 +#define PNG_COMPRESSION_TYPE_DEFAULT PNG_COMPRESSION_TYPE_BASE +#define PNG_NO_FILTERS 0x00 +#define PNG_FILTER_NONE 0x08 +#define PNG_FILTER_SUB 0x10 +#define PNG_FILTER_UP 0x20 +#define PNG_FILTER_AVG 0x40 +#define PNG_FILTER_PAETH 0x80 +#define PNG_ALL_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | \ + PNG_FILTER_AVG | PNG_FILTER_PAETH) + +/* +==================== +PNG_SaveImage_preflipped + +Save a preflipped PNG image to a file +==================== +*/ +qboolean PNG_SaveImage_preflipped (const char *filename, int width, int height, qboolean has_alpha, unsigned char *data) +{ + unsigned int offset, linesize; + qfile_t* file = NULL; + void *png, *pnginfo; + unsigned char ioBuffer[8192]; + int passes, i, j; + + // No DLL = no JPEGs + if (!png_dll) + { + Con_Print("You need the libpng library to save PNG images\n"); + return false; + } + + png = (void *)qpng_create_write_struct( + (qpng_access_version_number() / 100 == 102) ? PNG_LIBPNG_VER_STRING_12 : + (qpng_access_version_number() / 100 == 104) ? PNG_LIBPNG_VER_STRING_14 : + (qpng_access_version_number() / 100 == 105) ? PNG_LIBPNG_VER_STRING_15 : + PNG_LIBPNG_VER_STRING_16, // nasty hack... whatever + 0, PNG_error_fn, PNG_warning_fn + ); + if(!png) + return false; + pnginfo = (void *)qpng_create_info_struct(png); + if(!pnginfo) + { + qpng_destroy_write_struct(&png, NULL); + return false; + } + + // this must be memset before the setjmp error handler, because it relies + // on the fields in this struct for cleanup + memset(&my_png, 0, sizeof(my_png)); + + // NOTE: this relies on jmp_buf being the first thing in the png structure + // created by libpng! (this is correct for libpng 1.2.x) +#ifdef __cplusplus +#ifdef WIN64 + if (setjmp((_JBTYPE *)png)) +#elif defined(MACOSX) || defined(WIN32) + if (setjmp((int *)png)) +#else + if (setjmp((__jmp_buf_tag *)png)) +#endif +#else + if (setjmp(png)) +#endif + { + qpng_destroy_write_struct(&png, &pnginfo); + return false; + } + + // Open the file + file = FS_OpenRealFile(filename, "wb", true); + if (!file) + return false; + my_png.outfile = file; + qpng_set_write_fn(png, ioBuffer, PNG_fWriteData, PNG_fFlushData); + + //qpng_set_compression_level(png, Z_BEST_COMPRESSION); + qpng_set_compression_level(png, Z_BEST_SPEED); + qpng_set_IHDR(png, pnginfo, width, height, 8, has_alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_ADAM7, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + qpng_set_filter(png, 0, PNG_NO_FILTERS); + qpng_write_info(png, pnginfo); + qpng_set_packing(png); + qpng_set_bgr(png); + + passes = qpng_set_interlace_handling(png); + + linesize = width * (has_alpha ? 4 : 3); + offset = linesize * (height - 1); + for(i = 0; i < passes; ++i) + for(j = 0; j < height; ++j) + qpng_write_row(png, &data[offset - j * linesize]); + + qpng_write_end(png, NULL); + qpng_destroy_write_struct(&png, &pnginfo); + + FS_Close (file); + + return true; +} diff --git a/app/jni/image_png.h b/app/jni/image_png.h new file mode 100644 index 0000000..d290d98 --- /dev/null +++ b/app/jni/image_png.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2006 Serge "(515)" Ziryukin + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef PNG_H +#define PNG_H + +qboolean PNG_OpenLibrary (void); +void PNG_CloseLibrary (void); +unsigned char* PNG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel); +qboolean PNG_SaveImage_preflipped (const char *filename, int width, int height, qboolean has_alpha, unsigned char *data); + +#endif + diff --git a/app/jni/input.h b/app/jni/input.h new file mode 100644 index 0000000..e157294 --- /dev/null +++ b/app/jni/input.h @@ -0,0 +1,53 @@ +/* +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. + +*/ +/// \file input.h -- external (non-keyboard) input devices + +#ifndef INPUT_H +#define INPUT_H + +extern cvar_t in_pitch_min; +extern cvar_t in_pitch_max; + +extern qboolean in_client_mouse; +extern float in_windowmouse_x, in_windowmouse_y; +extern float in_mouse_x, in_mouse_y; + +//enum input_dest_e {input_game,input_message,input_menu} input_dest; + +void IN_Move (void); +// add additional movement on top of the keyboard move cmd + +#define IN_BESTWEAPON_MAX 32 +typedef struct +{ + char name[32]; + int impulse; + int activeweaponcode; + int weaponbit; + int ammostat; + int ammomin; + /// \TODO add a parameter for the picture to be used by the sbar, and use it there +} +in_bestweapon_info_t; +extern in_bestweapon_info_t in_bestweapon_info[IN_BESTWEAPON_MAX]; +void IN_BestWeapon_ResetData(void); ///< call before each map so QC can start from a clean state + +#endif + diff --git a/app/jni/intoverflow.h b/app/jni/intoverflow.h new file mode 100644 index 0000000..df90616 --- /dev/null +++ b/app/jni/intoverflow.h @@ -0,0 +1,22 @@ +#ifndef INTOVERFLOW_H +#define INTOVERFLOW_H + +// simple safe library to handle integer overflows when doing buffer size calculations +// Usage: +// - calculate data size using INTOVERFLOW_??? macros +// - compare: calculated-size <= INTOVERFLOW_NORMALIZE(buffersize) +// Functionality: +// - all overflows (values > INTOVERFLOW_MAX) and errors are mapped to INTOVERFLOW_MAX +// - if any input of an operation is INTOVERFLOW_MAX, INTOVERFLOW_MAX will be returned +// - otherwise, regular arithmetics apply + +#define INTOVERFLOW_MAX 2147483647 + +#define INTOVERFLOW_ADD(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (a) < INTOVERFLOW_MAX - (b)) ? ((a) + (b)) : INTOVERFLOW_MAX) +#define INTOVERFLOW_SUB(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (b) <= (a)) ? ((a) - (b)) : INTOVERFLOW_MAX) +#define INTOVERFLOW_MUL(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (a) < INTOVERFLOW_MAX / (b)) ? ((a) * (b)) : INTOVERFLOW_MAX) +#define INTOVERFLOW_DIV(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (b) > 0) ? ((a) / (b)) : INTOVERFLOW_MAX) + +#define INTOVERFLOW_NORMALIZE(a) (((a) < INTOVERFLOW_MAX) ? (a) : (INTOVERFLOW_MAX - 1)) + +#endif diff --git a/app/jni/jpeg.c b/app/jni/jpeg.c new file mode 100644 index 0000000..4b276d2 --- /dev/null +++ b/app/jni/jpeg.c @@ -0,0 +1,1108 @@ +/* + Copyright (C) 2002 Mathieu Olivier + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#include "quakedef.h" +#include "image.h" +#include "jpeg.h" +#include "image_png.h" + +cvar_t sv_writepicture_quality = {CVAR_SAVE, "sv_writepicture_quality", "10", "WritePicture quality offset (higher means better quality, but slower)"}; +cvar_t r_texture_jpeg_fastpicmip = {CVAR_SAVE, "r_texture_jpeg_fastpicmip", "1", "perform gl_picmip during decompression for JPEG files (faster)"}; + +// jboolean is unsigned char instead of int on Win32 +#ifdef WIN32 +typedef unsigned char jboolean; +#else +typedef int jboolean; +#endif + +#ifdef LINK_TO_LIBJPEG +#include +#define qjpeg_create_compress jpeg_create_compress +#define qjpeg_create_decompress jpeg_create_decompress +#define qjpeg_destroy_compress jpeg_destroy_compress +#define qjpeg_destroy_decompress jpeg_destroy_decompress +#define qjpeg_finish_compress jpeg_finish_compress +#define qjpeg_finish_decompress jpeg_finish_decompress +#define qjpeg_resync_to_restart jpeg_resync_to_restart +#define qjpeg_read_header jpeg_read_header +#define qjpeg_read_scanlines jpeg_read_scanlines +#define qjpeg_set_defaults jpeg_set_defaults +#define qjpeg_set_quality jpeg_set_quality +#define qjpeg_start_compress jpeg_start_compress +#define qjpeg_start_decompress jpeg_start_decompress +#define qjpeg_std_error jpeg_std_error +#define qjpeg_write_scanlines jpeg_write_scanlines +#define qjpeg_simple_progression jpeg_simple_progression +#define jpeg_dll true +#else +/* +================================================================= + + Minimal set of definitions from the JPEG lib + + WARNING: for a matter of simplicity, several pointer types are + casted to "void*", and most enumerated values are not included + +================================================================= +*/ + +typedef void *j_common_ptr; +typedef struct jpeg_compress_struct *j_compress_ptr; +typedef struct jpeg_decompress_struct *j_decompress_ptr; + +#define JPEG_LIB_VERSION 62 // Version 6b + +typedef enum +{ + JCS_UNKNOWN, + JCS_GRAYSCALE, + JCS_RGB, + JCS_YCbCr, + JCS_CMYK, + JCS_YCCK +} J_COLOR_SPACE; +typedef enum {JPEG_DUMMY1} J_DCT_METHOD; +typedef enum {JPEG_DUMMY2} J_DITHER_MODE; +typedef unsigned int JDIMENSION; + +#define JPOOL_PERMANENT 0 // lasts until master record is destroyed +#define JPOOL_IMAGE 1 // lasts until done with image/datastream + +#define JPEG_EOI 0xD9 // EOI marker code + +#define JMSG_STR_PARM_MAX 80 + +#define DCTSIZE2 64 +#define NUM_QUANT_TBLS 4 +#define NUM_HUFF_TBLS 4 +#define NUM_ARITH_TBLS 16 +#define MAX_COMPS_IN_SCAN 4 +#define C_MAX_BLOCKS_IN_MCU 10 +#define D_MAX_BLOCKS_IN_MCU 10 + +struct jpeg_memory_mgr +{ + void* (*alloc_small) (j_common_ptr cinfo, int pool_id, size_t sizeofobject); + void (*_reserve_space_for_alloc_large) (void *dummy, ...); + void (*_reserve_space_for_alloc_sarray) (void *dummy, ...); + void (*_reserve_space_for_alloc_barray) (void *dummy, ...); + void (*_reserve_space_for_request_virt_sarray) (void *dummy, ...); + void (*_reserve_space_for_request_virt_barray) (void *dummy, ...); + void (*_reserve_space_for_realize_virt_arrays) (void *dummy, ...); + void (*_reserve_space_for_access_virt_sarray) (void *dummy, ...); + void (*_reserve_space_for_access_virt_barray) (void *dummy, ...); + void (*_reserve_space_for_free_pool) (void *dummy, ...); + void (*_reserve_space_for_self_destruct) (void *dummy, ...); + + long max_memory_to_use; + long max_alloc_chunk; +}; + +struct jpeg_error_mgr +{ + void (*error_exit) (j_common_ptr cinfo); + void (*emit_message) (j_common_ptr cinfo, int msg_level); + void (*output_message) (j_common_ptr cinfo); + void (*format_message) (j_common_ptr cinfo, char * buffer); + void (*reset_error_mgr) (j_common_ptr cinfo); + int msg_code; + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + int trace_level; + long num_warnings; + const char * const * jpeg_message_table; + int last_jpeg_message; + const char * const * addon_message_table; + int first_addon_message; + int last_addon_message; +}; + +struct jpeg_source_mgr +{ + const unsigned char *next_input_byte; + size_t bytes_in_buffer; + + void (*init_source) (j_decompress_ptr cinfo); + jboolean (*fill_input_buffer) (j_decompress_ptr cinfo); + void (*skip_input_data) (j_decompress_ptr cinfo, long num_bytes); + jboolean (*resync_to_restart) (j_decompress_ptr cinfo, int desired); + void (*term_source) (j_decompress_ptr cinfo); +}; + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + jboolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is currently used only for decompression. + */ + void *quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + +struct jpeg_decompress_struct +{ + struct jpeg_error_mgr *err; // USED + struct jpeg_memory_mgr *mem; // USED + + void *progress; + void *client_data; + jboolean is_decompressor; + int global_state; + + struct jpeg_source_mgr *src; // USED + JDIMENSION image_width; // USED + JDIMENSION image_height; // USED + + int num_components; + J_COLOR_SPACE jpeg_color_space; + J_COLOR_SPACE out_color_space; + unsigned int scale_num, scale_denom; + double output_gamma; + jboolean buffered_image; + jboolean raw_data_out; + J_DCT_METHOD dct_method; + jboolean do_fancy_upsampling; + jboolean do_block_smoothing; + jboolean quantize_colors; + J_DITHER_MODE dither_mode; + jboolean two_pass_quantize; + int desired_number_of_colors; + jboolean enable_1pass_quant; + jboolean enable_external_quant; + jboolean enable_2pass_quant; + JDIMENSION output_width; + + JDIMENSION output_height; // USED + + int out_color_components; + + int output_components; // USED + + int rec_outbuf_height; + int actual_number_of_colors; + void *colormap; + + JDIMENSION output_scanline; // USED + + int input_scan_number; + JDIMENSION input_iMCU_row; + int output_scan_number; + JDIMENSION output_iMCU_row; + int (*coef_bits)[DCTSIZE2]; + void *quant_tbl_ptrs[NUM_QUANT_TBLS]; + void *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + void *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + int data_precision; + jpeg_component_info *comp_info; + jboolean progressive_mode; + jboolean arith_code; + unsigned char arith_dc_L[NUM_ARITH_TBLS]; + unsigned char arith_dc_U[NUM_ARITH_TBLS]; + unsigned char arith_ac_K[NUM_ARITH_TBLS]; + unsigned int restart_interval; + jboolean saw_JFIF_marker; + unsigned char JFIF_major_version; + unsigned char JFIF_minor_version; + unsigned char density_unit; + unsigned short X_density; + unsigned short Y_density; + jboolean saw_Adobe_marker; + unsigned char Adobe_transform; + jboolean CCIR601_sampling; + void *marker_list; + int max_h_samp_factor; + int max_v_samp_factor; + int min_DCT_scaled_size; + JDIMENSION total_iMCU_rows; + void *sample_range_limit; + int comps_in_scan; + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + JDIMENSION MCUs_per_row; + JDIMENSION MCU_rows_in_scan; + int blocks_in_MCU; + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + int Ss, Se, Ah, Al; + int unread_marker; + void *master; + void *main; + void *coef; + void *post; + void *inputctl; + void *marker; + void *entropy; + void *idct; + void *upsample; + void *cconvert; + void *cquantize; +}; + + +struct jpeg_compress_struct +{ + struct jpeg_error_mgr *err; + struct jpeg_memory_mgr *mem; + void *progress; + void *client_data; + jboolean is_decompressor; + int global_state; + + void *dest; + JDIMENSION image_width; + JDIMENSION image_height; + int input_components; + J_COLOR_SPACE in_color_space; + double input_gamma; + int data_precision; + + int num_components; + J_COLOR_SPACE jpeg_color_space; + jpeg_component_info *comp_info; + void *quant_tbl_ptrs[NUM_QUANT_TBLS]; + void *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + void *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + unsigned char arith_dc_L[NUM_ARITH_TBLS]; + unsigned char arith_dc_U[NUM_ARITH_TBLS]; + unsigned char arith_ac_K[NUM_ARITH_TBLS]; + + int num_scans; + const void *scan_info; + jboolean raw_data_in; + jboolean arith_code; + jboolean optimize_coding; + jboolean CCIR601_sampling; + int smoothing_factor; + J_DCT_METHOD dct_method; + + unsigned int restart_interval; + int restart_in_rows; + + jboolean write_JFIF_header; + unsigned char JFIF_major_version; + unsigned char JFIF_minor_version; + unsigned char density_unit; + unsigned short X_density; + unsigned short Y_density; + jboolean write_Adobe_marker; + JDIMENSION next_scanline; + + jboolean progressive_mode; + int max_h_samp_factor; + int max_v_samp_factor; + JDIMENSION total_iMCU_rows; + int comps_in_scan; + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + JDIMENSION MCUs_per_row; + JDIMENSION MCU_rows_in_scan; + int blocks_in_MCU; + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + int Ss, Se, Ah, Al; + + void *master; + void *main; + void *prep; + void *coef; + void *marker; + void *cconvert; + void *downsample; + void *fdct; + void *entropy; + void *script_space; + int script_space_size; +}; + +struct jpeg_destination_mgr +{ + unsigned char* next_output_byte; + size_t free_in_buffer; + + void (*init_destination) (j_compress_ptr cinfo); + jboolean (*empty_output_buffer) (j_compress_ptr cinfo); + void (*term_destination) (j_compress_ptr cinfo); +}; + + +/* +================================================================= + + DarkPlaces definitions + +================================================================= +*/ + +// Functions exported from libjpeg +#define qjpeg_create_compress(cinfo) \ + qjpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof(struct jpeg_compress_struct)) +#define qjpeg_create_decompress(cinfo) \ + qjpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof(struct jpeg_decompress_struct)) + +static void (*qjpeg_CreateCompress) (j_compress_ptr cinfo, int version, size_t structsize); +static void (*qjpeg_CreateDecompress) (j_decompress_ptr cinfo, int version, size_t structsize); +static void (*qjpeg_destroy_compress) (j_compress_ptr cinfo); +static void (*qjpeg_destroy_decompress) (j_decompress_ptr cinfo); +static void (*qjpeg_finish_compress) (j_compress_ptr cinfo); +static jboolean (*qjpeg_finish_decompress) (j_decompress_ptr cinfo); +static jboolean (*qjpeg_resync_to_restart) (j_decompress_ptr cinfo, int desired); +static int (*qjpeg_read_header) (j_decompress_ptr cinfo, jboolean require_image); +static JDIMENSION (*qjpeg_read_scanlines) (j_decompress_ptr cinfo, unsigned char** scanlines, JDIMENSION max_lines); +static void (*qjpeg_set_defaults) (j_compress_ptr cinfo); +static void (*qjpeg_set_quality) (j_compress_ptr cinfo, int quality, jboolean force_baseline); +static jboolean (*qjpeg_start_compress) (j_compress_ptr cinfo, jboolean write_all_tables); +static jboolean (*qjpeg_start_decompress) (j_decompress_ptr cinfo); +static struct jpeg_error_mgr* (*qjpeg_std_error) (struct jpeg_error_mgr *err); +static JDIMENSION (*qjpeg_write_scanlines) (j_compress_ptr cinfo, unsigned char** scanlines, JDIMENSION num_lines); +static void (*qjpeg_simple_progression) (j_compress_ptr cinfo); + +static dllfunction_t jpegfuncs[] = +{ + {"jpeg_CreateCompress", (void **) &qjpeg_CreateCompress}, + {"jpeg_CreateDecompress", (void **) &qjpeg_CreateDecompress}, + {"jpeg_destroy_compress", (void **) &qjpeg_destroy_compress}, + {"jpeg_destroy_decompress", (void **) &qjpeg_destroy_decompress}, + {"jpeg_finish_compress", (void **) &qjpeg_finish_compress}, + {"jpeg_finish_decompress", (void **) &qjpeg_finish_decompress}, + {"jpeg_resync_to_restart", (void **) &qjpeg_resync_to_restart}, + {"jpeg_read_header", (void **) &qjpeg_read_header}, + {"jpeg_read_scanlines", (void **) &qjpeg_read_scanlines}, + {"jpeg_set_defaults", (void **) &qjpeg_set_defaults}, + {"jpeg_set_quality", (void **) &qjpeg_set_quality}, + {"jpeg_start_compress", (void **) &qjpeg_start_compress}, + {"jpeg_start_decompress", (void **) &qjpeg_start_decompress}, + {"jpeg_std_error", (void **) &qjpeg_std_error}, + {"jpeg_write_scanlines", (void **) &qjpeg_write_scanlines}, + {"jpeg_simple_progression", (void **) &qjpeg_simple_progression}, + {NULL, NULL} +}; + +// Handle for JPEG DLL +dllhandle_t jpeg_dll = NULL; +qboolean jpeg_tried_loading = 0; +#endif + +static unsigned char jpeg_eoi_marker [2] = {0xFF, JPEG_EOI}; +static jmp_buf error_in_jpeg; +static qboolean jpeg_toolarge; + +// Our own output manager for JPEG compression +typedef struct +{ + struct jpeg_destination_mgr pub; + + qfile_t* outfile; + unsigned char* buffer; + size_t bufsize; // used if outfile is NULL +} my_destination_mgr; +typedef my_destination_mgr* my_dest_ptr; + + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +JPEG_OpenLibrary + +Try to load the JPEG DLL +==================== +*/ +qboolean JPEG_OpenLibrary (void) +{ +#ifdef LINK_TO_LIBJPEG + return true; +#else + const char* dllnames [] = + { +#if defined(WIN32) + "libjpeg.dll", +#elif defined(MACOSX) + "libjpeg.62.dylib", +#else + "libjpeg.so.62", + "libjpeg.so", +#endif + NULL + }; + + // Already loaded? + if (jpeg_dll) + return true; + + if (jpeg_tried_loading) // only try once + return false; + + jpeg_tried_loading = true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &jpeg_dll, jpegfuncs); +#endif +} + + +/* +==================== +JPEG_CloseLibrary + +Unload the JPEG DLL +==================== +*/ +void JPEG_CloseLibrary (void) +{ +#ifndef LINK_TO_LIBJPEG + Sys_UnloadLibrary (&jpeg_dll); + jpeg_tried_loading = false; // allow retry +#endif +} + + +/* +================================================================= + + JPEG decompression + +================================================================= +*/ + +static void JPEG_Noop (j_decompress_ptr cinfo) {} + +static jboolean JPEG_FillInputBuffer (j_decompress_ptr cinfo) +{ + // Insert a fake EOI marker + cinfo->src->next_input_byte = jpeg_eoi_marker; + cinfo->src->bytes_in_buffer = 2; + + return TRUE; +} + +static void JPEG_SkipInputData (j_decompress_ptr cinfo, long num_bytes) +{ + if (cinfo->src->bytes_in_buffer <= (unsigned long)num_bytes) + { + cinfo->src->bytes_in_buffer = 0; + return; + } + + cinfo->src->next_input_byte += num_bytes; + cinfo->src->bytes_in_buffer -= num_bytes; +} + +static void JPEG_MemSrc (j_decompress_ptr cinfo, const unsigned char *buffer, size_t filesize) +{ + cinfo->src = (struct jpeg_source_mgr *)cinfo->mem->alloc_small ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (struct jpeg_source_mgr)); + + cinfo->src->next_input_byte = buffer; + cinfo->src->bytes_in_buffer = filesize; + + cinfo->src->init_source = JPEG_Noop; + cinfo->src->fill_input_buffer = JPEG_FillInputBuffer; + cinfo->src->skip_input_data = JPEG_SkipInputData; + cinfo->src->resync_to_restart = qjpeg_resync_to_restart; // use the default method + cinfo->src->term_source = JPEG_Noop; +} + +static void JPEG_ErrorExit (j_common_ptr cinfo) +{ + ((struct jpeg_decompress_struct*)cinfo)->err->output_message (cinfo); + longjmp(error_in_jpeg, 1); +} + + +/* +==================== +JPEG_LoadImage + +Load a JPEG image into a BGRA buffer +==================== +*/ +unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + unsigned char *image_buffer = NULL, *scanline = NULL; + unsigned int line; + int submip = 0; + + // No DLL = no JPEGs + if (!jpeg_dll) + return NULL; + + if(miplevel && r_texture_jpeg_fastpicmip.integer) + submip = bound(0, *miplevel, 3); + + cinfo.err = qjpeg_std_error (&jerr); + qjpeg_create_decompress (&cinfo); + if(setjmp(error_in_jpeg)) + goto error_caught; + cinfo.err = qjpeg_std_error (&jerr); + cinfo.err->error_exit = JPEG_ErrorExit; + JPEG_MemSrc (&cinfo, f, filesize); + qjpeg_read_header (&cinfo, TRUE); + cinfo.scale_num = 1; + cinfo.scale_denom = (1 << submip); + qjpeg_start_decompress (&cinfo); + + image_width = cinfo.output_width; + image_height = cinfo.output_height; + + if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) + { + Con_Printf("JPEG_LoadImage: invalid image size %ix%i\n", image_width, image_height); + return NULL; + } + + image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + scanline = (unsigned char *)Mem_Alloc(tempmempool, image_width * cinfo.output_components); + if (!image_buffer || !scanline) + { + if (image_buffer) + Mem_Free (image_buffer); + if (scanline) + Mem_Free (scanline); + + Con_Printf("JPEG_LoadImage: not enough memory for %i by %i image\n", image_width, image_height); + qjpeg_finish_decompress (&cinfo); + qjpeg_destroy_decompress (&cinfo); + return NULL; + } + + // Decompress the image, line by line + line = 0; + while (cinfo.output_scanline < cinfo.output_height) + { + unsigned char *buffer_ptr; + int ind; + + qjpeg_read_scanlines (&cinfo, &scanline, 1); + + // Convert the image to BGRA + switch (cinfo.output_components) + { + // RGB images + case 3: + buffer_ptr = &image_buffer[image_width * line * 4]; + for (ind = 0; ind < image_width * 3; ind += 3, buffer_ptr += 4) + { + buffer_ptr[2] = scanline[ind]; + buffer_ptr[1] = scanline[ind + 1]; + buffer_ptr[0] = scanline[ind + 2]; + buffer_ptr[3] = 255; + } + break; + + // Greyscale images (default to it, just in case) + case 1: + default: + buffer_ptr = &image_buffer[image_width * line * 4]; + for (ind = 0; ind < image_width; ind++, buffer_ptr += 4) + { + buffer_ptr[0] = scanline[ind]; + buffer_ptr[1] = scanline[ind]; + buffer_ptr[2] = scanline[ind]; + buffer_ptr[3] = 255; + } + } + + line++; + } + Mem_Free (scanline); scanline = NULL; + + qjpeg_finish_decompress (&cinfo); + qjpeg_destroy_decompress (&cinfo); + + if(miplevel) + *miplevel -= submip; + + return image_buffer; + +error_caught: + if(scanline) + Mem_Free (scanline); + if(image_buffer) + Mem_Free (image_buffer); + qjpeg_destroy_decompress (&cinfo); + return NULL; +} + + +/* +================================================================= + + JPEG compression + +================================================================= +*/ + +#define JPEG_OUTPUT_BUF_SIZE 4096 +static void JPEG_InitDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + dest->buffer = (unsigned char*)cinfo->mem->alloc_small ((j_common_ptr) cinfo, JPOOL_IMAGE, JPEG_OUTPUT_BUF_SIZE * sizeof(unsigned char)); + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; +} + +static jboolean JPEG_EmptyOutputBuffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + + if (FS_Write (dest->outfile, dest->buffer, JPEG_OUTPUT_BUF_SIZE) != (size_t) JPEG_OUTPUT_BUF_SIZE) + longjmp(error_in_jpeg, 1); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; + return true; +} + +static void JPEG_TermDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + size_t datacount = JPEG_OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + // Write any data remaining in the buffer + if (datacount > 0) + if (FS_Write (dest->outfile, dest->buffer, datacount) != (fs_offset_t)datacount) + longjmp(error_in_jpeg, 1); +} + +static void JPEG_FileDest (j_compress_ptr cinfo, qfile_t* outfile) +{ + my_dest_ptr dest; + + // First time for this JPEG object? + if (cinfo->dest == NULL) + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_destination_mgr)); + + dest = (my_dest_ptr)cinfo->dest; + dest->pub.init_destination = JPEG_InitDestination; + dest->pub.empty_output_buffer = JPEG_EmptyOutputBuffer; + dest->pub.term_destination = JPEG_TermDestination; + dest->outfile = outfile; +} + +static void JPEG_Mem_InitDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = dest->bufsize; +} + +static jboolean JPEG_Mem_EmptyOutputBuffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + jpeg_toolarge = true; + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = dest->bufsize; + return true; +} + +static void JPEG_Mem_TermDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + dest->bufsize = dest->pub.next_output_byte - dest->buffer; +} +static void JPEG_MemDest (j_compress_ptr cinfo, void* buf, size_t bufsize) +{ + my_dest_ptr dest; + + // First time for this JPEG object? + if (cinfo->dest == NULL) + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_destination_mgr)); + + dest = (my_dest_ptr)cinfo->dest; + dest->pub.init_destination = JPEG_Mem_InitDestination; + dest->pub.empty_output_buffer = JPEG_Mem_EmptyOutputBuffer; + dest->pub.term_destination = JPEG_Mem_TermDestination; + dest->outfile = NULL; + + dest->buffer = (unsigned char *) buf; + dest->bufsize = bufsize; +} + + +/* +==================== +JPEG_SaveImage_preflipped + +Save a preflipped JPEG image to a file +==================== +*/ +qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + unsigned char *scanline; + unsigned int offset, linesize; + qfile_t* file; + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + // Open the file + file = FS_OpenRealFile(filename, "wb", true); + if (!file) + return false; + + if(setjmp(error_in_jpeg)) + goto error_caught; + cinfo.err = qjpeg_std_error (&jerr); + cinfo.err->error_exit = JPEG_ErrorExit; + + qjpeg_create_compress (&cinfo); + JPEG_FileDest (&cinfo, file); + + // Set the parameters for compression + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.in_color_space = JCS_RGB; + cinfo.input_components = 3; + qjpeg_set_defaults (&cinfo); + qjpeg_set_quality (&cinfo, (int)(scr_screenshot_jpeg_quality.value * 100), TRUE); + qjpeg_simple_progression (&cinfo); + + // turn off subsampling (to make text look better) + cinfo.optimize_coding = 1; + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + + qjpeg_start_compress (&cinfo, true); + + // Compress each scanline + linesize = cinfo.image_width * 3; + offset = linesize * (cinfo.image_height - 1); + while (cinfo.next_scanline < cinfo.image_height) + { + scanline = &data[offset - cinfo.next_scanline * linesize]; + + qjpeg_write_scanlines (&cinfo, &scanline, 1); + } + + qjpeg_finish_compress (&cinfo); + qjpeg_destroy_compress (&cinfo); + + FS_Close (file); + return true; + +error_caught: + qjpeg_destroy_compress (&cinfo); + FS_Close (file); + return false; +} + +static size_t JPEG_try_SaveImage_to_Buffer (struct jpeg_compress_struct *cinfo, char *jpegbuf, size_t jpegsize, int quality, int width, int height, unsigned char *data) +{ + unsigned char *scanline; + unsigned int linesize; + + jpeg_toolarge = false; + JPEG_MemDest (cinfo, jpegbuf, jpegsize); + + // Set the parameters for compression + cinfo->image_width = width; + cinfo->image_height = height; + cinfo->in_color_space = JCS_RGB; + cinfo->input_components = 3; + qjpeg_set_defaults (cinfo); + qjpeg_set_quality (cinfo, quality, FALSE); + + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 1; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 1; + cinfo->optimize_coding = 1; + + qjpeg_start_compress (cinfo, true); + + // Compress each scanline + linesize = width * 3; + while (cinfo->next_scanline < cinfo->image_height) + { + scanline = &data[cinfo->next_scanline * linesize]; + + qjpeg_write_scanlines (cinfo, &scanline, 1); + } + + qjpeg_finish_compress (cinfo); + + if(jpeg_toolarge) + return 0; + + return ((my_dest_ptr) cinfo->dest)->bufsize; +} + +size_t JPEG_SaveImage_to_Buffer (char *jpegbuf, size_t jpegsize, int width, int height, unsigned char *data) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + + int quality; + int quality_guess; + size_t result; + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + if(setjmp(error_in_jpeg)) + goto error_caught; + cinfo.err = qjpeg_std_error (&jerr); + cinfo.err->error_exit = JPEG_ErrorExit; + + qjpeg_create_compress (&cinfo); + +#if 0 + // used to get the formula below + { + char buf[1048576]; + unsigned char *img; + int i; + + img = Mem_Alloc(tempmempool, width * height * 3); + for(i = 0; i < width * height * 3; ++i) + img[i] = rand() & 0xFF; + + for(i = 0; i <= 100; ++i) + { + Con_Printf("! %d %d %d %d\n", width, height, i, (int) JPEG_try_SaveImage_to_Buffer(&cinfo, buf, sizeof(buf), i, width, height, img)); + } + + Mem_Free(img); + } +#endif + + //quality_guess = (100 * jpegsize - 41000) / (width*height) + 2; // fits random data + quality_guess = (256 * jpegsize - 81920) / (width*height) - 8; // fits Nexuiz's/Xonotic's map pictures + + quality_guess = bound(0, quality_guess, 100); + quality = bound(0, quality_guess + sv_writepicture_quality.integer, 100); // assume it can do 10 failed attempts + + while(!(result = JPEG_try_SaveImage_to_Buffer(&cinfo, jpegbuf, jpegsize, quality, width, height, data))) + { + --quality; + if(quality < 0) + { + Con_Printf("couldn't write image at all, probably too big\n"); + return 0; + } + } + qjpeg_destroy_compress (&cinfo); + Con_DPrintf("JPEG_SaveImage_to_Buffer: guessed quality/size %d/%d, actually got %d/%d\n", quality_guess, (int)jpegsize, quality, (int)result); + + return result; + +error_caught: + qjpeg_destroy_compress (&cinfo); + return 0; +} + +typedef struct CompressedImageCacheItem +{ + char imagename[MAX_QPATH]; + size_t maxsize; + void *compressed; + size_t compressed_size; + struct CompressedImageCacheItem *next; +} +CompressedImageCacheItem; +#define COMPRESSEDIMAGECACHE_SIZE 4096 +static CompressedImageCacheItem *CompressedImageCache[COMPRESSEDIMAGECACHE_SIZE]; + +static void CompressedImageCache_Add(const char *imagename, size_t maxsize, void *compressed, size_t compressed_size) +{ + char vabuf[1024]; + const char *hashkey = va(vabuf, sizeof(vabuf), "%s:%d", imagename, (int) maxsize); + int hashindex = CRC_Block((unsigned char *) hashkey, strlen(hashkey)) % COMPRESSEDIMAGECACHE_SIZE; + CompressedImageCacheItem *i; + + if(strlen(imagename) >= MAX_QPATH) + return; // can't add this + + i = (CompressedImageCacheItem*) Z_Malloc(sizeof(CompressedImageCacheItem)); + strlcpy(i->imagename, imagename, sizeof(i->imagename)); + i->maxsize = maxsize; + i->compressed = compressed; + i->compressed_size = compressed_size; + i->next = CompressedImageCache[hashindex]; + CompressedImageCache[hashindex] = i; +} + +static CompressedImageCacheItem *CompressedImageCache_Find(const char *imagename, size_t maxsize) +{ + char vabuf[1024]; + const char *hashkey = va(vabuf, sizeof(vabuf), "%s:%d", imagename, (int) maxsize); + int hashindex = CRC_Block((unsigned char *) hashkey, strlen(hashkey)) % COMPRESSEDIMAGECACHE_SIZE; + CompressedImageCacheItem *i = CompressedImageCache[hashindex]; + + while(i) + { + if(i->maxsize == maxsize) + if(!strcmp(i->imagename, imagename)) + return i; + i = i->next; + } + return NULL; +} + +qboolean Image_Compress(const char *imagename, size_t maxsize, void **buf, size_t *size) +{ + unsigned char *imagedata, *newimagedata; + int maxPixelCount; + int components[3] = {2, 1, 0}; + CompressedImageCacheItem *i; + + JPEG_OpenLibrary (); // for now; LH had the idea of replacing this by a better format + PNG_OpenLibrary (); // for loading + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + i = CompressedImageCache_Find(imagename, maxsize); + if(i) + { + *size = i->compressed_size; + *buf = i->compressed; + } + + // load the image + imagedata = loadimagepixelsbgra(imagename, true, false, false, NULL); + if(!imagedata) + return false; + + // find an appropriate size for somewhat okay compression + if(maxsize <= 768) + maxPixelCount = 32 * 32; + else if(maxsize <= 1024) + maxPixelCount = 64 * 64; + else if(maxsize <= 4096) + maxPixelCount = 128 * 128; + else + maxPixelCount = 256 * 256; + + while(image_width * image_height > maxPixelCount) + { + int one = 1; + Image_MipReduce32(imagedata, imagedata, &image_width, &image_height, &one, image_width/2, image_height/2, 1); + } + + newimagedata = (unsigned char *) Mem_Alloc(tempmempool, image_width * image_height * 3); + + // convert the image from BGRA to RGB + Image_CopyMux(newimagedata, imagedata, image_width, image_height, false, false, false, 3, 4, components); + Mem_Free(imagedata); + + // try to compress it to JPEG + *buf = Z_Malloc(maxsize); + *size = JPEG_SaveImage_to_Buffer((char *) *buf, maxsize, image_width, image_height, newimagedata); + Mem_Free(newimagedata); + + if(!*size) + { + Z_Free(*buf); + *buf = NULL; + Con_Printf("could not compress image %s to %d bytes\n", imagename, (int)maxsize); + // return false; + // also cache failures! + } + + // store it in the cache + CompressedImageCache_Add(imagename, maxsize, *buf, *size); + return (*buf != NULL); +} diff --git a/app/jni/jpeg.h b/app/jni/jpeg.h new file mode 100644 index 0000000..ff17eb7 --- /dev/null +++ b/app/jni/jpeg.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2002 Mathieu Olivier + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef JPEG_H +#define JPEG_H + + +qboolean JPEG_OpenLibrary (void); +void JPEG_CloseLibrary (void); +unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel); +qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data); + +/*! \returns 0 if failed, or the size actually used. + */ +size_t JPEG_SaveImage_to_Buffer (char *jpegbuf, size_t jpegsize, int width, int height, unsigned char *data); +qboolean Image_Compress(const char *imagename, size_t maxsize, void **buf, size_t *size); + + +#endif diff --git a/app/jni/keys.c b/app/jni/keys.c new file mode 100644 index 0000000..724d8c1 --- /dev/null +++ b/app/jni/keys.c @@ -0,0 +1,2003 @@ +/* + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + +#include "quakedef.h" +#include "cl_video.h" +#include "utf8lib.h" +#include "csprogs.h" + +cvar_t con_closeontoggleconsole = {CVAR_SAVE, "con_closeontoggleconsole","1", "allows toggleconsole binds to close the console as well; when set to 2, this even works when not at the start of the line in console input; when set to 3, this works even if the toggleconsole key is the color tag"}; + +/* +key up events are sent even if in console mode +*/ + +char key_line[MAX_INPUTLINE]; +int key_linepos; +qboolean key_insert = true; // insert key toggle (for editing) +keydest_t key_dest; +int key_consoleactive; +char *keybindings[MAX_BINDMAPS][MAX_KEYS]; + +int history_line; +char history_savedline[MAX_INPUTLINE]; +char history_searchstring[MAX_INPUTLINE]; +qboolean history_matchfound = false; +conbuffer_t history; + +extern cvar_t con_textsize; + + +static void Key_History_Init(void) +{ + qfile_t *historyfile; + ConBuffer_Init(&history, HIST_TEXTSIZE, HIST_MAXLINES, zonemempool); + + historyfile = FS_OpenRealFile("darkplaces_history.txt", "rb", false); // rb to handle unix line endings on windows too + if(historyfile) + { + char buf[MAX_INPUTLINE]; + int bufpos; + int c; + + bufpos = 0; + for(;;) + { + c = FS_Getc(historyfile); + if(c < 0 || c == 0 || c == '\r' || c == '\n') + { + if(bufpos > 0) + { + buf[bufpos] = 0; + ConBuffer_AddLine(&history, buf, bufpos, 0); + bufpos = 0; + } + if(c < 0) + break; + } + else + { + if(bufpos < MAX_INPUTLINE - 1) + buf[bufpos++] = c; + } + } + + FS_Close(historyfile); + } + + history_line = -1; +} + +static void Key_History_Shutdown(void) +{ + // TODO write history to a file + + qfile_t *historyfile = FS_OpenRealFile("darkplaces_history.txt", "w", false); + if(historyfile) + { + int i; + for(i = 0; i < CONBUFFER_LINES_COUNT(&history); ++i) + FS_Printf(historyfile, "%s\n", ConBuffer_GetLine(&history, i)); + FS_Close(historyfile); + } + + ConBuffer_Shutdown(&history); +} + +static void Key_History_Push(void) +{ + if(key_line[1]) // empty? + if(strcmp(key_line, "]quit")) // putting these into the history just sucks + if(strncmp(key_line, "]quit ", 6)) // putting these into the history just sucks + if(strcmp(key_line, "]rcon_password")) // putting these into the history just sucks + if(strncmp(key_line, "]rcon_password ", 15)) // putting these into the history just sucks + ConBuffer_AddLine(&history, key_line + 1, strlen(key_line) - 1, 0); + Con_Printf("%s\n", key_line); // don't mark empty lines as history + history_line = -1; + if (history_matchfound) + history_matchfound = false; +} + +static qboolean Key_History_Get_foundCommand(void) +{ + if (!history_matchfound) + return false; + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + history_matchfound = false; + return true; +} + +static void Key_History_Up(void) +{ + if(history_line == -1) // editing the "new" line + strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); + + if (Key_History_Get_foundCommand()) + return; + + if(history_line == -1) + { + history_line = CONBUFFER_LINES_COUNT(&history) - 1; + if(history_line != -1) + { + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + } + } + else if(history_line > 0) + { + --history_line; // this also does -1 -> 0, so it is good + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + } +} + +static void Key_History_Down(void) +{ + if(history_line == -1) // editing the "new" line + return; + + if (Key_History_Get_foundCommand()) + return; + + if(history_line < CONBUFFER_LINES_COUNT(&history) - 1) + { + ++history_line; + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + } + else + { + history_line = -1; + strlcpy(key_line + 1, history_savedline, sizeof(key_line) - 1); + } + + key_linepos = strlen(key_line); +} + +static void Key_History_First(void) +{ + if(history_line == -1) // editing the "new" line + strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); + + if (CONBUFFER_LINES_COUNT(&history) > 0) + { + history_line = 0; + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + } +} + +static void Key_History_Last(void) +{ + if(history_line == -1) // editing the "new" line + strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); + + if (CONBUFFER_LINES_COUNT(&history) > 0) + { + history_line = CONBUFFER_LINES_COUNT(&history) - 1; + strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); + key_linepos = strlen(key_line); + } +} + +static void Key_History_Find_Backwards(void) +{ + int i; + const char *partial = key_line + 1; + char vabuf[1024]; + size_t digits = strlen(va(vabuf, sizeof(vabuf), "%i", HIST_MAXLINES)); + + if (history_line == -1) // editing the "new" line + strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); + + if (strcmp(key_line + 1, history_searchstring)) // different string? Start a new search + { + strlcpy(history_searchstring, key_line + 1, sizeof(history_searchstring)); + i = CONBUFFER_LINES_COUNT(&history) - 1; + } + else if (history_line == -1) + i = CONBUFFER_LINES_COUNT(&history) - 1; + else + i = history_line - 1; + + if (!*partial) + partial = "*"; + else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? + partial = va(vabuf, sizeof(vabuf), "*%s*", partial); + + for ( ; i >= 0; i--) + if (matchpattern_with_separator(ConBuffer_GetLine(&history, i), partial, true, "", false)) + { + Con_Printf("^2%*i^7 %s\n", (int)digits, i+1, ConBuffer_GetLine(&history, i)); + history_line = i; + history_matchfound = true; + return; + } +} + +static void Key_History_Find_Forwards(void) +{ + int i; + const char *partial = key_line + 1; + char vabuf[1024]; + size_t digits = strlen(va(vabuf, sizeof(vabuf), "%i", HIST_MAXLINES)); + + if (history_line == -1) // editing the "new" line + return; + + if (strcmp(key_line + 1, history_searchstring)) // different string? Start a new search + { + strlcpy(history_searchstring, key_line + 1, sizeof(history_searchstring)); + i = 0; + } + else i = history_line + 1; + + if (!*partial) + partial = "*"; + else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? + partial = va(vabuf, sizeof(vabuf), "*%s*", partial); + + for ( ; i < CONBUFFER_LINES_COUNT(&history); i++) + if (matchpattern_with_separator(ConBuffer_GetLine(&history, i), partial, true, "", false)) + { + Con_Printf("^2%*i^7 %s\n", (int)digits, i+1, ConBuffer_GetLine(&history, i)); + history_line = i; + history_matchfound = true; + return; + } +} + +static void Key_History_Find_All(void) +{ + const char *partial = key_line + 1; + int i, count = 0; + char vabuf[1024]; + size_t digits = strlen(va(vabuf, sizeof(vabuf), "%i", HIST_MAXLINES)); + Con_Printf("History commands containing \"%s\":\n", key_line + 1); + + if (!*partial) + partial = "*"; + else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? + partial = va(vabuf, sizeof(vabuf), "*%s*", partial); + + for (i=0; i 1) + { + if (!strcmp(Cmd_Argv (1), "-c")) + { + ConBuffer_Clear(&history); + return; + } + i = strtol(Cmd_Argv (1), &errchar, 0); + if ((i < 0) || (i > CONBUFFER_LINES_COUNT(&history)) || (errchar && *errchar)) + i = 0; + else + i = CONBUFFER_LINES_COUNT(&history) - i; + } + + for ( ; i= MAX_INPUTLINE) + i= MAX_INPUTLINE - key_linepos - 1; + if (i > 0) + { + // terencehill: insert the clipboard text between the characters of the line + /* + char *temp = (char *) Z_Malloc(MAX_INPUTLINE); + cbd[i]=0; + temp[0]=0; + if ( key_linepos < (int)strlen(key_line) ) + strlcpy(temp, key_line + key_linepos, (int)strlen(key_line) - key_linepos +1); + key_line[key_linepos] = 0; + strlcat(key_line, cbd, sizeof(key_line)); + if (temp[0]) + strlcat(key_line, temp, sizeof(key_line)); + Z_Free(temp); + key_linepos += i; + */ + // blub: I'm changing this to use memmove() like the rest of the code does. + cbd[i] = 0; + memmove(key_line + key_linepos + i, key_line + key_linepos, sizeof(key_line) - key_linepos - i); + memcpy(key_line + key_linepos, cbd, i); + key_linepos += i; + } + Z_Free(cbd); + } + return; + } + + if (key == 'l' && keydown[K_CTRL]) + { + Cbuf_AddText ("clear\n"); + return; + } + + if (key == 'u' && keydown[K_CTRL]) // like vi/readline ^u: delete currently edited line + { + // clear line + key_line[0] = ']'; + key_line[1] = 0; + key_linepos = 1; + return; + } + + if (key == 'q' && keydown[K_CTRL]) // like zsh ^q: push line to history, don't execute, and clear + { + // clear line + Key_History_Push(); + key_line[0] = ']'; + key_line[1] = 0; + key_linepos = 1; + return; + } + + if (key == K_ENTER || key == K_KP_ENTER) + { + Cbuf_AddText (key_line+1); // skip the ] + Cbuf_AddText ("\n"); + Key_History_Push(); + key_line[0] = ']'; + key_line[1] = 0; // EvilTypeGuy: null terminate + key_linepos = 1; + // force an update, because the command may take some time + if (cls.state == ca_disconnected) + { + CL_BeginUpdateScreen(); + SCR_DrawScreen(); + CL_EndUpdateScreen(); + } + return; + } + + if (key == K_TAB) + { + if(keydown[K_CTRL]) // append to the cvar its value + { + int cvar_len, cvar_str_len, chars_to_move; + char k; + char cvar[MAX_INPUTLINE]; + const char *cvar_str; + + // go to the start of the variable + while(--key_linepos) + { + k = key_line[key_linepos]; + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + } + key_linepos++; + + // save the variable name in cvar + for(cvar_len=0; (k = key_line[key_linepos + cvar_len]) != 0; cvar_len++) + { + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + cvar[cvar_len] = k; + } + if (cvar_len==0) + return; + cvar[cvar_len] = 0; + + // go to the end of the cvar + key_linepos += cvar_len; + + // save the content of the variable in cvar_str + cvar_str = Cvar_VariableString(cvar); + cvar_str_len = strlen(cvar_str); + if (cvar_str_len==0) + return; + + // insert space and cvar_str in key_line + chars_to_move = strlen(&key_line[key_linepos]); + if (key_linepos + 1 + cvar_str_len + chars_to_move < MAX_INPUTLINE) + { + if (chars_to_move) + memmove(&key_line[key_linepos + 1 + cvar_str_len], &key_line[key_linepos], chars_to_move); + key_line[key_linepos++] = ' '; + memcpy(&key_line[key_linepos], cvar_str, cvar_str_len); + key_linepos += cvar_str_len; + key_line[key_linepos + chars_to_move] = 0; + } + else + Con_Printf("Couldn't append cvar value, edit line too long.\n"); + return; + } + // Enhanced command completion + // by EvilTypeGuy eviltypeguy@qeradiant.com + // Thanks to Fett, Taniwha + Con_CompleteCommandLine(); + return; + } + + // Advanced Console Editing by Radix radix@planetquake.com + // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com + // Enhanced by [515] + // Enhanced by terencehill + + // move cursor to the previous character + if (key == K_LEFTARROW || key == K_KP_LEFTARROW) + { + if (key_linepos < 2) + return; + if(keydown[K_CTRL]) // move cursor to the previous word + { + int pos; + char k; + pos = key_linepos-1; + + if(pos) // skip all "; ' after the word + while(--pos) + { + k = key_line[pos]; + if (!(k == '\"' || k == ';' || k == ' ' || k == '\'')) + break; + } + + if(pos) + while(--pos) + { + k = key_line[pos]; + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + } + key_linepos = pos + 1; + } + else if(keydown[K_SHIFT]) // move cursor to the previous character ignoring colors + { + int pos; + size_t inchar = 0; + pos = u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte + while (pos) + if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && isdigit(key_line[pos])) + pos-=2; + else if(pos-4 > 0 && key_line[pos-4] == STRING_COLOR_TAG && key_line[pos-3] == STRING_COLOR_RGB_TAG_CHAR + && isxdigit(key_line[pos-2]) && isxdigit(key_line[pos-1]) && isxdigit(key_line[pos])) + pos-=5; + else + { + if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && key_line[pos] == STRING_COLOR_TAG) // consider ^^ as a character + pos--; + pos--; + break; + } + // we need to move to the beginning of the character when in a wide character: + u8_charidx(key_line, pos + 1, &inchar); + key_linepos = pos + 1 - inchar; + } + else + { + key_linepos = u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte + } + return; + } + + // delete char before cursor + if (key == K_BACKSPACE || (key == 'h' && keydown[K_CTRL])) + { + if (key_linepos > 1) + { + int newpos = u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte + strlcpy(key_line + newpos, key_line + key_linepos, sizeof(key_line) + 1 - key_linepos); + key_linepos = newpos; + } + return; + } + + // delete char on cursor + if (key == K_DEL || key == K_KP_DEL) + { + size_t linelen; + linelen = strlen(key_line); + if (key_linepos < (int)linelen) + memmove(key_line + key_linepos, key_line + key_linepos + u8_bytelen(key_line + key_linepos, 1), linelen - key_linepos); + return; + } + + + // move cursor to the next character + if (key == K_RIGHTARROW || key == K_KP_RIGHTARROW) + { + if (key_linepos >= (int)strlen(key_line)) + return; + if(keydown[K_CTRL]) // move cursor to the next word + { + int pos, len; + char k; + len = (int)strlen(key_line); + pos = key_linepos; + + while(++pos < len) + { + k = key_line[pos]; + if(k == '\"' || k == ';' || k == ' ' || k == '\'') + break; + } + + if (pos < len) // skip all "; ' after the word + while(++pos < len) + { + k = key_line[pos]; + if (!(k == '\"' || k == ';' || k == ' ' || k == '\'')) + break; + } + key_linepos = pos; + } + else if(keydown[K_SHIFT]) // move cursor to the next character ignoring colors + { + int pos, len; + len = (int)strlen(key_line); + pos = key_linepos; + + // go beyond all initial consecutive color tags, if any + if(pos < len) + while (key_line[pos] == STRING_COLOR_TAG) + { + if(isdigit(key_line[pos+1])) + pos+=2; + else if(key_line[pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos+2]) && isxdigit(key_line[pos+3]) && isxdigit(key_line[pos+4])) + pos+=5; + else + break; + } + + // skip the char + if (key_line[pos] == STRING_COLOR_TAG && key_line[pos+1] == STRING_COLOR_TAG) // consider ^^ as a character + pos++; + pos += u8_bytelen(key_line + pos, 1); + + // now go beyond all next consecutive color tags, if any + if(pos < len) + while (key_line[pos] == STRING_COLOR_TAG) + { + if(isdigit(key_line[pos+1])) + pos+=2; + else if(key_line[pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos+2]) && isxdigit(key_line[pos+3]) && isxdigit(key_line[pos+4])) + pos+=5; + else + break; + } + key_linepos = pos; + } + else + key_linepos += u8_bytelen(key_line + key_linepos, 1); + return; + } + + if (key == K_INS || key == K_KP_INS) // toggle insert mode + { + key_insert ^= 1; + return; + } + + // End Advanced Console Editing + + if (key == K_UPARROW || key == K_KP_UPARROW || (key == 'p' && keydown[K_CTRL])) + { + Key_History_Up(); + return; + } + + if (key == K_DOWNARROW || key == K_KP_DOWNARROW || (key == 'n' && keydown[K_CTRL])) + { + Key_History_Down(); + return; + } + // ~1.0795 = 82/76 using con_textsize 64 76 is height of the char, 6 is the distance between 2 lines + + if (keydown[K_CTRL]) + { + // prints all the matching commands + if (key == 'f') + { + Key_History_Find_All(); + return; + } + // Search forwards/backwards, pointing the history's index to the + // matching command but without fetching it to let one continue the search. + // To fetch it, it suffices to just press UP or DOWN. + if (key == 'r') + { + if (keydown[K_SHIFT]) + Key_History_Find_Forwards(); + else + Key_History_Find_Backwards(); + return; + } + // go to the last/first command of the history + if (key == ',') + { + Key_History_First(); + return; + } + if (key == '.') + { + Key_History_Last(); + return; + } + } + + if (key == K_PGUP || key == K_KP_PGUP) + { + if(keydown[K_CTRL]) + { + con_backscroll += ((vid_conheight.integer >> 2) / con_textsize.integer)-1; + } + else + con_backscroll += ((vid_conheight.integer >> 1) / con_textsize.integer)-3; + return; + } + + if (key == K_PGDN || key == K_KP_PGDN) + { + if(keydown[K_CTRL]) + { + con_backscroll -= ((vid_conheight.integer >> 2) / con_textsize.integer)-1; + } + else + con_backscroll -= ((vid_conheight.integer >> 1) / con_textsize.integer)-3; + return; + } + + if (key == K_MWHEELUP) + { + if(keydown[K_CTRL]) + con_backscroll += 1; + else if(keydown[K_SHIFT]) + con_backscroll += ((vid_conheight.integer >> 2) / con_textsize.integer)-1; + else + con_backscroll += 5; + return; + } + + if (key == K_MWHEELDOWN) + { + if(keydown[K_CTRL]) + con_backscroll -= 1; + else if(keydown[K_SHIFT]) + con_backscroll -= ((vid_conheight.integer >> 2) / con_textsize.integer)-1; + else + con_backscroll -= 5; + return; + } + + if (keydown[K_CTRL]) + { + // text zoom in + if (key == '+' || key == K_KP_PLUS) + { + if (con_textsize.integer < 128) + Cvar_SetValueQuick(&con_textsize, con_textsize.integer + 1); + return; + } + // text zoom out + if (key == '-' || key == K_KP_MINUS) + { + if (con_textsize.integer > 1) + Cvar_SetValueQuick(&con_textsize, con_textsize.integer - 1); + return; + } + // text zoom reset + if (key == '0' || key == K_KP_INS) + { + Cvar_SetValueQuick(&con_textsize, atoi(Cvar_VariableDefString("con_textsize"))); + return; + } + } + + if (key == K_HOME || key == K_KP_HOME) + { + if (keydown[K_CTRL]) + con_backscroll = CON_TEXTSIZE; + else + key_linepos = 1; + return; + } + + if (key == K_END || key == K_KP_END) + { + if (keydown[K_CTRL]) + con_backscroll = 0; + else + key_linepos = (int)strlen(key_line); + return; + } + + // non printable + if (unicode < 32) + return; + + if (key_linepos < MAX_INPUTLINE-1) + { + char buf[16]; + int len; + int blen; + blen = u8_fromchar(unicode, buf, sizeof(buf)); + if (!blen) + return; + len = (int)strlen(&key_line[key_linepos]); + // check insert mode, or always insert if at end of line + if (key_insert || len == 0) + { + // can't use strcpy to move string to right + len++; + //memmove(&key_line[key_linepos + u8_bytelen(key_line + key_linepos, 1)], &key_line[key_linepos], len); + memmove(&key_line[key_linepos + blen], &key_line[key_linepos], len); + } + memcpy(key_line + key_linepos, buf, blen); + key_linepos += blen; + //key_linepos += u8_fromchar(unicode, key_line + key_linepos, sizeof(key_line) - key_linepos - 1); + //key_line[key_linepos] = ascii; + //key_linepos++; + } +} + +//============================================================================ + +int chat_mode; +char chat_buffer[MAX_INPUTLINE]; +unsigned int chat_bufferlen = 0; + +static void +Key_Message (int key, int ascii) +{ + char vabuf[1024]; + if (key == K_ENTER || ascii == 10 || ascii == 13) + { + if(chat_mode < 0) + Cmd_ExecuteString(chat_buffer, src_command, true); // not Cbuf_AddText to allow semiclons in args; however, this allows no variables then. Use aliases! + else + Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "%s %s", chat_mode ? "say_team" : "say ", chat_buffer)); + + key_dest = key_game; + chat_bufferlen = 0; + chat_buffer[0] = 0; + return; + } + + // TODO add support for arrow keys and simple editing + + if (key == K_ESCAPE) { + key_dest = key_game; + chat_bufferlen = 0; + chat_buffer[0] = 0; + return; + } + + if (key == K_BACKSPACE) { + if (chat_bufferlen) { + chat_bufferlen = u8_prevbyte(chat_buffer, chat_bufferlen); + chat_buffer[chat_bufferlen] = 0; + } + return; + } + + if(key == K_TAB) { + chat_bufferlen = Nicks_CompleteChatLine(chat_buffer, sizeof(chat_buffer), chat_bufferlen); + return; + } + + // ctrl+key generates an ascii value < 32 and shows a char from the charmap + if (ascii > 0 && ascii < 32 && utf8_enable.integer) + ascii = 0xE000 + ascii; + + if (chat_bufferlen == sizeof (chat_buffer) - 1) + return; // all full + + if (!ascii) + return; // non printable + + chat_bufferlen += u8_fromchar(ascii, chat_buffer+chat_bufferlen, sizeof(chat_buffer) - chat_bufferlen - 1); + + //chat_buffer[chat_bufferlen++] = ascii; + //chat_buffer[chat_bufferlen] = 0; +} + +//============================================================================ + + +/* +=================== +Returns a key number to be used to index keybindings[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. +=================== +*/ +int +Key_StringToKeynum (const char *str) +{ + const keyname_t *kn; + + if (!str || !str[0]) + return -1; + if (!str[1]) + return tolower(str[0]); + + for (kn = keynames; kn->name; kn++) { + if (!strcasecmp (str, kn->name)) + return kn->keynum; + } + return -1; +} + +/* +=================== +Returns a string (either a single ascii char, or a K_* name) for the +given keynum. +FIXME: handle quote special (general escape sequence?) +=================== +*/ +const char * +Key_KeynumToString (int keynum, char *tinystr, size_t tinystrlength) +{ + const keyname_t *kn; + + // -1 is an invalid code + if (keynum < 0) + return ""; + + // search overrides first, because some characters are special + for (kn = keynames; kn->name; kn++) + if (keynum == kn->keynum) + return kn->name; + + // if it is printable, output it as a single character + if (keynum > 32 && keynum < 256) + { + if (tinystrlength >= 2) + { + tinystr[0] = keynum; + tinystr[1] = 0; + } + return tinystr; + } + + // if it is not overridden and not printable, we don't know what to do with it + return ""; +} + + +qboolean +Key_SetBinding (int keynum, int bindmap, const char *binding) +{ + char *newbinding; + size_t l; + + if (keynum == -1 || keynum >= MAX_KEYS) + return false; + if ((bindmap < 0) || (bindmap >= MAX_BINDMAPS)) + return false; + +// free old bindings + if (keybindings[bindmap][keynum]) { + Z_Free (keybindings[bindmap][keynum]); + keybindings[bindmap][keynum] = NULL; + } + if(!binding[0]) // make "" binds be removed --blub + return true; +// allocate memory for new binding + l = strlen (binding); + newbinding = (char *)Z_Malloc (l + 1); + memcpy (newbinding, binding, l + 1); + newbinding[l] = 0; + keybindings[bindmap][keynum] = newbinding; + return true; +} + +void Key_GetBindMap(int *fg, int *bg) +{ + if(fg) + *fg = key_bmap; + if(bg) + *bg = key_bmap2; +} + +qboolean Key_SetBindMap(int fg, int bg) +{ + if(fg >= MAX_BINDMAPS) + return false; + if(bg >= MAX_BINDMAPS) + return false; + if(fg >= 0) + key_bmap = fg; + if(bg >= 0) + key_bmap2 = bg; + return true; +} + +static void +Key_In_Unbind_f (void) +{ + int b, m; + char *errchar = NULL; + + if (Cmd_Argc () != 3) { + Con_Print("in_unbind : remove commands from a key\n"); + return; + } + + m = strtol(Cmd_Argv (1), &errchar, 0); + if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); + return; + } + + b = Key_StringToKeynum (Cmd_Argv (2)); + if (b == -1) { + Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (2)); + return; + } + + if(!Key_SetBinding (b, m, "")) + Con_Printf("Key_SetBinding failed for unknown reason\n"); +} + +static void +Key_In_Bind_f (void) +{ + int i, c, b, m; + char cmd[MAX_INPUTLINE]; + char *errchar = NULL; + + c = Cmd_Argc (); + + if (c != 3 && c != 4) { + Con_Print("in_bind [command] : attach a command to a key\n"); + return; + } + + m = strtol(Cmd_Argv (1), &errchar, 0); + if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); + return; + } + + b = Key_StringToKeynum (Cmd_Argv (2)); + if (b == -1 || b >= MAX_KEYS) { + Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (2)); + return; + } + + if (c == 3) { + if (keybindings[m][b]) + Con_Printf("\"%s\" = \"%s\"\n", Cmd_Argv (2), keybindings[m][b]); + else + Con_Printf("\"%s\" is not bound\n", Cmd_Argv (2)); + return; + } +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for (i = 3; i < c; i++) { + strlcat (cmd, Cmd_Argv (i), sizeof (cmd)); + if (i != (c - 1)) + strlcat (cmd, " ", sizeof (cmd)); + } + + if(!Key_SetBinding (b, m, cmd)) + Con_Printf("Key_SetBinding failed for unknown reason\n"); +} + +static void +Key_In_Bindmap_f (void) +{ + int m1, m2, c; + char *errchar = NULL; + + c = Cmd_Argc (); + + if (c != 3) { + Con_Print("in_bindmap : set current bindmap and fallback\n"); + return; + } + + m1 = strtol(Cmd_Argv (1), &errchar, 0); + if ((m1 < 0) || (m1 >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); + return; + } + + m2 = strtol(Cmd_Argv (2), &errchar, 0); + if ((m2 < 0) || (m2 >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(2)); + return; + } + + key_bmap = m1; + key_bmap2 = m2; +} + +static void +Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc () != 2) { + Con_Print("unbind : remove commands from a key\n"); + return; + } + + b = Key_StringToKeynum (Cmd_Argv (1)); + if (b == -1) { + Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (1)); + return; + } + + if(!Key_SetBinding (b, 0, "")) + Con_Printf("Key_SetBinding failed for unknown reason\n"); +} + +static void +Key_Unbindall_f (void) +{ + int i, j; + + for (j = 0; j < MAX_BINDMAPS; j++) + for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) + if (keybindings[j][i]) + Key_SetBinding (i, j, ""); +} + +static void +Key_PrintBindList(int j) +{ + char bindbuf[MAX_INPUTLINE]; + char tinystr[2]; + const char *p; + int i; + + for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) + { + p = keybindings[j][i]; + if (p) + { + Cmd_QuoteString(bindbuf, sizeof(bindbuf), p, "\"\\", false); + if (j == 0) + Con_Printf("^2%s ^7= \"%s\"\n", Key_KeynumToString (i, tinystr, sizeof(tinystr)), bindbuf); + else + Con_Printf("^3bindmap %d: ^2%s ^7= \"%s\"\n", j, Key_KeynumToString (i, tinystr, sizeof(tinystr)), bindbuf); + } + } +} + +static void +Key_In_BindList_f (void) +{ + int m; + char *errchar = NULL; + + if(Cmd_Argc() >= 2) + { + m = strtol(Cmd_Argv(1), &errchar, 0); + if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { + Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); + return; + } + Key_PrintBindList(m); + } + else + { + for (m = 0; m < MAX_BINDMAPS; m++) + Key_PrintBindList(m); + } +} + +static void +Key_BindList_f (void) +{ + Key_PrintBindList(0); +} + +static void +Key_Bind_f (void) +{ + int i, c, b; + char cmd[MAX_INPUTLINE]; + + c = Cmd_Argc (); + + if (c != 2 && c != 3) { + Con_Print("bind [command] : attach a command to a key\n"); + return; + } + b = Key_StringToKeynum (Cmd_Argv (1)); + if (b == -1 || b >= MAX_KEYS) { + Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (1)); + return; + } + + if (c == 2) { + if (keybindings[0][b]) + Con_Printf("\"%s\" = \"%s\"\n", Cmd_Argv (1), keybindings[0][b]); + else + Con_Printf("\"%s\" is not bound\n", Cmd_Argv (1)); + return; + } +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for (i = 2; i < c; i++) { + strlcat (cmd, Cmd_Argv (i), sizeof (cmd)); + if (i != (c - 1)) + strlcat (cmd, " ", sizeof (cmd)); + } + + if(!Key_SetBinding (b, 0, cmd)) + Con_Printf("Key_SetBinding failed for unknown reason\n"); +} + +/* +============ +Writes lines containing "bind key value" +============ +*/ +void +Key_WriteBindings (qfile_t *f) +{ + int i, j; + char bindbuf[MAX_INPUTLINE]; + char tinystr[2]; + const char *p; + + for (j = 0; j < MAX_BINDMAPS; j++) + { + for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) + { + p = keybindings[j][i]; + if (p) + { + Cmd_QuoteString(bindbuf, sizeof(bindbuf), p, "\"\\", false); // don't need to escape $ because cvars are not expanded inside bind + if (j == 0) + FS_Printf(f, "bind %s \"%s\"\n", Key_KeynumToString (i, tinystr, sizeof(tinystr)), bindbuf); + else + FS_Printf(f, "in_bind %d %s \"%s\"\n", j, Key_KeynumToString (i, tinystr, sizeof(tinystr)), bindbuf); + } + } + } +} + + +void +Key_Init (void) +{ + Key_History_Init(); + key_line[0] = ']'; + key_line[1] = 0; + key_linepos = 1; + +// +// register our functions +// + Cmd_AddCommand ("in_bind", Key_In_Bind_f, "binds a command to the specified key in the selected bindmap"); + Cmd_AddCommand ("in_unbind", Key_In_Unbind_f, "removes command on the specified key in the selected bindmap"); + Cmd_AddCommand ("in_bindlist", Key_In_BindList_f, "bindlist: displays bound keys for all bindmaps, or the given bindmap"); + Cmd_AddCommand ("in_bindmap", Key_In_Bindmap_f, "selects active foreground and background (used only if a key is not bound in the foreground) bindmaps for typing"); + + Cmd_AddCommand ("bind", Key_Bind_f, "binds a command to the specified key in bindmap 0"); + Cmd_AddCommand ("unbind", Key_Unbind_f, "removes a command on the specified key in bindmap 0"); + Cmd_AddCommand ("bindlist", Key_BindList_f, "bindlist: displays bound keys for bindmap 0 bindmaps"); + Cmd_AddCommand ("unbindall", Key_Unbindall_f, "removes all commands from all keys in all bindmaps (leaving only shift-escape and escape)"); + + Cmd_AddCommand ("history", Key_History_f, "prints the history of executed commands (history X prints the last X entries, history -c clears the whole history)"); + + Cvar_RegisterVariable (&con_closeontoggleconsole); +} + +void +Key_Shutdown (void) +{ + Key_History_Shutdown(); +} + +const char *Key_GetBind (int key, int bindmap) +{ + const char *bind; + if (key < 0 || key >= MAX_KEYS) + return NULL; + if(bindmap >= MAX_BINDMAPS) + return NULL; + if(bindmap >= 0) + { + bind = keybindings[bindmap][key]; + } + else + { + bind = keybindings[key_bmap][key]; + if (!bind) + bind = keybindings[key_bmap2][key]; + } + return bind; +} + +void Key_FindKeysForCommand (const char *command, int *keys, int numkeys, int bindmap) +{ + int count; + int j; + const char *b; + + for (j = 0;j < numkeys;j++) + keys[j] = -1; + + if(bindmap >= MAX_BINDMAPS) + return; + + count = 0; + + for (j = 0; j < MAX_KEYS; ++j) + { + b = Key_GetBind(j, bindmap); + if (!b) + continue; + if (!strcmp (b, command) ) + { + keys[count++] = j; + if (count == numkeys) + break; + } + } +} + +/* +=================== +Called by the system between frames for both key up and key down events +Should NOT be called during an interrupt! +=================== +*/ +static char tbl_keyascii[MAX_KEYS]; +static keydest_t tbl_keydest[MAX_KEYS]; + +typedef struct eventqueueitem_s +{ + int key; + int ascii; + qboolean down; +} +eventqueueitem_t; +static int events_blocked = 0; +static eventqueueitem_t eventqueue[32]; +static unsigned eventqueue_idx = 0; + +static void Key_EventQueue_Add(int key, int ascii, qboolean down) +{ + if(eventqueue_idx < sizeof(eventqueue) / sizeof(*eventqueue)) + { + eventqueue[eventqueue_idx].key = key; + eventqueue[eventqueue_idx].ascii = ascii; + eventqueue[eventqueue_idx].down = down; + ++eventqueue_idx; + } +} + +void Key_EventQueue_Block(void) +{ + // block key events until call to Unblock + events_blocked = true; +} + +void Key_EventQueue_Unblock(void) +{ + // unblocks key events again + unsigned i; + events_blocked = false; + for(i = 0; i < eventqueue_idx; ++i) + Key_Event(eventqueue[i].key, eventqueue[i].ascii, eventqueue[i].down); + eventqueue_idx = 0; +} + +void +Key_Event (int key, int ascii, qboolean down) +{ + const char *bind; + qboolean q; + keydest_t keydest = key_dest; + char vabuf[1024]; + + if (key < 0 || key >= MAX_KEYS) + return; + + if(events_blocked) + { + Key_EventQueue_Add(key, ascii, down); + return; + } + + if (ascii == 0x80 && utf8_enable.integer) // pressing AltGr-5 (or AltGr-e) and for some reason we get windows-1252 encoding? + ascii = 0x20AC; // we want the Euro currency sign + // TODO find out which vid_ drivers do it and fix it there + // but catching U+0080 here is no loss as that char is not useful anyway + + // get key binding + bind = keybindings[key_bmap][key]; + if (!bind) + bind = keybindings[key_bmap2][key]; + + if (developer_insane.integer) + Con_DPrintf("Key_Event(%i, '%c', %s) keydown %i bind \"%s\"\n", key, ascii ? ascii : '?', down ? "down" : "up", keydown[key], bind ? bind : ""); + + if(key_consoleactive) + keydest = key_console; + + if (down) + { + // increment key repeat count each time a down is received so that things + // which want to ignore key repeat can ignore it + keydown[key] = min(keydown[key] + 1, 2); + if(keydown[key] == 1) { + tbl_keyascii[key] = ascii; + tbl_keydest[key] = keydest; + } else { + ascii = tbl_keyascii[key]; + keydest = tbl_keydest[key]; + } + } + else + { + // clear repeat count now that the key is released + keydown[key] = 0; + keydest = tbl_keydest[key]; + ascii = tbl_keyascii[key]; + } + + if(keydest == key_void) + return; + + // key_consoleactive is a flag not a key_dest because the console is a + // high priority overlay ontop of the normal screen (designed as a safety + // feature so that developers and users can rescue themselves from a bad + // situation). + // + // this also means that toggling the console on/off does not lose the old + // key_dest state + + // specially handle escape (togglemenu) and shift-escape (toggleconsole) + // engine bindings, these are not handled as normal binds so that the user + // can recover from a completely empty bindmap + if (key == K_ESCAPE) + { + // ignore key repeats on escape + if (keydown[key] > 1) + return; + + // escape does these things: + // key_consoleactive - close console + // key_message - abort messagemode + // key_menu - go to parent menu (or key_game) + // key_game - open menu + + // in all modes shift-escape toggles console + if (keydown[K_SHIFT]) + { + if(down) + { + Con_ToggleConsole_f (); + tbl_keydest[key] = key_void; // esc release should go nowhere (especially not to key_menu or key_game) + } + return; + } + + switch (keydest) + { + case key_console: + if(down) + { + if(key_consoleactive & KEY_CONSOLEACTIVE_FORCED) + { + key_consoleactive &= ~KEY_CONSOLEACTIVE_USER; + MR_ToggleMenu(1); + } + else + Con_ToggleConsole_f(); + } + break; + + case key_message: + if (down) + Key_Message (key, ascii); // that'll close the message input + break; + + case key_menu: + case key_menu_grabbed: + MR_KeyEvent (key, ascii, down); + break; + + case key_game: + // csqc has priority over toggle menu if it wants to (e.g. handling escape for UI stuff in-game.. :sick:) + q = CL_VM_InputEvent(down ? 0 : 1, key, ascii); + if (!q && down) + MR_ToggleMenu(1); + break; + + default: + Con_Printf ("Key_Event: Bad key_dest\n"); + } + return; + } + + // send function keydowns to interpreter no matter what mode is (unless the menu has specifically grabbed the keyboard, for rebinding keys) + // VorteX: Omnicide does bind F* keys + if (keydest != key_menu_grabbed) + if (key >= K_F1 && key <= K_F12 && gamemode != GAME_BLOODOMNICIDE) + { + if (bind) + { + if(keydown[key] == 1 && down) + { + // button commands add keynum as a parm + if (bind[0] == '+') + Cbuf_AddText (va(vabuf, sizeof(vabuf), "%s %i\n", bind, key)); + else + { + Cbuf_AddText (bind); + Cbuf_AddText ("\n"); + } + } else if(bind[0] == '+' && !down && keydown[key] == 0) + Cbuf_AddText(va(vabuf, sizeof(vabuf), "-%s %i\n", bind + 1, key)); + } + return; + } + + // send input to console if it wants it + if (keydest == key_console) + { + if (!down) + return; + // con_closeontoggleconsole enables toggleconsole keys to close the + // console, as long as they are not the color prefix character + // (special exemption for german keyboard layouts) + if (con_closeontoggleconsole.integer && bind && !strncmp(bind, "toggleconsole", strlen("toggleconsole")) && (key_consoleactive & KEY_CONSOLEACTIVE_USER) && (con_closeontoggleconsole.integer >= ((ascii != STRING_COLOR_TAG) ? 2 : 3) || key_linepos == 1)) + { + Con_ToggleConsole_f (); + return; + } + + if (COM_CheckParm ("-noconsole")) + return; // only allow the key bind to turn off console + + Key_Console (key, ascii); + return; + } + + // handle toggleconsole in menu too + if (keydest == key_menu) + { + if (down && con_closeontoggleconsole.integer && bind && !strncmp(bind, "toggleconsole", strlen("toggleconsole")) && ascii != STRING_COLOR_TAG) + { + Con_ToggleConsole_f (); + tbl_keydest[key] = key_void; // key release should go nowhere (especially not to key_menu or key_game) + return; + } + } + + // ignore binds while a video is played, let the video system handle the key event + if (cl_videoplaying) + { + if (gamemode == GAME_BLOODOMNICIDE) // menu controls key events + MR_KeyEvent(key, ascii, down); + else + CL_Video_KeyEvent (key, ascii, keydown[key] != 0); + return; + } + + // anything else is a key press into the game, chat line, or menu + switch (keydest) + { + case key_message: + if (down) + Key_Message (key, ascii); + break; + case key_menu: + case key_menu_grabbed: + MR_KeyEvent (key, ascii, down); + break; + case key_game: + q = CL_VM_InputEvent(down ? 0 : 1, key, ascii); + // ignore key repeats on binds and only send the bind if the event hasnt been already processed by csqc + if (!q && bind) + { + if(keydown[key] == 1 && down) + { + // button commands add keynum as a parm + if (bind[0] == '+') + Cbuf_AddText (va(vabuf, sizeof(vabuf), "%s %i\n", bind, key)); + else + { + Cbuf_AddText (bind); + Cbuf_AddText ("\n"); + } + } else if(bind[0] == '+' && !down && keydown[key] == 0) + Cbuf_AddText(va(vabuf, sizeof(vabuf), "-%s %i\n", bind + 1, key)); + } + break; + default: + Con_Printf ("Key_Event: Bad key_dest\n"); + } +} + +// a helper to simulate release of ALL keys +void +Key_ReleaseAll (void) +{ + int key; + // clear the event queue first + eventqueue_idx = 0; + // then send all down events (possibly into the event queue) + for(key = 0; key < MAX_KEYS; ++key) + if(keydown[key]) + Key_Event(key, 0, false); + // now all keys are guaranteed down (once the event queue is unblocked) + // and only future events count +} + +/* +=================== +Key_ClearStates +=================== +*/ +void +Key_ClearStates (void) +{ + memset(keydown, 0, sizeof(keydown)); +} diff --git a/app/jni/keys.h b/app/jni/keys.h new file mode 100644 index 0000000..cffaa3b --- /dev/null +++ b/app/jni/keys.h @@ -0,0 +1,392 @@ +/* + $RCSfile$ + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + + $Id: keys.h 11463 2011-10-22 23:52:58Z havoc $ +*/ + +#ifndef __KEYS_H +#define __KEYS_H + +#include "qtypes.h" + +// +// these are the key numbers that should be passed to Key_Event +// +typedef enum keynum_e +{ + K_TEXT = 1, // used only for unicode character input + K_TAB = 9, + K_ENTER = 13, + K_ESCAPE = 27, + K_SPACE = 32, + + // normal keys should be passed as lowercased ascii + + K_BACKSPACE = 127, + K_UPARROW, + K_DOWNARROW, + K_LEFTARROW, + K_RIGHTARROW, + + K_ALT, + K_CTRL, + K_SHIFT, + + K_F1, + K_F2, + K_F3, + K_F4, + K_F5, + K_F6, + K_F7, + K_F8, + K_F9, + K_F10, + K_F11, + K_F12, + + K_INS, + K_DEL, + K_PGDN, + K_PGUP, + K_HOME, + K_END, + + K_PAUSE, + + K_NUMLOCK, + K_CAPSLOCK, + K_SCROLLOCK, + + K_KP_0, + K_KP_INS = K_KP_0, + K_KP_1, + K_KP_END = K_KP_1, + K_KP_2, + K_KP_DOWNARROW = K_KP_2, + K_KP_3, + K_KP_PGDN = K_KP_3, + K_KP_4, + K_KP_LEFTARROW = K_KP_4, + K_KP_5, + K_KP_6, + K_KP_RIGHTARROW = K_KP_6, + K_KP_7, + K_KP_HOME = K_KP_7, + K_KP_8, + K_KP_UPARROW = K_KP_8, + K_KP_9, + K_KP_PGUP = K_KP_9, + K_KP_PERIOD, + K_KP_DEL = K_KP_PERIOD, + K_KP_DIVIDE, + K_KP_SLASH = K_KP_DIVIDE, + K_KP_MULTIPLY, + K_KP_MINUS, + K_KP_PLUS, + K_KP_ENTER, + K_KP_EQUALS, + + K_PRINTSCREEN, + + // mouse buttons generate virtual keys + + K_MOUSE1 = 512, + K_OTHERDEVICESBEGIN = K_MOUSE1, + K_MOUSE2, + K_MOUSE3, + K_MWHEELUP, + K_MWHEELDOWN, + K_MOUSE4, + K_MOUSE5, + K_MOUSE6, + K_MOUSE7, + K_MOUSE8, + K_MOUSE9, + K_MOUSE10, + K_MOUSE11, + K_MOUSE12, + K_MOUSE13, + K_MOUSE14, + K_MOUSE15, + K_MOUSE16, + +// +// joystick buttons +// + K_JOY1 = 768, + K_JOY2, + K_JOY3, + K_JOY4, + K_JOY5, + K_JOY6, + K_JOY7, + K_JOY8, + K_JOY9, + K_JOY10, + K_JOY11, + K_JOY12, + K_JOY13, + K_JOY14, + K_JOY15, + K_JOY16, + +// +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// + K_AUX1, + K_AUX2, + K_AUX3, + K_AUX4, + K_AUX5, + K_AUX6, + K_AUX7, + K_AUX8, + K_AUX9, + K_AUX10, + K_AUX11, + K_AUX12, + K_AUX13, + K_AUX14, + K_AUX15, + K_AUX16, + K_AUX17, + K_AUX18, + K_AUX19, + K_AUX20, + K_AUX21, + K_AUX22, + K_AUX23, + K_AUX24, + K_AUX25, + K_AUX26, + K_AUX27, + K_AUX28, + K_AUX29, + K_AUX30, + K_AUX31, + K_AUX32, + + // Microsoft Xbox 360 Controller For Windows + K_X360_DPAD_UP, + K_X360_DPAD_DOWN, + K_X360_DPAD_LEFT, + K_X360_DPAD_RIGHT, + K_X360_START, + K_X360_BACK, + K_X360_LEFT_THUMB, + K_X360_RIGHT_THUMB, + K_X360_LEFT_SHOULDER, + K_X360_RIGHT_SHOULDER, + K_X360_A, + K_X360_B, + K_X360_X, + K_X360_Y, + K_X360_LEFT_TRIGGER, + K_X360_RIGHT_TRIGGER, + K_X360_LEFT_THUMB_UP, + K_X360_LEFT_THUMB_DOWN, + K_X360_LEFT_THUMB_LEFT, + K_X360_LEFT_THUMB_RIGHT, + K_X360_RIGHT_THUMB_UP, + K_X360_RIGHT_THUMB_DOWN, + K_X360_RIGHT_THUMB_LEFT, + K_X360_RIGHT_THUMB_RIGHT, + + // generic joystick emulation for menu + K_JOY_UP, + K_JOY_DOWN, + K_JOY_LEFT, + K_JOY_RIGHT, + + K_MIDINOTE0 = 896, // to this, the note number is added + K_MIDINOTE1, + K_MIDINOTE2, + K_MIDINOTE3, + K_MIDINOTE4, + K_MIDINOTE5, + K_MIDINOTE6, + K_MIDINOTE7, + K_MIDINOTE8, + K_MIDINOTE9, + K_MIDINOTE10, + K_MIDINOTE11, + K_MIDINOTE12, + K_MIDINOTE13, + K_MIDINOTE14, + K_MIDINOTE15, + K_MIDINOTE16, + K_MIDINOTE17, + K_MIDINOTE18, + K_MIDINOTE19, + K_MIDINOTE20, + K_MIDINOTE21, + K_MIDINOTE22, + K_MIDINOTE23, + K_MIDINOTE24, + K_MIDINOTE25, + K_MIDINOTE26, + K_MIDINOTE27, + K_MIDINOTE28, + K_MIDINOTE29, + K_MIDINOTE30, + K_MIDINOTE31, + K_MIDINOTE32, + K_MIDINOTE33, + K_MIDINOTE34, + K_MIDINOTE35, + K_MIDINOTE36, + K_MIDINOTE37, + K_MIDINOTE38, + K_MIDINOTE39, + K_MIDINOTE40, + K_MIDINOTE41, + K_MIDINOTE42, + K_MIDINOTE43, + K_MIDINOTE44, + K_MIDINOTE45, + K_MIDINOTE46, + K_MIDINOTE47, + K_MIDINOTE48, + K_MIDINOTE49, + K_MIDINOTE50, + K_MIDINOTE51, + K_MIDINOTE52, + K_MIDINOTE53, + K_MIDINOTE54, + K_MIDINOTE55, + K_MIDINOTE56, + K_MIDINOTE57, + K_MIDINOTE58, + K_MIDINOTE59, + K_MIDINOTE60, + K_MIDINOTE61, + K_MIDINOTE62, + K_MIDINOTE63, + K_MIDINOTE64, + K_MIDINOTE65, + K_MIDINOTE66, + K_MIDINOTE67, + K_MIDINOTE68, + K_MIDINOTE69, + K_MIDINOTE70, + K_MIDINOTE71, + K_MIDINOTE72, + K_MIDINOTE73, + K_MIDINOTE74, + K_MIDINOTE75, + K_MIDINOTE76, + K_MIDINOTE77, + K_MIDINOTE78, + K_MIDINOTE79, + K_MIDINOTE80, + K_MIDINOTE81, + K_MIDINOTE82, + K_MIDINOTE83, + K_MIDINOTE84, + K_MIDINOTE85, + K_MIDINOTE86, + K_MIDINOTE87, + K_MIDINOTE88, + K_MIDINOTE89, + K_MIDINOTE90, + K_MIDINOTE91, + K_MIDINOTE92, + K_MIDINOTE93, + K_MIDINOTE94, + K_MIDINOTE95, + K_MIDINOTE96, + K_MIDINOTE97, + K_MIDINOTE98, + K_MIDINOTE99, + K_MIDINOTE100, + K_MIDINOTE101, + K_MIDINOTE102, + K_MIDINOTE103, + K_MIDINOTE104, + K_MIDINOTE105, + K_MIDINOTE106, + K_MIDINOTE107, + K_MIDINOTE108, + K_MIDINOTE109, + K_MIDINOTE110, + K_MIDINOTE111, + K_MIDINOTE112, + K_MIDINOTE113, + K_MIDINOTE114, + K_MIDINOTE115, + K_MIDINOTE116, + K_MIDINOTE117, + K_MIDINOTE118, + K_MIDINOTE119, + K_MIDINOTE120, + K_MIDINOTE121, + K_MIDINOTE122, + K_MIDINOTE123, + K_MIDINOTE124, + K_MIDINOTE125, + K_MIDINOTE126, + K_MIDINOTE127, + + MAX_KEYS +} +keynum_t; + +typedef enum keydest_e { key_game, key_message, key_menu, key_menu_grabbed, key_console, key_void } keydest_t; + +extern char key_line[MAX_INPUTLINE]; +extern int key_linepos; +extern qboolean key_insert; // insert key toggle (for editing) +extern keydest_t key_dest; +// key_consoleactive bits +// user wants console (halfscreen) +#define KEY_CONSOLEACTIVE_USER 1 +// console forced because there's nothing else active (fullscreen) +#define KEY_CONSOLEACTIVE_FORCED 4 +extern int key_consoleactive; +extern char *keybindings[MAX_BINDMAPS][MAX_KEYS]; + +extern int chat_mode; // 0 for say, 1 for say_team, -1 for command +extern char chat_buffer[MAX_INPUTLINE]; +extern unsigned int chat_bufferlen; + +void Key_ClearEditLine(int edit_line); +void Key_WriteBindings(qfile_t *f); +void Key_Init(void); +void Key_Shutdown(void); +void Key_Init_Cvars(void); +void Key_Event(int key, int ascii, qboolean down); +void Key_ReleaseAll (void); +void Key_ClearStates (void); // FIXME: should this function still exist? Or should Key_ReleaseAll be used instead when shutting down a vid driver? +void Key_EventQueue_Block(void); +void Key_EventQueue_Unblock(void); + +qboolean Key_SetBinding (int keynum, int bindmap, const char *binding); +const char *Key_GetBind (int key, int bindmap); +void Key_FindKeysForCommand (const char *command, int *keys, int numkeys, int bindmap); +qboolean Key_SetBindMap(int fg, int bg); +void Key_GetBindMap(int *fg, int *bg); + +#endif // __KEYS_H + diff --git a/app/jni/lhfont.h b/app/jni/lhfont.h new file mode 100644 index 0000000..e651180 --- /dev/null +++ b/app/jni/lhfont.h @@ -0,0 +1,106 @@ +0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x08,0x08,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x8B,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8B,0xFF,0x89,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x9D,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x8A,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x89,0xFF, +0x87,0x00,0x85,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x84,0xFF,0x88,0x00,0x8B,0xFF,0x88,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x9B,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x99,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF, +0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8A,0x00,0x82,0xFF,0x8D,0x00, +0x83,0xFF,0x8D,0x00,0x82,0xFF,0x96,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x94,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x95,0x00,0x84,0xFF,0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF, +0x87,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x99,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x9B,0x00,0x83,0xFF,0x85,0x00,0xFF,0x00,0xB6,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x93,0x00,0xFF,0x00,0xB7,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x92,0x00,0xFF,0x00,0xE0,0x00,0x86,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xFF,0x00,0xE1,0x00,0x85,0xFF, +0x81,0x00,0x83,0xFF,0x91,0x00,0xF3,0x00,0x85,0xFF,0xFF,0x00,0x85,0x00,0xF3,0x00,0x86,0xFF,0xFF,0x00,0x84,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF, +0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x86,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x81,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF, +0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x84,0xFF,0x90,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x81,0x00,0x84,0x00,0x84,0xFF,0x89,0x00,0x88,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x89,0xFF,0x84,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0xA4,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x88,0x00,0x8B,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x82,0x00,0x85,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0xA5,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF, +0x87,0x00,0x87,0xFF,0x83,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x94,0x00,0x86,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x95,0x00,0x85,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x97,0x00,0x85,0xFF,0xFF,0x00,0xE1,0x00,0x96,0x00,0x86,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8E,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x91,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF, +0x89,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8F,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0x8E,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00, +0x81,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x91,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x92,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF, +0x8A,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x93,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x84,0xFF,0x8C,0x00,0x87,0xFF,0x86,0x00,0x87,0xFF,0x94,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x95,0x00,0xFF,0x00,0xE4,0x00,0x83,0xFF,0x96,0x00,0xFF,0x00,0xE5,0x00,0x81,0xFF,0x97,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x8B,0xFF,0x83,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x82,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8A,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x81,0x00, +0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00, +0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, +0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x00,0x00,0x86,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x84,0x00,0x82,0xFF,0x83,0x00,0x82,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00, +0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x81,0xFF,0x83,0x00,0x81,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x86,0xFF,0x00,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x82,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF, +0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB3,0x00,0x83,0xFF,0xC7,0x00,0xFF,0x00,0xB3,0x00,0x84,0xFF,0xC6,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00, +0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x82,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x95,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x94,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00, +0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00, +0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x83,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x86,0xFF,0x82,0x00,0x82,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00, +0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x81,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF, +0x97,0x00,0x84,0xFF,0x88,0x00,0x8A,0xFF,0x82,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x89,0x00,0x89,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC5,0x00,0x83,0xFF,0xB5,0x00,0xFF,0x00,0xC5,0x00,0x84,0xFF,0xB4,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x95,0x00,0x83,0xFF, +0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x84,0x00,0x84,0xFF,0x84,0x00,0x83,0xFF,0x82,0x00,0x8C,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0xB0,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0xB0,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x85,0x00,0x84,0xFF, +0x82,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x97,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x86,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x96,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x94,0x00,0x84,0xFF,0x87,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x87,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x95,0x00,0x83,0xFF, +0x8F,0x00,0x83,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x95,0x00,0x84,0xFF,0x86,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x84,0xFF,0x86,0x00,0x88,0xFF,0x00,0x00,0x82,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBA,0x00,0x84,0xFF,0x85,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x8D,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x82,0x00,0x81,0xFF,0x88,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBB,0x00,0x84,0xFF, +0x84,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBC,0x00,0x84,0xFF,0x83,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBD,0x00,0x84,0xFF, +0x82,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0xDD,0x00,0x84,0xFF,0x81,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0xDF,0x00,0x83,0xFF,0x81,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB6,0x00, +0x87,0xFF,0x9A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0xE6,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB7,0x00,0x85,0xFF,0x9C,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xE7,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0xC3,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF, +0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x99,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x96,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF, +0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x94,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF, +0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x95,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF, +0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x98,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xB8,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB2,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xB1,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0x81,0x00,0xAB,0xFF, +0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF,0xA7,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00, +0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00, +0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x82,0x00,0xA9,0xFF,0x83,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF, +0xA7,0x00,0x83,0x00,0xA7,0xFF,0x84,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0xB1,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0xB2,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xFF,0x00,0xFF,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x8B,0x00, +0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8B,0xFF,0x89,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x9D,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x8A,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x84,0xFF,0x88,0x00,0x8B,0xFF,0x88,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x9B,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00, +0x89,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x99,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF, +0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8A,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x96,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x94,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x95,0x00,0x84,0xFF,0x87,0x00, +0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x99,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF, +0x8B,0x00,0x83,0xFF,0x9B,0x00,0x83,0xFF,0x85,0x00,0xFF,0x00,0xB6,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x93,0x00,0xFF,0x00,0xB7,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x92,0x00,0xFF,0x00,0xE0,0x00,0x86,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xFF,0x00,0xE1,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xF3,0x00,0x85,0xFF,0xFF,0x00,0x85,0x00,0xF3,0x00,0x86,0xFF,0xFF,0x00,0x84,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x86,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x84,0x00,0x89,0xFF,0x82,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x84,0xFF,0x90,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x84,0x00,0x84,0xFF,0x89,0x00,0x88,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x89,0xFF,0x84,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0xA4,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x85,0x00, +0x83,0xFF,0x88,0x00,0x8B,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x82,0x00,0x85,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0xA5,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x83,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x94,0x00,0x86,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF, +0x95,0x00,0x85,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x97,0x00,0x85,0xFF,0xFF,0x00,0xE1,0x00,0x96,0x00,0x86,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF, +0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8E,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x91,0x00, +0x84,0xFF,0x88,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00, +0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8F,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0x8E,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x81,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF, +0x86,0x00,0x85,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x91,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x92,0x00, +0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x93,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00, +0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x84,0xFF,0x8C,0x00,0x87,0xFF,0x86,0x00,0x87,0xFF,0x94,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF, +0x87,0x00,0x85,0xFF,0x95,0x00,0xFF,0x00,0xE4,0x00,0x83,0xFF,0x96,0x00,0xFF,0x00,0xE5,0x00,0x81,0xFF,0x97,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x82,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x83,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8A,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF, +0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x00,0x00,0x86,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00, +0x85,0xFF,0x84,0x00,0x82,0xFF,0x83,0x00,0x82,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x81,0xFF,0x83,0x00,0x81,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF, +0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x86,0xFF,0x00,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x82,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00, +0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF, +0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB3,0x00,0x83,0xFF,0xC7,0x00,0xFF,0x00,0xB3,0x00,0x84,0xFF,0xC6,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x82,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF, +0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x95,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, +0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x94,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x83,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, +0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x86,0xFF,0x82,0x00,0x82,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x87,0xFF, +0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x81,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x88,0x00,0x8A,0xFF,0x82,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x89,0x00,0x89,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC5,0x00,0x83,0xFF,0xB5,0x00,0xFF,0x00, +0xC5,0x00,0x84,0xFF,0xB4,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x84,0x00,0x84,0xFF,0x84,0x00,0x83,0xFF,0x82,0x00,0x8C,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF, +0x89,0x00,0x84,0xFF,0x8A,0x00,0xB0,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0xB0,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x85,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x97,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x87,0x00, +0x81,0xFF,0x81,0x00,0x81,0xFF,0x86,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x96,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x94,0x00,0x84,0xFF,0x87,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x87,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x95,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x95,0x00,0x84,0xFF,0x86,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x84,0xFF,0x86,0x00,0x88,0xFF,0x00,0x00,0x82,0xFF,0x88,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBA,0x00,0x84,0xFF,0x85,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x8D,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x82,0x00,0x81,0xFF,0x88,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBB,0x00,0x84,0xFF,0x84,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBC,0x00,0x84,0xFF,0x83,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBD,0x00,0x84,0xFF,0x82,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00, +0x84,0xFF,0xDD,0x00,0x84,0xFF,0x81,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0xDF,0x00,0x83,0xFF,0x81,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB6,0x00,0x87,0xFF,0x9A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0xE6,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB7,0x00,0x85,0xFF,0x9C,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xE7,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00, +0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0xC3,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x99,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x96,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x94,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, +0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00, +0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF, +0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x95,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x98,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xB8,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00, +0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC2,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xC1,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00, +0x85,0xFF,0xA7,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF, +0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00, +0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x92,0x00,0xA9,0xFF,0x83,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF,0xA7,0x00,0x93,0x00,0xA7,0xFF,0x84,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0xC1,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0xC2,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xFF,0x00, +0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0x52,0x55,0x45,0x56,0x49,0x53,0x49,0x4F,0x4E,0x2D,0x58,0x46,0x49,0x4C,0x45,0x2E,0x00 diff --git a/app/jni/lhnet.c b/app/jni/lhnet.c new file mode 100644 index 0000000..e3bc6b7 --- /dev/null +++ b/app/jni/lhnet.c @@ -0,0 +1,1419 @@ + +// Written by Forest Hale 2003-06-15 and placed into public domain. + +#ifdef WIN32 +#ifdef _MSC_VER +#pragma comment(lib, "ws2_32.lib") +#endif +# ifdef SUPPORTIPV6 +// Windows XP or higher is required for getaddrinfo, but the inclusion of wspiapi provides fallbacks for older versions +# define _WIN32_WINNT 0x0501 +# endif +# include +# include +# ifdef USE_WSPIAPI_H +# include +# endif +#endif + +#ifndef STANDALONETEST +#include "quakedef.h" +#endif + +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SUPPORTIPV6 +#include +#endif +#endif + +#ifdef __MORPHOS__ +#include +#endif + +// for Z_Malloc/Z_Free in quake +#ifndef STANDALONETEST +#include "zone.h" +#include "sys.h" +#include "netconn.h" +#else +#define Con_Print printf +#define Con_Printf printf +#define Z_Malloc malloc +#define Z_Free free +#endif + +#include "lhnet.h" + +#if defined(WIN32) +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ECONNREFUSED WSAECONNREFUSED + +#define SOCKETERRNO WSAGetLastError() + +#define IOC_VENDOR 0x18000000 +#define _WSAIOW(x,y) (IOC_IN|(x)|(y)) +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) + +#define SOCKLEN_T int +#elif defined(__MORPHOS__) +#define ioctlsocket IoctlSocket +#define closesocket CloseSocket +#define SOCKETERRNO Errno() + +#define SOCKLEN_T int +#else +#define ioctlsocket ioctl +#define closesocket close +#define SOCKETERRNO errno + +#define SOCKLEN_T socklen_t +#endif + +#ifdef MSG_DONTWAIT +#define LHNET_RECVFROM_FLAGS MSG_DONTWAIT +#define LHNET_SENDTO_FLAGS 0 +#else +#define LHNET_RECVFROM_FLAGS 0 +#define LHNET_SENDTO_FLAGS 0 +#endif + +typedef struct lhnetaddressnative_s +{ + lhnetaddresstype_t addresstype; + int port; + union + { + struct sockaddr sock; + struct sockaddr_in in; +#ifdef SUPPORTIPV6 + struct sockaddr_in6 in6; +#endif + } + addr; +} +lhnetaddressnative_t; + +// to make LHNETADDRESS_FromString resolve repeated hostnames faster, cache them +#define MAX_NAMECACHE 64 +static struct namecache_s +{ + lhnetaddressnative_t address; + double expirationtime; + char name[64]; +} +namecache[MAX_NAMECACHE]; +static int namecacheposition = 0; + +int LHNETADDRESS_FromPort(lhnetaddress_t *vaddress, lhnetaddresstype_t addresstype, int port) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + if (!address) + return 0; + switch(addresstype) + { + default: + break; + case LHNETADDRESSTYPE_LOOP: + // local:port (loopback) + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_LOOP; + address->port = port; + return 1; + case LHNETADDRESSTYPE_INET4: + // 0.0.0.0:port (INADDR_ANY, binds to all interfaces) + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = port; + address->addr.in.sin_family = AF_INET; + address->addr.in.sin_port = htons((unsigned short)port); + return 1; +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: + // [0:0:0:0:0:0:0:0]:port (IN6ADDR_ANY, binds to all interfaces) + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_INET6; + address->port = port; + address->addr.in6.sin6_family = AF_INET6; + address->addr.in6.sin6_port = htons((unsigned short)port); + return 1; +#endif + } + return 0; +} + +#ifdef SUPPORTIPV6 +static int LHNETADDRESS_Resolve(lhnetaddressnative_t *address, const char *name, int port) +{ + char port_buff [16]; + struct addrinfo hints; + struct addrinfo* addrinf; + int err; + + dpsnprintf (port_buff, sizeof (port_buff), "%d", port); + port_buff[sizeof (port_buff) - 1] = '\0'; + + memset(&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + //hints.ai_flags = AI_PASSIVE; + + err = getaddrinfo(name, port_buff, &hints, &addrinf); + if (err != 0 || addrinf == NULL) + return 0; + if (addrinf->ai_addr->sa_family != AF_INET6 && addrinf->ai_addr->sa_family != AF_INET) + { + freeaddrinfo (addrinf); + return 0; + } + + // great it worked + if (addrinf->ai_addr->sa_family == AF_INET6) + { + address->addresstype = LHNETADDRESSTYPE_INET6; + memcpy(&address->addr.in6, addrinf->ai_addr, sizeof(address->addr.in6)); + } + else + { + address->addresstype = LHNETADDRESSTYPE_INET4; + memcpy(&address->addr.in, addrinf->ai_addr, sizeof(address->addr.in)); + } + address->port = port; + + freeaddrinfo (addrinf); + return 1; +} + +int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int defaultport) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + int i, port, d1, d2, d3, d4, resolved; + size_t namelen; + unsigned char *a; + char name[128]; +#ifdef STANDALONETEST + char string2[128]; +#endif + const char* addr_start; + const char* addr_end = NULL; + const char* port_name = NULL; + int addr_family = AF_UNSPEC; + + if (!address || !string || !*string) + return 0; + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_NONE; + port = 0; + + // If it's a bracketed IPv6 address + if (string[0] == '[') + { + const char* end_bracket = strchr(string, ']'); + + if (end_bracket == NULL) + return 0; + + if (end_bracket[1] == ':') + port_name = end_bracket + 2; + else if (end_bracket[1] != '\0') + return 0; + + addr_family = AF_INET6; + addr_start = &string[1]; + addr_end = end_bracket; + } + else + { + const char* first_colon; + + addr_start = string; + + // If it's a numeric non-bracket IPv6 address (-> no port), + // or it's a numeric IPv4 address, or a name, with a port + first_colon = strchr(string, ':'); + if (first_colon != NULL) + { + const char* last_colon = strrchr(first_colon + 1, ':'); + + // If it's an numeric IPv4 address, or a name, with a port + if (last_colon == NULL) + { + addr_end = first_colon; + port_name = first_colon + 1; + } + else + addr_family = AF_INET6; + } + } + + if (addr_end != NULL) + namelen = addr_end - addr_start; + else + namelen = strlen (addr_start); + + if (namelen >= sizeof(name)) + namelen = sizeof(name) - 1; + memcpy (name, addr_start, namelen); + name[namelen] = 0; + + if (port_name) + port = atoi(port_name); + + if (port == 0) + port = defaultport; + + // handle loopback + if (!strcmp(name, "local")) + { + address->addresstype = LHNETADDRESSTYPE_LOOP; + address->port = port; + return 1; + } + // try to parse as dotted decimal ipv4 address first + // note this supports partial ip addresses + d1 = d2 = d3 = d4 = 0; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + if (addr_family != AF_INET6 && + sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4) >= 1 && (unsigned int)d1 < 256 && (unsigned int)d2 < 256 && (unsigned int)d3 < 256 && (unsigned int)d4 < 256) + { + // parsed a valid ipv4 address + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = port; + address->addr.in.sin_family = AF_INET; + address->addr.in.sin_port = htons((unsigned short)port); + a = (unsigned char *)&address->addr.in.sin_addr; + a[0] = d1; + a[1] = d2; + a[2] = d3; + a[3] = d4; +#ifdef STANDALONETEST + LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); + printf("manual parsing of ipv4 dotted decimal address \"%s\" successful: %s\n", string, string2); +#endif + return 1; + } + for (i = 0;i < MAX_NAMECACHE;i++) + if (!strcmp(namecache[i].name, name)) + break; +#ifdef STANDALONETEST + if (i < MAX_NAMECACHE) +#else + if (i < MAX_NAMECACHE && realtime < namecache[i].expirationtime) +#endif + { + *address = namecache[i].address; + address->port = port; + if (address->addresstype == LHNETADDRESSTYPE_INET6) + { + address->addr.in6.sin6_port = htons((unsigned short)port); + return 1; + } + else if (address->addresstype == LHNETADDRESSTYPE_INET4) + { + address->addr.in.sin_port = htons((unsigned short)port); + return 1; + } + return 0; + } + + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; +#ifndef STANDALONETEST + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours +#endif + + // try resolving the address (handles dns and other ip formats) + resolved = LHNETADDRESS_Resolve(address, name, port); + if (resolved) + { +#ifdef STANDALONETEST + const char *protoname; + + switch (address->addresstype) + { + case LHNETADDRESSTYPE_INET6: + protoname = "ipv6"; + break; + case LHNETADDRESSTYPE_INET4: + protoname = "ipv4"; + break; + default: + protoname = "UNKNOWN"; + break; + } + LHNETADDRESS_ToString(vaddress, string2, sizeof(string2), 1); + Con_Printf("LHNETADDRESS_Resolve(\"%s\") returned %s address %s\n", string, protoname, string2); +#endif + namecache[namecacheposition].address = *address; + } + else + { +#ifdef STANDALONETEST + printf("name resolution failed on address \"%s\"\n", name); +#endif + namecache[namecacheposition].address.addresstype = LHNETADDRESSTYPE_NONE; + } + + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; + return resolved; +} +#else +int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int defaultport) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + int i, port, namelen, d1, d2, d3, d4; + struct hostent *hostentry; + unsigned char *a; + const char *colon; + char name[128]; +#ifdef STANDALONETEST + char string2[128]; +#endif + if (!address || !string || !*string) + return 0; + memset(address, 0, sizeof(*address)); + address->addresstype = LHNETADDRESSTYPE_NONE; + port = 0; + colon = strrchr(string, ':'); + if (colon && (colon == strchr(string, ':') || (string[0] == '[' && colon - string > 0 && colon[-1] == ']'))) + // EITHER: colon is the ONLY colon OR: colon comes after [...] delimited IPv6 address + // fixes misparsing of IPv6 addresses without port + { + port = atoi(colon + 1); + } + else + colon = string + strlen(string); + if (port == 0) + port = defaultport; + namelen = colon - string; + if (namelen > 127) + namelen = 127; + if (string[0] == '[' && namelen > 0 && string[namelen-1] == ']') // ipv6 + { + string++; + namelen -= 2; + } + memcpy(name, string, namelen); + name[namelen] = 0; + // handle loopback + if (!strcmp(name, "local")) + { + address->addresstype = LHNETADDRESSTYPE_LOOP; + address->port = port; + return 1; + } + // try to parse as dotted decimal ipv4 address first + // note this supports partial ip addresses + d1 = d2 = d3 = d4 = 0; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + if (sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4) >= 1 && (unsigned int)d1 < 256 && (unsigned int)d2 < 256 && (unsigned int)d3 < 256 && (unsigned int)d4 < 256) + { + // parsed a valid ipv4 address + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = port; + address->addr.in.sin_family = AF_INET; + address->addr.in.sin_port = htons((unsigned short)port); + a = (unsigned char *)&address->addr.in.sin_addr; + a[0] = d1; + a[1] = d2; + a[2] = d3; + a[3] = d4; +#ifdef STANDALONETEST + LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); + printf("manual parsing of ipv4 dotted decimal address \"%s\" successful: %s\n", string, string2); +#endif + return 1; + } + for (i = 0;i < MAX_NAMECACHE;i++) + if (!strcmp(namecache[i].name, name)) + break; +#ifdef STANDALONETEST + if (i < MAX_NAMECACHE) +#else + if (i < MAX_NAMECACHE && realtime < namecache[i].expirationtime) +#endif + { + *address = namecache[i].address; + address->port = port; + if (address->addresstype == LHNETADDRESSTYPE_INET6) + { +#ifdef SUPPORTIPV6 + address->addr.in6.sin6_port = htons((unsigned short)port); + return 1; +#endif + } + else if (address->addresstype == LHNETADDRESSTYPE_INET4) + { + address->addr.in.sin_port = htons((unsigned short)port); + return 1; + } + return 0; + } + // try gethostbyname (handles dns and other ip formats) + hostentry = gethostbyname(name); + if (hostentry) + { + if (hostentry->h_addrtype == AF_INET6) + { +#ifdef SUPPORTIPV6 + // great it worked + address->addresstype = LHNETADDRESSTYPE_INET6; + address->port = port; + address->addr.in6.sin6_family = hostentry->h_addrtype; + address->addr.in6.sin6_port = htons((unsigned short)port); + memcpy(&address->addr.in6.sin6_addr, hostentry->h_addr_list[0], sizeof(address->addr.in6.sin6_addr)); + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; +#ifndef STANDALONETEST + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours +#endif + namecache[namecacheposition].address = *address; + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; +#ifdef STANDALONETEST + LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); + printf("gethostbyname(\"%s\") returned ipv6 address %s\n", string, string2); +#endif + return 1; +#endif + } + else if (hostentry->h_addrtype == AF_INET) + { + // great it worked + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = port; + address->addr.in.sin_family = hostentry->h_addrtype; + address->addr.in.sin_port = htons((unsigned short)port); + memcpy(&address->addr.in.sin_addr, hostentry->h_addr_list[0], sizeof(address->addr.in.sin_addr)); + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; +#ifndef STANDALONETEST + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours +#endif + namecache[namecacheposition].address = *address; + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; +#ifdef STANDALONETEST + LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); + printf("gethostbyname(\"%s\") returned ipv4 address %s\n", string, string2); +#endif + return 1; + } + } +#ifdef STANDALONETEST + printf("gethostbyname failed on address \"%s\"\n", name); +#endif + for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) + namecache[namecacheposition].name[i] = name[i]; + namecache[namecacheposition].name[i] = 0; +#ifndef STANDALONETEST + namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours +#endif + namecache[namecacheposition].address.addresstype = LHNETADDRESSTYPE_NONE; + namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; + return 0; +} +#endif + +int LHNETADDRESS_ToString(const lhnetaddress_t *vaddress, char *string, int stringbuffersize, int includeport) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + const unsigned char *a; + *string = 0; + if (!address || !string || stringbuffersize < 1) + return 0; + switch(address->addresstype) + { + default: + break; + case LHNETADDRESSTYPE_LOOP: + if (includeport) + { + if (stringbuffersize >= 12) + { + dpsnprintf(string, stringbuffersize, "local:%d", address->port); + return 1; + } + } + else + { + if (stringbuffersize >= 6) + { + memcpy(string, "local", 6); + return 1; + } + } + break; + case LHNETADDRESSTYPE_INET4: + a = (const unsigned char *)(&address->addr.in.sin_addr); + if (includeport) + { + if (stringbuffersize >= 22) + { + dpsnprintf(string, stringbuffersize, "%d.%d.%d.%d:%d", a[0], a[1], a[2], a[3], address->port); + return 1; + } + } + else + { + if (stringbuffersize >= 16) + { + dpsnprintf(string, stringbuffersize, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]); + return 1; + } + } + break; +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: + a = (const unsigned char *)(&address->addr.in6.sin6_addr); + if (includeport) + { + if (stringbuffersize >= 88) + { + dpsnprintf(string, stringbuffersize, "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", a[0] * 256 + a[1], a[2] * 256 + a[3], a[4] * 256 + a[5], a[6] * 256 + a[7], a[8] * 256 + a[9], a[10] * 256 + a[11], a[12] * 256 + a[13], a[14] * 256 + a[15], address->port); + return 1; + } + } + else + { + if (stringbuffersize >= 80) + { + dpsnprintf(string, stringbuffersize, "%x:%x:%x:%x:%x:%x:%x:%x", a[0] * 256 + a[1], a[2] * 256 + a[3], a[4] * 256 + a[5], a[6] * 256 + a[7], a[8] * 256 + a[9], a[10] * 256 + a[11], a[12] * 256 + a[13], a[14] * 256 + a[15]); + return 1; + } + } + break; +#endif + } + return 0; +} + +int LHNETADDRESS_GetAddressType(const lhnetaddress_t *address) +{ + if (address) + return address->addresstype; + else + return LHNETADDRESSTYPE_NONE; +} + +const char *LHNETADDRESS_GetInterfaceName(const lhnetaddress_t *vaddress, char *ifname, size_t ifnamelength) +{ +#ifdef SUPPORTIPV6 + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + + if (address && address->addresstype == LHNETADDRESSTYPE_INET6) + { +#ifndef _WIN32 + + if (if_indextoname(address->addr.in6.sin6_scope_id, ifname) == ifname) + return ifname; + +#else + + // The Win32 API doesn't have if_indextoname() until Windows Vista, + // but luckily it just uses the interface ID as the interface name + + if (dpsnprintf(ifname, ifnamelength, "%lu", address->addr.in6.sin6_scope_id) > 0) + return ifname; + +#endif + } +#endif + + return NULL; +} + +int LHNETADDRESS_GetPort(const lhnetaddress_t *address) +{ + if (!address) + return -1; + return address->port; +} + +int LHNETADDRESS_SetPort(lhnetaddress_t *vaddress, int port) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + if (!address) + return 0; + address->port = port; + switch(address->addresstype) + { + case LHNETADDRESSTYPE_LOOP: + return 1; + case LHNETADDRESSTYPE_INET4: + address->addr.in.sin_port = htons((unsigned short)port); + return 1; +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: + address->addr.in6.sin6_port = htons((unsigned short)port); + return 1; +#endif + default: + return 0; + } +} + +int LHNETADDRESS_Compare(const lhnetaddress_t *vaddress1, const lhnetaddress_t *vaddress2) +{ + lhnetaddressnative_t *address1 = (lhnetaddressnative_t *)vaddress1; + lhnetaddressnative_t *address2 = (lhnetaddressnative_t *)vaddress2; + if (!address1 || !address2) + return 1; + if (address1->addresstype != address2->addresstype) + return 1; + switch(address1->addresstype) + { + case LHNETADDRESSTYPE_LOOP: + if (address1->port != address2->port) + return -1; + return 0; + case LHNETADDRESSTYPE_INET4: + if (address1->addr.in.sin_family != address2->addr.in.sin_family) + return 1; + if (memcmp(&address1->addr.in.sin_addr, &address2->addr.in.sin_addr, sizeof(address1->addr.in.sin_addr))) + return 1; + if (address1->port != address2->port) + return -1; + return 0; +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: + if (address1->addr.in6.sin6_family != address2->addr.in6.sin6_family) + return 1; + if (memcmp(&address1->addr.in6.sin6_addr, &address2->addr.in6.sin6_addr, sizeof(address1->addr.in6.sin6_addr))) + return 1; + if (address1->port != address2->port) + return -1; + return 0; +#endif + default: + return 1; + } +} + +typedef struct lhnetpacket_s +{ + void *data; + int length; + int sourceport; + int destinationport; + time_t timeout; +#ifndef STANDALONETEST + double sentdoubletime; +#endif + struct lhnetpacket_s *next, *prev; +} +lhnetpacket_t; + +static int lhnet_active; +static lhnetsocket_t lhnet_socketlist; +static lhnetpacket_t lhnet_packetlist; +static int lhnet_default_dscp = 0; +#ifdef WIN32 +static int lhnet_didWSAStartup = 0; +static WSADATA lhnet_winsockdata; +#endif + +void LHNET_Init(void) +{ + if (lhnet_active) + return; + lhnet_socketlist.next = lhnet_socketlist.prev = &lhnet_socketlist; + lhnet_packetlist.next = lhnet_packetlist.prev = &lhnet_packetlist; + lhnet_active = 1; +#ifdef WIN32 + lhnet_didWSAStartup = !WSAStartup(MAKEWORD(1, 1), &lhnet_winsockdata); + if (!lhnet_didWSAStartup) + Con_Print("LHNET_Init: WSAStartup failed, networking disabled\n"); +#endif +} + +int LHNET_DefaultDSCP(int dscp) +{ +#ifdef IP_TOS + int prev = lhnet_default_dscp; + if(dscp >= 0) + lhnet_default_dscp = dscp; + return prev; +#else + return -1; +#endif +} + +void LHNET_Shutdown(void) +{ + lhnetpacket_t *p; + if (!lhnet_active) + return; + while (lhnet_socketlist.next != &lhnet_socketlist) + LHNET_CloseSocket(lhnet_socketlist.next); + while (lhnet_packetlist.next != &lhnet_packetlist) + { + p = lhnet_packetlist.next; + p->prev->next = p->next; + p->next->prev = p->prev; + Z_Free(p); + } +#ifdef WIN32 + if (lhnet_didWSAStartup) + { + lhnet_didWSAStartup = 0; + WSACleanup(); + } +#endif + lhnet_active = 0; +} + +static const char *LHNETPRIVATE_StrError(void) +{ +#ifdef WIN32 + int i = WSAGetLastError(); + switch (i) + { + case WSAEINTR: return "WSAEINTR"; + case WSAEBADF: return "WSAEBADF"; + case WSAEACCES: return "WSAEACCES"; + case WSAEFAULT: return "WSAEFAULT"; + case WSAEINVAL: return "WSAEINVAL"; + case WSAEMFILE: return "WSAEMFILE"; + case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: return "WSAEINPROGRESS"; + case WSAEALREADY: return "WSAEALREADY"; + case WSAENOTSOCK: return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: return "WSAENETDOWN"; + case WSAENETUNREACH: return "WSAENETUNREACH"; + case WSAENETRESET: return "WSAENETRESET"; + case WSAECONNABORTED: return "WSAECONNABORTED"; + case WSAECONNRESET: return "WSAECONNRESET"; + case WSAENOBUFS: return "WSAENOBUFS"; + case WSAEISCONN: return "WSAEISCONN"; + case WSAENOTCONN: return "WSAENOTCONN"; + case WSAESHUTDOWN: return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: return "WSAETIMEDOUT"; + case WSAECONNREFUSED: return "WSAECONNREFUSED"; + case WSAELOOP: return "WSAELOOP"; + case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; + case WSAEHOSTUNREACH: return "WSAEHOSTUNREACH"; + case WSAENOTEMPTY: return "WSAENOTEMPTY"; + case WSAEPROCLIM: return "WSAEPROCLIM"; + case WSAEUSERS: return "WSAEUSERS"; + case WSAEDQUOT: return "WSAEDQUOT"; + case WSAESTALE: return "WSAESTALE"; + case WSAEREMOTE: return "WSAEREMOTE"; + case WSAEDISCON: return "WSAEDISCON"; + case 0: return "no error"; + default: return "unknown WSAE error"; + } +#else + return strerror(errno); +#endif +} + +void LHNET_SleepUntilPacket_Microseconds(int microseconds) +{ +#ifdef FD_SET + fd_set fdreadset; + struct timeval tv; + int lastfd; + lhnetsocket_t *s; + FD_ZERO(&fdreadset); + lastfd = 0; + for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) + { + if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6) + { + if (lastfd < s->inetsocket) + lastfd = s->inetsocket; +#if defined(WIN32) && !defined(_MSC_VER) + FD_SET((int)s->inetsocket, &fdreadset); +#else + FD_SET((unsigned int)s->inetsocket, &fdreadset); +#endif + } + } + tv.tv_sec = microseconds / 1000000; + tv.tv_usec = microseconds % 1000000; + select(lastfd + 1, &fdreadset, NULL, NULL, &tv); +#else + Sys_Sleep(microseconds); +#endif +} + +lhnetsocket_t *LHNET_OpenSocket_Connectionless(lhnetaddress_t *address) +{ + lhnetsocket_t *lhnetsocket, *s; + if (!address) + return NULL; + lhnetsocket = (lhnetsocket_t *)Z_Malloc(sizeof(*lhnetsocket)); + if (lhnetsocket) + { + memset(lhnetsocket, 0, sizeof(*lhnetsocket)); + lhnetsocket->address = *address; + switch(lhnetsocket->address.addresstype) + { + case LHNETADDRESSTYPE_LOOP: + if (lhnetsocket->address.port == 0) + { + // allocate a port dynamically + // this search will always terminate because there is never + // an allocated socket with port 0, so if the number wraps it + // will find the port is unused, and then refuse to use port + // 0, causing an intentional failure condition + lhnetsocket->address.port = 1024; + for (;;) + { + for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) + if (s->address.addresstype == lhnetsocket->address.addresstype && s->address.port == lhnetsocket->address.port) + break; + if (s == &lhnet_socketlist) + break; + lhnetsocket->address.port++; + } + } + // check if the port is available + for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) + if (s->address.addresstype == lhnetsocket->address.addresstype && s->address.port == lhnetsocket->address.port) + break; + if (s == &lhnet_socketlist && lhnetsocket->address.port != 0) + { + lhnetsocket->next = &lhnet_socketlist; + lhnetsocket->prev = lhnetsocket->next->prev; + lhnetsocket->next->prev = lhnetsocket; + lhnetsocket->prev->next = lhnetsocket; + return lhnetsocket; + } + break; + case LHNETADDRESSTYPE_INET4: +#ifdef SUPPORTIPV6 + case LHNETADDRESSTYPE_INET6: +#endif +#ifdef WIN32 + if (lhnet_didWSAStartup) + { +#endif +#ifdef SUPPORTIPV6 + if ((lhnetsocket->inetsocket = socket(address->addresstype == LHNETADDRESSTYPE_INET6 ? PF_INET6 : PF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) +#else + if ((lhnetsocket->inetsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) +#endif + { +#ifdef WIN32 + u_long _false = 0; +#endif +#ifdef MSG_DONTWAIT + if (1) +#else +#ifdef WIN32 + u_long _true = 1; +#else + char _true = 1; +#endif + if (ioctlsocket(lhnetsocket->inetsocket, FIONBIO, &_true) != -1) +#endif + { +#ifdef IPV6_V6ONLY + // We need to set this flag to tell the OS that we only listen on IPv6. If we don't + // most OSes will create a dual-protocol socket that also listens on IPv4. In this case + // if an IPv4 socket is already bound to the port we want, our bind() call will fail. + int ipv6_only = 1; + if (address->addresstype != LHNETADDRESSTYPE_INET6 + || setsockopt (lhnetsocket->inetsocket, IPPROTO_IPV6, IPV6_V6ONLY, + (const char *)&ipv6_only, sizeof(ipv6_only)) == 0 +#ifdef WIN32 + // The Win32 API only supports IPV6_V6ONLY since Windows Vista, but fortunately + // the default value is what we want on Win32 anyway (IPV6_V6ONLY = true) + || SOCKETERRNO == WSAENOPROTOOPT +#endif + ) +#endif + { + lhnetaddressnative_t *localaddress = (lhnetaddressnative_t *)&lhnetsocket->address; + SOCKLEN_T namelen; + int bindresult; + +#if defined(SOL_RFC1149) && defined(RFC1149_1149ONLY) + // we got reports of massive lags when this protocol was chosen as transport + // so better turn it off + { + int rfc1149only = 0; + int rfc1149enabled = 0; + if(setsockopt(lhnetsocket->inetsocket, SOL_RFC1149, RFC1149_1149ONLY, &rfc1149only)) + Con_Printf("LHNET_OpenSocket_Connectionless: warning: setsockopt(RFC1149_1149ONLY) returned error: %s\n", LHNETPRIVATE_StrError()); + if(setsockopt(lhnetsocket->inetsocket, SOL_RFC1149, RFC1149_ENABLED, &rfc1149enabled)) + Con_Printf("LHNET_OpenSocket_Connectionless: warning: setsockopt(RFC1149_ENABLED) returned error: %s\n", LHNETPRIVATE_StrError()); + } +#endif + +#ifdef SUPPORTIPV6 + if (address->addresstype == LHNETADDRESSTYPE_INET6) + { + namelen = sizeof(localaddress->addr.in6); + bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); + if (bindresult != -1) + getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen); + } + else +#endif + { + namelen = sizeof(localaddress->addr.in); + bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); + if (bindresult != -1) + getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen); + } + if (bindresult != -1) + { + int i = 1; + // enable broadcast on this socket + setsockopt(lhnetsocket->inetsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)); +#ifdef IP_TOS + { + // enable DSCP for ToS support + int tos = lhnet_default_dscp << 2; + setsockopt(lhnetsocket->inetsocket, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof(tos)); + } +#endif + lhnetsocket->next = &lhnet_socketlist; + lhnetsocket->prev = lhnetsocket->next->prev; + lhnetsocket->next->prev = lhnetsocket; + lhnetsocket->prev->next = lhnetsocket; +#ifdef WIN32 + if (ioctlsocket(lhnetsocket->inetsocket, SIO_UDP_CONNRESET, &_false) == -1) + Con_DPrintf("LHNET_OpenSocket_Connectionless: ioctlsocket SIO_UDP_CONNRESET returned error: %s\n", LHNETPRIVATE_StrError()); +#endif + return lhnetsocket; + } + else + Con_Printf("LHNET_OpenSocket_Connectionless: bind returned error: %s\n", LHNETPRIVATE_StrError()); + } +#ifdef IPV6_V6ONLY + else + Con_Printf("LHNET_OpenSocket_Connectionless: setsockopt(IPV6_V6ONLY) returned error: %s\n", LHNETPRIVATE_StrError()); +#endif + } + else + Con_Printf("LHNET_OpenSocket_Connectionless: ioctlsocket returned error: %s\n", LHNETPRIVATE_StrError()); + closesocket(lhnetsocket->inetsocket); + } + else + Con_Printf("LHNET_OpenSocket_Connectionless: socket returned error: %s\n", LHNETPRIVATE_StrError()); +#ifdef WIN32 + } + else + Con_Print("LHNET_OpenSocket_Connectionless: can't open a socket (WSAStartup failed during LHNET_Init)\n"); +#endif + break; + default: + break; + } + Z_Free(lhnetsocket); + } + return NULL; +} + +void LHNET_CloseSocket(lhnetsocket_t *lhnetsocket) +{ + if (lhnetsocket) + { + // unlink from socket list + if (lhnetsocket->next == NULL) + return; // invalid! + lhnetsocket->next->prev = lhnetsocket->prev; + lhnetsocket->prev->next = lhnetsocket->next; + lhnetsocket->next = NULL; + lhnetsocket->prev = NULL; + + // no special close code for loopback, just inet + if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4 || lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) + { + closesocket(lhnetsocket->inetsocket); + } + Z_Free(lhnetsocket); + } +} + +lhnetaddress_t *LHNET_AddressFromSocket(lhnetsocket_t *sock) +{ + if (sock) + return &sock->address; + else + return NULL; +} + +int LHNET_Read(lhnetsocket_t *lhnetsocket, void *content, int maxcontentlength, lhnetaddress_t *vaddress) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + int value = 0; + if (!lhnetsocket || !address || !content || maxcontentlength < 1) + return -1; + if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_LOOP) + { + time_t currenttime; + lhnetpacket_t *p, *pnext; + // scan for any old packets to timeout while searching for a packet + // that is waiting to be delivered to this socket + currenttime = time(NULL); + for (p = lhnet_packetlist.next;p != &lhnet_packetlist;p = pnext) + { + pnext = p->next; + if (p->timeout < currenttime) + { + // unlink and free + p->next->prev = p->prev; + p->prev->next = p->next; + Z_Free(p); + continue; + } +#ifndef STANDALONETEST + if (cl_netlocalping.value && (realtime - cl_netlocalping.value * (1.0 / 2000.0)) < p->sentdoubletime) + continue; +#endif + if (value == 0 && p->destinationport == lhnetsocket->address.port) + { + if (p->length <= maxcontentlength) + { + lhnetaddressnative_t *localaddress = (lhnetaddressnative_t *)&lhnetsocket->address; + *address = *localaddress; + address->port = p->sourceport; + memcpy(content, p->data, p->length); + value = p->length; + } + else + value = -1; + // unlink and free + p->next->prev = p->prev; + p->prev->next = p->next; + Z_Free(p); + } + } + } + else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4) + { + SOCKLEN_T inetaddresslength; + address->addresstype = LHNETADDRESSTYPE_NONE; + inetaddresslength = sizeof(address->addr.in); + value = recvfrom(lhnetsocket->inetsocket, (char *)content, maxcontentlength, LHNET_RECVFROM_FLAGS, &address->addr.sock, &inetaddresslength); + if (value > 0) + { + address->addresstype = LHNETADDRESSTYPE_INET4; + address->port = ntohs(address->addr.in.sin_port); + return value; + } + else if (value < 0) + { + int e = SOCKETERRNO; + if (e == EWOULDBLOCK) + return 0; + switch (e) + { + case ECONNREFUSED: + Con_Print("Connection refused\n"); + return 0; + } + Con_DPrintf("LHNET_Read: recvfrom returned error: %s\n", LHNETPRIVATE_StrError()); + } + } +#ifdef SUPPORTIPV6 + else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) + { + SOCKLEN_T inetaddresslength; + address->addresstype = LHNETADDRESSTYPE_NONE; + inetaddresslength = sizeof(address->addr.in6); + value = recvfrom(lhnetsocket->inetsocket, (char *)content, maxcontentlength, LHNET_RECVFROM_FLAGS, &address->addr.sock, &inetaddresslength); + if (value > 0) + { + address->addresstype = LHNETADDRESSTYPE_INET6; + address->port = ntohs(address->addr.in6.sin6_port); + return value; + } + else if (value == -1) + { + int e = SOCKETERRNO; + if (e == EWOULDBLOCK) + return 0; + switch (e) + { + case ECONNREFUSED: + Con_Print("Connection refused\n"); + return 0; + } + Con_DPrintf("LHNET_Read: recvfrom returned error: %s\n", LHNETPRIVATE_StrError()); + } + } +#endif + return value; +} + +int LHNET_Write(lhnetsocket_t *lhnetsocket, const void *content, int contentlength, const lhnetaddress_t *vaddress) +{ + lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; + int value = -1; + if (!lhnetsocket || !address || !content || contentlength < 1) + return -1; + if (lhnetsocket->address.addresstype != address->addresstype) + return -1; + if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_LOOP) + { + lhnetpacket_t *p; + p = (lhnetpacket_t *)Z_Malloc(sizeof(*p) + contentlength); + p->data = (void *)(p + 1); + memcpy(p->data, content, contentlength); + p->length = contentlength; + p->sourceport = lhnetsocket->address.port; + p->destinationport = address->port; + p->timeout = time(NULL) + 10; + p->next = &lhnet_packetlist; + p->prev = p->next->prev; + p->next->prev = p; + p->prev->next = p; +#ifndef STANDALONETEST + p->sentdoubletime = realtime; +#endif + value = contentlength; + } + else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4) + { + value = sendto(lhnetsocket->inetsocket, (char *)content, contentlength, LHNET_SENDTO_FLAGS, (struct sockaddr *)&address->addr.in, sizeof(struct sockaddr_in)); + if (value == -1) + { + if (SOCKETERRNO == EWOULDBLOCK) + return 0; + Con_DPrintf("LHNET_Write: sendto returned error: %s\n", LHNETPRIVATE_StrError()); + } + } +#ifdef SUPPORTIPV6 + else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) + { + value = sendto(lhnetsocket->inetsocket, (char *)content, contentlength, 0, (struct sockaddr *)&address->addr.in6, sizeof(struct sockaddr_in6)); + if (value == -1) + { + if (SOCKETERRNO == EWOULDBLOCK) + return 0; + Con_DPrintf("LHNET_Write: sendto returned error: %s\n", LHNETPRIVATE_StrError()); + } + } +#endif + return value; +} + +#ifdef STANDALONETEST +int main(int argc, char **argv) +{ +#if 1 + char *buffer = "test", buffer2[1024]; + int blen = strlen(buffer); + int b2len = 1024; + lhnetsocket_t *sock1; + lhnetsocket_t *sock2; + lhnetaddress_t myaddy1; + lhnetaddress_t myaddy2; + lhnetaddress_t myaddy3; + lhnetaddress_t localhostaddy1; + lhnetaddress_t localhostaddy2; + int test1; + int test2; + + printf("calling LHNET_Init\n"); + LHNET_Init(); + + printf("calling LHNET_FromPort twice to create two local addresses\n"); + LHNETADDRESS_FromPort(&myaddy1, LHNETADDRESSTYPE_INET4, 4000); + LHNETADDRESS_FromPort(&myaddy2, LHNETADDRESSTYPE_INET4, 4001); + LHNETADDRESS_FromString(&localhostaddy1, "127.0.0.1", 4000); + LHNETADDRESS_FromString(&localhostaddy2, "127.0.0.1", 4001); + + printf("calling LHNET_OpenSocket_Connectionless twice to create two local sockets\n"); + sock1 = LHNET_OpenSocket_Connectionless(&myaddy1); + sock2 = LHNET_OpenSocket_Connectionless(&myaddy2); + + printf("calling LHNET_Write to send a packet from the first socket to the second socket\n"); + test1 = LHNET_Write(sock1, buffer, blen, &localhostaddy2); + printf("sleeping briefly\n"); +#ifdef WIN32 + Sleep (100); +#else + usleep (100000); +#endif + printf("calling LHNET_Read on the second socket to read the packet sent from the first socket\n"); + test2 = LHNET_Read(sock2, buffer2, b2len - 1, &myaddy3); + if (test2 > 0) + Con_Printf("socket to socket test succeeded\n"); + else + Con_Printf("socket to socket test failed\n"); + +#ifdef WIN32 + printf("press any key to exit\n"); + getchar(); +#endif + + printf("calling LHNET_Shutdown\n"); + LHNET_Shutdown(); + printf("exiting\n"); + return 0; +#else + lhnetsocket_t *sock[16], *sendsock; + int i; + int numsockets; + int count; + int length; + int port; + time_t oldtime; + time_t newtime; + char *sendmessage; + int sendmessagelength; + lhnetaddress_t destaddress; + lhnetaddress_t receiveaddress; + lhnetaddress_t sockaddress[16]; + char buffer[1536], addressstring[128], addressstring2[128]; + if ((argc == 2 || argc == 5) && (port = atoi(argv[1])) >= 1 && port < 65535) + { + printf("calling LHNET_Init()\n"); + LHNET_Init(); + + numsockets = 0; + LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_LOOP, port); + LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_INET4, port); + LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_INET6, port+1); + + sendsock = NULL; + sendmessage = NULL; + sendmessagelength = 0; + + for (i = 0;i < numsockets;i++) + { + LHNETADDRESS_ToString(&sockaddress[i], addressstring, sizeof(addressstring), 1); + printf("calling LHNET_OpenSocket_Connectionless(<%s>)\n", addressstring); + if ((sock[i] = LHNET_OpenSocket_Connectionless(&sockaddress[i]))) + { + LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); + printf("opened socket successfully (address \"%s\")\n", addressstring2); + } + else + { + printf("failed to open socket\n"); + if (i == 0) + { + LHNET_Shutdown(); + return -1; + } + } + } + count = 0; + if (argc == 5) + { + count = atoi(argv[2]); + if (LHNETADDRESS_FromString(&destaddress, argv[3], -1)) + { + sendmessage = argv[4]; + sendmessagelength = strlen(sendmessage); + sendsock = NULL; + for (i = 0;i < numsockets;i++) + if (sock[i] && LHNETADDRESS_GetAddressType(&destaddress) == LHNETADDRESS_GetAddressType(&sockaddress[i])) + sendsock = sock[i]; + if (sendsock == NULL) + { + printf("Could not find an open socket matching the addresstype (%i) of destination address, switching to listen only mode\n", LHNETADDRESS_GetAddressType(&destaddress)); + argc = 2; + } + } + else + { + printf("LHNETADDRESS_FromString did not like the address \"%s\", switching to listen only mode\n", argv[3]); + argc = 2; + } + } + printf("started, now listening for \"exit\" on the opened sockets\n"); + oldtime = time(NULL); + for(;;) + { +#ifdef WIN32 + Sleep(1); +#else + usleep(1); +#endif + for (i = 0;i < numsockets;i++) + { + if (sock[i]) + { + length = LHNET_Read(sock[i], buffer, sizeof(buffer), &receiveaddress); + if (length < 0) + printf("localsock read error: length < 0"); + else if (length > 0 && length < (int)sizeof(buffer)) + { + buffer[length] = 0; + LHNETADDRESS_ToString(&receiveaddress, addressstring, sizeof(addressstring), 1); + LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); + printf("received message \"%s\" from \"%s\" on socket \"%s\"\n", buffer, addressstring, addressstring2); + if (!strcmp(buffer, "exit")) + break; + } + } + } + if (i < numsockets) + break; + if (argc == 5 && count > 0) + { + newtime = time(NULL); + if (newtime != oldtime) + { + LHNETADDRESS_ToString(&destaddress, addressstring, sizeof(addressstring), 1); + LHNETADDRESS_ToString(LHNET_AddressFromSocket(sendsock), addressstring2, sizeof(addressstring2), 1); + printf("calling LHNET_Write(<%s>, \"%s\", %i, <%s>)\n", addressstring2, sendmessage, sendmessagelength, addressstring); + length = LHNET_Write(sendsock, sendmessage, sendmessagelength, &destaddress); + if (length == sendmessagelength) + printf("sent successfully\n"); + else + printf("LH_Write failed, returned %i (length of message was %i)\n", length, strlen(argv[4])); + oldtime = newtime; + count--; + if (count <= 0) + printf("Done sending, still listening for \"exit\"\n"); + } + } + } + for (i = 0;i < numsockets;i++) + { + if (sock[i]) + { + LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); + printf("calling LHNET_CloseSocket(<%s>)\n", addressstring2); + LHNET_CloseSocket(sock[i]); + } + } + printf("calling LHNET_Shutdown()\n"); + LHNET_Shutdown(); + return 0; + } + printf("Testing code for lhnet.c\nusage: lhnettest [ ]\n"); + return -1; +#endif +} +#endif + diff --git a/app/jni/lhnet.h b/app/jni/lhnet.h new file mode 100644 index 0000000..e66e5a5 --- /dev/null +++ b/app/jni/lhnet.h @@ -0,0 +1,52 @@ + +// Written by Forest Hale 2003-06-15 and placed into public domain. + +#ifndef LHNET_H +#define LHNET_H + +typedef enum lhnetaddresstype_e +{ + LHNETADDRESSTYPE_NONE, + LHNETADDRESSTYPE_LOOP, + LHNETADDRESSTYPE_INET4, + LHNETADDRESSTYPE_INET6 +} +lhnetaddresstype_t; + +typedef struct lhnetaddress_s +{ + lhnetaddresstype_t addresstype; + int port; // used by LHNETADDRESSTYPE_LOOP + unsigned char storage[256]; // sockaddr_in or sockaddr_in6 +} +lhnetaddress_t; + +int LHNETADDRESS_FromPort(lhnetaddress_t *address, lhnetaddresstype_t addresstype, int port); +int LHNETADDRESS_FromString(lhnetaddress_t *address, const char *string, int defaultport); +int LHNETADDRESS_ToString(const lhnetaddress_t *address, char *string, int stringbuffersize, int includeport); +int LHNETADDRESS_GetAddressType(const lhnetaddress_t *address); +const char *LHNETADDRESS_GetInterfaceName(const lhnetaddress_t *address, char *ifname, size_t ifnamelength); +int LHNETADDRESS_GetPort(const lhnetaddress_t *address); +int LHNETADDRESS_SetPort(lhnetaddress_t *address, int port); +int LHNETADDRESS_Compare(const lhnetaddress_t *address1, const lhnetaddress_t *address2); + +typedef struct lhnetsocket_s +{ + lhnetaddress_t address; + int inetsocket; + struct lhnetsocket_s *next, *prev; +} +lhnetsocket_t; + +void LHNET_Init(void); +void LHNET_Shutdown(void); +int LHNET_DefaultDSCP(int dscp); // < 0: query; >= 0: set (returns previous value) +void LHNET_SleepUntilPacket_Microseconds(int microseconds); +lhnetsocket_t *LHNET_OpenSocket_Connectionless(lhnetaddress_t *address); +void LHNET_CloseSocket(lhnetsocket_t *lhnetsocket); +lhnetaddress_t *LHNET_AddressFromSocket(lhnetsocket_t *sock); +int LHNET_Read(lhnetsocket_t *lhnetsocket, void *content, int maxcontentlength, lhnetaddress_t *address); +int LHNET_Write(lhnetsocket_t *lhnetsocket, const void *content, int contentlength, const lhnetaddress_t *address); + +#endif + diff --git a/app/jni/libcurl.c b/app/jni/libcurl.c new file mode 100644 index 0000000..2d95afb --- /dev/null +++ b/app/jni/libcurl.c @@ -0,0 +1,1828 @@ +#include "quakedef.h" +#include "fs.h" +#include "libcurl.h" +#include "thread.h" + +#include "image.h" +#include "jpeg.h" +#include "image_png.h" + +static cvar_t cl_curl_maxdownloads = {CVAR_SAVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"}; +static cvar_t cl_curl_maxspeed = {CVAR_SAVE, "cl_curl_maxspeed","300", "maximum download speed (KiB/s)"}; +static cvar_t sv_curl_defaulturl = {CVAR_SAVE, "sv_curl_defaulturl","", "default autodownload source URL"}; +static cvar_t sv_curl_serverpackages = {CVAR_SAVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"}; +static cvar_t sv_curl_maxspeed = {CVAR_SAVE, "sv_curl_maxspeed","0", "maximum download speed for clients downloading from sv_curl_defaulturl (KiB/s)"}; +static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"}; +static cvar_t cl_curl_useragent = {0, "cl_curl_useragent","1", "send the User-Agent string (note: turning this off may break stuff)"}; +static cvar_t cl_curl_useragent_append = {0, "cl_curl_useragent_append","", "a string to append to the User-Agent string (useful for name and version number of your mod)"}; + +/* +================================================================= + + Minimal set of definitions from libcurl + + WARNING: for a matter of simplicity, several pointer types are + casted to "void*", and most enumerated values are not included + +================================================================= +*/ + +typedef struct CURL_s CURL; +typedef struct CURLM_s CURLM; +typedef struct curl_slist curl_slist; +typedef enum +{ + CURLE_OK = 0 +} +CURLcode; +typedef enum +{ + CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */ + CURLM_OK = 0 +} +CURLMcode; +#define CURL_GLOBAL_NOTHING 0 +#define CURL_GLOBAL_SSL 1 +#define CURL_GLOBAL_WIN32 2 +#define CURLOPTTYPE_LONG 0 +#define CURLOPTTYPE_OBJECTPOINT 10000 +#define CURLOPTTYPE_FUNCTIONPOINT 20000 +#define CURLOPTTYPE_OFF_T 30000 +#define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number +typedef enum +{ + CINIT(WRITEDATA, OBJECTPOINT, 1), + CINIT(URL, OBJECTPOINT, 2), + CINIT(ERRORBUFFER, OBJECTPOINT, 10), + CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11), + CINIT(POSTFIELDS, OBJECTPOINT, 15), + CINIT(REFERER, OBJECTPOINT, 16), + CINIT(USERAGENT, OBJECTPOINT, 18), + CINIT(LOW_SPEED_LIMIT, LONG , 19), + CINIT(LOW_SPEED_TIME, LONG, 20), + CINIT(RESUME_FROM, LONG, 21), + CINIT(HTTPHEADER, OBJECTPOINT, 23), + CINIT(POST, LONG, 47), /* HTTP POST method */ + CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */ + CINIT(POSTFIELDSIZE, LONG, 60), + CINIT(PRIVATE, OBJECTPOINT, 103), + CINIT(PROTOCOLS, LONG, 181), + CINIT(REDIR_PROTOCOLS, LONG, 182) +} +CURLoption; +#define CURLPROTO_HTTP (1<<0) +#define CURLPROTO_HTTPS (1<<1) +#define CURLPROTO_FTP (1<<2) +typedef enum +{ + CURLINFO_TEXT = 0, + CURLINFO_HEADER_IN, /* 1 */ + CURLINFO_HEADER_OUT, /* 2 */ + CURLINFO_DATA_IN, /* 3 */ + CURLINFO_DATA_OUT, /* 4 */ + CURLINFO_SSL_DATA_IN, /* 5 */ + CURLINFO_SSL_DATA_OUT, /* 6 */ + CURLINFO_END +} +curl_infotype; +#define CURLINFO_STRING 0x100000 +#define CURLINFO_LONG 0x200000 +#define CURLINFO_DOUBLE 0x300000 +#define CURLINFO_SLIST 0x400000 +#define CURLINFO_MASK 0x0fffff +#define CURLINFO_TYPEMASK 0xf00000 +typedef enum +{ + CURLINFO_NONE, /* first, never use this */ + CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1, + CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, + CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3, + CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4, + CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5, + CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6, + CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7, + CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8, + CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9, + CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10, + CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11, + CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12, + CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13, + CURLINFO_FILETIME = CURLINFO_LONG + 14, + CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, + CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16, + CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17, + CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, + CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19, + CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20, + CURLINFO_PRIVATE = CURLINFO_STRING + 21, + CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22, + CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23, + CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24, + CURLINFO_OS_ERRNO = CURLINFO_LONG + 25, + CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26, + CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27 +} +CURLINFO; + +typedef enum +{ + CURLMSG_NONE, /* first, not used */ + CURLMSG_DONE, /* This easy handle has completed. 'result' contains + the CURLcode of the transfer */ + CURLMSG_LAST +} +CURLMSG; +typedef struct +{ + CURLMSG msg; /* what this message means */ + CURL *easy_handle; /* the handle it concerns */ + union + { + void *whatever; /* message-specific data */ + CURLcode result; /* return code for transfer */ + } + data; +} +CURLMsg; + +static void (*qcurl_global_init) (long flags); +static void (*qcurl_global_cleanup) (void); + +static CURL * (*qcurl_easy_init) (void); +static void (*qcurl_easy_cleanup) (CURL *handle); +static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...); +static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...); +static const char * (*qcurl_easy_strerror) (CURLcode); + +static CURLM * (*qcurl_multi_init) (void); +static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles); +static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle); +static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle); +static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue); +static void (*qcurl_multi_cleanup) (CURLM *); +static const char * (*qcurl_multi_strerror) (CURLcode); +static curl_slist * (*qcurl_slist_append) (curl_slist *list, const char *string); +static void (*qcurl_slist_free_all) (curl_slist *list); + +static dllfunction_t curlfuncs[] = +{ + {"curl_global_init", (void **) &qcurl_global_init}, + {"curl_global_cleanup", (void **) &qcurl_global_cleanup}, + {"curl_easy_init", (void **) &qcurl_easy_init}, + {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup}, + {"curl_easy_setopt", (void **) &qcurl_easy_setopt}, + {"curl_easy_strerror", (void **) &qcurl_easy_strerror}, + {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo}, + {"curl_multi_init", (void **) &qcurl_multi_init}, + {"curl_multi_perform", (void **) &qcurl_multi_perform}, + {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle}, + {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle}, + {"curl_multi_info_read", (void **) &qcurl_multi_info_read}, + {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup}, + {"curl_multi_strerror", (void **) &qcurl_multi_strerror}, + {"curl_slist_append", (void **) &qcurl_slist_append}, + {"curl_slist_free_all", (void **) &qcurl_slist_free_all}, + {NULL, NULL} +}; + +// Handle for CURL DLL +static dllhandle_t curl_dll = NULL; +// will be checked at many places to find out if qcurl calls are allowed + +#define LOADTYPE_NONE 0 +#define LOADTYPE_PAK 1 +#define LOADTYPE_CACHEPIC 2 +#define LOADTYPE_SKINFRAME 3 + +void *curl_mutex = NULL; + +typedef struct downloadinfo_s +{ + char filename[MAX_OSPATH]; + char url[1024]; + char referer[256]; + qfile_t *stream; + fs_offset_t startpos; + CURL *curle; + qboolean started; + int loadtype; + unsigned long bytes_received; // for buffer + double bytes_received_curl; // for throttling + double bytes_sent_curl; // for throttling + struct downloadinfo_s *next, *prev; + qboolean forthismap; + double maxspeed; + curl_slist *slist; // http headers + + unsigned char *buffer; + size_t buffersize; + curl_callback_t callback; + void *callback_data; + + const unsigned char *postbuf; + size_t postbufsize; + const char *post_content_type; + const char *extraheaders; +} +downloadinfo; +static downloadinfo *downloads = NULL; +static int numdownloads = 0; + +static qboolean noclear = FALSE; + +static int numdownloads_fail = 0; +static int numdownloads_success = 0; +static int numdownloads_added = 0; +static char command_when_done[256] = ""; +static char command_when_error[256] = ""; + +/* +==================== +Curl_CommandWhenDone + +Sets the command which is to be executed when the last download completes AND +all downloads since last server connect ended with a successful status. +Setting the command to NULL clears it. +==================== +*/ +static void Curl_CommandWhenDone(const char *cmd) +{ + if(!curl_dll) + return; + if(cmd) + strlcpy(command_when_done, cmd, sizeof(command_when_done)); + else + *command_when_done = 0; +} + +/* +FIXME +Do not use yet. Not complete. +Problem: what counts as an error? +*/ + +static void Curl_CommandWhenError(const char *cmd) +{ + if(!curl_dll) + return; + if(cmd) + strlcpy(command_when_error, cmd, sizeof(command_when_error)); + else + *command_when_error = 0; +} + +/* +==================== +Curl_Clear_forthismap + +Clears the "will disconnect on failure" flags. +==================== +*/ +void Curl_Clear_forthismap(void) +{ + downloadinfo *di; + if(noclear) + return; + if (curl_mutex) Thread_LockMutex(curl_mutex); + for(di = downloads; di; di = di->next) + di->forthismap = false; + Curl_CommandWhenError(NULL); + Curl_CommandWhenDone(NULL); + numdownloads_fail = 0; + numdownloads_success = 0; + numdownloads_added = 0; + if (curl_mutex) Thread_UnlockMutex(curl_mutex); +} + +/* +==================== +Curl_Have_forthismap + +Returns true if a download needed for the current game is running. +==================== +*/ +qboolean Curl_Have_forthismap(void) +{ + return numdownloads_added != 0; +} + +void Curl_Register_predownload(void) +{ + if (curl_mutex) Thread_LockMutex(curl_mutex); + Curl_CommandWhenDone("cl_begindownloads"); + Curl_CommandWhenError("cl_begindownloads"); + if (curl_mutex) Thread_UnlockMutex(curl_mutex); +} + +/* +==================== +Curl_CheckCommandWhenDone + +Checks if a "done command" is to be executed. +All downloads finished, at least one success since connect, no single failure +-> execute the command. +*/ +static void Curl_CheckCommandWhenDone(void) +{ + if(!curl_dll) + return; + if(numdownloads_added && ((numdownloads_success + numdownloads_fail) == numdownloads_added)) + { + if(numdownloads_fail == 0) + { + Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done); + Cbuf_AddText("\n"); + Cbuf_AddText(command_when_done); + Cbuf_AddText("\n"); + } + else + { + Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error); + Cbuf_AddText("\n"); + Cbuf_AddText(command_when_error); + Cbuf_AddText("\n"); + } + Curl_Clear_forthismap(); + } +} + +/* +==================== +CURL_CloseLibrary + +Load the cURL DLL +==================== +*/ +static qboolean CURL_OpenLibrary (void) +{ + const char* dllnames [] = + { +#if defined(WIN32) + "libcurl-4.dll", + "libcurl-3.dll", +#elif defined(MACOSX) + "libcurl.4.dylib", // Mac OS X Notyetreleased + "libcurl.3.dylib", // Mac OS X Tiger + "libcurl.2.dylib", // Mac OS X Panther +#else + "libcurl.so.4", + "libcurl.so.3", + "libcurl.so", // FreeBSD +#endif + NULL + }; + + // Already loaded? + if (curl_dll) + return true; + + // Load the DLL + return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs); +} + + +/* +==================== +CURL_CloseLibrary + +Unload the cURL DLL +==================== +*/ +static void CURL_CloseLibrary (void) +{ + Sys_UnloadLibrary (&curl_dll); +} + + +static CURLM *curlm = NULL; +static double bytes_received = 0; // used for bandwidth throttling +static double bytes_sent = 0; // used for bandwidth throttling +static double curltime = 0; + +/* +==================== +CURL_fwrite + +fwrite-compatible function that writes the data to a file. libcurl can call +this. +==================== +*/ +static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi) +{ + fs_offset_t ret = -1; + size_t bytes = size * nmemb; + downloadinfo *di = (downloadinfo *) vdi; + + if(di->buffer) + { + if(di->bytes_received + bytes <= di->buffersize) + { + memcpy(di->buffer + di->bytes_received, data, bytes); + ret = bytes; + } + // otherwise: buffer overrun, ret stays -1 + } + + if(di->stream) + { + ret = FS_Write(di->stream, data, bytes); + } + + di->bytes_received += bytes; + + return ret; // why not ret / nmemb? +} + +typedef enum +{ + CURL_DOWNLOAD_SUCCESS = 0, + CURL_DOWNLOAD_FAILED, + CURL_DOWNLOAD_ABORTED, + CURL_DOWNLOAD_SERVERERROR +} +CurlStatus; + +static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) +{ + downloadinfo *di = (downloadinfo *) cbdata; + switch(status) + { + case CURLCBSTATUS_OK: + Con_DPrintf("Download of %s: OK\n", di->filename); + break; + case CURLCBSTATUS_FAILED: + Con_DPrintf("Download of %s: FAILED\n", di->filename); + break; + case CURLCBSTATUS_ABORTED: + Con_DPrintf("Download of %s: ABORTED\n", di->filename); + break; + case CURLCBSTATUS_SERVERERROR: + Con_DPrintf("Download of %s: (unknown server error)\n", di->filename); + break; + case CURLCBSTATUS_UNKNOWN: + Con_DPrintf("Download of %s: (unknown client error)\n", di->filename); + break; + default: + Con_DPrintf("Download of %s: %d\n", di->filename, status); + break; + } +} + +static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) +{ + curl_default_callback(status, length_received, buffer, cbdata); +} + +static unsigned char *decode_image(downloadinfo *di, const char *content_type) +{ + unsigned char *pixels = NULL; + fs_offset_t filesize = 0; + unsigned char *data = FS_LoadFile(di->filename, tempmempool, true, &filesize); + if(data) + { + int mip = 0; + if(!strcmp(content_type, "image/jpeg")) + pixels = JPEG_LoadImage_BGRA(data, filesize, &mip); + else if(!strcmp(content_type, "image/png")) + pixels = PNG_LoadImage_BGRA(data, filesize, &mip); + else if(filesize >= 7 && !strncmp((char *) data, "\xFF\xD8", 7)) + pixels = JPEG_LoadImage_BGRA(data, filesize, &mip); + else if(filesize >= 7 && !strncmp((char *) data, "\x89PNG\x0D\x0A\x1A\x0A", 7)) + pixels = PNG_LoadImage_BGRA(data, filesize, &mip); + else + Con_Printf("Did not detect content type: %s\n", content_type); + Mem_Free(data); + } + // do we call Image_MakeLinearColorsFromsRGB or not? + return pixels; +} + +/* +==================== +Curl_EndDownload + +stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS, +CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error +code from libcurl, or 0, if another error has occurred. +==================== +*/ +static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, int loadtype, qboolean forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); +static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error, const char *content_type_) +{ + char content_type[64]; + qboolean ok = false; + if(!curl_dll) + return; + switch(status) + { + case CURL_DOWNLOAD_SUCCESS: + ok = true; + di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data); + break; + case CURL_DOWNLOAD_FAILED: + di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data); + break; + case CURL_DOWNLOAD_ABORTED: + di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data); + break; + case CURL_DOWNLOAD_SERVERERROR: + // reopen to enforce it to have zero bytes again + if(di->stream) + { + FS_Close(di->stream); + di->stream = FS_OpenRealFile(di->filename, "wb", false); + } + + if(di->callback) + di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data); + break; + default: + if(di->callback) + di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data); + break; + } + if(content_type_) + strlcpy(content_type, content_type_, sizeof(content_type)); + else + *content_type = 0; + + if(di->curle) + { + qcurl_multi_remove_handle(curlm, di->curle); + qcurl_easy_cleanup(di->curle); + if(di->slist) + qcurl_slist_free_all(di->slist); + } + + if(!di->callback && ok && !di->bytes_received) + { + Con_Printf("ERROR: empty file\n"); + ok = false; + } + + if(di->stream) + FS_Close(di->stream); + +#define CLEAR_AND_RETRY() \ + do \ + { \ + di->stream = FS_OpenRealFile(di->filename, "wb", false); \ + FS_Close(di->stream); \ + if(di->startpos && !di->callback) \ + { \ + Curl_Begin(di->url, di->extraheaders, di->maxspeed, di->filename, di->loadtype, di->forthismap, di->post_content_type, di->postbuf, di->postbufsize, NULL, 0, NULL, NULL); \ + di->forthismap = false; \ + } \ + } \ + while(0) + + if(ok && di->loadtype == LOADTYPE_PAK) + { + ok = FS_AddPack(di->filename, NULL, true); + if(!ok) + CLEAR_AND_RETRY(); + } + else if(ok && di->loadtype == LOADTYPE_CACHEPIC) + { + const char *p; + unsigned char *pixels = NULL; + + p = di->filename; +#ifdef WE_ARE_EVIL + if(!strncmp(p, "dlcache/", 8)) + p += 8; +#endif + + pixels = decode_image(di, content_type); + if(pixels) + Draw_NewPic(p, image_width, image_height, true, pixels); + else + CLEAR_AND_RETRY(); + } + else if(ok && di->loadtype == LOADTYPE_SKINFRAME) + { + const char *p; + unsigned char *pixels = NULL; + + p = di->filename; +#ifdef WE_ARE_EVIL + if(!strncmp(p, "dlcache/", 8)) + p += 8; +#endif + + pixels = decode_image(di, content_type); + if(pixels) + R_SkinFrame_LoadInternalBGRA(p, TEXF_FORCE_RELOAD | TEXF_MIPMAP | TEXF_ALPHA, pixels, image_width, image_height, false); // TODO what sRGB argument to put here? + else + CLEAR_AND_RETRY(); + } + + if(di->prev) + di->prev->next = di->next; + else + downloads = di->next; + if(di->next) + di->next->prev = di->prev; + + --numdownloads; + if(di->forthismap) + { + if(ok) + ++numdownloads_success; + else + ++numdownloads_fail; + } + Z_Free(di); +} + +/* +==================== +CleanURL + +Returns a "cleaned up" URL for display (to strip login data) +==================== +*/ +static const char *CleanURL(const char *url, char *urlbuf, size_t urlbuflength) +{ + const char *p, *q, *r; + + // if URL is of form anything://foo-without-slash@rest, replace by anything://rest + p = strstr(url, "://"); + if(p) + { + q = strchr(p + 3, '@'); + if(q) + { + r = strchr(p + 3, '/'); + if(!r || q < r) + { + dpsnprintf(urlbuf, urlbuflength, "%.*s%s", (int)(p - url + 3), url, q + 1); + return urlbuf; + } + } + } + + return url; +} + +/* +==================== +CheckPendingDownloads + +checks if there are free download slots to start new downloads in. +To not start too many downloads at once, only one download is added at a time, +up to a maximum number of cl_curl_maxdownloads are running. +==================== +*/ +static void CheckPendingDownloads(void) +{ + const char *h; + char urlbuf[1024]; + char vabuf[1024]; + if(!curl_dll) + return; + if(numdownloads < cl_curl_maxdownloads.integer) + { + downloadinfo *di; + for(di = downloads; di; di = di->next) + { + if(!di->started) + { + if(!di->buffer) + { + Con_Printf("Downloading %s -> %s", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename); + + di->stream = FS_OpenRealFile(di->filename, "ab", false); + if(!di->stream) + { + Con_Printf("\nFAILED: Could not open output file %s\n", di->filename); + Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK, NULL); + return; + } + FS_Seek(di->stream, 0, SEEK_END); + di->startpos = FS_Tell(di->stream); + + if(di->startpos > 0) + Con_Printf(", resuming from position %ld", (long) di->startpos); + Con_Print("...\n"); + } + else + { + Con_DPrintf("Downloading %s -> memory\n", CleanURL(di->url, urlbuf, sizeof(urlbuf))); + di->startpos = 0; + } + + di->curle = qcurl_easy_init(); + di->slist = NULL; + qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url); + if(cl_curl_useragent.integer) + { + const char *ua +#ifdef HTTP_USER_AGENT + = HTTP_USER_AGENT; +#else + = engineversion; +#endif + if(!ua) + ua = ""; + if(*cl_curl_useragent_append.string) + ua = va(vabuf, sizeof(vabuf), "%s%s%s", + ua, + (ua[0] && ua[strlen(ua)-1] != ' ') + ? " " + : "", + cl_curl_useragent_append.string); + qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, ua); + } + else + qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, ""); + qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer); + qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos); + qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1); + qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite); + qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256); + qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45); + qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di); + qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di); + qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP); + if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK) + { + Con_Printf("^1WARNING:^7 for security reasons, please upgrade to libcurl 7.19.4 or above. In a later version of DarkPlaces, HTTP redirect support will be disabled for this libcurl version.\n"); + //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0); + } + if(di->post_content_type) + { + qcurl_easy_setopt(di->curle, CURLOPT_POST, 1); + qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDS, di->postbuf); + qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDSIZE, di->postbufsize); + di->slist = qcurl_slist_append(di->slist, va(vabuf, sizeof(vabuf), "Content-Type: %s", di->post_content_type)); + } + + // parse extra headers into slist + // \n separated list! + h = di->extraheaders; + while(h) + { + const char *hh = strchr(h, '\n'); + if(hh) + { + char *buf = (char *) Mem_Alloc(tempmempool, hh - h + 1); + memcpy(buf, h, hh - h); + buf[hh - h] = 0; + di->slist = qcurl_slist_append(di->slist, buf); + h = hh + 1; + } + else + { + di->slist = qcurl_slist_append(di->slist, h); + h = NULL; + } + } + + qcurl_easy_setopt(di->curle, CURLOPT_HTTPHEADER, di->slist); + + qcurl_multi_add_handle(curlm, di->curle); + di->started = true; + ++numdownloads; + if(numdownloads >= cl_curl_maxdownloads.integer) + break; + } + } + } +} + +/* +==================== +Curl_Init + +this function MUST be called before using anything else in this file. +On Win32, this must be called AFTER WSAStartup has been done! +==================== +*/ +void Curl_Init(void) +{ + CURL_OpenLibrary(); + if(!curl_dll) + return; + if (Thread_HasThreads()) curl_mutex = Thread_CreateMutex(); + qcurl_global_init(CURL_GLOBAL_NOTHING); + curlm = qcurl_multi_init(); +} + +/* +==================== +Curl_Shutdown + +Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET. +==================== +*/ +void Curl_ClearRequirements(void); +void Curl_Shutdown(void) +{ + if(!curl_dll) + return; + Curl_ClearRequirements(); + Curl_CancelAll(); + if (curl_mutex) Thread_DestroyMutex(curl_mutex); + CURL_CloseLibrary(); + curl_dll = NULL; +} + +/* +==================== +Curl_Find + +Finds the internal information block for a download given by file name. +==================== +*/ +static downloadinfo *Curl_Find(const char *filename) +{ + downloadinfo *di; + if(!curl_dll) + return NULL; + for(di = downloads; di; di = di->next) + if(!strcasecmp(di->filename, filename)) + return di; + return NULL; +} + +void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata) +{ + downloadinfo *di; + if(!curl_dll) + return; + for(di = downloads; di; ) + { + if(di->callback == callback && di->callback_data == cbdata) + { + di->callback = curl_quiet_callback; // do NOT call the callback + Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL); + di = downloads; + } + else + di = di->next; + } +} + +/* +==================== +Curl_Begin + +Starts a download of a given URL to the file name portion of this URL (or name +if given) in the "dlcache/" folder. +==================== +*/ +static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, int loadtype, qboolean forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) +{ + if(buf) + if(loadtype != LOADTYPE_NONE) + Host_Error("Curl_Begin: loadtype and buffer are both set"); + + if(!curl_dll) + { + return false; + } + else + { + char fn[MAX_OSPATH]; + char urlbuf[1024]; + const char *p, *q; + size_t length; + downloadinfo *di; + + // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server + p = strchr(URL, ':'); + if(p) + { + if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4)) + { + char addressstring[128]; + *addressstring = 0; + InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring)); + q = strchr(addressstring, ':'); + if(!q) + q = addressstring + strlen(addressstring); + if(*addressstring) + { + dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3); + URL = urlbuf; + } + } + } + + // Note: This extraction of the file name portion is NOT entirely correct. + // + // It does the following: + // + // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3 + // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3 + // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php + // + // However, I'd like to keep this "buggy" behavior so that PHP script + // authors can write download scripts without having to enable + // AcceptPathInfo on Apache. They just have to ensure that their script + // can be called with such a "fake" path name like + // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 + // + // By the way, such PHP scripts should either send the file or a + // "Location:" redirect; PHP code example: + // + // header("Location: http://www.example.com/"); + // + // By the way, this will set User-Agent to something like + // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to + // dp://serverhost:serverport/ so you can filter on this; an example + // httpd log file line might be: + // + // 141.2.16.3 - - [17/Mar/2006:22:32:43 +0100] "GET /maps/tznex07.pk3 HTTP/1.1" 200 1077455 "dp://141.2.16.7:26000/" "Nexuiz Linux 22:07:43 Mar 17 2006" + + if (curl_mutex) Thread_LockMutex(curl_mutex); + + if(buf) + { + if(!name) + name = CleanURL(URL, urlbuf, sizeof(urlbuf)); + } + else + { + if(!name) + { + name = CleanURL(URL, urlbuf, sizeof(urlbuf)); + p = strrchr(name, '/'); + p = p ? (p+1) : name; + q = strchr(p, '?'); + length = q ? (size_t)(q - p) : strlen(p); + dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p); + } + else + { + dpsnprintf(fn, sizeof(fn), "dlcache/%s", name); + } + + name = fn; // make it point back + + // already downloading the file? + { + downloadinfo *di = Curl_Find(fn); + if(di) + { + Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(di->url, urlbuf, sizeof(urlbuf))); + + // however, if it was not for this map yet... + if(forthismap && !di->forthismap) + { + di->forthismap = true; + // this "fakes" a download attempt so the client will wait for + // the download to finish and then reconnect + ++numdownloads_added; + } + + return false; + } + } + + if(FS_FileExists(fn)) + { + if(loadtype == LOADTYPE_PAK) + { + qboolean already_loaded; + if(FS_AddPack(fn, &already_loaded, true)) + { + Con_DPrintf("%s already exists, not downloading!\n", fn); + if(already_loaded) + Con_DPrintf("(pak was already loaded)\n"); + else + { + if(forthismap) + { + ++numdownloads_added; + ++numdownloads_success; + } + } + + return false; + } + else + { + qfile_t *f = FS_OpenRealFile(fn, "rb", false); + if(f) + { + char buf[4] = {0}; + FS_Read(f, buf, sizeof(buf)); // no "-1", I will use memcmp + + if(memcmp(buf, "PK\x03\x04", 4) && memcmp(buf, "PACK", 4)) + { + Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn); + FS_Close(f); + f = FS_OpenRealFile(fn, "wb", false); + if(f) + FS_Close(f); + } + else + { + // OK + FS_Close(f); + } + } + } + } + else + { + // never resume these + qfile_t *f = FS_OpenRealFile(fn, "wb", false); + if(f) + FS_Close(f); + } + } + } + + // if we get here, we actually want to download... so first verify the + // URL scheme (so one can't read local files using file://) + if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8)) + { + Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL); + if (curl_mutex) Thread_UnlockMutex(curl_mutex); + return false; + } + + if(forthismap) + ++numdownloads_added; + di = (downloadinfo *) Z_Malloc(sizeof(*di)); + strlcpy(di->filename, name, sizeof(di->filename)); + strlcpy(di->url, URL, sizeof(di->url)); + dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid"); + di->forthismap = forthismap; + di->stream = NULL; + di->startpos = 0; + di->curle = NULL; + di->started = false; + di->loadtype = loadtype; + di->maxspeed = maxspeed; + di->bytes_received = 0; + di->bytes_received_curl = 0; + di->bytes_sent_curl = 0; + di->extraheaders = extraheaders; + di->next = downloads; + di->prev = NULL; + if(di->next) + di->next->prev = di; + + di->buffer = buf; + di->buffersize = bufsize; + if(callback == NULL) + { + di->callback = curl_default_callback; + di->callback_data = di; + } + else + { + di->callback = callback; + di->callback_data = cbdata; + } + + if(post_content_type) + { + di->post_content_type = post_content_type; + di->postbuf = postbuf; + di->postbufsize = postbufsize; + } + else + { + di->post_content_type = NULL; + di->postbuf = NULL; + di->postbufsize = 0; + } + + downloads = di; + if (curl_mutex) Thread_UnlockMutex(curl_mutex); + return true; + } +} + +qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qboolean forthismap) +{ + return Curl_Begin(URL, NULL, maxspeed, name, loadtype, forthismap, NULL, NULL, 0, NULL, 0, NULL, NULL); +} +qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) +{ + return Curl_Begin(URL, NULL, maxspeed, NULL, false, false, NULL, NULL, 0, buf, bufsize, callback, cbdata); +} +qboolean Curl_Begin_ToMemory_POST(const char *URL, const char *extraheaders, double maxspeed, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) +{ + return Curl_Begin(URL, extraheaders, maxspeed, NULL, false, false, post_content_type, postbuf, postbufsize, buf, bufsize, callback, cbdata); +} + +/* +==================== +Curl_Run + +call this regularily as this will always download as much as possible without +blocking. +==================== +*/ +void Curl_Run(void) +{ + double maxspeed; + downloadinfo *di; + + noclear = FALSE; + + if(!cl_curl_enabled.integer) + return; + + if(!curl_dll) + return; + + if (curl_mutex) Thread_LockMutex(curl_mutex); + + Curl_CheckCommandWhenDone(); + + if(!downloads) + { + if (curl_mutex) Thread_UnlockMutex(curl_mutex); + return; + } + + if(realtime < curltime) // throttle + { + if (curl_mutex) Thread_UnlockMutex(curl_mutex); + return; + } + + { + int remaining; + CURLMcode mc; + + do + { + mc = qcurl_multi_perform(curlm, &remaining); + } + while(mc == CURLM_CALL_MULTI_PERFORM); + + for(di = downloads; di; di = di->next) + { + double b = 0; + if(di->curle) + { + qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_UPLOAD, &b); + bytes_sent += (b - di->bytes_sent_curl); + di->bytes_sent_curl = b; + qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_DOWNLOAD, &b); + bytes_sent += (b - di->bytes_received_curl); + di->bytes_received_curl = b; + } + } + + for(;;) + { + CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining); + if(!msg) + break; + if(msg->msg == CURLMSG_DONE) + { + const char *ct = NULL; + CurlStatus failed = CURL_DOWNLOAD_SUCCESS; + CURLcode result; + qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di); + result = msg->data.result; + if(result) + { + failed = CURL_DOWNLOAD_FAILED; + } + else + { + long code; + qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code); + switch(code / 100) + { + case 4: // e.g. 404? + case 5: // e.g. 500? + failed = CURL_DOWNLOAD_SERVERERROR; + result = (CURLcode) code; + break; + } + qcurl_easy_getinfo(msg->easy_handle, CURLINFO_CONTENT_TYPE, &ct); + } + + Curl_EndDownload(di, failed, result, ct); + } + } + } + + CheckPendingDownloads(); + + // when will we curl the next time? + // we will wait a bit to ensure our download rate is kept. + // we now know that realtime >= curltime... so set up a new curltime + + // use the slowest allowing download to derive the maxspeed... this CAN + // be done better, but maybe later + maxspeed = cl_curl_maxspeed.value; + for(di = downloads; di; di = di->next) + if(di->maxspeed > 0) + if(di->maxspeed < maxspeed || maxspeed <= 0) + maxspeed = di->maxspeed; + + if(maxspeed > 0) + { + double bytes = bytes_sent + bytes_received; // maybe smoothen a bit? + curltime = realtime + bytes / (cl_curl_maxspeed.value * 1024.0); + bytes_sent = 0; + bytes_received = 0; + } + else + curltime = realtime; + + if (curl_mutex) Thread_UnlockMutex(curl_mutex); +} + +/* +==================== +Curl_CancelAll + +Stops ALL downloads. +==================== +*/ +void Curl_CancelAll(void) +{ + if(!curl_dll) + return; + + if (curl_mutex) Thread_LockMutex(curl_mutex); + + while(downloads) + { + Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL); + // INVARIANT: downloads will point to the next download after that! + } + + if (curl_mutex) Thread_UnlockMutex(curl_mutex); +} + +/* +==================== +Curl_Running + +returns true iff there is a download running. +==================== +*/ +qboolean Curl_Running(void) +{ + if(!curl_dll) + return false; + + return downloads != NULL; +} + +/* +==================== +Curl_GetDownloadAmount + +returns a value from 0.0 to 1.0 which represents the downloaded amount of data +for the given download. +==================== +*/ +static double Curl_GetDownloadAmount(downloadinfo *di) +{ + if(!curl_dll) + return -2; + if(di->curle) + { + double length; + qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length); + if(length > 0) + return (di->startpos + di->bytes_received) / (di->startpos + length); + else + return 0; + } + else + return -1; +} + +/* +==================== +Curl_GetDownloadSpeed + +returns the speed of the given download in bytes per second +==================== +*/ +static double Curl_GetDownloadSpeed(downloadinfo *di) +{ + if(!curl_dll) + return -2; + if(di->curle) + { + double speed; + qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed); + return speed; + } + else + return -1; +} + +/* +==================== +Curl_Info_f + +prints the download list +==================== +*/ +// TODO rewrite using Curl_GetDownloadInfo? +static void Curl_Info_f(void) +{ + downloadinfo *di; + char urlbuf[1024]; + if(!curl_dll) + return; + if(Curl_Running()) + { + if (curl_mutex) Thread_LockMutex(curl_mutex); + Con_Print("Currently running downloads:\n"); + for(di = downloads; di; di = di->next) + { + double speed, percent; + Con_Printf(" %s -> %s ", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename); + percent = 100.0 * Curl_GetDownloadAmount(di); + speed = Curl_GetDownloadSpeed(di); + if(percent >= 0) + Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0); + else + Con_Print("(queued)\n"); + } + if (curl_mutex) Thread_UnlockMutex(curl_mutex); + } + else + { + Con_Print("No downloads running.\n"); + } +} + +/* +==================== +Curl_Curl_f + +implements the "curl" console command + +curl --info +curl --cancel +curl --cancel filename +curl url + +For internal use: + +curl [--pak] [--forthismap] [--for filename filename...] url + --pak: after downloading, load the package into the virtual file system + --for filename...: only download of at least one of the named files is missing + --forthismap: don't reconnect on failure + +curl --clear_autodownload + clears the download success/failure counters + +curl --finish_autodownload + if at least one download has been started, disconnect and drop to the menu + once the last download completes successfully, reconnect to the current server +==================== +*/ +static void Curl_Curl_f(void) +{ + double maxspeed = 0; + int i; + int end; + int loadtype = LOADTYPE_NONE; + qboolean forthismap = false; + const char *url; + const char *name = 0; + + if(!curl_dll) + { + Con_Print("libcurl DLL not found, this command is inactive.\n"); + return; + } + + if(!cl_curl_enabled.integer) + { + Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n"); + return; + } + + if(Cmd_Argc() < 2) + { + Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n"); + return; + } + + url = Cmd_Argv(Cmd_Argc() - 1); + end = Cmd_Argc(); + + for(i = 1; i != end; ++i) + { + const char *a = Cmd_Argv(i); + if(!strcmp(a, "--info")) + { + Curl_Info_f(); + return; + } + else if(!strcmp(a, "--cancel")) + { + if(i == end - 1) // last argument + Curl_CancelAll(); + else + { + downloadinfo *di = Curl_Find(url); + if(di) + Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL); + else + Con_Print("download not found\n"); + } + return; + } + else if(!strcmp(a, "--pak")) + { + loadtype = LOADTYPE_PAK; + } + else if(!strcmp(a, "--cachepic")) + { + loadtype = LOADTYPE_CACHEPIC; + } + else if(!strcmp(a, "--skinframe")) + { + loadtype = LOADTYPE_SKINFRAME; + } + else if(!strcmp(a, "--for")) // must be last option + { + for(i = i + 1; i != end - 1; ++i) + { + if(!FS_FileExists(Cmd_Argv(i))) + goto needthefile; // why can't I have a "double break"? + } + // if we get here, we have all the files... + return; + } + else if(!strcmp(a, "--forthismap")) + { + forthismap = true; + } + else if(!strcmp(a, "--as")) + { + if(i < end - 1) + { + ++i; + name = Cmd_Argv(i); + } + } + else if(!strcmp(a, "--clear_autodownload")) + { + // mark all running downloads as "not for this map", so if they + // fail, it does not matter + Curl_Clear_forthismap(); + return; + } + else if(!strcmp(a, "--finish_autodownload")) + { + if(numdownloads_added) + { + char donecommand[256]; + if(cls.netcon) + { + if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect + { + dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address); + Curl_CommandWhenDone(donecommand); + noclear = TRUE; + CL_Disconnect(); + noclear = FALSE; + Curl_CheckCommandWhenDone(); + } + else + Curl_Register_predownload(); + } + } + return; + } + else if(!strncmp(a, "--maxspeed=", 11)) + { + maxspeed = atof(a + 11); + } + else if(*a == '-') + { + Con_Printf("curl: invalid option %s\n", a); + // but we ignore the option + } + } + +needthefile: + Curl_Begin_ToFile(url, maxspeed, name, loadtype, forthismap); +} + +/* +static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata) +{ + Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer); + Z_Free(buffer); +} + +void Curl_CurlCat_f(void) +{ + unsigned char *buf; + const char *url = Cmd_Argv(1); + buf = Z_Malloc(16384); + Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL); +} +*/ + +/* +==================== +Curl_Init_Commands + +loads the commands and cvars this library uses +==================== +*/ +void Curl_Init_Commands(void) +{ + Cvar_RegisterVariable (&cl_curl_enabled); + Cvar_RegisterVariable (&cl_curl_maxdownloads); + Cvar_RegisterVariable (&cl_curl_maxspeed); + Cvar_RegisterVariable (&sv_curl_defaulturl); + Cvar_RegisterVariable (&sv_curl_serverpackages); + Cvar_RegisterVariable (&sv_curl_maxspeed); + Cvar_RegisterVariable (&cl_curl_useragent); + Cvar_RegisterVariable (&cl_curl_useragent_append); + Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path"); + //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)"); +} + +/* +==================== +Curl_GetDownloadInfo + +returns an array of Curl_downloadinfo_t structs for usage by GUIs. +The number of elements in the array is returned in int *nDownloads. +const char **additional_info may be set to a string of additional user +information, or to NULL if no such display shall occur. The returned +array must be freed later using Z_Free. +==================== +*/ +Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info, char *addinfo, size_t addinfolength) +{ + int i; + downloadinfo *di; + Curl_downloadinfo_t *downinfo; + + if(!curl_dll) + { + *nDownloads = 0; + if(additional_info) + *additional_info = NULL; + return NULL; + } + + if (curl_mutex) Thread_LockMutex(curl_mutex); + + i = 0; + for(di = downloads; di; di = di->next) + ++i; + + downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i); + i = 0; + for(di = downloads; di; di = di->next) + { + // do not show infobars for background downloads + if(developer.integer <= 0) + if(di->buffer) + continue; + strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename)); + if(di->curle) + { + downinfo[i].progress = Curl_GetDownloadAmount(di); + downinfo[i].speed = Curl_GetDownloadSpeed(di); + downinfo[i].queued = false; + } + else + { + downinfo[i].queued = true; + } + ++i; + } + + if(additional_info) + { + // TODO: can I clear command_when_done as soon as the first download fails? + if(*command_when_done && !numdownloads_fail && numdownloads_added) + { + if(!strncmp(command_when_done, "connect ", 8)) + dpsnprintf(addinfo, addinfolength, "(will join %s when done)", command_when_done + 8); + else if(!strcmp(command_when_done, "cl_begindownloads")) + dpsnprintf(addinfo, addinfolength, "(will enter the game when done)"); + else + dpsnprintf(addinfo, addinfolength, "(will do '%s' when done)", command_when_done); + *additional_info = addinfo; + } + else + *additional_info = NULL; + } + + *nDownloads = i; + if (curl_mutex) Thread_UnlockMutex(curl_mutex); + return downinfo; +} + + +/* +==================== +Curl_FindPackURL + +finds the URL where to find a given package. + +For this, it reads a file "curl_urls.txt" of the following format: + + data*.pk3 - + revdm*.pk3 http://revdm/downloads/are/here/ + * http://any/other/stuff/is/here/ + +The URLs should end in /. If not, downloads will still work, but the cached files +can't be just put into the data directory with the same download configuration +(you might want to do this if you want to tag downloaded files from your +server, but you should not). "-" means "don't download". + +If no single pattern matched, the cvar sv_curl_defaulturl is used as download +location instead. + +Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in +this file for obvious reasons. +==================== +*/ +static const char *Curl_FindPackURL(const char *filename) +{ + static char foundurl[1024]; // invoked only by server + fs_offset_t filesize; + char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize); + if(buf && filesize) + { + // read lines of format "pattern url" + char *p = buf; + char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL; + qboolean eof = false; + + pattern = p; + while(!eof) + { + switch(*p) + { + case 0: + eof = true; + // fallthrough + case '\n': + case '\r': + if(pattern && url && patternend) + { + if(!urlend) + urlend = p; + *patternend = 0; + *urlend = 0; + if(matchpattern(filename, pattern, true)) + { + strlcpy(foundurl, url, sizeof(foundurl)); + Z_Free(buf); + return foundurl; + } + } + pattern = NULL; + patternend = NULL; + url = NULL; + urlend = NULL; + break; + case ' ': + case '\t': + if(pattern && !patternend) + patternend = p; + else if(url && !urlend) + urlend = p; + break; + default: + if(!pattern) + pattern = p; + else if(pattern && patternend && !url) + url = p; + break; + } + ++p; + } + } + if(buf) + Z_Free(buf); + return sv_curl_defaulturl.string; +} + +typedef struct requirement_s +{ + struct requirement_s *next; + char filename[MAX_OSPATH]; +} +requirement; +static requirement *requirements = NULL; + + +/* +==================== +Curl_RequireFile + +Adds the given file to the list of requirements. +==================== +*/ +void Curl_RequireFile(const char *filename) +{ + requirement *req = (requirement *) Z_Malloc(sizeof(*requirements)); + req->next = requirements; + strlcpy(req->filename, filename, sizeof(req->filename)); + requirements = req; +} + +/* +==================== +Curl_ClearRequirements + +Clears the list of required files for playing on the current map. +This should be called at every map change. +==================== +*/ +void Curl_ClearRequirements(void) +{ + while(requirements) + { + requirement *req = requirements; + requirements = requirements->next; + Z_Free(req); + } +} + +/* +==================== +Curl_SendRequirements + +Makes the current host_clients download all files he needs. +This is done by sending him the following console commands: + + curl --clear_autodownload + curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3 + curl --finish_autodownload +==================== +*/ +static qboolean Curl_SendRequirement(const char *filename, qboolean foundone, char *sendbuffer, size_t sendbuffer_len) +{ + const char *p; + const char *thispack = FS_WhichPack(filename); + const char *packurl; + + if(!thispack) + return false; + + p = strrchr(thispack, '/'); + if(p) + thispack = p + 1; + + packurl = Curl_FindPackURL(thispack); + + if(packurl && *packurl && strcmp(packurl, "-")) + { + if(!foundone) + strlcat(sendbuffer, "curl --clear_autodownload\n", sendbuffer_len); + + strlcat(sendbuffer, "curl --pak --forthismap --as ", sendbuffer_len); + strlcat(sendbuffer, thispack, sendbuffer_len); + if(sv_curl_maxspeed.value > 0) + dpsnprintf(sendbuffer + strlen(sendbuffer), sendbuffer_len - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value); + strlcat(sendbuffer, " --for ", sendbuffer_len); + strlcat(sendbuffer, filename, sendbuffer_len); + strlcat(sendbuffer, " ", sendbuffer_len); + strlcat(sendbuffer, packurl, sendbuffer_len); + strlcat(sendbuffer, thispack, sendbuffer_len); + strlcat(sendbuffer, "\n", sendbuffer_len); + + return true; + } + + return false; +} +void Curl_SendRequirements(void) +{ + // for each requirement, find the pack name + char sendbuffer[4096] = ""; + requirement *req; + qboolean foundone = false; + const char *p; + + for(req = requirements; req; req = req->next) + foundone = Curl_SendRequirement(req->filename, foundone, sendbuffer, sizeof(sendbuffer)) || foundone; + + p = sv_curl_serverpackages.string; + while(COM_ParseToken_Simple(&p, false, false, true)) + foundone = Curl_SendRequirement(com_token, foundone, sendbuffer, sizeof(sendbuffer)) || foundone; + + if(foundone) + strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer)); + + if(strlen(sendbuffer) + 1 < sizeof(sendbuffer)) + Host_ClientCommands("%s", sendbuffer); + else + Con_Printf("Could not initiate autodownload due to URL buffer overflow\n"); +} diff --git a/app/jni/libcurl.h b/app/jni/libcurl.h new file mode 100644 index 0000000..c01aed3 --- /dev/null +++ b/app/jni/libcurl.h @@ -0,0 +1,44 @@ +enum +{ + CURLCBSTATUS_OK = 0, + CURLCBSTATUS_FAILED = -1, // failed for generic reason (e.g. buffer too small) + CURLCBSTATUS_ABORTED = -2, // aborted by curl --cancel + CURLCBSTATUS_SERVERERROR = -3, // only used if no HTTP status code is available + CURLCBSTATUS_UNKNOWN = -4 // should never happen +}; +typedef void (*curl_callback_t) (int status, size_t length_received, unsigned char *buffer, void *cbdata); +// code is one of the CURLCBSTATUS constants, or the HTTP error code (when > 0). + +void Curl_Run(void); +qboolean Curl_Running(void); +qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qboolean forthismap); + +qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); +qboolean Curl_Begin_ToMemory_POST(const char *URL, const char *extraheaders, double maxspeed, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); + +void Curl_Init(void); +void Curl_Init_Commands(void); +void Curl_Shutdown(void); +void Curl_CancelAll(void); +void Curl_Clear_forthismap(void); +qboolean Curl_Have_forthismap(void); +void Curl_Register_predownload(void); + +void Curl_ClearRequirements(void); +void Curl_RequireFile(const char *filename); +void Curl_SendRequirements(void); + +typedef struct Curl_downloadinfo_s +{ + char filename[MAX_QPATH]; + double progress; + double speed; + qboolean queued; +} +Curl_downloadinfo_t; +Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info, char *addinfo, size_t addinfolength); + // this may and should be Z_Free()ed + // the result is actually an array + // an additional info string may be returned in additional_info as a + // pointer to a static string (but the argument may be NULL if the caller + // does not care) diff --git a/app/jni/mathlib.c b/app/jni/mathlib.c new file mode 100644 index 0000000..60cc04b --- /dev/null +++ b/app/jni/mathlib.c @@ -0,0 +1,805 @@ +/* +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. + +*/ +// mathlib.c -- math primitives + +#include "quakedef.h" + +#include + +vec3_t vec3_origin = {0,0,0}; +float ixtable[4096]; + +/*-----------------------------------------------------------------*/ + +float m_bytenormals[NUMVERTEXNORMALS][3] = +{ +{-0.525731, 0.000000, 0.850651}, {-0.442863, 0.238856, 0.864188}, +{-0.295242, 0.000000, 0.955423}, {-0.309017, 0.500000, 0.809017}, +{-0.162460, 0.262866, 0.951056}, {0.000000, 0.000000, 1.000000}, +{0.000000, 0.850651, 0.525731}, {-0.147621, 0.716567, 0.681718}, +{0.147621, 0.716567, 0.681718}, {0.000000, 0.525731, 0.850651}, +{0.309017, 0.500000, 0.809017}, {0.525731, 0.000000, 0.850651}, +{0.295242, 0.000000, 0.955423}, {0.442863, 0.238856, 0.864188}, +{0.162460, 0.262866, 0.951056}, {-0.681718, 0.147621, 0.716567}, +{-0.809017, 0.309017, 0.500000}, {-0.587785, 0.425325, 0.688191}, +{-0.850651, 0.525731, 0.000000}, {-0.864188, 0.442863, 0.238856}, +{-0.716567, 0.681718, 0.147621}, {-0.688191, 0.587785, 0.425325}, +{-0.500000, 0.809017, 0.309017}, {-0.238856, 0.864188, 0.442863}, +{-0.425325, 0.688191, 0.587785}, {-0.716567, 0.681718, -0.147621}, +{-0.500000, 0.809017, -0.309017}, {-0.525731, 0.850651, 0.000000}, +{0.000000, 0.850651, -0.525731}, {-0.238856, 0.864188, -0.442863}, +{0.000000, 0.955423, -0.295242}, {-0.262866, 0.951056, -0.162460}, +{0.000000, 1.000000, 0.000000}, {0.000000, 0.955423, 0.295242}, +{-0.262866, 0.951056, 0.162460}, {0.238856, 0.864188, 0.442863}, +{0.262866, 0.951056, 0.162460}, {0.500000, 0.809017, 0.309017}, +{0.238856, 0.864188, -0.442863}, {0.262866, 0.951056, -0.162460}, +{0.500000, 0.809017, -0.309017}, {0.850651, 0.525731, 0.000000}, +{0.716567, 0.681718, 0.147621}, {0.716567, 0.681718, -0.147621}, +{0.525731, 0.850651, 0.000000}, {0.425325, 0.688191, 0.587785}, +{0.864188, 0.442863, 0.238856}, {0.688191, 0.587785, 0.425325}, +{0.809017, 0.309017, 0.500000}, {0.681718, 0.147621, 0.716567}, +{0.587785, 0.425325, 0.688191}, {0.955423, 0.295242, 0.000000}, +{1.000000, 0.000000, 0.000000}, {0.951056, 0.162460, 0.262866}, +{0.850651, -0.525731, 0.000000}, {0.955423, -0.295242, 0.000000}, +{0.864188, -0.442863, 0.238856}, {0.951056, -0.162460, 0.262866}, +{0.809017, -0.309017, 0.500000}, {0.681718, -0.147621, 0.716567}, +{0.850651, 0.000000, 0.525731}, {0.864188, 0.442863, -0.238856}, +{0.809017, 0.309017, -0.500000}, {0.951056, 0.162460, -0.262866}, +{0.525731, 0.000000, -0.850651}, {0.681718, 0.147621, -0.716567}, +{0.681718, -0.147621, -0.716567}, {0.850651, 0.000000, -0.525731}, +{0.809017, -0.309017, -0.500000}, {0.864188, -0.442863, -0.238856}, +{0.951056, -0.162460, -0.262866}, {0.147621, 0.716567, -0.681718}, +{0.309017, 0.500000, -0.809017}, {0.425325, 0.688191, -0.587785}, +{0.442863, 0.238856, -0.864188}, {0.587785, 0.425325, -0.688191}, +{0.688191, 0.587785, -0.425325}, {-0.147621, 0.716567, -0.681718}, +{-0.309017, 0.500000, -0.809017}, {0.000000, 0.525731, -0.850651}, +{-0.525731, 0.000000, -0.850651}, {-0.442863, 0.238856, -0.864188}, +{-0.295242, 0.000000, -0.955423}, {-0.162460, 0.262866, -0.951056}, +{0.000000, 0.000000, -1.000000}, {0.295242, 0.000000, -0.955423}, +{0.162460, 0.262866, -0.951056}, {-0.442863, -0.238856, -0.864188}, +{-0.309017, -0.500000, -0.809017}, {-0.162460, -0.262866, -0.951056}, +{0.000000, -0.850651, -0.525731}, {-0.147621, -0.716567, -0.681718}, +{0.147621, -0.716567, -0.681718}, {0.000000, -0.525731, -0.850651}, +{0.309017, -0.500000, -0.809017}, {0.442863, -0.238856, -0.864188}, +{0.162460, -0.262866, -0.951056}, {0.238856, -0.864188, -0.442863}, +{0.500000, -0.809017, -0.309017}, {0.425325, -0.688191, -0.587785}, +{0.716567, -0.681718, -0.147621}, {0.688191, -0.587785, -0.425325}, +{0.587785, -0.425325, -0.688191}, {0.000000, -0.955423, -0.295242}, +{0.000000, -1.000000, 0.000000}, {0.262866, -0.951056, -0.162460}, +{0.000000, -0.850651, 0.525731}, {0.000000, -0.955423, 0.295242}, +{0.238856, -0.864188, 0.442863}, {0.262866, -0.951056, 0.162460}, +{0.500000, -0.809017, 0.309017}, {0.716567, -0.681718, 0.147621}, +{0.525731, -0.850651, 0.000000}, {-0.238856, -0.864188, -0.442863}, +{-0.500000, -0.809017, -0.309017}, {-0.262866, -0.951056, -0.162460}, +{-0.850651, -0.525731, 0.000000}, {-0.716567, -0.681718, -0.147621}, +{-0.716567, -0.681718, 0.147621}, {-0.525731, -0.850651, 0.000000}, +{-0.500000, -0.809017, 0.309017}, {-0.238856, -0.864188, 0.442863}, +{-0.262866, -0.951056, 0.162460}, {-0.864188, -0.442863, 0.238856}, +{-0.809017, -0.309017, 0.500000}, {-0.688191, -0.587785, 0.425325}, +{-0.681718, -0.147621, 0.716567}, {-0.442863, -0.238856, 0.864188}, +{-0.587785, -0.425325, 0.688191}, {-0.309017, -0.500000, 0.809017}, +{-0.147621, -0.716567, 0.681718}, {-0.425325, -0.688191, 0.587785}, +{-0.162460, -0.262866, 0.951056}, {0.442863, -0.238856, 0.864188}, +{0.162460, -0.262866, 0.951056}, {0.309017, -0.500000, 0.809017}, +{0.147621, -0.716567, 0.681718}, {0.000000, -0.525731, 0.850651}, +{0.425325, -0.688191, 0.587785}, {0.587785, -0.425325, 0.688191}, +{0.688191, -0.587785, 0.425325}, {-0.955423, 0.295242, 0.000000}, +{-0.951056, 0.162460, 0.262866}, {-1.000000, 0.000000, 0.000000}, +{-0.850651, 0.000000, 0.525731}, {-0.955423, -0.295242, 0.000000}, +{-0.951056, -0.162460, 0.262866}, {-0.864188, 0.442863, -0.238856}, +{-0.951056, 0.162460, -0.262866}, {-0.809017, 0.309017, -0.500000}, +{-0.864188, -0.442863, -0.238856}, {-0.951056, -0.162460, -0.262866}, +{-0.809017, -0.309017, -0.500000}, {-0.681718, 0.147621, -0.716567}, +{-0.681718, -0.147621, -0.716567}, {-0.850651, 0.000000, -0.525731}, +{-0.688191, 0.587785, -0.425325}, {-0.587785, 0.425325, -0.688191}, +{-0.425325, 0.688191, -0.587785}, {-0.425325, -0.688191, -0.587785}, +{-0.587785, -0.425325, -0.688191}, {-0.688191, -0.587785, -0.425325}, +}; + +#if 0 +unsigned char NormalToByte(const vec3_t n) +{ + int i, best; + float bestdistance, distance; + + best = 0; + bestdistance = DotProduct (n, m_bytenormals[0]); + for (i = 1;i < NUMVERTEXNORMALS;i++) + { + distance = DotProduct (n, m_bytenormals[i]); + if (distance > bestdistance) + { + bestdistance = distance; + best = i; + } + } + return best; +} + +// note: uses byte partly to force unsigned for the validity check +void ByteToNormal(unsigned char num, vec3_t n) +{ + if (num < NUMVERTEXNORMALS) + VectorCopy(m_bytenormals[num], n); + else + VectorClear(n); // FIXME: complain? +} + +// assumes "src" is normalized +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + // LordHavoc: optimized to death and beyond + int pos; + float minelem; + + if (src[0]) + { + dst[0] = 0; + if (src[1]) + { + dst[1] = 0; + if (src[2]) + { + dst[2] = 0; + pos = 0; + minelem = fabs(src[0]); + if (fabs(src[1]) < minelem) + { + pos = 1; + minelem = fabs(src[1]); + } + if (fabs(src[2]) < minelem) + pos = 2; + + dst[pos] = 1; + dst[0] -= src[pos] * src[0]; + dst[1] -= src[pos] * src[1]; + dst[2] -= src[pos] * src[2]; + + // normalize the result + VectorNormalize(dst); + } + else + dst[2] = 1; + } + else + { + dst[1] = 1; + dst[2] = 0; + } + } + else + { + dst[0] = 1; + dst[1] = 0; + dst[2] = 0; + } +} +#endif + + +// LordHavoc: like AngleVectors, but taking a forward vector instead of angles, useful! +void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up) +{ + // NOTE: this is consistent to AngleVectors applied to AnglesFromVectors + if (forward[0] == 0 && forward[1] == 0) + { + if(forward[2] > 0) + { + VectorSet(right, 0, -1, 0); + VectorSet(up, -1, 0, 0); + } + else + { + VectorSet(right, 0, -1, 0); + VectorSet(up, 1, 0, 0); + } + } + else + { + right[0] = forward[1]; + right[1] = -forward[0]; + right[2] = 0; + VectorNormalize(right); + + up[0] = (-forward[2]*forward[0]); + up[1] = (-forward[2]*forward[1]); + up[2] = (forward[0]*forward[0] + forward[1]*forward[1]); + VectorNormalize(up); + } +} + +void VectorVectorsDouble(const double *forward, double *right, double *up) +{ + if (forward[0] == 0 && forward[1] == 0) + { + if(forward[2] > 0) + { + VectorSet(right, 0, -1, 0); + VectorSet(up, -1, 0, 0); + } + else + { + VectorSet(right, 0, -1, 0); + VectorSet(up, 1, 0, 0); + } + } + else + { + right[0] = forward[1]; + right[1] = -forward[0]; + right[2] = 0; + VectorNormalize(right); + + up[0] = (-forward[2]*forward[0]); + up[1] = (-forward[2]*forward[1]); + up[2] = (forward[0]*forward[0] + forward[1]*forward[1]); + VectorNormalize(up); + } +} + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float t0, t1; + float angle, c, s; + vec3_t vr, vu, vf; + + angle = DEG2RAD(degrees); + c = cos(angle); + s = sin(angle); + VectorCopy(dir, vf); + VectorVectors(vf, vr, vu); + + t0 = vr[0] * c + vu[0] * -s; + t1 = vr[0] * s + vu[0] * c; + dst[0] = (t0 * vr[0] + t1 * vu[0] + vf[0] * vf[0]) * point[0] + + (t0 * vr[1] + t1 * vu[1] + vf[0] * vf[1]) * point[1] + + (t0 * vr[2] + t1 * vu[2] + vf[0] * vf[2]) * point[2]; + + t0 = vr[1] * c + vu[1] * -s; + t1 = vr[1] * s + vu[1] * c; + dst[1] = (t0 * vr[0] + t1 * vu[0] + vf[1] * vf[0]) * point[0] + + (t0 * vr[1] + t1 * vu[1] + vf[1] * vf[1]) * point[1] + + (t0 * vr[2] + t1 * vu[2] + vf[1] * vf[2]) * point[2]; + + t0 = vr[2] * c + vu[2] * -s; + t1 = vr[2] * s + vu[2] * c; + dst[2] = (t0 * vr[0] + t1 * vu[0] + vf[2] * vf[0]) * point[0] + + (t0 * vr[1] + t1 * vu[1] + vf[2] * vf[1]) * point[1] + + (t0 * vr[2] + t1 * vu[2] + vf[2] * vf[2]) * point[2]; +} + +/*-----------------------------------------------------------------*/ + +// returns the smallest integer greater than or equal to "value", or 0 if "value" is too big +unsigned int CeilPowerOf2(unsigned int value) +{ + unsigned int ceilvalue; + + if (value > (1U << (sizeof(int) * 8 - 1))) + return 0; + + ceilvalue = 1; + while (ceilvalue < value) + ceilvalue <<= 1; + + return ceilvalue; +} + + +/*-----------------------------------------------------------------*/ + + +void PlaneClassify(mplane_t *p) +{ + // for optimized plane comparisons + if (p->normal[0] == 1) + p->type = 0; + else if (p->normal[1] == 1) + p->type = 1; + else if (p->normal[2] == 1) + p->type = 2; + else + p->type = 3; + // for BoxOnPlaneSide + p->signbits = 0; + if (p->normal[0] < 0) // 1 + p->signbits |= 1; + if (p->normal[1] < 0) // 2 + p->signbits |= 2; + if (p->normal[2] < 0) // 4 + p->signbits |= 4; +} + +int BoxOnPlaneSide(const vec3_t emins, const vec3_t emaxs, const mplane_t *p) +{ + if (p->type < 3) + return ((emaxs[p->type] >= p->dist) | ((emins[p->type] < p->dist) << 1)); + switch(p->signbits) + { + default: + case 0: return (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) < p->dist) << 1)); + case 1: return (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) < p->dist) << 1)); + case 2: return (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) < p->dist) << 1)); + case 3: return (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) < p->dist) << 1)); + case 4: return (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); + case 5: return (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); + case 6: return (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); + case 7: return (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); + } +} + +#if 0 +int BoxOnPlaneSide_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, const vec_t dist) +{ + switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) + { + default: + case 0: return (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2]) < dist) << 1)); + case 1: return (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2]) < dist) << 1)); + case 2: return (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) < dist) << 1)); + case 3: return (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) < dist) << 1)); + case 4: return (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) < dist) << 1)); + case 5: return (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) < dist) << 1)); + case 6: return (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) < dist) << 1)); + case 7: return (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) < dist) << 1)); + } +} +#endif + +void BoxPlaneCorners(const vec3_t emins, const vec3_t emaxs, const mplane_t *p, vec3_t outnear, vec3_t outfar) +{ + if (p->type < 3) + { + outnear[0] = outnear[1] = outnear[2] = outfar[0] = outfar[1] = outfar[2] = 0; + outnear[p->type] = emins[p->type]; + outfar[p->type] = emaxs[p->type]; + return; + } + switch(p->signbits) + { + default: + case 0: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emins[2];break; + case 1: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emins[2];break; + case 2: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; + case 3: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; + case 4: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; + case 5: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; + case 6: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; + case 7: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; + } +} + +void BoxPlaneCorners_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec3_t outnear, vec3_t outfar) +{ + switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) + { + default: + case 0: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emins[2];break; + case 1: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emins[2];break; + case 2: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; + case 3: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; + case 4: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; + case 5: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; + case 6: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; + case 7: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; + } +} + +void BoxPlaneCornerDistances(const vec3_t emins, const vec3_t emaxs, const mplane_t *p, vec_t *outneardist, vec_t *outfardist) +{ + if (p->type < 3) + { + *outneardist = emins[p->type] - p->dist; + *outfardist = emaxs[p->type] - p->dist; + return; + } + switch(p->signbits) + { + default: + case 0: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;break; + case 1: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;break; + case 2: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;break; + case 3: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;break; + case 4: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;break; + case 5: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;break; + case 6: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;break; + case 7: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;break; + } +} + +void BoxPlaneCornerDistances_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec_t *outneardist, vec_t *outfardist) +{ + switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) + { + default: + case 0: *outneardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2];break; + case 1: *outneardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2];break; + case 2: *outneardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2];break; + case 3: *outneardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2];break; + case 4: *outneardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2];*outfardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2];break; + case 5: *outneardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2];break; + case 6: *outneardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2];*outfardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];break; + case 7: *outneardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];break; + } +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + double angle, sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right || up) + { + if (angles[ROLL]) + { + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + if (right) + { + right[0] = -1*(sr*sp*cy+cr*-sy); + right[1] = -1*(sr*sp*sy+cr*cy); + right[2] = -1*(sr*cp); + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } + } + else + { + if (right) + { + right[0] = sy; + right[1] = -cy; + right[2] = 0; + } + if (up) + { + up[0] = (sp*cy); + up[1] = (sp*sy); + up[2] = cp; + } + } + } +} + +void AngleVectorsFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up) +{ + double angle, sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (left || up) + { + if (angles[ROLL]) + { + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + if (left) + { + left[0] = sr*sp*cy+cr*-sy; + left[1] = sr*sp*sy+cr*cy; + left[2] = sr*cp; + } + if (up) + { + up[0] = cr*sp*cy+-sr*-sy; + up[1] = cr*sp*sy+-sr*cy; + up[2] = cr*cp; + } + } + else + { + if (left) + { + left[0] = -sy; + left[1] = cy; + left[2] = 0; + } + if (up) + { + up[0] = sp*cy; + up[1] = sp*sy; + up[2] = cp; + } + } + } +} + +// LordHavoc: calculates pitch/yaw/roll angles from forward and up vectors +void AnglesFromVectors (vec3_t angles, const vec3_t forward, const vec3_t up, qboolean flippitch) +{ + if (forward[0] == 0 && forward[1] == 0) + { + if(forward[2] > 0) + { + angles[PITCH] = -M_PI * 0.5; + angles[YAW] = up ? atan2(-up[1], -up[0]) : 0; + } + else + { + angles[PITCH] = M_PI * 0.5; + angles[YAW] = up ? atan2(up[1], up[0]) : 0; + } + angles[ROLL] = 0; + } + else + { + angles[YAW] = atan2(forward[1], forward[0]); + angles[PITCH] = -atan2(forward[2], sqrt(forward[0]*forward[0] + forward[1]*forward[1])); + // note: we know that angles[PITCH] is in ]-pi/2..pi/2[ due to atan2(anything, positive) + if (up) + { + vec_t cp = cos(angles[PITCH]), sp = sin(angles[PITCH]); + // note: we know cp > 0, due to the range angles[pitch] is in + vec_t cy = cos(angles[YAW]), sy = sin(angles[YAW]); + vec3_t tleft, tup; + tleft[0] = -sy; + tleft[1] = cy; + tleft[2] = 0; + tup[0] = sp*cy; + tup[1] = sp*sy; + tup[2] = cp; + angles[ROLL] = -atan2(DotProduct(up, tleft), DotProduct(up, tup)); + // for up == '0 0 1', this is + // angles[ROLL] = -atan2(0, cp); + // which is 0 + } + else + angles[ROLL] = 0; + + // so no up vector is equivalent to '1 0 0'! + } + + // now convert radians to degrees, and make all values positive + VectorScale(angles, 180.0 / M_PI, angles); + if (flippitch) + angles[PITCH] *= -1; + if (angles[PITCH] < 0) angles[PITCH] += 360; + if (angles[YAW] < 0) angles[YAW] += 360; + if (angles[ROLL] < 0) angles[ROLL] += 360; + +#if 0 +{ + // debugging code + vec3_t tforward, tleft, tup, nforward, nup; + VectorCopy(forward, nforward); + VectorNormalize(nforward); + if (up) + { + VectorCopy(up, nup); + VectorNormalize(nup); + AngleVectors(angles, tforward, tleft, tup); + if (VectorDistance(tforward, nforward) > 0.01 || VectorDistance(tup, nup) > 0.01) + { + Con_Printf("vectoangles('%f %f %f', '%f %f %f') = %f %f %f\n", nforward[0], nforward[1], nforward[2], nup[0], nup[1], nup[2], angles[0], angles[1], angles[2]); + Con_Printf("^3But that is '%f %f %f', '%f %f %f'\n", tforward[0], tforward[1], tforward[2], tup[0], tup[1], tup[2]); + } + } + else + { + AngleVectors(angles, tforward, tleft, tup); + if (VectorDistance(tforward, nforward) > 0.01) + { + Con_Printf("vectoangles('%f %f %f') = %f %f %f\n", nforward[0], nforward[1], nforward[2], angles[0], angles[1], angles[2]); + Con_Printf("^3But that is '%f %f %f'\n", tforward[0], tforward[1], tforward[2]); + } + } +} +#endif +} + +#if 0 +void AngleMatrix (const vec3_t angles, const vec3_t translate, vec_t matrix[][4]) +{ + double angle, sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + matrix[0][0] = cp*cy; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[0][2] = cr*sp*cy+-sr*-sy; + matrix[0][3] = translate[0]; + matrix[1][0] = cp*sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = cr*sp*sy+-sr*cy; + matrix[1][3] = translate[1]; + matrix[2][0] = -sp; + matrix[2][1] = sr*cp; + matrix[2][2] = cr*cp; + matrix[2][3] = translate[2]; +} +#endif + + +// LordHavoc: renamed this to Length, and made the normal one a #define +float VectorNormalizeLength (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (const float in1[3*3], const float in2[3*3], float out[3*3]) +{ + out[0*3+0] = in1[0*3+0] * in2[0*3+0] + in1[0*3+1] * in2[1*3+0] + in1[0*3+2] * in2[2*3+0]; + out[0*3+1] = in1[0*3+0] * in2[0*3+1] + in1[0*3+1] * in2[1*3+1] + in1[0*3+2] * in2[2*3+1]; + out[0*3+2] = in1[0*3+0] * in2[0*3+2] + in1[0*3+1] * in2[1*3+2] + in1[0*3+2] * in2[2*3+2]; + out[1*3+0] = in1[1*3+0] * in2[0*3+0] + in1[1*3+1] * in2[1*3+0] + in1[1*3+2] * in2[2*3+0]; + out[1*3+1] = in1[1*3+0] * in2[0*3+1] + in1[1*3+1] * in2[1*3+1] + in1[1*3+2] * in2[2*3+1]; + out[1*3+2] = in1[1*3+0] * in2[0*3+2] + in1[1*3+1] * in2[1*3+2] + in1[1*3+2] * in2[2*3+2]; + out[2*3+0] = in1[2*3+0] * in2[0*3+0] + in1[2*3+1] * in2[1*3+0] + in1[2*3+2] * in2[2*3+0]; + out[2*3+1] = in1[2*3+0] * in2[0*3+1] + in1[2*3+1] * in2[1*3+1] + in1[2*3+2] * in2[2*3+1]; + out[2*3+2] = in1[2*3+0] * in2[0*3+2] + in1[2*3+1] * in2[1*3+2] + in1[2*3+2] * in2[2*3+2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (const float in1[3*4], const float in2[3*4], float out[3*4]) +{ + out[0*4+0] = in1[0*4+0] * in2[0*4+0] + in1[0*4+1] * in2[1*4+0] + in1[0*4+2] * in2[2*4+0]; + out[0*4+1] = in1[0*4+0] * in2[0*4+1] + in1[0*4+1] * in2[1*4+1] + in1[0*4+2] * in2[2*4+1]; + out[0*4+2] = in1[0*4+0] * in2[0*4+2] + in1[0*4+1] * in2[1*4+2] + in1[0*4+2] * in2[2*4+2]; + out[0*4+3] = in1[0*4+0] * in2[0*4+3] + in1[0*4+1] * in2[1*4+3] + in1[0*4+2] * in2[2*4+3] + in1[0*4+3]; + out[1*4+0] = in1[1*4+0] * in2[0*4+0] + in1[1*4+1] * in2[1*4+0] + in1[1*4+2] * in2[2*4+0]; + out[1*4+1] = in1[1*4+0] * in2[0*4+1] + in1[1*4+1] * in2[1*4+1] + in1[1*4+2] * in2[2*4+1]; + out[1*4+2] = in1[1*4+0] * in2[0*4+2] + in1[1*4+1] * in2[1*4+2] + in1[1*4+2] * in2[2*4+2]; + out[1*4+3] = in1[1*4+0] * in2[0*4+3] + in1[1*4+1] * in2[1*4+3] + in1[1*4+2] * in2[2*4+3] + in1[1*4+3]; + out[2*4+0] = in1[2*4+0] * in2[0*4+0] + in1[2*4+1] * in2[1*4+0] + in1[2*4+2] * in2[2*4+0]; + out[2*4+1] = in1[2*4+0] * in2[0*4+1] + in1[2*4+1] * in2[1*4+1] + in1[2*4+2] * in2[2*4+1]; + out[2*4+2] = in1[2*4+0] * in2[0*4+2] + in1[2*4+1] * in2[1*4+2] + in1[2*4+2] * in2[2*4+2]; + out[2*4+3] = in1[2*4+0] * in2[0*4+3] + in1[2*4+1] * in2[1*4+3] + in1[2*4+2] * in2[2*4+3] + in1[2*4+3]; +} + +float RadiusFromBounds (const vec3_t mins, const vec3_t maxs) +{ + vec3_t m1, m2; + VectorMultiply(mins, mins, m1); + VectorMultiply(maxs, maxs, m2); + return sqrt(max(m1[0], m2[0]) + max(m1[1], m2[1]) + max(m1[2], m2[2])); +} + +float RadiusFromBoundsAndOrigin (const vec3_t mins, const vec3_t maxs, const vec3_t origin) +{ + vec3_t m1, m2; + VectorSubtract(mins, origin, m1);VectorMultiply(m1, m1, m1); + VectorSubtract(maxs, origin, m2);VectorMultiply(m2, m2, m2); + return sqrt(max(m1[0], m2[0]) + max(m1[1], m2[1]) + max(m1[2], m2[2])); +} + +void Mathlib_Init(void) +{ + int a; + + // LordHavoc: setup 1.0f / N table for quick recipricols of integers + ixtable[0] = 0; + for (a = 1;a < 4096;a++) + ixtable[a] = 1.0f / a; +} + +#include "matrixlib.h" + +void Matrix4x4_Print(const matrix4x4_t *in) +{ + Con_Printf("%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n" + , in->m[0][0], in->m[0][1], in->m[0][2], in->m[0][3] + , in->m[1][0], in->m[1][1], in->m[1][2], in->m[1][3] + , in->m[2][0], in->m[2][1], in->m[2][2], in->m[2][3] + , in->m[3][0], in->m[3][1], in->m[3][2], in->m[3][3]); +} + +int Math_atov(const char *s, prvm_vec3_t out) +{ + int i; + VectorClear(out); + if (*s == '\'') + s++; + for (i = 0;i < 3;i++) + { + while (*s == ' ' || *s == '\t') + s++; + out[i] = atof (s); + if (out[i] == 0 && *s != '-' && *s != '+' && (*s < '0' || *s > '9')) + break; // not a number + while (*s && *s != ' ' && *s !='\t' && *s != '\'') + s++; + if (*s == '\'') + break; + } + return i; +} + +void BoxFromPoints(vec3_t mins, vec3_t maxs, int numpoints, vec_t *point3f) +{ + int i; + VectorCopy(point3f, mins); + VectorCopy(point3f, maxs); + for (i = 1, point3f += 3;i < numpoints;i++, point3f += 3) + { + mins[0] = min(mins[0], point3f[0]);maxs[0] = max(maxs[0], point3f[0]); + mins[1] = min(mins[1], point3f[1]);maxs[1] = max(maxs[1], point3f[1]); + mins[2] = min(mins[2], point3f[2]);maxs[2] = max(maxs[2], point3f[2]); + } +} + +// LordHavoc: this has to be done right or you get severe precision breakdown +int LoopingFrameNumberFromDouble(double t, int loopframes) +{ + if (loopframes) + return (int)(t - floor(t/loopframes)*loopframes); + else + return (int)t; +} + diff --git a/app/jni/mathlib.h b/app/jni/mathlib.h new file mode 100644 index 0000000..9a9cd73 --- /dev/null +++ b/app/jni/mathlib.h @@ -0,0 +1,305 @@ +/* +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. + +*/ +// mathlib.h + +#ifndef MATHLIB_H +#define MATHLIB_H + +#include "qtypes.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct mplane_s; +extern vec3_t vec3_origin; + +#define float_nanmask (0x7F800000) +#define double_nanmask (0x7FF8000000000000) +#define FLOAT_IS_NAN(x) (((*(int *)&x)&float_nanmask)==float_nanmask) +#define DOUBLE_IS_NAN(x) (((*(long long *)&x)&double_nanmask)==double_nanmask) + +#ifdef VEC_64 +#define VEC_IS_NAN(x) DOUBLE_IS_NAN(x) +#else +#define VEC_IS_NAN(x) FLOAT_IS_NAN(x) +#endif + +#ifdef PRVM_64 +#define PRVM_IS_NAN(x) DOUBLE_IS_NAN(x) +#else +#define PRVM_IS_NAN(x) FLOAT_IS_NAN(x) +#endif + +#define bound(min,num,max) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) + +#ifndef min +#define min(A,B) ((A) < (B) ? (A) : (B)) +#define max(A,B) ((A) > (B) ? (A) : (B)) +#endif + +/// LordHavoc: this function never returns exactly MIN or exactly MAX, because +/// of a QuakeC bug in id1 where the line +/// self.nextthink = self.nexthink + random() * 0.5; +/// can result in 0 (self.nextthink is 0 at this point in the code to begin +/// with), causing "stone monsters" that never spawned properly, also MAX is +/// avoided because some people use random() as an index into arrays or for +/// loop conditions, where hitting exactly MAX may be a fatal error +#define lhrandom(MIN,MAX) (((double)(rand() + 0.5) / ((double)RAND_MAX + 1)) * ((MAX)-(MIN)) + (MIN)) + +#define invpow(base,number) (log(number) / log(base)) + +/// returns log base 2 of "n" +/// \WARNING: "n" MUST be a power of 2! +#define log2i(n) ((((n) & 0xAAAAAAAA) != 0 ? 1 : 0) | (((n) & 0xCCCCCCCC) != 0 ? 2 : 0) | (((n) & 0xF0F0F0F0) != 0 ? 4 : 0) | (((n) & 0xFF00FF00) != 0 ? 8 : 0) | (((n) & 0xFFFF0000) != 0 ? 16 : 0)) + +/// \TODO: what is this function supposed to do? +#define bit2i(n) log2i((n) << 1) + +/// boolean XOR (why doesn't C have the ^^ operator for this purpose?) +#define boolxor(a,b) (!(a) != !(b)) + +/// returns the smallest integer greater than or equal to "value", or 0 if "value" is too big +unsigned int CeilPowerOf2(unsigned int value); + +#define DEG2RAD(a) ((a) * ((float) M_PI / 180.0f)) +#define RAD2DEG(a) ((a) * (180.0f / (float) M_PI)) +#define ANGLEMOD(a) ((a) - 360.0 * floor((a) / 360.0)) + +#define DotProduct2(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]) +#define Vector2Clear(a) ((a)[0]=(a)[1]=0) +#define Vector2Compare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])) +#define Vector2Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1]) +#define Vector2Negate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1])) +#define Vector2Set(a,b,c) ((a)[0]=(b),(a)[1]=(c)) +#define Vector2Scale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale)) +#define Vector2Normalize2(v,dest) {float ilength = (float) sqrt(DotProduct2((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;} + +#define DotProduct4(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]+(a)[3]*(b)[3]) +#define Vector4Clear(a) ((a)[0]=(a)[1]=(a)[2]=(a)[3]=0) +#define Vector4Compare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])&&((a)[2]==(b)[2])&&((a)[3]==(b)[3])) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define Vector4Negate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1]),(b)[2]=-((a)[2]),(b)[3]=-((a)[3])) +#define Vector4Set(a,b,c,d,e) ((a)[0]=(b),(a)[1]=(c),(a)[2]=(d),(a)[3]=(e)) +#define Vector4Normalize2(v,dest) {float ilength = (float) sqrt(DotProduct4((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;dest[2] = (v)[2] * ilength;dest[3] = (v)[3] * ilength;} +#define Vector4Subtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2],(c)[3]=(a)[3]-(b)[3]) +#define Vector4Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3]) +#define Vector4Scale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale),(out)[3] = (in)[3] * (scale)) +#define Vector4Multiply(a,b,c) ((c)[0]=(a)[0]*(b)[0],(c)[1]=(a)[1]*(b)[1],(c)[2]=(a)[2]*(b)[2],(c)[3]=(a)[3]*(b)[3]) +#define Vector4MA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2],(c)[3] = (a)[3] + (scale) * (b)[3]) +#define Vector4Lerp(v1,lerp,v2,c) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2]), (c)[3] = (v1)[3] + (lerp) * ((v2)[3] - (v1)[3])) + +#define VectorNegate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1]),(b)[2]=-((a)[2])) +#define VectorSet(a,b,c,d) ((a)[0]=(b),(a)[1]=(c),(a)[2]=(d)) +#define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0) +#define DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) +#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorMultiply(a,b,c) ((c)[0]=(a)[0]*(b)[0],(c)[1]=(a)[1]*(b)[1],(c)[2]=(a)[2]*(b)[2]) +#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) +#define VectorNormalize(v) {float ilength = (float) sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0f / ilength;(v)[0] *= ilength;(v)[1] *= ilength;(v)[2] *= ilength;} +#define VectorNormalize2(v,dest) {float ilength = (float) sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;dest[2] = (v)[2] * ilength;} +#define VectorNormalizeDouble(v) {double ilength = sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0 / ilength;(v)[0] *= ilength;(v)[1] *= ilength;(v)[2] *= ilength;} +#define VectorDistance2(a, b) (((a)[0] - (b)[0]) * ((a)[0] - (b)[0]) + ((a)[1] - (b)[1]) * ((a)[1] - (b)[1]) + ((a)[2] - (b)[2]) * ((a)[2] - (b)[2])) +#define VectorDistance(a, b) (sqrt(VectorDistance2(a,b))) +#define VectorLength(a) (sqrt((double)DotProduct(a, a))) +#define VectorLength2(a) (DotProduct(a, a)) +#define VectorScale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale)) +#define VectorScaleCast(in, scale, outtype, out) ((out)[0] = (outtype) ((in)[0] * (scale)),(out)[1] = (outtype) ((in)[1] * (scale)),(out)[2] = (outtype) ((in)[2] * (scale))) +#define VectorCompare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])&&((a)[2]==(b)[2])) +#define VectorMA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2]) +#define VectorM(scale1, b1, c) ((c)[0] = (scale1) * (b1)[0],(c)[1] = (scale1) * (b1)[1],(c)[2] = (scale1) * (b1)[2]) +#define VectorMAM(scale1, b1, scale2, b2, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2]) +#define VectorMAMAM(scale1, b1, scale2, b2, scale3, b3, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2]) +#define VectorMAMAMAM(scale1, b1, scale2, b2, scale3, b3, scale4, b4, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0] + (scale4) * (b4)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1] + (scale4) * (b4)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2] + (scale4) * (b4)[2]) +#define VectorRandom(v) do{(v)[0] = lhrandom(-1, 1);(v)[1] = lhrandom(-1, 1);(v)[2] = lhrandom(-1, 1);}while(DotProduct(v, v) > 1) +#define VectorLerp(v1,lerp,v2,c) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2])) +#define VectorReflect(a,r,b,c) do{double d;d = DotProduct((a), (b)) * -(1.0 + (r));VectorMA((a), (d), (b), (c));}while(0) +#define BoxesOverlap(a,b,c,d) ((a)[0] <= (d)[0] && (b)[0] >= (c)[0] && (a)[1] <= (d)[1] && (b)[1] >= (c)[1] && (a)[2] <= (d)[2] && (b)[2] >= (c)[2]) +#define BoxInsideBox(a,b,c,d) ((a)[0] >= (c)[0] && (b)[0] <= (d)[0] && (a)[1] >= (c)[1] && (b)[1] <= (d)[1] && (a)[2] >= (c)[2] && (b)[2] <= (d)[2]) +#define TriangleBBoxOverlapsBox(a,b,c,d,e) (min((a)[0], min((b)[0], (c)[0])) < (e)[0] && max((a)[0], max((b)[0], (c)[0])) > (d)[0] && min((a)[1], min((b)[1], (c)[1])) < (e)[1] && max((a)[1], max((b)[1], (c)[1])) > (d)[1] && min((a)[2], min((b)[2], (c)[2])) < (e)[2] && max((a)[2], max((b)[2], (c)[2])) > (d)[2]) + +#define TriangleNormal(a,b,c,n) ( \ + (n)[0] = ((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1]), \ + (n)[1] = ((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2]), \ + (n)[2] = ((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0]) \ + ) + +/*! Fast PointInfrontOfTriangle. + * subtracts v1 from v0 and v2, combined into a crossproduct, combined with a + * dotproduct of the light location relative to the first point of the + * triangle (any point works, since any triangle is obviously flat), and + * finally a comparison to determine if the light is infront of the triangle + * (the goal of this statement) we do not need to normalize the surface + * normal because both sides of the comparison use it, therefore they are + * both multiplied the same amount... furthermore a subtract can be done on + * the point to eliminate one dotproduct + * this is ((p - a) * cross(a-b,c-b)) + */ +#define PointInfrontOfTriangle(p,a,b,c) \ +( ((p)[0] - (a)[0]) * (((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1])) \ ++ ((p)[1] - (a)[1]) * (((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2])) \ ++ ((p)[2] - (a)[2]) * (((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0])) > 0) + +#if 0 +// readable version, kept only for explanatory reasons +int PointInfrontOfTriangle(const float *p, const float *a, const float *b, const float *c) +{ + float dir0[3], dir1[3], normal[3]; + + // calculate two mostly perpendicular edge directions + VectorSubtract(a, b, dir0); + VectorSubtract(c, b, dir1); + + // we have two edge directions, we can calculate a third vector from + // them, which is the direction of the surface normal (its magnitude + // is not 1 however) + CrossProduct(dir0, dir1, normal); + + // compare distance of light along normal, with distance of any point + // of the triangle along the same normal (the triangle is planar, + // I.E. flat, so all points give the same answer) + return DotProduct(p, normal) > DotProduct(a, normal); +} +#endif + +#define lhcheeserand() (seed = (seed * 987211u) ^ (seed >> 13u) ^ 914867) +#define lhcheeserandom(MIN,MAX) ((double)(lhcheeserand() + 0.5) / ((double)4096.0*1024.0*1024.0) * ((MAX)-(MIN)) + (MIN)) +#define VectorCheeseRandom(v) do{(v)[0] = lhcheeserandom(-1, 1);(v)[1] = lhcheeserandom(-1, 1);(v)[2] = lhcheeserandom(-1, 1);}while(DotProduct(v, v) > 1) + +/* +// LordHavoc: quaternion math, untested, don't know if these are correct, +// need to add conversion to/from matrices +// LordHavoc: later note: the matrix faq is useful: http://skal.planet-d.net/demo/matrixfaq.htm +// LordHavoc: these are probably very wrong and I'm not sure I care, not used by anything + +// returns length of quaternion +#define qlen(a) ((float) sqrt((a)[0]*(a)[0]+(a)[1]*(a)[1]+(a)[2]*(a)[2]+(a)[3]*(a)[3])) +// returns squared length of quaternion +#define qlen2(a) ((a)[0]*(a)[0]+(a)[1]*(a)[1]+(a)[2]*(a)[2]+(a)[3]*(a)[3]) +// makes a quaternion from x, y, z, and a rotation angle (in degrees) +#define QuatMake(x,y,z,r,c)\ +{\ +if (r == 0)\ +{\ +(c)[0]=(float) ((x) * (1.0f / 0.0f));\ +(c)[1]=(float) ((y) * (1.0f / 0.0f));\ +(c)[2]=(float) ((z) * (1.0f / 0.0f));\ +(c)[3]=(float) 1.0f;\ +}\ +else\ +{\ +float r2 = (r) * 0.5 * (M_PI / 180);\ +float r2is = 1.0f / sin(r2);\ +(c)[0]=(float) ((x)/r2is);\ +(c)[1]=(float) ((y)/r2is);\ +(c)[2]=(float) ((z)/r2is);\ +(c)[3]=(float) (cos(r2));\ +}\ +} +// makes a quaternion from a vector and a rotation angle (in degrees) +#define QuatFromVec(a,r,c) QuatMake((a)[0],(a)[1],(a)[2],(r)) +// copies a quaternion +#define QuatCopy(a,c) {(c)[0]=(a)[0];(c)[1]=(a)[1];(c)[2]=(a)[2];(c)[3]=(a)[3];} +#define QuatSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];(c)[3]=(a)[3]-(b)[3];} +#define QuatAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];(c)[3]=(a)[3]+(b)[3];} +#define QuatScale(a,b,c) {(c)[0]=(a)[0]*b;(c)[1]=(a)[1]*b;(c)[2]=(a)[2]*b;(c)[3]=(a)[3]*b;} +// FIXME: this is wrong, do some more research on quaternions +//#define QuatMultiply(a,b,c) {(c)[0]=(a)[0]*(b)[0];(c)[1]=(a)[1]*(b)[1];(c)[2]=(a)[2]*(b)[2];(c)[3]=(a)[3]*(b)[3];} +// FIXME: this is wrong, do some more research on quaternions +//#define QuatMultiplyAdd(a,b,d,c) {(c)[0]=(a)[0]*(b)[0]+d[0];(c)[1]=(a)[1]*(b)[1]+d[1];(c)[2]=(a)[2]*(b)[2]+d[2];(c)[3]=(a)[3]*(b)[3]+d[3];} +#define qdist(a,b) ((float) sqrt(((b)[0]-(a)[0])*((b)[0]-(a)[0])+((b)[1]-(a)[1])*((b)[1]-(a)[1])+((b)[2]-(a)[2])*((b)[2]-(a)[2])+((b)[3]-(a)[3])*((b)[3]-(a)[3]))) +#define qdist2(a,b) (((b)[0]-(a)[0])*((b)[0]-(a)[0])+((b)[1]-(a)[1])*((b)[1]-(a)[1])+((b)[2]-(a)[2])*((b)[2]-(a)[2])+((b)[3]-(a)[3])*((b)[3]-(a)[3])) +*/ + +#define VectorCopy4(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];(b)[3]=(a)[3];} + +vec_t Length (vec3_t v); + +/// returns vector length +float VectorNormalizeLength (vec3_t v); + +/// returns vector length +float VectorNormalizeLength2 (vec3_t v, vec3_t dest); + +#define NUMVERTEXNORMALS 162 +extern float m_bytenormals[NUMVERTEXNORMALS][3]; + +unsigned char NormalToByte(const vec3_t n); +void ByteToNormal(unsigned char num, vec3_t n); + +void R_ConcatRotations (const float in1[3*3], const float in2[3*3], float out[3*3]); +void R_ConcatTransforms (const float in1[3*4], const float in2[3*4], float out[3*4]); + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +/// LordHavoc: proper matrix version of AngleVectors +void AngleVectorsFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up); +/// LordHavoc: builds a [3][4] matrix +void AngleMatrix (const vec3_t angles, const vec3_t translate, vec_t matrix[][4]); +/// LordHavoc: calculates pitch/yaw/roll angles from forward and up vectors +void AnglesFromVectors (vec3_t angles, const vec3_t forward, const vec3_t up, qboolean flippitch); + +/// LordHavoc: like AngleVectors, but taking a forward vector instead of angles, useful! +void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up); +void VectorVectorsDouble(const double *forward, double *right, double *up); + +void PlaneClassify(struct mplane_s *p); +int BoxOnPlaneSide(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p); +int BoxOnPlaneSide_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, const vec_t dist); +void BoxPlaneCorners(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p, vec3_t outnear, vec3_t outfar); +void BoxPlaneCorners_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec3_t outnear, vec3_t outfar); +void BoxPlaneCornerDistances(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p, vec_t *outnear, vec_t *outfar); +void BoxPlaneCornerDistances_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec_t *outnear, vec_t *outfar); + +#define PlaneDist(point,plane) ((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) +#define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) + +/// LordHavoc: minimal plane structure +typedef struct tinyplane_s +{ + float normal[3], dist; +} +tinyplane_t; + +typedef struct tinydoubleplane_s +{ + double normal[3], dist; +} +tinydoubleplane_t; + +void RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees); + +float RadiusFromBounds (const vec3_t mins, const vec3_t maxs); +float RadiusFromBoundsAndOrigin (const vec3_t mins, const vec3_t maxs, const vec3_t origin); + +struct matrix4x4_s; +/// print a matrix to the console +void Matrix4x4_Print(const struct matrix4x4_s *in); +int Math_atov(const char *s, prvm_vec3_t out); + +void BoxFromPoints(vec3_t mins, vec3_t maxs, int numpoints, vec_t *point3f); + +int LoopingFrameNumberFromDouble(double t, int loopframes); + +void Mathlib_Init(void); + +#endif + diff --git a/app/jni/matrixlib.c b/app/jni/matrixlib.c new file mode 100644 index 0000000..78c859c --- /dev/null +++ b/app/jni/matrixlib.c @@ -0,0 +1,1825 @@ +#include "quakedef.h" + +#include +#include "matrixlib.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4244) // LordHavoc: MSVC++ 4 x86, double/float +#pragma warning(disable : 4305) // LordHavoc: MSVC++ 6 x86, double/float +#endif + +const matrix4x4_t identitymatrix = +{ + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1} + } +}; + +void Matrix4x4_Copy (matrix4x4_t *out, const matrix4x4_t *in) +{ + *out = *in; +} + +void Matrix4x4_CopyRotateOnly (matrix4x4_t *out, const matrix4x4_t *in) +{ + out->m[0][0] = in->m[0][0]; + out->m[0][1] = in->m[0][1]; + out->m[0][2] = in->m[0][2]; + out->m[0][3] = 0.0f; + out->m[1][0] = in->m[1][0]; + out->m[1][1] = in->m[1][1]; + out->m[1][2] = in->m[1][2]; + out->m[1][3] = 0.0f; + out->m[2][0] = in->m[2][0]; + out->m[2][1] = in->m[2][1]; + out->m[2][2] = in->m[2][2]; + out->m[2][3] = 0.0f; + out->m[3][0] = 0.0f; + out->m[3][1] = 0.0f; + out->m[3][2] = 0.0f; + out->m[3][3] = 1.0f; +} + +void Matrix4x4_CopyTranslateOnly (matrix4x4_t *out, const matrix4x4_t *in) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = 1.0f; + out->m[1][0] = 0.0f; + out->m[2][0] = 0.0f; + out->m[3][0] = in->m[0][3]; + out->m[0][1] = 0.0f; + out->m[1][1] = 1.0f; + out->m[2][1] = 0.0f; + out->m[3][1] = in->m[1][3]; + out->m[0][2] = 0.0f; + out->m[1][2] = 0.0f; + out->m[2][2] = 1.0f; + out->m[3][2] = in->m[2][3]; + out->m[0][3] = 0.0f; + out->m[1][3] = 0.0f; + out->m[2][3] = 0.0f; + out->m[3][3] = 1.0f; +#else + out->m[0][0] = 1.0f; + out->m[0][1] = 0.0f; + out->m[0][2] = 0.0f; + out->m[0][3] = in->m[0][3]; + out->m[1][0] = 0.0f; + out->m[1][1] = 1.0f; + out->m[1][2] = 0.0f; + out->m[1][3] = in->m[1][3]; + out->m[2][0] = 0.0f; + out->m[2][1] = 0.0f; + out->m[2][2] = 1.0f; + out->m[2][3] = in->m[2][3]; + out->m[3][0] = 0.0f; + out->m[3][1] = 0.0f; + out->m[3][2] = 0.0f; + out->m[3][3] = 1.0f; +#endif +} + +void Matrix4x4_Concat (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in1->m[0][0] * in2->m[0][0] + in1->m[1][0] * in2->m[0][1] + in1->m[2][0] * in2->m[0][2] + in1->m[3][0] * in2->m[0][3]; + out->m[1][0] = in1->m[0][0] * in2->m[1][0] + in1->m[1][0] * in2->m[1][1] + in1->m[2][0] * in2->m[1][2] + in1->m[3][0] * in2->m[1][3]; + out->m[2][0] = in1->m[0][0] * in2->m[2][0] + in1->m[1][0] * in2->m[2][1] + in1->m[2][0] * in2->m[2][2] + in1->m[3][0] * in2->m[2][3]; + out->m[3][0] = in1->m[0][0] * in2->m[3][0] + in1->m[1][0] * in2->m[3][1] + in1->m[2][0] * in2->m[3][2] + in1->m[3][0] * in2->m[3][3]; + out->m[0][1] = in1->m[0][1] * in2->m[0][0] + in1->m[1][1] * in2->m[0][1] + in1->m[2][1] * in2->m[0][2] + in1->m[3][1] * in2->m[0][3]; + out->m[1][1] = in1->m[0][1] * in2->m[1][0] + in1->m[1][1] * in2->m[1][1] + in1->m[2][1] * in2->m[1][2] + in1->m[3][1] * in2->m[1][3]; + out->m[2][1] = in1->m[0][1] * in2->m[2][0] + in1->m[1][1] * in2->m[2][1] + in1->m[2][1] * in2->m[2][2] + in1->m[3][1] * in2->m[2][3]; + out->m[3][1] = in1->m[0][1] * in2->m[3][0] + in1->m[1][1] * in2->m[3][1] + in1->m[2][1] * in2->m[3][2] + in1->m[3][1] * in2->m[3][3]; + out->m[0][2] = in1->m[0][2] * in2->m[0][0] + in1->m[1][2] * in2->m[0][1] + in1->m[2][2] * in2->m[0][2] + in1->m[3][2] * in2->m[0][3]; + out->m[1][2] = in1->m[0][2] * in2->m[1][0] + in1->m[1][2] * in2->m[1][1] + in1->m[2][2] * in2->m[1][2] + in1->m[3][2] * in2->m[1][3]; + out->m[2][2] = in1->m[0][2] * in2->m[2][0] + in1->m[1][2] * in2->m[2][1] + in1->m[2][2] * in2->m[2][2] + in1->m[3][2] * in2->m[2][3]; + out->m[3][2] = in1->m[0][2] * in2->m[3][0] + in1->m[1][2] * in2->m[3][1] + in1->m[2][2] * in2->m[3][2] + in1->m[3][2] * in2->m[3][3]; + out->m[0][3] = in1->m[0][3] * in2->m[0][0] + in1->m[1][3] * in2->m[0][1] + in1->m[2][3] * in2->m[0][2] + in1->m[3][3] * in2->m[0][3]; + out->m[1][3] = in1->m[0][3] * in2->m[1][0] + in1->m[1][3] * in2->m[1][1] + in1->m[2][3] * in2->m[1][2] + in1->m[3][3] * in2->m[1][3]; + out->m[2][3] = in1->m[0][3] * in2->m[2][0] + in1->m[1][3] * in2->m[2][1] + in1->m[2][3] * in2->m[2][2] + in1->m[3][3] * in2->m[2][3]; + out->m[3][3] = in1->m[0][3] * in2->m[3][0] + in1->m[1][3] * in2->m[3][1] + in1->m[2][3] * in2->m[3][2] + in1->m[3][3] * in2->m[3][3]; +#else + out->m[0][0] = in1->m[0][0] * in2->m[0][0] + in1->m[0][1] * in2->m[1][0] + in1->m[0][2] * in2->m[2][0] + in1->m[0][3] * in2->m[3][0]; + out->m[0][1] = in1->m[0][0] * in2->m[0][1] + in1->m[0][1] * in2->m[1][1] + in1->m[0][2] * in2->m[2][1] + in1->m[0][3] * in2->m[3][1]; + out->m[0][2] = in1->m[0][0] * in2->m[0][2] + in1->m[0][1] * in2->m[1][2] + in1->m[0][2] * in2->m[2][2] + in1->m[0][3] * in2->m[3][2]; + out->m[0][3] = in1->m[0][0] * in2->m[0][3] + in1->m[0][1] * in2->m[1][3] + in1->m[0][2] * in2->m[2][3] + in1->m[0][3] * in2->m[3][3]; + out->m[1][0] = in1->m[1][0] * in2->m[0][0] + in1->m[1][1] * in2->m[1][0] + in1->m[1][2] * in2->m[2][0] + in1->m[1][3] * in2->m[3][0]; + out->m[1][1] = in1->m[1][0] * in2->m[0][1] + in1->m[1][1] * in2->m[1][1] + in1->m[1][2] * in2->m[2][1] + in1->m[1][3] * in2->m[3][1]; + out->m[1][2] = in1->m[1][0] * in2->m[0][2] + in1->m[1][1] * in2->m[1][2] + in1->m[1][2] * in2->m[2][2] + in1->m[1][3] * in2->m[3][2]; + out->m[1][3] = in1->m[1][0] * in2->m[0][3] + in1->m[1][1] * in2->m[1][3] + in1->m[1][2] * in2->m[2][3] + in1->m[1][3] * in2->m[3][3]; + out->m[2][0] = in1->m[2][0] * in2->m[0][0] + in1->m[2][1] * in2->m[1][0] + in1->m[2][2] * in2->m[2][0] + in1->m[2][3] * in2->m[3][0]; + out->m[2][1] = in1->m[2][0] * in2->m[0][1] + in1->m[2][1] * in2->m[1][1] + in1->m[2][2] * in2->m[2][1] + in1->m[2][3] * in2->m[3][1]; + out->m[2][2] = in1->m[2][0] * in2->m[0][2] + in1->m[2][1] * in2->m[1][2] + in1->m[2][2] * in2->m[2][2] + in1->m[2][3] * in2->m[3][2]; + out->m[2][3] = in1->m[2][0] * in2->m[0][3] + in1->m[2][1] * in2->m[1][3] + in1->m[2][2] * in2->m[2][3] + in1->m[2][3] * in2->m[3][3]; + out->m[3][0] = in1->m[3][0] * in2->m[0][0] + in1->m[3][1] * in2->m[1][0] + in1->m[3][2] * in2->m[2][0] + in1->m[3][3] * in2->m[3][0]; + out->m[3][1] = in1->m[3][0] * in2->m[0][1] + in1->m[3][1] * in2->m[1][1] + in1->m[3][2] * in2->m[2][1] + in1->m[3][3] * in2->m[3][1]; + out->m[3][2] = in1->m[3][0] * in2->m[0][2] + in1->m[3][1] * in2->m[1][2] + in1->m[3][2] * in2->m[2][2] + in1->m[3][3] * in2->m[3][2]; + out->m[3][3] = in1->m[3][0] * in2->m[0][3] + in1->m[3][1] * in2->m[1][3] + in1->m[3][2] * in2->m[2][3] + in1->m[3][3] * in2->m[3][3]; +#endif +} + +void Matrix4x4_Transpose (matrix4x4_t *out, const matrix4x4_t *in1) +{ + out->m[0][0] = in1->m[0][0]; + out->m[0][1] = in1->m[1][0]; + out->m[0][2] = in1->m[2][0]; + out->m[0][3] = in1->m[3][0]; + out->m[1][0] = in1->m[0][1]; + out->m[1][1] = in1->m[1][1]; + out->m[1][2] = in1->m[2][1]; + out->m[1][3] = in1->m[3][1]; + out->m[2][0] = in1->m[0][2]; + out->m[2][1] = in1->m[1][2]; + out->m[2][2] = in1->m[2][2]; + out->m[2][3] = in1->m[3][2]; + out->m[3][0] = in1->m[0][3]; + out->m[3][1] = in1->m[1][3]; + out->m[3][2] = in1->m[2][3]; + out->m[3][3] = in1->m[3][3]; +} + +#if 1 +// Adapted from code contributed to Mesa by David Moore (Mesa 7.6 under SGI Free License B - which is MIT/X11-type) +// added helper for common subexpression elimination by eihrul, and other optimizations by div0 +int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) +{ + float det; + + // note: orientation does not matter, as transpose(invert(transpose(m))) == invert(m), proof: + // transpose(invert(transpose(m))) * m + // = transpose(invert(transpose(m))) * transpose(transpose(m)) + // = transpose(transpose(m) * invert(transpose(m))) + // = transpose(identity) + // = identity + + // this seems to help gcc's common subexpression elimination, and also makes the code look nicer + float m00 = in1->m[0][0], m01 = in1->m[0][1], m02 = in1->m[0][2], m03 = in1->m[0][3], + m10 = in1->m[1][0], m11 = in1->m[1][1], m12 = in1->m[1][2], m13 = in1->m[1][3], + m20 = in1->m[2][0], m21 = in1->m[2][1], m22 = in1->m[2][2], m23 = in1->m[2][3], + m30 = in1->m[3][0], m31 = in1->m[3][1], m32 = in1->m[3][2], m33 = in1->m[3][3]; + + // calculate the adjoint + out->m[0][0] = (m11*(m22*m33 - m23*m32) - m21*(m12*m33 - m13*m32) + m31*(m12*m23 - m13*m22)); + out->m[0][1] = -(m01*(m22*m33 - m23*m32) - m21*(m02*m33 - m03*m32) + m31*(m02*m23 - m03*m22)); + out->m[0][2] = (m01*(m12*m33 - m13*m32) - m11*(m02*m33 - m03*m32) + m31*(m02*m13 - m03*m12)); + out->m[0][3] = -(m01*(m12*m23 - m13*m22) - m11*(m02*m23 - m03*m22) + m21*(m02*m13 - m03*m12)); + out->m[1][0] = -(m10*(m22*m33 - m23*m32) - m20*(m12*m33 - m13*m32) + m30*(m12*m23 - m13*m22)); + out->m[1][1] = (m00*(m22*m33 - m23*m32) - m20*(m02*m33 - m03*m32) + m30*(m02*m23 - m03*m22)); + out->m[1][2] = -(m00*(m12*m33 - m13*m32) - m10*(m02*m33 - m03*m32) + m30*(m02*m13 - m03*m12)); + out->m[1][3] = (m00*(m12*m23 - m13*m22) - m10*(m02*m23 - m03*m22) + m20*(m02*m13 - m03*m12)); + out->m[2][0] = (m10*(m21*m33 - m23*m31) - m20*(m11*m33 - m13*m31) + m30*(m11*m23 - m13*m21)); + out->m[2][1] = -(m00*(m21*m33 - m23*m31) - m20*(m01*m33 - m03*m31) + m30*(m01*m23 - m03*m21)); + out->m[2][2] = (m00*(m11*m33 - m13*m31) - m10*(m01*m33 - m03*m31) + m30*(m01*m13 - m03*m11)); + out->m[2][3] = -(m00*(m11*m23 - m13*m21) - m10*(m01*m23 - m03*m21) + m20*(m01*m13 - m03*m11)); + out->m[3][0] = -(m10*(m21*m32 - m22*m31) - m20*(m11*m32 - m12*m31) + m30*(m11*m22 - m12*m21)); + out->m[3][1] = (m00*(m21*m32 - m22*m31) - m20*(m01*m32 - m02*m31) + m30*(m01*m22 - m02*m21)); + out->m[3][2] = -(m00*(m11*m32 - m12*m31) - m10*(m01*m32 - m02*m31) + m30*(m01*m12 - m02*m11)); + out->m[3][3] = (m00*(m11*m22 - m12*m21) - m10*(m01*m22 - m02*m21) + m20*(m01*m12 - m02*m11)); + + // calculate the determinant (as inverse == 1/det * adjoint, adjoint * m == identity * det, so this calculates the det) + det = m00*out->m[0][0] + m10*out->m[0][1] + m20*out->m[0][2] + m30*out->m[0][3]; + if (det == 0.0f) + return 0; + + // multiplications are faster than divisions, usually + det = 1.0f / det; + + // manually unrolled loop to multiply all matrix elements by 1/det + out->m[0][0] *= det; out->m[0][1] *= det; out->m[0][2] *= det; out->m[0][3] *= det; + out->m[1][0] *= det; out->m[1][1] *= det; out->m[1][2] *= det; out->m[1][3] *= det; + out->m[2][0] *= det; out->m[2][1] *= det; out->m[2][2] *= det; out->m[2][3] *= det; + out->m[3][0] *= det; out->m[3][1] *= det; out->m[3][2] *= det; out->m[3][3] *= det; + + return 1; +} +#elif 1 +// Adapted from code contributed to Mesa by David Moore (Mesa 7.6 under SGI Free License B - which is MIT/X11-type) +int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) +{ + matrix4x4_t temp; + double det; + int i, j; + +#ifdef MATRIX4x4_OPENGLORIENTATION + temp.m[0][0] = in1->m[1][1]*in1->m[2][2]*in1->m[3][3] - in1->m[1][1]*in1->m[2][3]*in1->m[3][2] - in1->m[2][1]*in1->m[1][2]*in1->m[3][3] + in1->m[2][1]*in1->m[1][3]*in1->m[3][2] + in1->m[3][1]*in1->m[1][2]*in1->m[2][3] - in1->m[3][1]*in1->m[1][3]*in1->m[2][2]; + temp.m[1][0] = -in1->m[1][0]*in1->m[2][2]*in1->m[3][3] + in1->m[1][0]*in1->m[2][3]*in1->m[3][2] + in1->m[2][0]*in1->m[1][2]*in1->m[3][3] - in1->m[2][0]*in1->m[1][3]*in1->m[3][2] - in1->m[3][0]*in1->m[1][2]*in1->m[2][3] + in1->m[3][0]*in1->m[1][3]*in1->m[2][2]; + temp.m[2][0] = in1->m[1][0]*in1->m[2][1]*in1->m[3][3] - in1->m[1][0]*in1->m[2][3]*in1->m[3][1] - in1->m[2][0]*in1->m[1][1]*in1->m[3][3] + in1->m[2][0]*in1->m[1][3]*in1->m[3][1] + in1->m[3][0]*in1->m[1][1]*in1->m[2][3] - in1->m[3][0]*in1->m[1][3]*in1->m[2][1]; + temp.m[3][0] = -in1->m[1][0]*in1->m[2][1]*in1->m[3][2] + in1->m[1][0]*in1->m[2][2]*in1->m[3][1] + in1->m[2][0]*in1->m[1][1]*in1->m[3][2] - in1->m[2][0]*in1->m[1][2]*in1->m[3][1] - in1->m[3][0]*in1->m[1][1]*in1->m[2][2] + in1->m[3][0]*in1->m[1][2]*in1->m[2][1]; + temp.m[0][1] = -in1->m[0][1]*in1->m[2][2]*in1->m[3][3] + in1->m[0][1]*in1->m[2][3]*in1->m[3][2] + in1->m[2][1]*in1->m[0][2]*in1->m[3][3] - in1->m[2][1]*in1->m[0][3]*in1->m[3][2] - in1->m[3][1]*in1->m[0][2]*in1->m[2][3] + in1->m[3][1]*in1->m[0][3]*in1->m[2][2]; + temp.m[1][1] = in1->m[0][0]*in1->m[2][2]*in1->m[3][3] - in1->m[0][0]*in1->m[2][3]*in1->m[3][2] - in1->m[2][0]*in1->m[0][2]*in1->m[3][3] + in1->m[2][0]*in1->m[0][3]*in1->m[3][2] + in1->m[3][0]*in1->m[0][2]*in1->m[2][3] - in1->m[3][0]*in1->m[0][3]*in1->m[2][2]; + temp.m[2][1] = -in1->m[0][0]*in1->m[2][1]*in1->m[3][3] + in1->m[0][0]*in1->m[2][3]*in1->m[3][1] + in1->m[2][0]*in1->m[0][1]*in1->m[3][3] - in1->m[2][0]*in1->m[0][3]*in1->m[3][1] - in1->m[3][0]*in1->m[0][1]*in1->m[2][3] + in1->m[3][0]*in1->m[0][3]*in1->m[2][1]; + temp.m[3][1] = in1->m[0][0]*in1->m[2][1]*in1->m[3][2] - in1->m[0][0]*in1->m[2][2]*in1->m[3][1] - in1->m[2][0]*in1->m[0][1]*in1->m[3][2] + in1->m[2][0]*in1->m[0][2]*in1->m[3][1] + in1->m[3][0]*in1->m[0][1]*in1->m[2][2] - in1->m[3][0]*in1->m[0][2]*in1->m[2][1]; + temp.m[0][2] = in1->m[0][1]*in1->m[1][2]*in1->m[3][3] - in1->m[0][1]*in1->m[1][3]*in1->m[3][2] - in1->m[1][1]*in1->m[0][2]*in1->m[3][3] + in1->m[1][1]*in1->m[0][3]*in1->m[3][2] + in1->m[3][1]*in1->m[0][2]*in1->m[1][3] - in1->m[3][1]*in1->m[0][3]*in1->m[1][2]; + temp.m[1][2] = -in1->m[0][0]*in1->m[1][2]*in1->m[3][3] + in1->m[0][0]*in1->m[1][3]*in1->m[3][2] + in1->m[1][0]*in1->m[0][2]*in1->m[3][3] - in1->m[1][0]*in1->m[0][3]*in1->m[3][2] - in1->m[3][0]*in1->m[0][2]*in1->m[1][3] + in1->m[3][0]*in1->m[0][3]*in1->m[1][2]; + temp.m[2][2] = in1->m[0][0]*in1->m[1][1]*in1->m[3][3] - in1->m[0][0]*in1->m[1][3]*in1->m[3][1] - in1->m[1][0]*in1->m[0][1]*in1->m[3][3] + in1->m[1][0]*in1->m[0][3]*in1->m[3][1] + in1->m[3][0]*in1->m[0][1]*in1->m[1][3] - in1->m[3][0]*in1->m[0][3]*in1->m[1][1]; + temp.m[3][2] = -in1->m[0][0]*in1->m[1][1]*in1->m[3][2] + in1->m[0][0]*in1->m[1][2]*in1->m[3][1] + in1->m[1][0]*in1->m[0][1]*in1->m[3][2] - in1->m[1][0]*in1->m[0][2]*in1->m[3][1] - in1->m[3][0]*in1->m[0][1]*in1->m[1][2] + in1->m[3][0]*in1->m[0][2]*in1->m[1][1]; + temp.m[0][3] = -in1->m[0][1]*in1->m[1][2]*in1->m[2][3] + in1->m[0][1]*in1->m[1][3]*in1->m[2][2] + in1->m[1][1]*in1->m[0][2]*in1->m[2][3] - in1->m[1][1]*in1->m[0][3]*in1->m[2][2] - in1->m[2][1]*in1->m[0][2]*in1->m[1][3] + in1->m[2][1]*in1->m[0][3]*in1->m[1][2]; + temp.m[1][3] = in1->m[0][0]*in1->m[1][2]*in1->m[2][3] - in1->m[0][0]*in1->m[1][3]*in1->m[2][2] - in1->m[1][0]*in1->m[0][2]*in1->m[2][3] + in1->m[1][0]*in1->m[0][3]*in1->m[2][2] + in1->m[2][0]*in1->m[0][2]*in1->m[1][3] - in1->m[2][0]*in1->m[0][3]*in1->m[1][2]; + temp.m[2][3] = -in1->m[0][0]*in1->m[1][1]*in1->m[2][3] + in1->m[0][0]*in1->m[1][3]*in1->m[2][1] + in1->m[1][0]*in1->m[0][1]*in1->m[2][3] - in1->m[1][0]*in1->m[0][3]*in1->m[2][1] - in1->m[2][0]*in1->m[0][1]*in1->m[1][3] + in1->m[2][0]*in1->m[0][3]*in1->m[1][1]; + temp.m[3][3] = in1->m[0][0]*in1->m[1][1]*in1->m[2][2] - in1->m[0][0]*in1->m[1][2]*in1->m[2][1] - in1->m[1][0]*in1->m[0][1]*in1->m[2][2] + in1->m[1][0]*in1->m[0][2]*in1->m[2][1] + in1->m[2][0]*in1->m[0][1]*in1->m[1][2] - in1->m[2][0]*in1->m[0][2]*in1->m[1][1]; +#else + temp.m[0][0] = in1->m[1][1]*in1->m[2][2]*in1->m[3][3] - in1->m[1][1]*in1->m[3][2]*in1->m[2][3] - in1->m[1][2]*in1->m[2][1]*in1->m[3][3] + in1->m[1][2]*in1->m[3][1]*in1->m[2][3] + in1->m[1][3]*in1->m[2][1]*in1->m[3][2] - in1->m[1][3]*in1->m[3][1]*in1->m[2][2]; + temp.m[0][1] = -in1->m[0][1]*in1->m[2][2]*in1->m[3][3] + in1->m[0][1]*in1->m[3][2]*in1->m[2][3] + in1->m[0][2]*in1->m[2][1]*in1->m[3][3] - in1->m[0][2]*in1->m[3][1]*in1->m[2][3] - in1->m[0][3]*in1->m[2][1]*in1->m[3][2] + in1->m[0][3]*in1->m[3][1]*in1->m[2][2]; + temp.m[0][2] = in1->m[0][1]*in1->m[1][2]*in1->m[3][3] - in1->m[0][1]*in1->m[3][2]*in1->m[1][3] - in1->m[0][2]*in1->m[1][1]*in1->m[3][3] + in1->m[0][2]*in1->m[3][1]*in1->m[1][3] + in1->m[0][3]*in1->m[1][1]*in1->m[3][2] - in1->m[0][3]*in1->m[3][1]*in1->m[1][2]; + temp.m[0][3] = -in1->m[0][1]*in1->m[1][2]*in1->m[2][3] + in1->m[0][1]*in1->m[2][2]*in1->m[1][3] + in1->m[0][2]*in1->m[1][1]*in1->m[2][3] - in1->m[0][2]*in1->m[2][1]*in1->m[1][3] - in1->m[0][3]*in1->m[1][1]*in1->m[2][2] + in1->m[0][3]*in1->m[2][1]*in1->m[1][2]; + temp.m[1][0] = -in1->m[1][0]*in1->m[2][2]*in1->m[3][3] + in1->m[1][0]*in1->m[3][2]*in1->m[2][3] + in1->m[1][2]*in1->m[2][0]*in1->m[3][3] - in1->m[1][2]*in1->m[3][0]*in1->m[2][3] - in1->m[1][3]*in1->m[2][0]*in1->m[3][2] + in1->m[1][3]*in1->m[3][0]*in1->m[2][2]; + temp.m[1][1] = in1->m[0][0]*in1->m[2][2]*in1->m[3][3] - in1->m[0][0]*in1->m[3][2]*in1->m[2][3] - in1->m[0][2]*in1->m[2][0]*in1->m[3][3] + in1->m[0][2]*in1->m[3][0]*in1->m[2][3] + in1->m[0][3]*in1->m[2][0]*in1->m[3][2] - in1->m[0][3]*in1->m[3][0]*in1->m[2][2]; + temp.m[1][2] = -in1->m[0][0]*in1->m[1][2]*in1->m[3][3] + in1->m[0][0]*in1->m[3][2]*in1->m[1][3] + in1->m[0][2]*in1->m[1][0]*in1->m[3][3] - in1->m[0][2]*in1->m[3][0]*in1->m[1][3] - in1->m[0][3]*in1->m[1][0]*in1->m[3][2] + in1->m[0][3]*in1->m[3][0]*in1->m[1][2]; + temp.m[1][3] = in1->m[0][0]*in1->m[1][2]*in1->m[2][3] - in1->m[0][0]*in1->m[2][2]*in1->m[1][3] - in1->m[0][2]*in1->m[1][0]*in1->m[2][3] + in1->m[0][2]*in1->m[2][0]*in1->m[1][3] + in1->m[0][3]*in1->m[1][0]*in1->m[2][2] - in1->m[0][3]*in1->m[2][0]*in1->m[1][2]; + temp.m[2][0] = in1->m[1][0]*in1->m[2][1]*in1->m[3][3] - in1->m[1][0]*in1->m[3][1]*in1->m[2][3] - in1->m[1][1]*in1->m[2][0]*in1->m[3][3] + in1->m[1][1]*in1->m[3][0]*in1->m[2][3] + in1->m[1][3]*in1->m[2][0]*in1->m[3][1] - in1->m[1][3]*in1->m[3][0]*in1->m[2][1]; + temp.m[2][1] = -in1->m[0][0]*in1->m[2][1]*in1->m[3][3] + in1->m[0][0]*in1->m[3][1]*in1->m[2][3] + in1->m[0][1]*in1->m[2][0]*in1->m[3][3] - in1->m[0][1]*in1->m[3][0]*in1->m[2][3] - in1->m[0][3]*in1->m[2][0]*in1->m[3][1] + in1->m[0][3]*in1->m[3][0]*in1->m[2][1]; + temp.m[2][2] = in1->m[0][0]*in1->m[1][1]*in1->m[3][3] - in1->m[0][0]*in1->m[3][1]*in1->m[1][3] - in1->m[0][1]*in1->m[1][0]*in1->m[3][3] + in1->m[0][1]*in1->m[3][0]*in1->m[1][3] + in1->m[0][3]*in1->m[1][0]*in1->m[3][1] - in1->m[0][3]*in1->m[3][0]*in1->m[1][1]; + temp.m[2][3] = -in1->m[0][0]*in1->m[1][1]*in1->m[2][3] + in1->m[0][0]*in1->m[2][1]*in1->m[1][3] + in1->m[0][1]*in1->m[1][0]*in1->m[2][3] - in1->m[0][1]*in1->m[2][0]*in1->m[1][3] - in1->m[0][3]*in1->m[1][0]*in1->m[2][1] + in1->m[0][3]*in1->m[2][0]*in1->m[1][1]; + temp.m[3][0] = -in1->m[1][0]*in1->m[2][1]*in1->m[3][2] + in1->m[1][0]*in1->m[3][1]*in1->m[2][2] + in1->m[1][1]*in1->m[2][0]*in1->m[3][2] - in1->m[1][1]*in1->m[3][0]*in1->m[2][2] - in1->m[1][2]*in1->m[2][0]*in1->m[3][1] + in1->m[1][2]*in1->m[3][0]*in1->m[2][1]; + temp.m[3][1] = in1->m[0][0]*in1->m[2][1]*in1->m[3][2] - in1->m[0][0]*in1->m[3][1]*in1->m[2][2] - in1->m[0][1]*in1->m[2][0]*in1->m[3][2] + in1->m[0][1]*in1->m[3][0]*in1->m[2][2] + in1->m[0][2]*in1->m[2][0]*in1->m[3][1] - in1->m[0][2]*in1->m[3][0]*in1->m[2][1]; + temp.m[3][2] = -in1->m[0][0]*in1->m[1][1]*in1->m[3][2] + in1->m[0][0]*in1->m[3][1]*in1->m[1][2] + in1->m[0][1]*in1->m[1][0]*in1->m[3][2] - in1->m[0][1]*in1->m[3][0]*in1->m[1][2] - in1->m[0][2]*in1->m[1][0]*in1->m[3][1] + in1->m[0][2]*in1->m[3][0]*in1->m[1][1]; + temp.m[3][3] = in1->m[0][0]*in1->m[1][1]*in1->m[2][2] - in1->m[0][0]*in1->m[2][1]*in1->m[1][2] - in1->m[0][1]*in1->m[1][0]*in1->m[2][2] + in1->m[0][1]*in1->m[2][0]*in1->m[1][2] + in1->m[0][2]*in1->m[1][0]*in1->m[2][1] - in1->m[0][2]*in1->m[2][0]*in1->m[1][1]; +#endif + + det = in1->m[0][0]*temp.m[0][0] + in1->m[1][0]*temp.m[0][1] + in1->m[2][0]*temp.m[0][2] + in1->m[3][0]*temp.m[0][3]; + if (det == 0.0f) + return 0; + + det = 1.0f / det; + + for (i = 0;i < 4;i++) + for (j = 0;j < 4;j++) + out->m[i][j] = temp.m[i][j] * det; + + return 1; +} +#else +int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) +{ + double *temp; + double *r[4]; + double rtemp[4][8]; + double m[4]; + double s; + + r[0] = rtemp[0]; + r[1] = rtemp[1]; + r[2] = rtemp[2]; + r[3] = rtemp[3]; + +#ifdef MATRIX4x4_OPENGLORIENTATION + r[0][0] = in1->m[0][0]; r[0][1] = in1->m[1][0]; r[0][2] = in1->m[2][0]; r[0][3] = in1->m[3][0]; + r[0][4] = 1.0; r[0][5] = r[0][6] = r[0][7] = 0.0; + + r[1][0] = in1->m[0][1]; r[1][1] = in1->m[1][1]; r[1][2] = in1->m[2][1]; r[1][3] = in1->m[3][1]; + r[1][5] = 1.0; r[1][4] = r[1][6] = r[1][7] = 0.0; + + r[2][0] = in1->m[0][2]; r[2][1] = in1->m[1][2]; r[2][2] = in1->m[2][2]; r[2][3] = in1->m[3][2]; + r[2][6] = 1.0; r[2][4] = r[2][5] = r[2][7] = 0.0; + + r[3][0] = in1->m[0][3]; r[3][1] = in1->m[1][3]; r[3][2] = in1->m[2][3]; r[3][3] = in1->m[3][3]; + r[3][7] = 1.0; r[3][4] = r[3][5] = r[3][6] = 0.0; +#else + r[0][0] = in1->m[0][0]; r[0][1] = in1->m[0][1]; r[0][2] = in1->m[0][2]; r[0][3] = in1->m[0][3]; + r[0][4] = 1.0; r[0][5] = r[0][6] = r[0][7] = 0.0; + + r[1][0] = in1->m[1][0]; r[1][1] = in1->m[1][1]; r[1][2] = in1->m[1][2]; r[1][3] = in1->m[1][3]; + r[1][5] = 1.0; r[1][4] = r[1][6] = r[1][7] = 0.0; + + r[2][0] = in1->m[2][0]; r[2][1] = in1->m[2][1]; r[2][2] = in1->m[2][2]; r[2][3] = in1->m[2][3]; + r[2][6] = 1.0; r[2][4] = r[2][5] = r[2][7] = 0.0; + + r[3][0] = in1->m[3][0]; r[3][1] = in1->m[3][1]; r[3][2] = in1->m[3][2]; r[3][3] = in1->m[3][3]; + r[3][7] = 1.0; r[3][4] = r[3][5] = r[3][6] = 0.0; +#endif + + if (fabs (r[3][0]) > fabs (r[2][0])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } + if (fabs (r[2][0]) > fabs (r[1][0])) { temp = r[2]; r[2] = r[1]; r[1] = temp; } + if (fabs (r[1][0]) > fabs (r[0][0])) { temp = r[1]; r[1] = r[0]; r[0] = temp; } + + if (r[0][0]) + { + m[1] = r[1][0] / r[0][0]; + m[2] = r[2][0] / r[0][0]; + m[3] = r[3][0] / r[0][0]; + + s = r[0][1]; r[1][1] -= m[1] * s; r[2][1] -= m[2] * s; r[3][1] -= m[3] * s; + s = r[0][2]; r[1][2] -= m[1] * s; r[2][2] -= m[2] * s; r[3][2] -= m[3] * s; + s = r[0][3]; r[1][3] -= m[1] * s; r[2][3] -= m[2] * s; r[3][3] -= m[3] * s; + + s = r[0][4]; if (s) { r[1][4] -= m[1] * s; r[2][4] -= m[2] * s; r[3][4] -= m[3] * s; } + s = r[0][5]; if (s) { r[1][5] -= m[1] * s; r[2][5] -= m[2] * s; r[3][5] -= m[3] * s; } + s = r[0][6]; if (s) { r[1][6] -= m[1] * s; r[2][6] -= m[2] * s; r[3][6] -= m[3] * s; } + s = r[0][7]; if (s) { r[1][7] -= m[1] * s; r[2][7] -= m[2] * s; r[3][7] -= m[3] * s; } + + if (fabs (r[3][1]) > fabs (r[2][1])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } + if (fabs (r[2][1]) > fabs (r[1][1])) { temp = r[2]; r[2] = r[1]; r[1] = temp; } + + if (r[1][1]) + { + m[2] = r[2][1] / r[1][1]; + m[3] = r[3][1] / r[1][1]; + r[2][2] -= m[2] * r[1][2]; + r[3][2] -= m[3] * r[1][2]; + r[2][3] -= m[2] * r[1][3]; + r[3][3] -= m[3] * r[1][3]; + + s = r[1][4]; if (s) { r[2][4] -= m[2] * s; r[3][4] -= m[3] * s; } + s = r[1][5]; if (s) { r[2][5] -= m[2] * s; r[3][5] -= m[3] * s; } + s = r[1][6]; if (s) { r[2][6] -= m[2] * s; r[3][6] -= m[3] * s; } + s = r[1][7]; if (s) { r[2][7] -= m[2] * s; r[3][7] -= m[3] * s; } + + if (fabs (r[3][2]) > fabs (r[2][2])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } + + if (r[2][2]) + { + m[3] = r[3][2] / r[2][2]; + r[3][3] -= m[3] * r[2][3]; + r[3][4] -= m[3] * r[2][4]; + r[3][5] -= m[3] * r[2][5]; + r[3][6] -= m[3] * r[2][6]; + r[3][7] -= m[3] * r[2][7]; + + if (r[3][3]) + { + s = 1.0 / r[3][3]; + r[3][4] *= s; + r[3][5] *= s; + r[3][6] *= s; + r[3][7] *= s; + + m[2] = r[2][3]; + s = 1.0 / r[2][2]; + r[2][4] = s * (r[2][4] - r[3][4] * m[2]); + r[2][5] = s * (r[2][5] - r[3][5] * m[2]); + r[2][6] = s * (r[2][6] - r[3][6] * m[2]); + r[2][7] = s * (r[2][7] - r[3][7] * m[2]); + + m[1] = r[1][3]; + r[1][4] -= r[3][4] * m[1], r[1][5] -= r[3][5] * m[1]; + r[1][6] -= r[3][6] * m[1], r[1][7] -= r[3][7] * m[1]; + + m[0] = r[0][3]; + r[0][4] -= r[3][4] * m[0], r[0][5] -= r[3][5] * m[0]; + r[0][6] -= r[3][6] * m[0], r[0][7] -= r[3][7] * m[0]; + + m[1] = r[1][2]; + s = 1.0 / r[1][1]; + r[1][4] = s * (r[1][4] - r[2][4] * m[1]), r[1][5] = s * (r[1][5] - r[2][5] * m[1]); + r[1][6] = s * (r[1][6] - r[2][6] * m[1]), r[1][7] = s * (r[1][7] - r[2][7] * m[1]); + + m[0] = r[0][2]; + r[0][4] -= r[2][4] * m[0], r[0][5] -= r[2][5] * m[0]; + r[0][6] -= r[2][6] * m[0], r[0][7] -= r[2][7] * m[0]; + + m[0] = r[0][1]; + s = 1.0 / r[0][0]; + r[0][4] = s * (r[0][4] - r[1][4] * m[0]), r[0][5] = s * (r[0][5] - r[1][5] * m[0]); + r[0][6] = s * (r[0][6] - r[1][6] * m[0]), r[0][7] = s * (r[0][7] - r[1][7] * m[0]); + +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = r[0][4]; + out->m[0][1] = r[1][4]; + out->m[0][2] = r[2][4]; + out->m[0][3] = r[3][4]; + out->m[1][0] = r[0][5]; + out->m[1][1] = r[1][5]; + out->m[1][2] = r[2][5]; + out->m[1][3] = r[3][5]; + out->m[2][0] = r[0][6]; + out->m[2][1] = r[1][6]; + out->m[2][2] = r[2][6]; + out->m[2][3] = r[3][6]; + out->m[3][0] = r[0][7]; + out->m[3][1] = r[1][7]; + out->m[3][2] = r[2][7]; + out->m[3][3] = r[3][7]; +#else + out->m[0][0] = r[0][4]; + out->m[0][1] = r[0][5]; + out->m[0][2] = r[0][6]; + out->m[0][3] = r[0][7]; + out->m[1][0] = r[1][4]; + out->m[1][1] = r[1][5]; + out->m[1][2] = r[1][6]; + out->m[1][3] = r[1][7]; + out->m[2][0] = r[2][4]; + out->m[2][1] = r[2][5]; + out->m[2][2] = r[2][6]; + out->m[2][3] = r[2][7]; + out->m[3][0] = r[3][4]; + out->m[3][1] = r[3][5]; + out->m[3][2] = r[3][6]; + out->m[3][3] = r[3][7]; +#endif + + return 1; + } + } + } + } + + return 0; +} +#endif + +void Matrix4x4_Invert_Simple (matrix4x4_t *out, const matrix4x4_t *in1) +{ + // we only support uniform scaling, so assume the first row is enough + // (note the lack of sqrt here, because we're trying to undo the scaling, + // this means multiplying by the inverse scale twice - squaring it, which + // makes the sqrt a waste of time) +#if 1 + double scale = 1.0 / (in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2]); +#else + double scale = 3.0 / sqrt + (in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2] + + in1->m[1][0] * in1->m[1][0] + in1->m[1][1] * in1->m[1][1] + in1->m[1][2] * in1->m[1][2] + + in1->m[2][0] * in1->m[2][0] + in1->m[2][1] * in1->m[2][1] + in1->m[2][2] * in1->m[2][2]); + scale *= scale; +#endif + + // invert the rotation by transposing and multiplying by the squared + // recipricol of the input matrix scale as described above + out->m[0][0] = in1->m[0][0] * scale; + out->m[0][1] = in1->m[1][0] * scale; + out->m[0][2] = in1->m[2][0] * scale; + out->m[1][0] = in1->m[0][1] * scale; + out->m[1][1] = in1->m[1][1] * scale; + out->m[1][2] = in1->m[2][1] * scale; + out->m[2][0] = in1->m[0][2] * scale; + out->m[2][1] = in1->m[1][2] * scale; + out->m[2][2] = in1->m[2][2] * scale; + +#ifdef MATRIX4x4_OPENGLORIENTATION + // invert the translate + out->m[3][0] = -(in1->m[3][0] * out->m[0][0] + in1->m[3][1] * out->m[1][0] + in1->m[3][2] * out->m[2][0]); + out->m[3][1] = -(in1->m[3][0] * out->m[0][1] + in1->m[3][1] * out->m[1][1] + in1->m[3][2] * out->m[2][1]); + out->m[3][2] = -(in1->m[3][0] * out->m[0][2] + in1->m[3][1] * out->m[1][2] + in1->m[3][2] * out->m[2][2]); + + // don't know if there's anything worth doing here + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + // invert the translate + out->m[0][3] = -(in1->m[0][3] * out->m[0][0] + in1->m[1][3] * out->m[0][1] + in1->m[2][3] * out->m[0][2]); + out->m[1][3] = -(in1->m[0][3] * out->m[1][0] + in1->m[1][3] * out->m[1][1] + in1->m[2][3] * out->m[1][2]); + out->m[2][3] = -(in1->m[0][3] * out->m[2][0] + in1->m[1][3] * out->m[2][1] + in1->m[2][3] * out->m[2][2]); + + // don't know if there's anything worth doing here + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif +} + +void Matrix4x4_Interpolate (matrix4x4_t *out, matrix4x4_t *in1, matrix4x4_t *in2, double frac) +{ + int i, j; + for (i = 0;i < 4;i++) + for (j = 0;j < 4;j++) + out->m[i][j] = in1->m[i][j] + frac * (in2->m[i][j] - in1->m[i][j]); +} + +void Matrix4x4_Clear (matrix4x4_t *out) +{ + int i, j; + for (i = 0;i < 4;i++) + for (j = 0;j < 4;j++) + out->m[i][j] = 0; +} + +void Matrix4x4_Accumulate (matrix4x4_t *out, matrix4x4_t *in, double weight) +{ + int i, j; + for (i = 0;i < 4;i++) + for (j = 0;j < 4;j++) + out->m[i][j] += in->m[i][j] * weight; +} + +void Matrix4x4_Normalize (matrix4x4_t *out, matrix4x4_t *in1) +{ + // scale rotation matrix vectors to a length of 1 + // note: this is only designed to undo uniform scaling + double scale = 1.0 / sqrt(in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2]); + *out = *in1; + Matrix4x4_Scale(out, scale, 1); +} + +void Matrix4x4_Normalize3 (matrix4x4_t *out, matrix4x4_t *in1) +{ + int i; + double scale; + // scale each rotation matrix vector to a length of 1 + // intended for use after Matrix4x4_Interpolate or Matrix4x4_Accumulate + *out = *in1; + for (i = 0;i < 3;i++) + { +#ifdef MATRIX4x4_OPENGLORIENTATION + scale = sqrt(in1->m[i][0] * in1->m[i][0] + in1->m[i][1] * in1->m[i][1] + in1->m[i][2] * in1->m[i][2]); + if (scale) + scale = 1.0 / scale; + out->m[i][0] *= scale; + out->m[i][1] *= scale; + out->m[i][2] *= scale; +#else + scale = sqrt(in1->m[0][i] * in1->m[0][i] + in1->m[1][i] * in1->m[1][i] + in1->m[2][i] * in1->m[2][i]); + if (scale) + scale = 1.0 / scale; + out->m[0][i] *= scale; + out->m[1][i] *= scale; + out->m[2][i] *= scale; +#endif + } +} + +void Matrix4x4_Reflect (matrix4x4_t *out, double normalx, double normaly, double normalz, double dist, double axisscale) +{ + int i; + double d; + double p[4], p2[4]; + p[0] = normalx; + p[1] = normaly; + p[2] = normalz; + p[3] = -dist; + p2[0] = p[0] * axisscale; + p2[1] = p[1] * axisscale; + p2[2] = p[2] * axisscale; + p2[3] = 0; + for (i = 0;i < 4;i++) + { +#ifdef MATRIX4x4_OPENGLORIENTATION + d = out->m[i][0] * p[0] + out->m[i][1] * p[1] + out->m[i][2] * p[2] + out->m[i][3] * p[3]; + out->m[i][0] += p2[0] * d; + out->m[i][1] += p2[1] * d; + out->m[i][2] += p2[2] * d; +#else + d = out->m[0][i] * p[0] + out->m[1][i] * p[1] + out->m[2][i] * p[2] + out->m[3][i] * p[3]; + out->m[0][i] += p2[0] * d; + out->m[1][i] += p2[1] * d; + out->m[2][i] += p2[2] * d; +#endif + } +} + +void Matrix4x4_CreateIdentity (matrix4x4_t *out) +{ + out->m[0][0]=1.0f; + out->m[0][1]=0.0f; + out->m[0][2]=0.0f; + out->m[0][3]=0.0f; + out->m[1][0]=0.0f; + out->m[1][1]=1.0f; + out->m[1][2]=0.0f; + out->m[1][3]=0.0f; + out->m[2][0]=0.0f; + out->m[2][1]=0.0f; + out->m[2][2]=1.0f; + out->m[2][3]=0.0f; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +} + +void Matrix4x4_CreateTranslate (matrix4x4_t *out, double x, double y, double z) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0]=1.0f; + out->m[1][0]=0.0f; + out->m[2][0]=0.0f; + out->m[3][0]=x; + out->m[0][1]=0.0f; + out->m[1][1]=1.0f; + out->m[2][1]=0.0f; + out->m[3][1]=y; + out->m[0][2]=0.0f; + out->m[1][2]=0.0f; + out->m[2][2]=1.0f; + out->m[3][2]=z; + out->m[0][3]=0.0f; + out->m[1][3]=0.0f; + out->m[2][3]=0.0f; + out->m[3][3]=1.0f; +#else + out->m[0][0]=1.0f; + out->m[0][1]=0.0f; + out->m[0][2]=0.0f; + out->m[0][3]=x; + out->m[1][0]=0.0f; + out->m[1][1]=1.0f; + out->m[1][2]=0.0f; + out->m[1][3]=y; + out->m[2][0]=0.0f; + out->m[2][1]=0.0f; + out->m[2][2]=1.0f; + out->m[2][3]=z; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +#endif +} + +void Matrix4x4_CreateRotate (matrix4x4_t *out, double angle, double x, double y, double z) +{ + double len, c, s; + + len = x*x+y*y+z*z; + if (len != 0.0f) + len = 1.0f / sqrt(len); + x *= len; + y *= len; + z *= len; + + angle *= (-M_PI / 180.0); + c = cos(angle); + s = sin(angle); + +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0]=x * x + c * (1 - x * x); + out->m[1][0]=x * y * (1 - c) + z * s; + out->m[2][0]=z * x * (1 - c) - y * s; + out->m[3][0]=0.0f; + out->m[0][1]=x * y * (1 - c) - z * s; + out->m[1][1]=y * y + c * (1 - y * y); + out->m[2][1]=y * z * (1 - c) + x * s; + out->m[3][1]=0.0f; + out->m[0][2]=z * x * (1 - c) + y * s; + out->m[1][2]=y * z * (1 - c) - x * s; + out->m[2][2]=z * z + c * (1 - z * z); + out->m[3][2]=0.0f; + out->m[0][3]=0.0f; + out->m[1][3]=0.0f; + out->m[2][3]=0.0f; + out->m[3][3]=1.0f; +#else + out->m[0][0]=x * x + c * (1 - x * x); + out->m[0][1]=x * y * (1 - c) + z * s; + out->m[0][2]=z * x * (1 - c) - y * s; + out->m[0][3]=0.0f; + out->m[1][0]=x * y * (1 - c) - z * s; + out->m[1][1]=y * y + c * (1 - y * y); + out->m[1][2]=y * z * (1 - c) + x * s; + out->m[1][3]=0.0f; + out->m[2][0]=z * x * (1 - c) + y * s; + out->m[2][1]=y * z * (1 - c) - x * s; + out->m[2][2]=z * z + c * (1 - z * z); + out->m[2][3]=0.0f; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +#endif +} + +void Matrix4x4_CreateScale (matrix4x4_t *out, double x) +{ + out->m[0][0]=x; + out->m[0][1]=0.0f; + out->m[0][2]=0.0f; + out->m[0][3]=0.0f; + out->m[1][0]=0.0f; + out->m[1][1]=x; + out->m[1][2]=0.0f; + out->m[1][3]=0.0f; + out->m[2][0]=0.0f; + out->m[2][1]=0.0f; + out->m[2][2]=x; + out->m[2][3]=0.0f; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +} + +void Matrix4x4_CreateScale3 (matrix4x4_t *out, double x, double y, double z) +{ + out->m[0][0]=x; + out->m[0][1]=0.0f; + out->m[0][2]=0.0f; + out->m[0][3]=0.0f; + out->m[1][0]=0.0f; + out->m[1][1]=y; + out->m[1][2]=0.0f; + out->m[1][3]=0.0f; + out->m[2][0]=0.0f; + out->m[2][1]=0.0f; + out->m[2][2]=z; + out->m[2][3]=0.0f; + out->m[3][0]=0.0f; + out->m[3][1]=0.0f; + out->m[3][2]=0.0f; + out->m[3][3]=1.0f; +} + +void Matrix4x4_CreateFromQuakeEntity(matrix4x4_t *out, double x, double y, double z, double pitch, double yaw, double roll, double scale) +{ + double angle, sr, sp, sy, cr, cp, cy; + + if (roll) + { + angle = yaw * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = pitch * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = roll * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = (cp*cy) * scale; + out->m[1][0] = (sr*sp*cy+cr*-sy) * scale; + out->m[2][0] = (cr*sp*cy+-sr*-sy) * scale; + out->m[3][0] = x; + out->m[0][1] = (cp*sy) * scale; + out->m[1][1] = (sr*sp*sy+cr*cy) * scale; + out->m[2][1] = (cr*sp*sy+-sr*cy) * scale; + out->m[3][1] = y; + out->m[0][2] = (-sp) * scale; + out->m[1][2] = (sr*cp) * scale; + out->m[2][2] = (cr*cp) * scale; + out->m[3][2] = z; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = (cp*cy) * scale; + out->m[0][1] = (sr*sp*cy+cr*-sy) * scale; + out->m[0][2] = (cr*sp*cy+-sr*-sy) * scale; + out->m[0][3] = x; + out->m[1][0] = (cp*sy) * scale; + out->m[1][1] = (sr*sp*sy+cr*cy) * scale; + out->m[1][2] = (cr*sp*sy+-sr*cy) * scale; + out->m[1][3] = y; + out->m[2][0] = (-sp) * scale; + out->m[2][1] = (sr*cp) * scale; + out->m[2][2] = (cr*cp) * scale; + out->m[2][3] = z; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif + } + else if (pitch) + { + angle = yaw * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = pitch * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = (cp*cy) * scale; + out->m[1][0] = (-sy) * scale; + out->m[2][0] = (sp*cy) * scale; + out->m[3][0] = x; + out->m[0][1] = (cp*sy) * scale; + out->m[1][1] = (cy) * scale; + out->m[2][1] = (sp*sy) * scale; + out->m[3][1] = y; + out->m[0][2] = (-sp) * scale; + out->m[1][2] = 0; + out->m[2][2] = (cp) * scale; + out->m[3][2] = z; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = (cp*cy) * scale; + out->m[0][1] = (-sy) * scale; + out->m[0][2] = (sp*cy) * scale; + out->m[0][3] = x; + out->m[1][0] = (cp*sy) * scale; + out->m[1][1] = (cy) * scale; + out->m[1][2] = (sp*sy) * scale; + out->m[1][3] = y; + out->m[2][0] = (-sp) * scale; + out->m[2][1] = 0; + out->m[2][2] = (cp) * scale; + out->m[2][3] = z; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif + } + else if (yaw) + { + angle = yaw * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = (cy) * scale; + out->m[1][0] = (-sy) * scale; + out->m[2][0] = 0; + out->m[3][0] = x; + out->m[0][1] = (sy) * scale; + out->m[1][1] = (cy) * scale; + out->m[2][1] = 0; + out->m[3][1] = y; + out->m[0][2] = 0; + out->m[1][2] = 0; + out->m[2][2] = scale; + out->m[3][2] = z; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = (cy) * scale; + out->m[0][1] = (-sy) * scale; + out->m[0][2] = 0; + out->m[0][3] = x; + out->m[1][0] = (sy) * scale; + out->m[1][1] = (cy) * scale; + out->m[1][2] = 0; + out->m[1][3] = y; + out->m[2][0] = 0; + out->m[2][1] = 0; + out->m[2][2] = scale; + out->m[2][3] = z; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif + } + else + { +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = scale; + out->m[1][0] = 0; + out->m[2][0] = 0; + out->m[3][0] = x; + out->m[0][1] = 0; + out->m[1][1] = scale; + out->m[2][1] = 0; + out->m[3][1] = y; + out->m[0][2] = 0; + out->m[1][2] = 0; + out->m[2][2] = scale; + out->m[3][2] = z; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = scale; + out->m[0][1] = 0; + out->m[0][2] = 0; + out->m[0][3] = x; + out->m[1][0] = 0; + out->m[1][1] = scale; + out->m[1][2] = 0; + out->m[1][3] = y; + out->m[2][0] = 0; + out->m[2][1] = 0; + out->m[2][2] = scale; + out->m[2][3] = z; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif + } +} + +void Matrix4x4_ToVectors(const matrix4x4_t *in, float vx[3], float vy[3], float vz[3], float t[3]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + vx[0] = in->m[0][0]; + vx[1] = in->m[0][1]; + vx[2] = in->m[0][2]; + vy[0] = in->m[1][0]; + vy[1] = in->m[1][1]; + vy[2] = in->m[1][2]; + vz[0] = in->m[2][0]; + vz[1] = in->m[2][1]; + vz[2] = in->m[2][2]; + t [0] = in->m[3][0]; + t [1] = in->m[3][1]; + t [2] = in->m[3][2]; +#else + vx[0] = in->m[0][0]; + vx[1] = in->m[1][0]; + vx[2] = in->m[2][0]; + vy[0] = in->m[0][1]; + vy[1] = in->m[1][1]; + vy[2] = in->m[2][1]; + vz[0] = in->m[0][2]; + vz[1] = in->m[1][2]; + vz[2] = in->m[2][2]; + t [0] = in->m[0][3]; + t [1] = in->m[1][3]; + t [2] = in->m[2][3]; +#endif +} + +void Matrix4x4_FromVectors(matrix4x4_t *out, const float vx[3], const float vy[3], const float vz[3], const float t[3]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = vx[0]; + out->m[1][0] = vy[0]; + out->m[2][0] = vz[0]; + out->m[3][0] = t[0]; + out->m[0][1] = vx[1]; + out->m[1][1] = vy[1]; + out->m[2][1] = vz[1]; + out->m[3][1] = t[1]; + out->m[0][2] = vx[2]; + out->m[1][2] = vy[2]; + out->m[2][2] = vz[2]; + out->m[3][2] = t[2]; + out->m[0][3] = 0.0f; + out->m[1][3] = 0.0f; + out->m[2][3] = 0.0f; + out->m[3][3] = 1.0f; +#else + out->m[0][0] = vx[0]; + out->m[0][1] = vy[0]; + out->m[0][2] = vz[0]; + out->m[0][3] = t[0]; + out->m[1][0] = vx[1]; + out->m[1][1] = vy[1]; + out->m[1][2] = vz[1]; + out->m[1][3] = t[1]; + out->m[2][0] = vx[2]; + out->m[2][1] = vy[2]; + out->m[2][2] = vz[2]; + out->m[2][3] = t[2]; + out->m[3][0] = 0.0f; + out->m[3][1] = 0.0f; + out->m[3][2] = 0.0f; + out->m[3][3] = 1.0f; +#endif +} + +void Matrix4x4_ToArrayDoubleGL(const matrix4x4_t *in, double out[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; + out[12] = in->m[3][0]; + out[13] = in->m[3][1]; + out[14] = in->m[3][2]; + out[15] = in->m[3][3]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; + out[12] = in->m[0][3]; + out[13] = in->m[1][3]; + out[14] = in->m[2][3]; + out[15] = in->m[3][3]; +#endif +} + +void Matrix4x4_FromArrayDoubleGL (matrix4x4_t *out, const double in[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = in[12]; + out->m[3][1] = in[13]; + out->m[3][2] = in[14]; + out->m[3][3] = in[15]; +#else + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = in[12]; + out->m[1][3] = in[13]; + out->m[2][3] = in[14]; + out->m[3][3] = in[15]; +#endif +} + +void Matrix4x4_ToArrayDoubleD3D(const matrix4x4_t *in, double out[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; + out[12] = in->m[0][3]; + out[13] = in->m[1][3]; + out[14] = in->m[2][3]; + out[15] = in->m[3][3]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; + out[12] = in->m[3][0]; + out[13] = in->m[3][1]; + out[14] = in->m[3][2]; + out[15] = in->m[3][3]; +#endif +} + +void Matrix4x4_FromArrayDoubleD3D (matrix4x4_t *out, const double in[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = in[12]; + out->m[1][3] = in[13]; + out->m[2][3] = in[14]; + out->m[3][3] = in[15]; +#else + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = in[12]; + out->m[3][1] = in[13]; + out->m[3][2] = in[14]; + out->m[3][3] = in[15]; +#endif +} + +void Matrix4x4_ToArrayFloatGL(const matrix4x4_t *in, float out[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; + out[12] = in->m[3][0]; + out[13] = in->m[3][1]; + out[14] = in->m[3][2]; + out[15] = in->m[3][3]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; + out[12] = in->m[0][3]; + out[13] = in->m[1][3]; + out[14] = in->m[2][3]; + out[15] = in->m[3][3]; +#endif +} + +void Matrix4x4_FromArrayFloatGL (matrix4x4_t *out, const float in[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = in[12]; + out->m[3][1] = in[13]; + out->m[3][2] = in[14]; + out->m[3][3] = in[15]; +#else + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = in[12]; + out->m[1][3] = in[13]; + out->m[2][3] = in[14]; + out->m[3][3] = in[15]; +#endif +} + +void Matrix4x4_ToArrayFloatD3D(const matrix4x4_t *in, float out[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; + out[12] = in->m[0][3]; + out[13] = in->m[1][3]; + out[14] = in->m[2][3]; + out[15] = in->m[3][3]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; + out[12] = in->m[3][0]; + out[13] = in->m[3][1]; + out[14] = in->m[3][2]; + out[15] = in->m[3][3]; +#endif +} + +void Matrix4x4_FromArrayFloatD3D (matrix4x4_t *out, const float in[16]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = in[12]; + out->m[1][3] = in[13]; + out->m[2][3] = in[14]; + out->m[3][3] = in[15]; +#else + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = in[12]; + out->m[3][1] = in[13]; + out->m[3][2] = in[14]; + out->m[3][3] = in[15]; +#endif +} + +void Matrix4x4_ToArray12FloatGL(const matrix4x4_t *in, float out[12]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[1][0]; + out[ 4] = in->m[1][1]; + out[ 5] = in->m[1][2]; + out[ 6] = in->m[2][0]; + out[ 7] = in->m[2][1]; + out[ 8] = in->m[2][2]; + out[ 9] = in->m[3][0]; + out[10] = in->m[3][1]; + out[11] = in->m[3][2]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[0][1]; + out[ 4] = in->m[1][1]; + out[ 5] = in->m[2][1]; + out[ 6] = in->m[0][2]; + out[ 7] = in->m[1][2]; + out[ 8] = in->m[2][2]; + out[ 9] = in->m[0][3]; + out[10] = in->m[1][3]; + out[11] = in->m[2][3]; +#endif +} + +void Matrix4x4_FromArray12FloatGL(matrix4x4_t *out, const float in[12]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = 0; + out->m[1][0] = in[3]; + out->m[1][1] = in[4]; + out->m[1][2] = in[5]; + out->m[1][3] = 0; + out->m[2][0] = in[6]; + out->m[2][1] = in[7]; + out->m[2][2] = in[8]; + out->m[2][3] = 0; + out->m[3][0] = in[9]; + out->m[3][1] = in[10]; + out->m[3][2] = in[11]; + out->m[3][3] = 1; +#else + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = 0; + out->m[0][1] = in[3]; + out->m[1][1] = in[4]; + out->m[2][1] = in[5]; + out->m[3][1] = 0; + out->m[0][2] = in[6]; + out->m[1][2] = in[7]; + out->m[2][2] = in[8]; + out->m[3][2] = 0; + out->m[0][3] = in[9]; + out->m[1][3] = in[10]; + out->m[2][3] = in[11]; + out->m[3][3] = 1; +#endif +} + +void Matrix4x4_ToArray12FloatD3D(const matrix4x4_t *in, float out[12]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[ 0] = in->m[0][0]; + out[ 1] = in->m[1][0]; + out[ 2] = in->m[2][0]; + out[ 3] = in->m[3][0]; + out[ 4] = in->m[0][1]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[2][1]; + out[ 7] = in->m[3][1]; + out[ 8] = in->m[0][2]; + out[ 9] = in->m[1][2]; + out[10] = in->m[2][2]; + out[11] = in->m[3][2]; +#else + out[ 0] = in->m[0][0]; + out[ 1] = in->m[0][1]; + out[ 2] = in->m[0][2]; + out[ 3] = in->m[0][3]; + out[ 4] = in->m[1][0]; + out[ 5] = in->m[1][1]; + out[ 6] = in->m[1][2]; + out[ 7] = in->m[1][3]; + out[ 8] = in->m[2][0]; + out[ 9] = in->m[2][1]; + out[10] = in->m[2][2]; + out[11] = in->m[2][3]; +#endif +} + +void Matrix4x4_FromArray12FloatD3D(matrix4x4_t *out, const float in[12]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[0][0] = in[0]; + out->m[1][0] = in[1]; + out->m[2][0] = in[2]; + out->m[3][0] = in[3]; + out->m[0][1] = in[4]; + out->m[1][1] = in[5]; + out->m[2][1] = in[6]; + out->m[3][1] = in[7]; + out->m[0][2] = in[8]; + out->m[1][2] = in[9]; + out->m[2][2] = in[10]; + out->m[3][2] = in[11]; + out->m[0][3] = 0; + out->m[1][3] = 0; + out->m[2][3] = 0; + out->m[3][3] = 1; +#else + out->m[0][0] = in[0]; + out->m[0][1] = in[1]; + out->m[0][2] = in[2]; + out->m[0][3] = in[3]; + out->m[1][0] = in[4]; + out->m[1][1] = in[5]; + out->m[1][2] = in[6]; + out->m[1][3] = in[7]; + out->m[2][0] = in[8]; + out->m[2][1] = in[9]; + out->m[2][2] = in[10]; + out->m[2][3] = in[11]; + out->m[3][0] = 0; + out->m[3][1] = 0; + out->m[3][2] = 0; + out->m[3][3] = 1; +#endif +} + +void Matrix4x4_FromOriginQuat(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z, double w) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + m->m[0][0]=1-2*(y*y+z*z);m->m[1][0]= 2*(x*y-z*w);m->m[2][0]= 2*(x*z+y*w);m->m[3][0]=ox; + m->m[0][1]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[2][1]= 2*(y*z-x*w);m->m[3][1]=oy; + m->m[0][2]= 2*(x*z-y*w);m->m[1][2]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[3][2]=oz; + m->m[0][3]= 0 ;m->m[1][3]= 0 ;m->m[2][3]= 0 ;m->m[3][3]=1; +#else + m->m[0][0]=1-2*(y*y+z*z);m->m[0][1]= 2*(x*y-z*w);m->m[0][2]= 2*(x*z+y*w);m->m[0][3]=ox; + m->m[1][0]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[1][2]= 2*(y*z-x*w);m->m[1][3]=oy; + m->m[2][0]= 2*(x*z-y*w);m->m[2][1]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[2][3]=oz; + m->m[3][0]= 0 ;m->m[3][1]= 0 ;m->m[3][2]= 0 ;m->m[3][3]=1; +#endif +} + +// see http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm +void Matrix4x4_ToOrigin3Quat4Float(const matrix4x4_t *m, float *origin, float *quat) +{ +#if 0 + float s; + quat[3] = sqrt(1.0f + m->m[0][0] + m->m[1][1] + m->m[2][2]) * 0.5f; + s = 0.25f / quat[3]; +#ifdef MATRIX4x4_OPENGLORIENTATION + origin[0] = m->m[3][0]; + origin[1] = m->m[3][1]; + origin[2] = m->m[3][2]; + quat[0] = (m->m[1][2] - m->m[2][1]) * s; + quat[1] = (m->m[2][0] - m->m[0][2]) * s; + quat[2] = (m->m[0][1] - m->m[1][0]) * s; +#else + origin[0] = m->m[0][3]; + origin[1] = m->m[1][3]; + origin[2] = m->m[2][3]; + quat[0] = (m->m[2][1] - m->m[1][2]) * s; + quat[1] = (m->m[0][2] - m->m[2][0]) * s; + quat[2] = (m->m[1][0] - m->m[0][1]) * s; +#endif + +#else + +#ifdef MATRIX4x4_OPENGLORIENTATION + float trace = m->m[0][0] + m->m[1][1] + m->m[2][2]; + origin[0] = m->m[3][0]; + origin[1] = m->m[3][1]; + origin[2] = m->m[3][2]; + if(trace > 0) + { + float r = sqrt(1.0f + trace), inv = 0.5f / r; + quat[0] = (m->m[1][2] - m->m[2][1]) * inv; + quat[1] = (m->m[2][0] - m->m[0][2]) * inv; + quat[2] = (m->m[0][1] - m->m[1][0]) * inv; + quat[3] = 0.5f * r; + } + else if(m->m[0][0] > m->m[1][1] && m->m[0][0] > m->m[2][2]) + { + float r = sqrt(1.0f + m->m[0][0] - m->m[1][1] - m->m[2][2]), inv = 0.5f / r; + quat[0] = 0.5f * r; + quat[1] = (m->m[0][1] + m->m[1][0]) * inv; + quat[2] = (m->m[2][0] + m->m[0][2]) * inv; + quat[3] = (m->m[1][2] - m->m[2][1]) * inv; + } + else if(m->m[1][1] > m->m[2][2]) + { + float r = sqrt(1.0f + m->m[1][1] - m->m[0][0] - m->m[2][2]), inv = 0.5f / r; + quat[0] = (m->m[0][1] + m->m[1][0]) * inv; + quat[1] = 0.5f * r; + quat[2] = (m->m[1][2] + m->m[2][1]) * inv; + quat[3] = (m->m[2][0] - m->m[0][2]) * inv; + } + else + { + float r = sqrt(1.0f + m->m[2][2] - m->m[0][0] - m->m[1][1]), inv = 0.5f / r; + quat[0] = (m->m[2][0] + m->m[0][2]) * inv; + quat[1] = (m->m[1][2] + m->m[2][1]) * inv; + quat[2] = 0.5f * r; + quat[3] = (m->m[0][1] - m->m[1][0]) * inv; + } +#else + float trace = m->m[0][0] + m->m[1][1] + m->m[2][2]; + origin[0] = m->m[0][3]; + origin[1] = m->m[1][3]; + origin[2] = m->m[2][3]; + if(trace > 0) + { + float r = sqrt(1.0f + trace), inv = 0.5f / r; + quat[0] = (m->m[2][1] - m->m[1][2]) * inv; + quat[1] = (m->m[0][2] - m->m[2][0]) * inv; + quat[2] = (m->m[1][0] - m->m[0][1]) * inv; + quat[3] = 0.5f * r; + } + else if(m->m[0][0] > m->m[1][1] && m->m[0][0] > m->m[2][2]) + { + float r = sqrt(1.0f + m->m[0][0] - m->m[1][1] - m->m[2][2]), inv = 0.5f / r; + quat[0] = 0.5f * r; + quat[1] = (m->m[1][0] + m->m[0][1]) * inv; + quat[2] = (m->m[0][2] + m->m[2][0]) * inv; + quat[3] = (m->m[2][1] - m->m[1][2]) * inv; + } + else if(m->m[1][1] > m->m[2][2]) + { + float r = sqrt(1.0f + m->m[1][1] - m->m[0][0] - m->m[2][2]), inv = 0.5f / r; + quat[0] = (m->m[1][0] + m->m[0][1]) * inv; + quat[1] = 0.5f * r; + quat[2] = (m->m[2][1] + m->m[1][2]) * inv; + quat[3] = (m->m[0][2] - m->m[2][0]) * inv; + } + else + { + float r = sqrt(1.0f + m->m[2][2] - m->m[0][0] - m->m[1][1]), inv = 0.5f / r; + quat[0] = (m->m[0][2] + m->m[2][0]) * inv; + quat[1] = (m->m[2][1] + m->m[1][2]) * inv; + quat[2] = 0.5f * r; + quat[3] = (m->m[1][0] - m->m[0][1]) * inv; + } +#endif + +#endif +} + +// LordHavoc: I got this code from: +//http://www.doom3world.org/phpbb2/viewtopic.php?t=2884 +void Matrix4x4_FromDoom3Joint(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z) +{ + double w = 1.0f - (x*x+y*y+z*z); + w = w > 0.0f ? -sqrt(w) : 0.0f; +#ifdef MATRIX4x4_OPENGLORIENTATION + m->m[0][0]=1-2*(y*y+z*z);m->m[1][0]= 2*(x*y-z*w);m->m[2][0]= 2*(x*z+y*w);m->m[3][0]=ox; + m->m[0][1]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[2][1]= 2*(y*z-x*w);m->m[3][1]=oy; + m->m[0][2]= 2*(x*z-y*w);m->m[1][2]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[3][2]=oz; + m->m[0][3]= 0 ;m->m[1][3]= 0 ;m->m[2][3]= 0 ;m->m[3][3]=1; +#else + m->m[0][0]=1-2*(y*y+z*z);m->m[0][1]= 2*(x*y-z*w);m->m[0][2]= 2*(x*z+y*w);m->m[0][3]=ox; + m->m[1][0]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[1][2]= 2*(y*z-x*w);m->m[1][3]=oy; + m->m[2][0]= 2*(x*z-y*w);m->m[2][1]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[2][3]=oz; + m->m[3][0]= 0 ;m->m[3][1]= 0 ;m->m[3][2]= 0 ;m->m[3][3]=1; +#endif +} + +void Matrix4x4_FromBonePose7s(matrix4x4_t *m, float originscale, const short *pose7s) +{ + float origin[3]; + float quat[4]; + float quatscale = pose7s[6] > 0 ? -1.0f / 32767.0f : 1.0f / 32767.0f; + origin[0] = pose7s[0] * originscale; + origin[1] = pose7s[1] * originscale; + origin[2] = pose7s[2] * originscale; + quat[0] = pose7s[3] * quatscale; + quat[1] = pose7s[4] * quatscale; + quat[2] = pose7s[5] * quatscale; + quat[3] = pose7s[6] * quatscale; + Matrix4x4_FromOriginQuat(m, origin[0], origin[1], origin[2], quat[0], quat[1], quat[2], quat[3]); +} + +void Matrix4x4_ToBonePose7s(const matrix4x4_t *m, float origininvscale, short *pose7s) +{ + float origin[3]; + float quat[4]; + float quatscale; + Matrix4x4_ToOrigin3Quat4Float(m, origin, quat); + // normalize quaternion so that it is unit length + quatscale = quat[0]*quat[0]+quat[1]*quat[1]+quat[2]*quat[2]+quat[3]*quat[3]; + if (quatscale) + quatscale = (quat[3] >= 0 ? -32767.0f : 32767.0f) / sqrt(quatscale); + // use a negative scale on the quat because the above function produces a + // positive quat[3] and canonical quaternions have negative quat[3] + pose7s[0] = origin[0] * origininvscale; + pose7s[1] = origin[1] * origininvscale; + pose7s[2] = origin[2] * origininvscale; + pose7s[3] = quat[0] * quatscale; + pose7s[4] = quat[1] * quatscale; + pose7s[5] = quat[2] * quatscale; + pose7s[6] = quat[3] * quatscale; +} + +void Matrix4x4_Blend (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2, double blend) +{ + double iblend = 1 - blend; + out->m[0][0] = in1->m[0][0] * iblend + in2->m[0][0] * blend; + out->m[0][1] = in1->m[0][1] * iblend + in2->m[0][1] * blend; + out->m[0][2] = in1->m[0][2] * iblend + in2->m[0][2] * blend; + out->m[0][3] = in1->m[0][3] * iblend + in2->m[0][3] * blend; + out->m[1][0] = in1->m[1][0] * iblend + in2->m[1][0] * blend; + out->m[1][1] = in1->m[1][1] * iblend + in2->m[1][1] * blend; + out->m[1][2] = in1->m[1][2] * iblend + in2->m[1][2] * blend; + out->m[1][3] = in1->m[1][3] * iblend + in2->m[1][3] * blend; + out->m[2][0] = in1->m[2][0] * iblend + in2->m[2][0] * blend; + out->m[2][1] = in1->m[2][1] * iblend + in2->m[2][1] * blend; + out->m[2][2] = in1->m[2][2] * iblend + in2->m[2][2] * blend; + out->m[2][3] = in1->m[2][3] * iblend + in2->m[2][3] * blend; + out->m[3][0] = in1->m[3][0] * iblend + in2->m[3][0] * blend; + out->m[3][1] = in1->m[3][1] * iblend + in2->m[3][1] * blend; + out->m[3][2] = in1->m[3][2] * iblend + in2->m[3][2] * blend; + out->m[3][3] = in1->m[3][3] * iblend + in2->m[3][3] * blend; +} + + +void Matrix4x4_Transform (const matrix4x4_t *in, const float v[3], float out[3]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0] + in->m[3][0]; + out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1] + in->m[3][1]; + out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2] + in->m[3][2]; +#else + out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2] + in->m[0][3]; + out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2] + in->m[1][3]; + out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2] + in->m[2][3]; +#endif +} + +void Matrix4x4_Transform4 (const matrix4x4_t *in, const float v[4], float out[4]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0] + v[3] * in->m[3][0]; + out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1] + v[3] * in->m[3][1]; + out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2] + v[3] * in->m[3][2]; + out[3] = v[0] * in->m[0][3] + v[1] * in->m[1][3] + v[2] * in->m[2][3] + v[3] * in->m[3][3]; +#else + out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2] + v[3] * in->m[0][3]; + out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2] + v[3] * in->m[1][3]; + out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2] + v[3] * in->m[2][3]; + out[3] = v[0] * in->m[3][0] + v[1] * in->m[3][1] + v[2] * in->m[3][2] + v[3] * in->m[3][3]; +#endif +} + +void Matrix4x4_Transform3x3 (const matrix4x4_t *in, const float v[3], float out[3]) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0]; + out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1]; + out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2]; +#else + out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2]; + out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2]; + out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2]; +#endif +} + +// transforms a positive distance plane (A*x+B*y+C*z-D=0) through a rotation or translation matrix +void Matrix4x4_TransformPositivePlane(const matrix4x4_t *in, float x, float y, float z, float d, float *o) +{ + float scale = sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); + float iscale = 1.0f / scale; +#ifdef MATRIX4x4_OPENGLORIENTATION + o[0] = (x * in->m[0][0] + y * in->m[1][0] + z * in->m[2][0]) * iscale; + o[1] = (x * in->m[0][1] + y * in->m[1][1] + z * in->m[2][1]) * iscale; + o[2] = (x * in->m[0][2] + y * in->m[1][2] + z * in->m[2][2]) * iscale; + o[3] = d * scale + (o[0] * in->m[3][0] + o[1] * in->m[3][1] + o[2] * in->m[3][2]); +#else + o[0] = (x * in->m[0][0] + y * in->m[0][1] + z * in->m[0][2]) * iscale; + o[1] = (x * in->m[1][0] + y * in->m[1][1] + z * in->m[1][2]) * iscale; + o[2] = (x * in->m[2][0] + y * in->m[2][1] + z * in->m[2][2]) * iscale; + o[3] = d * scale + (o[0] * in->m[0][3] + o[1] * in->m[1][3] + o[2] * in->m[2][3]); +#endif +} + +// transforms a standard plane (A*x+B*y+C*z+D=0) through a rotation or translation matrix +void Matrix4x4_TransformStandardPlane(const matrix4x4_t *in, float x, float y, float z, float d, float *o) +{ + float scale = sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); + float iscale = 1.0f / scale; +#ifdef MATRIX4x4_OPENGLORIENTATION + o[0] = (x * in->m[0][0] + y * in->m[1][0] + z * in->m[2][0]) * iscale; + o[1] = (x * in->m[0][1] + y * in->m[1][1] + z * in->m[2][1]) * iscale; + o[2] = (x * in->m[0][2] + y * in->m[1][2] + z * in->m[2][2]) * iscale; + o[3] = d * scale - (o[0] * in->m[3][0] + o[1] * in->m[3][1] + o[2] * in->m[3][2]); +#else + o[0] = (x * in->m[0][0] + y * in->m[0][1] + z * in->m[0][2]) * iscale; + o[1] = (x * in->m[1][0] + y * in->m[1][1] + z * in->m[1][2]) * iscale; + o[2] = (x * in->m[2][0] + y * in->m[2][1] + z * in->m[2][2]) * iscale; + o[3] = d * scale - (o[0] * in->m[0][3] + o[1] * in->m[1][3] + o[2] * in->m[2][3]); +#endif +} + +/* +void Matrix4x4_SimpleUntransform (const matrix4x4_t *in, const float v[3], float out[3]) +{ + double t[3]; +#ifdef MATRIX4x4_OPENGLORIENTATION + t[0] = v[0] - in->m[3][0]; + t[1] = v[1] - in->m[3][1]; + t[2] = v[2] - in->m[3][2]; + out[0] = t[0] * in->m[0][0] + t[1] * in->m[0][1] + t[2] * in->m[0][2]; + out[1] = t[0] * in->m[1][0] + t[1] * in->m[1][1] + t[2] * in->m[1][2]; + out[2] = t[0] * in->m[2][0] + t[1] * in->m[2][1] + t[2] * in->m[2][2]; +#else + t[0] = v[0] - in->m[0][3]; + t[1] = v[1] - in->m[1][3]; + t[2] = v[2] - in->m[2][3]; + out[0] = t[0] * in->m[0][0] + t[1] * in->m[1][0] + t[2] * in->m[2][0]; + out[1] = t[0] * in->m[0][1] + t[1] * in->m[1][1] + t[2] * in->m[2][1]; + out[2] = t[0] * in->m[0][2] + t[1] * in->m[1][2] + t[2] * in->m[2][2]; +#endif +} +*/ + +// FIXME: optimize +void Matrix4x4_ConcatTranslate (matrix4x4_t *out, double x, double y, double z) +{ + matrix4x4_t base, temp; + base = *out; + Matrix4x4_CreateTranslate(&temp, x, y, z); + Matrix4x4_Concat(out, &base, &temp); +} + +// FIXME: optimize +void Matrix4x4_ConcatRotate (matrix4x4_t *out, double angle, double x, double y, double z) +{ + matrix4x4_t base, temp; + base = *out; + Matrix4x4_CreateRotate(&temp, angle, x, y, z); + Matrix4x4_Concat(out, &base, &temp); +} + +// FIXME: optimize +void Matrix4x4_ConcatScale (matrix4x4_t *out, double x) +{ + matrix4x4_t base, temp; + base = *out; + Matrix4x4_CreateScale(&temp, x); + Matrix4x4_Concat(out, &base, &temp); +} + +// FIXME: optimize +void Matrix4x4_ConcatScale3 (matrix4x4_t *out, double x, double y, double z) +{ + matrix4x4_t base, temp; + base = *out; + Matrix4x4_CreateScale3(&temp, x, y, z); + Matrix4x4_Concat(out, &base, &temp); +} + +void Matrix4x4_OriginFromMatrix (const matrix4x4_t *in, float *out) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out[0] = in->m[3][0]; + out[1] = in->m[3][1]; + out[2] = in->m[3][2]; +#else + out[0] = in->m[0][3]; + out[1] = in->m[1][3]; + out[2] = in->m[2][3]; +#endif +} + +double Matrix4x4_ScaleFromMatrix (const matrix4x4_t *in) +{ + // we only support uniform scaling, so assume the first row is enough + return sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); +} + +void Matrix4x4_SetOrigin (matrix4x4_t *out, double x, double y, double z) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[3][0] = x; + out->m[3][1] = y; + out->m[3][2] = z; +#else + out->m[0][3] = x; + out->m[1][3] = y; + out->m[2][3] = z; +#endif +} + +void Matrix4x4_AdjustOrigin (matrix4x4_t *out, double x, double y, double z) +{ +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[3][0] += x; + out->m[3][1] += y; + out->m[3][2] += z; +#else + out->m[0][3] += x; + out->m[1][3] += y; + out->m[2][3] += z; +#endif +} + +void Matrix4x4_Scale (matrix4x4_t *out, double rotatescale, double originscale) +{ + out->m[0][0] *= rotatescale; + out->m[0][1] *= rotatescale; + out->m[0][2] *= rotatescale; + out->m[1][0] *= rotatescale; + out->m[1][1] *= rotatescale; + out->m[1][2] *= rotatescale; + out->m[2][0] *= rotatescale; + out->m[2][1] *= rotatescale; + out->m[2][2] *= rotatescale; +#ifdef MATRIX4x4_OPENGLORIENTATION + out->m[3][0] *= originscale; + out->m[3][1] *= originscale; + out->m[3][2] *= originscale; +#else + out->m[0][3] *= originscale; + out->m[1][3] *= originscale; + out->m[2][3] *= originscale; +#endif +} + +void Matrix4x4_Abs (matrix4x4_t *out) +{ + out->m[0][0] = fabs(out->m[0][0]); + out->m[0][1] = fabs(out->m[0][1]); + out->m[0][2] = fabs(out->m[0][2]); + out->m[1][0] = fabs(out->m[1][0]); + out->m[1][1] = fabs(out->m[1][1]); + out->m[1][2] = fabs(out->m[1][2]); + out->m[2][0] = fabs(out->m[2][0]); + out->m[2][1] = fabs(out->m[2][1]); + out->m[2][2] = fabs(out->m[2][2]); +} + diff --git a/app/jni/matrixlib.h b/app/jni/matrixlib.h new file mode 100644 index 0000000..232f1ad --- /dev/null +++ b/app/jni/matrixlib.h @@ -0,0 +1,172 @@ + +#ifndef MATRIXLIB_H +#define MATRIXLIB_H + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +//#define MATRIX4x4_OPENGLORIENTATION + +typedef struct matrix4x4_s +{ + vec_t m[4][4]; +} +matrix4x4_t; + +extern const matrix4x4_t identitymatrix; + +// functions for manipulating 4x4 matrices + +// copy a matrix4x4 +void Matrix4x4_Copy (matrix4x4_t *out, const matrix4x4_t *in); +// copy only the rotation portion of a matrix4x4 +void Matrix4x4_CopyRotateOnly (matrix4x4_t *out, const matrix4x4_t *in); +// copy only the translate portion of a matrix4x4 +void Matrix4x4_CopyTranslateOnly (matrix4x4_t *out, const matrix4x4_t *in); +// multiply two matrix4x4 together, combining their transformations +// (warning: order matters - Concat(a, b, c) != Concat(a, c, b)) +void Matrix4x4_Concat (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2); +// swaps the rows and columns of the matrix +// (is this useful for anything?) +void Matrix4x4_Transpose (matrix4x4_t *out, const matrix4x4_t *in1); +// creates a matrix that does the opposite of the matrix provided +// this is a full matrix inverter, it should be able to invert any matrix that +// is possible to invert +// (non-uniform scaling, rotation, shearing, and translation, possibly others) +// warning: this function is SLOW +int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1); +// creates a matrix that does the opposite of the matrix provided +// only supports translate, rotate, scale (not scale3) matrices +void Matrix4x4_Invert_Simple (matrix4x4_t *out, const matrix4x4_t *in1); +// blends between two matrices, used primarily for animation interpolation +// (note: it is recommended to follow this with Matrix4x4_Normalize, a method +// known as nlerp rotation, often better for animation purposes than slerp) +void Matrix4x4_Interpolate (matrix4x4_t *out, matrix4x4_t *in1, matrix4x4_t *in2, double frac); +// zeros all matrix components, used with Matrix4x4_Accumulate +void Matrix4x4_Clear (matrix4x4_t *out); +// adds a weighted contribution from the supplied matrix, used to blend 3 or +// more matrices with weighting, it is recommended that Matrix4x4_Normalize be +// called afterward (a method known as nlerp rotation, often better for +// animation purposes than slerp) +void Matrix4x4_Accumulate (matrix4x4_t *out, matrix4x4_t *in, double weight); +// creates a matrix that does the same rotation and translation as the matrix +// provided, but no uniform scaling, does not support scale3 matrices +void Matrix4x4_Normalize (matrix4x4_t *out, matrix4x4_t *in1); +// creates a matrix with vectors normalized individually (use after +// Matrix4x4_Accumulate) +void Matrix4x4_Normalize3 (matrix4x4_t *out, matrix4x4_t *in1); +// modifies a matrix to have all vectors and origin reflected across the plane +// to the opposite side (at least if axisscale is -2) +void Matrix4x4_Reflect (matrix4x4_t *out, double normalx, double normaly, double normalz, double dist, double axisscale); + +// creates an identity matrix +// (a matrix which does nothing) +void Matrix4x4_CreateIdentity (matrix4x4_t *out); +// creates a translate matrix +// (moves vectors) +void Matrix4x4_CreateTranslate (matrix4x4_t *out, double x, double y, double z); +// creates a rotate matrix +// (rotates vectors) +void Matrix4x4_CreateRotate (matrix4x4_t *out, double angle, double x, double y, double z); +// creates a scaling matrix +// (expands or contracts vectors) +// (warning: do not apply this kind of matrix to direction vectors) +void Matrix4x4_CreateScale (matrix4x4_t *out, double x); +// creates a squishing matrix +// (expands or contracts vectors differently in different axis) +// (warning: this is not reversed by Invert_Simple) +// (warning: do not apply this kind of matrix to direction vectors) +void Matrix4x4_CreateScale3 (matrix4x4_t *out, double x, double y, double z); +// creates a matrix for a quake entity +void Matrix4x4_CreateFromQuakeEntity(matrix4x4_t *out, double x, double y, double z, double pitch, double yaw, double roll, double scale); + +// converts a matrix4x4 to a set of 3D vectors for the 3 axial directions, and the translate +void Matrix4x4_ToVectors(const matrix4x4_t *in, vec_t vx[3], vec_t vy[3], vec_t vz[3], vec_t t[3]); +// creates a matrix4x4 from a set of 3D vectors for axial directions, and translate +void Matrix4x4_FromVectors(matrix4x4_t *out, const vec_t vx[3], const vec_t vy[3], const vec_t vz[3], const vec_t t[3]); + +// converts a matrix4x4 to a double[16] array in the OpenGL orientation +void Matrix4x4_ToArrayDoubleGL(const matrix4x4_t *in, double out[16]); +// creates a matrix4x4 from a double[16] array in the OpenGL orientation +void Matrix4x4_FromArrayDoubleGL(matrix4x4_t *out, const double in[16]); +// converts a matrix4x4 to a double[16] array in the Direct3D orientation +void Matrix4x4_ToArrayDoubleD3D(const matrix4x4_t *in, double out[16]); +// creates a matrix4x4 from a double[16] array in the Direct3D orientation +void Matrix4x4_FromArrayDoubleD3D(matrix4x4_t *out, const double in[16]); + +// converts a matrix4x4 to a float[16] array in the OpenGL orientation +void Matrix4x4_ToArrayFloatGL(const matrix4x4_t *in, float out[16]); +// creates a matrix4x4 from a float[16] array in the OpenGL orientation +void Matrix4x4_FromArrayFloatGL(matrix4x4_t *out, const float in[16]); +// converts a matrix4x4 to a float[16] array in the Direct3D orientation +void Matrix4x4_ToArrayFloatD3D(const matrix4x4_t *in, float out[16]); +// creates a matrix4x4 from a float[16] array in the Direct3D orientation +void Matrix4x4_FromArrayFloatD3D(matrix4x4_t *out, const float in[16]); + +// converts a matrix4x4 to a float[12] array in the OpenGL orientation +void Matrix4x4_ToArray12FloatGL(const matrix4x4_t *in, float out[12]); +// creates a matrix4x4 from a float[12] array in the OpenGL orientation +void Matrix4x4_FromArray12FloatGL(matrix4x4_t *out, const float in[12]); +// converts a matrix4x4 to a float[12] array in the Direct3D orientation +void Matrix4x4_ToArray12FloatD3D(const matrix4x4_t *in, float out[12]); +// creates a matrix4x4 from a float[12] array in the Direct3D orientation +void Matrix4x4_FromArray12FloatD3D(matrix4x4_t *out, const float in[12]); + +// creates a matrix4x4 from an origin and quaternion (used mostly with skeletal model formats such as PSK) +void Matrix4x4_FromOriginQuat(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z, double w); +// creates an origin and quaternion from a matrix4x4_t, quat[3] is always positive +void Matrix4x4_ToOrigin3Quat4Float(const matrix4x4_t *m, float *origin, float *quat); +// creates a matrix4x4 from an origin and canonical unit-length quaternion (used mostly with skeletal model formats such as MD5) +void Matrix4x4_FromDoom3Joint(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z); + +// creates a matrix4x4_t from an origin and canonical unit-length quaternion in short[7] normalized format +void Matrix4x4_FromBonePose7s(matrix4x4_t *m, float originscale, const short *pose7s); +// creates a short[7] representation from normalized matrix4x4_t +void Matrix4x4_ToBonePose7s(const matrix4x4_t *m, float origininvscale, short *pose7s); + +// blends two matrices together, at a given percentage (blend controls percentage of in2) +void Matrix4x4_Blend (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2, double blend); + +// transforms a 3D vector through a matrix4x4 +void Matrix4x4_Transform (const matrix4x4_t *in, const vec_t v[3], vec_t out[3]); +// transforms a 4D vector through a matrix4x4 +// (warning: if you don't know why you would need this, you don't need it) +// (warning: the 4th component of the vector should be 1.0) +void Matrix4x4_Transform4 (const matrix4x4_t *in, const vec_t v[4], vec_t out[4]); +// reverse transforms a 3D vector through a matrix4x4, at least for *simple* +// cases (rotation and translation *ONLY*), this attempts to undo the results +// of Transform +//void Matrix4x4_SimpleUntransform (const matrix4x4_t *in, const vec_t v[3], vec_t out[3]); +// transforms a direction vector through the rotation part of a matrix +void Matrix4x4_Transform3x3 (const matrix4x4_t *in, const vec_t v[3], vec_t out[3]); +// transforms a positive distance plane (A*x+B*y+C*z-D=0) through a rotation or translation matrix +void Matrix4x4_TransformPositivePlane (const matrix4x4_t *in, vec_t x, vec_t y, vec_t z, vec_t d, vec_t *o); +// transforms a standard plane (A*x+B*y+C*z+D=0) through a rotation or translation matrix +void Matrix4x4_TransformStandardPlane (const matrix4x4_t *in, vec_t x, vec_t y, vec_t z, vec_t d, vec_t *o); + +// ease of use functions +// immediately applies a Translate to the matrix +void Matrix4x4_ConcatTranslate (matrix4x4_t *out, double x, double y, double z); +// immediately applies a Rotate to the matrix +void Matrix4x4_ConcatRotate (matrix4x4_t *out, double angle, double x, double y, double z); +// immediately applies a Scale to the matrix +void Matrix4x4_ConcatScale (matrix4x4_t *out, double x); +// immediately applies a Scale3 to the matrix +void Matrix4x4_ConcatScale3 (matrix4x4_t *out, double x, double y, double z); + +// extracts origin vector (translate) from matrix +void Matrix4x4_OriginFromMatrix (const matrix4x4_t *in, vec_t *out); +// extracts scaling factor from matrix (only works for uniform scaling) +double Matrix4x4_ScaleFromMatrix (const matrix4x4_t *in); + +// replaces origin vector (translate) in matrix +void Matrix4x4_SetOrigin (matrix4x4_t *out, double x, double y, double z); +// moves origin vector (translate) in matrix by a simple translate +void Matrix4x4_AdjustOrigin (matrix4x4_t *out, double x, double y, double z); +// scales vectors of a matrix in place and allows you to scale origin as well +void Matrix4x4_Scale (matrix4x4_t *out, double rotatescale, double originscale); +// ensures each element of the 3x3 rotation matrix is facing in the + direction +void Matrix4x4_Abs (matrix4x4_t *out); + +#endif diff --git a/app/jni/mdfour.c b/app/jni/mdfour.c new file mode 100644 index 0000000..ddb4f26 --- /dev/null +++ b/app/jni/mdfour.c @@ -0,0 +1,229 @@ +/* + mdfour.c + + An implementation of MD4 designed for use in the samba SMB + authentication protocol + + Copyright (C) 1997-1998 Andrew Tridgell + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + + $Id: mdfour.c 10534 2010-10-15 13:47:19Z divverent $ +*/ + +#include "quakedef.h" + +#include /* XoXus: needed for memset call */ +#include "mdfour.h" + +/* NOTE: This code makes no attempt to be fast! + + It assumes that a int is at least 32 bits long +*/ + +static struct mdfour *m; + +#define F(X,Y,Z) (((X)&(Y)) | ((~(X))&(Z))) +#define G(X,Y,Z) (((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z))) +#define H(X,Y,Z) ((X)^(Y)^(Z)) +#ifdef LARGE_INT32 +#define lshift(x,s) ((((x)<<(s))&0xFFFFFFFF) | (((x)>>(32-(s)))&0xFFFFFFFF)) +#else +#define lshift(x,s) (((x)<<(s)) | ((x)>>(32-(s)))) +#endif + +#define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s) +#define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + 0x5A827999,s) +#define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + 0x6ED9EBA1,s) + +/* this applies md4 to 64 byte chunks */ +static void mdfour64(uint32 *M) +{ + int j; + uint32 AA, BB, CC, DD; + uint32 X[16]; + uint32 A,B,C,D; + + for (j=0;j<16;j++) + X[j] = M[j]; + + A = m->A; B = m->B; C = m->C; D = m->D; + AA = A; BB = B; CC = C; DD = D; + + ROUND1(A,B,C,D, 0, 3); ROUND1(D,A,B,C, 1, 7); + ROUND1(C,D,A,B, 2, 11); ROUND1(B,C,D,A, 3, 19); + ROUND1(A,B,C,D, 4, 3); ROUND1(D,A,B,C, 5, 7); + ROUND1(C,D,A,B, 6, 11); ROUND1(B,C,D,A, 7, 19); + ROUND1(A,B,C,D, 8, 3); ROUND1(D,A,B,C, 9, 7); + ROUND1(C,D,A,B, 10, 11); ROUND1(B,C,D,A, 11, 19); + ROUND1(A,B,C,D, 12, 3); ROUND1(D,A,B,C, 13, 7); + ROUND1(C,D,A,B, 14, 11); ROUND1(B,C,D,A, 15, 19); + + ROUND2(A,B,C,D, 0, 3); ROUND2(D,A,B,C, 4, 5); + ROUND2(C,D,A,B, 8, 9); ROUND2(B,C,D,A, 12, 13); + ROUND2(A,B,C,D, 1, 3); ROUND2(D,A,B,C, 5, 5); + ROUND2(C,D,A,B, 9, 9); ROUND2(B,C,D,A, 13, 13); + ROUND2(A,B,C,D, 2, 3); ROUND2(D,A,B,C, 6, 5); + ROUND2(C,D,A,B, 10, 9); ROUND2(B,C,D,A, 14, 13); + ROUND2(A,B,C,D, 3, 3); ROUND2(D,A,B,C, 7, 5); + ROUND2(C,D,A,B, 11, 9); ROUND2(B,C,D,A, 15, 13); + + ROUND3(A,B,C,D, 0, 3); ROUND3(D,A,B,C, 8, 9); + ROUND3(C,D,A,B, 4, 11); ROUND3(B,C,D,A, 12, 15); + ROUND3(A,B,C,D, 2, 3); ROUND3(D,A,B,C, 10, 9); + ROUND3(C,D,A,B, 6, 11); ROUND3(B,C,D,A, 14, 15); + ROUND3(A,B,C,D, 1, 3); ROUND3(D,A,B,C, 9, 9); + ROUND3(C,D,A,B, 5, 11); ROUND3(B,C,D,A, 13, 15); + ROUND3(A,B,C,D, 3, 3); ROUND3(D,A,B,C, 11, 9); + ROUND3(C,D,A,B, 7, 11); ROUND3(B,C,D,A, 15, 15); + + A += AA; B += BB; C += CC; D += DD; + +#ifdef LARGE_INT32 + A &= 0xFFFFFFFF; B &= 0xFFFFFFFF; + C &= 0xFFFFFFFF; D &= 0xFFFFFFFF; +#endif + + for (j=0;j<16;j++) + X[j] = 0; + + m->A = A; m->B = B; m->C = C; m->D = D; +} + +static void copy64(uint32 *M, const unsigned char *in) +{ + int i; + + for (i=0;i<16;i++) + M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) | + (in[i*4+1]<<8) | (in[i*4+0]<<0); +} + +static void copy4(unsigned char *out,uint32 x) +{ + out[0] = x&0xFF; + out[1] = (x>>8)&0xFF; + out[2] = (x>>16)&0xFF; + out[3] = (x>>24)&0xFF; +} + +void mdfour_begin(struct mdfour *md) +{ + md->A = 0x67452301; + md->B = 0xefcdab89; + md->C = 0x98badcfe; + md->D = 0x10325476; + md->totalN = 0; +} + + +static void mdfour_tail(const unsigned char *in, int n) +{ + unsigned char buf[128]; + uint32 M[16]; + uint32 b; + + m->totalN += n; + + b = m->totalN * 8; + + memset(buf, 0, 128); + if (n) memcpy(buf, in, n); + buf[n] = 0x80; + + if (n <= 55) { + copy4(buf+56, b); + copy64(M, buf); + mdfour64(M); + } else { + copy4(buf+120, b); + copy64(M, buf); + mdfour64(M); + copy64(M, buf+64); + mdfour64(M); + } +} + +void mdfour_update(struct mdfour *md, const unsigned char *in, int n) +{ + uint32 M[16]; + +// start of edit by Forest 'LordHavoc' Hale +// commented out to prevent crashing when length is 0 +// if (n == 0) mdfour_tail(in, n); +// end of edit by Forest 'LordHavoc' Hale + + m = md; + + while (n >= 64) { + copy64(M, in); + mdfour64(M); + in += 64; + n -= 64; + m->totalN += 64; + } + + mdfour_tail(in, n); +} + + +void mdfour_result(struct mdfour *md, unsigned char *out) +{ + m = md; + + copy4(out, m->A); + copy4(out+4, m->B); + copy4(out+8, m->C); + copy4(out+12, m->D); +} + + +void mdfour(unsigned char *out, const unsigned char *in, int n) +{ + struct mdfour md; + mdfour_begin(&md); + mdfour_update(&md, in, n); + mdfour_result(&md, out); +} + +/////////////////////////////////////////////////////////////// +// MD4-based checksum utility functions +// +// Copyright (C) 2000 Jeff Teunissen +// +// Author: Jeff Teunissen +// Date: 01 Jan 2000 + +unsigned Com_BlockChecksum (void *buffer, int length) +{ + int digest[4]; + unsigned val; + + mdfour ( (unsigned char *) digest, (unsigned char *) buffer, length ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} + +void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf) +{ + mdfour ( outbuf, (unsigned char *) buffer, len ); +} + diff --git a/app/jni/mdfour.h b/app/jni/mdfour.h new file mode 100644 index 0000000..3ef654c --- /dev/null +++ b/app/jni/mdfour.h @@ -0,0 +1,54 @@ +/* + mdfour.h + + an implementation of MD4 designed for use in the SMB authentication + protocol + + Copyright (C) Andrew Tridgell 1997-1998 + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + +#ifndef _MDFOUR_H +#define _MDFOUR_H + +#ifndef int32 +#define int32 int +#endif + +#if SIZEOF_INT > 4 +#define LARGE_INT32 +#endif + +#ifndef uint32 +#define uint32 unsigned int32 +#endif + +struct mdfour { + uint32 A, B, C, D; + uint32 totalN; +}; + +void mdfour_begin(struct mdfour *md); // old: MD4Init +void mdfour_update(struct mdfour *md, const unsigned char *in, int n); //old: MD4Update +void mdfour_result(struct mdfour *md, unsigned char *out); // old: MD4Final +void mdfour(unsigned char *out, const unsigned char *in, int n); + +#endif // _MDFOUR_H + diff --git a/app/jni/menu.c b/app/jni/menu.c new file mode 100644 index 0000000..bb74e71 --- /dev/null +++ b/app/jni/menu.c @@ -0,0 +1,6055 @@ +/* +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" +#include "image.h" +#include "progsvm.h" + +#include "mprogdefs.h" + +#define QVR_VERSION "1.0.0" + +#define TYPE_DEMO 1 +#define TYPE_GAME 2 +#define TYPE_BOTH 3 + +static cvar_t forceqmenu = { 0, "forceqmenu", "0", "enables the quake menu instead of the quakec menu.dat (if present)" }; +static cvar_t menu_progs = { 0, "menu_progs", "menu.dat", "name of quakec menu.dat file" }; + +static int NehGameType; + +static const char* sNo = "No"; +static const char* sYes = "Yes"; + +//Game always starts in vr mode, showing the menu on the big screen in stereo +int vrMode = 2; +int bigScreen = 1; +int stereoMode = 1; + +extern int andrw; + +enum m_state_e m_state = m_main; +char m_return_reason[128]; + +extern vec3_t hmdorientation; +extern char *strGameFolder; + +extern cvar_t r_worldscale; +extern cvar_t v_eyebufferresolution; + +extern void jni_SwitchVRMode(int mode); +extern void jni_BigScreenMode(int mode); +extern void jni_SwitchStereoMode(int mode); + +//Record yaw at the moment the menu is invoked +static float hmdYaw = 0; + +void M_Menu_Main_f (void); + void M_Menu_SinglePlayer_f (void); + void M_Menu_Transfusion_Episode_f (void); + void M_Menu_Transfusion_Skill_f (void); + void M_Menu_Load_f (void); + void M_Menu_Save_f (void); + void M_Menu_MultiPlayer_f (void); + void M_Menu_Setup_f (void); + void M_Menu_Options_f (void); + void M_Menu_Options_Effects_f (void); + void M_Menu_Options_Graphics_f (void); + void M_Menu_Options_ColorControl_f (void); + void M_Menu_Keys_f (void); + void M_Menu_Reset_f (void); + void M_Menu_Video_f (void); + void M_Menu_YawPitchControl_f (void); + void M_Menu_Help_f (void); + void M_Menu_Credits_f (void); + void M_Menu_Quit_f (void); +void M_Menu_LanConfig_f (void); +void M_Menu_GameOptions_f (void); +void M_Menu_ServerList_f (void); +void M_Menu_ModList_f (void); + +static void M_Main_Draw (void); + static void M_SinglePlayer_Draw (void); + static void M_Transfusion_Episode_Draw (void); + static void M_Transfusion_Skill_Draw (void); + static void M_Load_Draw (void); + static void M_Save_Draw (void); + static void M_MultiPlayer_Draw (void); + static void M_Setup_Draw (void); + static void M_Options_Draw (void); + static void M_Options_Effects_Draw (void); + static void M_Options_Graphics_Draw (void); + static void M_Options_ColorControl_Draw (void); + static void M_Keys_Draw (void); + static void M_Reset_Draw (void); + static void M_Video_Draw (void); + static void M_Menu_YawPitchControl_Draw (void); + static void M_Help_Draw (void); + static void M_Credits_Draw (void); + static void M_Quit_Draw (void); +static void M_LanConfig_Draw (void); +static void M_GameOptions_Draw (void); +static void M_ServerList_Draw (void); +static void M_ModList_Draw (void); + + +static void M_Main_Key (int key, int ascii); + static void M_SinglePlayer_Key (int key, int ascii); + static void M_Transfusion_Episode_Key (int key, int ascii); + static void M_Transfusion_Skill_Key (int key, int ascii); + static void M_Load_Key (int key, int ascii); + static void M_Save_Key (int key, int ascii); + static void M_MultiPlayer_Key (int key, int ascii); + static void M_Setup_Key (int key, int ascii); + static void M_Options_Key (int key, int ascii); + static void M_Options_Effects_Key (int key, int ascii); + static void M_Options_Graphics_Key (int key, int ascii); + static void M_Options_ColorControl_Key (int key, int ascii); + static void M_Keys_Key (int key, int ascii); + static void M_Reset_Key (int key, int ascii); + static void M_Video_Key (int key, int ascii); + static void M_Menu_YawPitchControl_Key (int key, int ascii); + static void M_Help_Key (int key, int ascii); + static void M_Credits_Key (int key, int ascii); + static void M_Quit_Key (int key, int ascii); +static void M_LanConfig_Key (int key, int ascii); +static void M_GameOptions_Key (int key, int ascii); +static void M_ServerList_Key (int key, int ascii); +static void M_ModList_Key (int key, int ascii); + +static qboolean m_entersound; ///< play after drawing a frame, so caching won't disrupt the sound + +void M_Update_Return_Reason(const char *s) +{ + strlcpy(m_return_reason, s, sizeof(m_return_reason)); + if (s) + Con_DPrintf("%s\n", s); +} + +#define StartingGame (m_multiplayer_cursor == 1) +#define JoiningGame (m_multiplayer_cursor == 0) + +// Nehahra +#define NumberOfNehahraDemos 34 +typedef struct nehahrademonames_s +{ + const char *name; + const char *desc; +} nehahrademonames_t; + +static nehahrademonames_t NehahraDemos[NumberOfNehahraDemos] = +{ + {"intro", "Prologue"}, + {"genf", "The Beginning"}, + {"genlab", "A Doomed Project"}, + {"nehcre", "The New Recruits"}, + {"maxneh", "Breakthrough"}, + {"maxchar", "Renewal and Duty"}, + {"crisis", "Worlds Collide"}, + {"postcris", "Darkening Skies"}, + {"hearing", "The Hearing"}, + {"getjack", "On a Mexican Radio"}, + {"prelude", "Honor and Justice"}, + {"abase", "A Message Sent"}, + {"effect", "The Other Side"}, + {"uhoh", "Missing in Action"}, + {"prepare", "The Response"}, + {"vision", "Farsighted Eyes"}, + {"maxturns", "Enter the Immortal"}, + {"backlot", "Separate Ways"}, + {"maxside", "The Ancient Runes"}, + {"counter", "The New Initiative"}, + {"warprep", "Ghosts to the World"}, + {"counter1", "A Fate Worse Than Death"}, + {"counter2", "Friendly Fire"}, + {"counter3", "Minor Setback"}, + {"madmax", "Scores to Settle"}, + {"quake", "One Man"}, + {"cthmm", "Shattered Masks"}, + {"shades", "Deal with the Dead"}, + {"gophil", "An Unlikely Hero"}, + {"cstrike", "War in Hell"}, + {"shubset", "The Conspiracy"}, + {"shubdie", "Even Death May Die"}, + {"newranks", "An Empty Throne"}, + {"seal", "The Seal is Broken"} +}; + +static float menu_x, menu_y, menu_width, menu_height; + +static void M_Background(int width, int height) +{ + menu_width = bound(1.0f, (float)width, vid_conwidth.value); + menu_height = bound(1.0f, (float)height, vid_conheight.value); + menu_x = (vid_conwidth.integer - menu_width) * 0.5; + menu_y = (vid_conheight.integer - menu_height) * 0.5; + + //Make the background barely visible when menu active.. this should avoid people + //throwing up while the demo is running! + DrawQ_Fill(0, 0, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, 0.75, 0); +} + +/* +================ +M_DrawCharacter + +Draws one solid graphics character +================ +*/ +static void M_DrawCharacter (float cx, float cy, int num) +{ + char temp[2]; + temp[0] = num; + temp[1] = 0; + DrawQ_String(menu_x + cx, menu_y + cy, temp, 1, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); +} + +static void M_PrintColored(float cx, float cy, const char *str) +{ + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_MENU); +} + +static void M_Print(float cx, float cy, const char *str) +{ + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); +} + +static void M_Print_Big(float cx, float cy, const char *str) +{ + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 12, 12, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); +} + +static void M_PrintRed(float cx, float cy, const char *str) +{ + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 0, 0, 1, 0, NULL, true, FONT_MENU); +} + +static void M_PrintRed_Big(float cx, float cy, const char *str) +{ + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 12, 12, 1, 0, 0, 1, 0, NULL, true, FONT_MENU); +} + +static void M_ItemPrint(float cx, float cy, const char *str, int unghosted) +{ + if (unghosted) + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); + else + DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 0.4, 0.4, 0.4, 1, 0, NULL, true, FONT_MENU); +} + +static void M_DrawPic(float cx, float cy, const char *picname) +{ + DrawQ_Pic(menu_x + cx, menu_y + cy, Draw_CachePic (picname), 0, 0, 1, 1, 1, 1, 0); +} + +static void M_DrawTextBox(float x, float y, float width, float height) +{ + int n; + float cx, cy; + + // draw left side + cx = x; + cy = y; + M_DrawPic (cx, cy, "gfx/box_tl"); + for (n = 0; n < height; n++) + { + cy += 8; + M_DrawPic (cx, cy, "gfx/box_ml"); + } + M_DrawPic (cx, cy+8, "gfx/box_bl"); + + // draw middle + cx += 8; + while (width > 0) + { + cy = y; + M_DrawPic (cx, cy, "gfx/box_tm"); + for (n = 0; n < height; n++) + { + cy += 8; + if (n >= 1) + M_DrawPic (cx, cy, "gfx/box_mm2"); + else + M_DrawPic (cx, cy, "gfx/box_mm"); + } + M_DrawPic (cx, cy+8, "gfx/box_bm"); + width -= 2; + cx += 16; + } + + // draw right side + cy = y; + M_DrawPic (cx, cy, "gfx/box_tr"); + for (n = 0; n < height; n++) + { + cy += 8; + M_DrawPic (cx, cy, "gfx/box_mr"); + } + M_DrawPic (cx, cy+8, "gfx/box_br"); +} + +//============================================================================= + +//int m_save_demonum; + +extern cvar_t cl_nosplashscreen; +extern cvar_t cl_autocentreoffset; + +/* +================ +M_ToggleMenu +================ +*/ +static void M_ToggleMenu(int mode) +{ + m_entersound = true; + + if ((key_dest != key_menu && key_dest != key_menu_grabbed) || m_state != m_main) + { + if(mode == 0) + return; // the menu is off, and we want it off + + hmdYaw = hmdorientation[YAW]; + + if (mode == 1 || cl_nosplashscreen.integer == 1) + M_Menu_Main_f(); + else + //These are only shown at the start of the game + M_Menu_Credits_f(); + + jni_BigScreenMode(1); + } + else + { + if(mode == 1) + return; // the menu is on, and we want it on + key_dest = key_game; + m_state = m_none; + jni_BigScreenMode(0); + } +} + + +static int demo_cursor; +static void M_Demo_Draw (void) +{ + int i; + + M_Background(320, 200); + + for (i = 0;i < NumberOfNehahraDemos;i++) + M_Print(16, 16 + 8*i, NehahraDemos[i].desc); + + // line cursor + M_DrawCharacter (8, 16 + demo_cursor*8, 12+((int)(realtime*4)&1)); +} + + +static void M_Menu_Demos_f (void) +{ + key_dest = key_menu; + m_state = m_demo; + m_entersound = true; +} + + +static void M_Demo_Key (int k, int ascii) +{ + char vabuf[1024]; + switch (k) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + case K_MOUSE1: + case K_ENTER: + S_LocalSound ("sound/misc/menu2.wav"); + m_state = m_none; + key_dest = key_game; + Cbuf_AddText (va(vabuf, sizeof(vabuf), "playdemo %s\n", NehahraDemos[demo_cursor].name)); + return; + + case K_UPARROW: + case K_LEFTARROW: + case 'a': + S_LocalSound ("sound/misc/menu1.wav"); + demo_cursor--; + if (demo_cursor < 0) + demo_cursor = NumberOfNehahraDemos-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + case 'd': + S_LocalSound ("sound/misc/menu1.wav"); + demo_cursor++; + if (demo_cursor >= NumberOfNehahraDemos) + demo_cursor = 0; + break; + } +} + +//============================================================================= +/* MAIN MENU */ + +static int m_main_cursor; +static qboolean m_missingdata = false; +int gameAssetsDownloadStatus = -1; + +static int MAIN_ITEMS = 5; // Nehahra: Menu Disable + + +void M_Menu_Main_f (void) +{ + const char *s; + s = "gfx/mainmenu"; + + if (gamemode == GAME_NEHAHRA) + { + if (FS_FileExists("maps/neh1m4.bsp")) + { + if (FS_FileExists("hearing.dem")) + { + Con_DPrint("Main menu: Nehahra movie and game detected.\n"); + NehGameType = TYPE_BOTH; + } + else + { + Con_DPrint("Nehahra game detected.\n"); + NehGameType = TYPE_GAME; + } + } + else + { + if (FS_FileExists("hearing.dem")) + { + Con_DPrint("Nehahra movie detected.\n"); + NehGameType = TYPE_DEMO; + } + else + { + Con_DPrint("Nehahra not found.\n"); + NehGameType = TYPE_GAME; // could just complain, but... + } + } + if (NehGameType == TYPE_DEMO) + MAIN_ITEMS = 4; + else if (NehGameType == TYPE_GAME) + MAIN_ITEMS = 5; + else + MAIN_ITEMS = 6; + } + else if (gamemode == GAME_TRANSFUSION) + { + s = "gfx/menu/mainmenu1"; + if (sv.active && !cl.intermission && cl.islocalgame) + MAIN_ITEMS = 8; + else + MAIN_ITEMS = 7; + } + else + MAIN_ITEMS = 6; + + // check if the game data is missing and use a different main menu if so + m_missingdata = !forceqmenu.integer && Draw_CachePic (s)->tex == r_texture_notexture; + if (m_missingdata) + MAIN_ITEMS = 2; + + /* + if (key_dest != key_menu) + { + m_save_demonum = cls.demonum; + cls.demonum = -1; + } + */ + key_dest = key_menu; + m_state = m_main; + m_entersound = true; + jni_BigScreenMode(1); +} + + +static void M_Main_Draw (void) +{ + int f; + cachepic_t *p; + char vabuf[1024]; + + if (m_missingdata) + { + //If we are in VR mode, switch out of it + if (vrMode > 0) { + vrMode = 0; + jni_SwitchVRMode(0); + showfps.integer = 0; + } + + float y; + const char *s; + M_Background(640, 480); //fall back is always to 640x480, this makes it most readable at that. + y = 480/3-16; + + if (gameAssetsDownloadStatus == -1) + { + s = "** YOU NEED TO COPY GAME FILES TO YOUR PHONE **"; + M_PrintRed_Big ((640-strlen(s)*12)*0.5, (480/3)-16, s);y+=32; + s = "Due to copyright, game data files can't be included";M_Print_Big (30, y, s);y+=20; + s = "Please download the shareware version from:";M_Print_Big(30, y, s);y+=20; + s = "http://bit.ly/1PTsnsb";M_Print_Big(30, y, s);y+=20; + s = "or copy the pak files from the full version ";M_Print_Big(30, y, s);y+=20; + s = "to the following folder :";M_Print_Big(30, y, s);y+=20; + s = "{PHONE_MEMORY} / QVR / id1";M_Print_Big(30, y, s);y+=28; + s = "Full instructions doc: http://bit.ly/21GHVXI";M_Print_Big(30, y, s);y+=20; + } + else if (gameAssetsDownloadStatus == 0) + { + s = "** SHAREWARE DOWNLOAD FAILED **"; + M_PrintRed_Big((640 - strlen(s) * 12) * 0.5, (480 / 3) - 16, s); + y += 32; + s = "Please restart QVR"; + M_Print_Big(30, y, s); + y += 20; + s = "and the download will try again"; + M_Print_Big(30, y, s); + y += 20; + s = "If you own the full game you can"; + M_Print_Big(30, y, s); + y += 20; + s = "copy the pak files from the full version "; + M_Print_Big(30, y, s); + y += 20; + s = "to the following folder :"; + M_Print_Big(30, y, s); + y += 20; + s = "{PHONE_MEMORY} / QVR / id1"; + M_Print_Big(30, y, s); + y += 28; + s = "Full instructions doc: http://bit.ly/21GHVXI"; + M_Print_Big(30, y, s); + y += 20; + } + else if (gameAssetsDownloadStatus == 1) { + s = "** SHAREWARE DOWNLOAD COMPLETED SUCCESSFULLY **"; + M_PrintRed_Big((640 - strlen(s) * 12) * 0.5, (480 / 3) - 16, s); + y += 32; + s = "Please restart QVR"; + M_Print_Big(30, y, s); + y += 20; + s = "If you own the full game you can"; + M_Print_Big(30, y, s); + y += 20; + s = "copy the pak files from the full version "; + M_Print_Big(30, y, s); + y += 20; + s = "to the following folder :"; + M_Print_Big(30, y, s); + y += 20; + s = "{PHONE_MEMORY} / QVR / id1"; + M_Print_Big(30, y, s); + y += 28; + s = "Full instructions doc: http://bit.ly/21GHVXI"; + M_Print_Big(30, y, s); + y += 20; + } + else if (gameAssetsDownloadStatus == 2) + { + s = "** GAME FILES NEED TO DOWNLOAD TO YOUR PHONE **"; + M_PrintRed_Big((640 - strlen(s) * 12) * 0.5, (480 / 3) - 16, s); + y += 32; + s = "Due to copyright, game data files cannot be included"; + M_Print_Big(30, y, s); + y += 20; + s = "The shareware version is downloading."; + M_Print_Big(30, y, s); + } + + M_Print_Big (640/2 - 128, 480/2 + 128, " ++ Tap Screen to Quit ++"); + + M_DrawCharacter(640/2 - 128, 480/2 + 128, 12+((int)(realtime*4)&1)); + return; + } + + if (gamemode == GAME_TRANSFUSION) { + int y1, y2, y3; + M_Background(640, 480); + p = Draw_CachePic ("gfx/menu/tb-transfusion"); + M_DrawPic (640/2 - p->width/2, 40, "gfx/menu/tb-transfusion"); + y2 = 120; + // 8 rather than MAIN_ITEMS to skip a number and not miss the last option + for (y1 = 1; y1 <= 8; y1++) + { + if (MAIN_ITEMS == 7 && y1 == 4) + y1++; + M_DrawPic (0, y2, va(vabuf, sizeof(vabuf), "gfx/menu/mainmenu%i", y1)); + y2 += 40; + } + if (MAIN_ITEMS == 7 && m_main_cursor > 2) + y3 = m_main_cursor + 2; + else + y3 = m_main_cursor + 1; + M_DrawPic (0, 120 + m_main_cursor * 40, va(vabuf, sizeof(vabuf), "gfx/menu/mainmenu%iselected", y3)); + return; + } + + M_Background(320, 200); + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/ttl_main"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_main"); + + M_DrawTextBox(72, 30, 28, 1); + char *mode = "Off"; + if (vrMode == 1) mode = "Side-by-Side"; + if (vrMode == 2) mode = "Cardboard"; + M_Print(86, 38, va(vabuf, sizeof(vabuf), "VR Mode: %s", mode)); + + +// Nehahra + if (gamemode == GAME_NEHAHRA) + { + if (NehGameType == TYPE_BOTH) + M_DrawPic (72, 54, "gfx/mainmenu"); + else if (NehGameType == TYPE_GAME) + M_DrawPic (72, 54, "gfx/gamemenu"); + else + M_DrawPic (72, 54, "gfx/demomenu"); + } + else + M_DrawPic (72, 54, "gfx/mainmenu"); + + f = (int)(realtime * 10)%6; + + M_DrawPic (54, 34 + m_main_cursor * 20, va(vabuf, sizeof(vabuf), "gfx/menudot%i", f+1)); +} + + +static void M_Main_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + key_dest = key_game; + m_state = m_none; + jni_BigScreenMode(0); + //cls.demonum = m_save_demonum; + //if (cls.demonum != -1 && !cls.demoplayback && cls.state != ca_connected) + // CL_NextDemo (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (++m_main_cursor >= MAIN_ITEMS) + m_main_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (--m_main_cursor < 0) + m_main_cursor = MAIN_ITEMS - 1; + break; + case K_MOUSE1: + case K_ENTER: + m_entersound = true; + + if (m_missingdata) + { + Host_Quit_f (); + key_dest = key_game; + m_state = m_none; + } + else if (gamemode == GAME_NEHAHRA) + { + switch (NehGameType) + { + case TYPE_BOTH: + switch (m_main_cursor) + { + case 0: + M_Menu_SinglePlayer_f (); + break; + + case 1: + M_Menu_Demos_f (); + break; + + case 2: + M_Menu_MultiPlayer_f (); + break; + + case 3: + M_Menu_Options_f (); + break; + + case 4: + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("playdemo endcred\n"); + break; + + case 5: + M_Menu_Quit_f (); + break; + } + break; + case TYPE_GAME: + switch (m_main_cursor) + { + case 0: + M_Menu_SinglePlayer_f (); + break; + + case 1: + M_Menu_MultiPlayer_f (); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("playdemo endcred\n"); + break; + + case 4: + M_Menu_Quit_f (); + break; + } + break; + case TYPE_DEMO: + switch (m_main_cursor) + { + case 0: + M_Menu_Demos_f (); + break; + + case 1: + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("playdemo endcred\n"); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Quit_f (); + break; + } + break; + } + } + else if (gamemode == GAME_TRANSFUSION) { + if (MAIN_ITEMS == 7) + { + switch (m_main_cursor) + { + case 0: + M_Menu_Transfusion_Episode_f (); + break; + + case 1: + M_Menu_MultiPlayer_f (); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Load_f (); + break; + + case 4: + M_Menu_Help_f (); + break; + + case 5: + M_Menu_Credits_f (); + break; + + case 6: + M_Menu_Quit_f (); + break; + } + } + else + { + switch (m_main_cursor) + { + case 0: + M_Menu_Transfusion_Episode_f (); + break; + + case 1: + M_Menu_MultiPlayer_f (); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Save_f (); + break; + + case 4: + M_Menu_Load_f (); + break; + + case 5: + M_Menu_Help_f (); + break; + + case 6: + M_Menu_Credits_f (); + break; + + case 7: + M_Menu_Quit_f (); + break; + } + } + } + else + { + switch (m_main_cursor) + { + case 0: + vrMode = (vrMode + 1) % 3; + jni_SwitchVRMode(vrMode); + break; + + case 1: + M_Menu_SinglePlayer_f (); + break; + + case 2: + M_Menu_MultiPlayer_f (); + break; + + case 3: + M_Menu_Options_f (); + break; + + case 4: + M_Menu_Help_f (); + break; + + case 5: + M_Menu_Quit_f (); + break; + + } + } + } +} + +//============================================================================= +/* SINGLE PLAYER MENU */ + +static int m_singleplayer_cursor; +#define SINGLEPLAYER_ITEMS 3 + + +void M_Menu_SinglePlayer_f (void) +{ + key_dest = key_menu; + m_state = m_singleplayer; + m_entersound = true; +} + + +static void M_SinglePlayer_Draw (void) +{ + cachepic_t *p; + char vabuf[1024]; + + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/ttl_sgl"); + + // Some mods don't have a single player mode + if (gamemode == GAME_GOODVSBAD2 || gamemode == GAME_BATTLEMECH) + { + M_DrawPic ((320 - p->width) / 2, 4, "gfx/ttl_sgl"); + + M_DrawTextBox (60, 8 * 8, 23, 4); + if (gamemode == GAME_GOODVSBAD2) + M_Print(95, 10 * 8, "Good Vs Bad 2 is for"); + else // if (gamemode == GAME_BATTLEMECH) + M_Print(95, 10 * 8, "Battlemech is for"); + M_Print(83, 11 * 8, "multiplayer play only"); + } + else + { + int f; + + M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_sgl"); + M_DrawPic (72, 32, "gfx/sp_menu"); + + f = (int)(realtime * 10)%6; + + M_DrawPic (54, 32 + m_singleplayer_cursor * 20, va(vabuf, sizeof(vabuf), "gfx/menudot%i", f+1)); + } +} + + +static void M_SinglePlayer_Key (int key, int ascii) +{ + if (gamemode == GAME_GOODVSBAD2 || gamemode == GAME_BATTLEMECH) + { + if (key == K_ESCAPE || key == K_ENTER || key == K_MOUSE1) + m_state = m_main; + return; + } + + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (++m_singleplayer_cursor >= SINGLEPLAYER_ITEMS) + m_singleplayer_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (--m_singleplayer_cursor < 0) + m_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1; + break; + case K_MOUSE1: + case K_ENTER: + m_entersound = true; + + switch (m_singleplayer_cursor) + { + case 0: + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("maxplayers 1\n"); + Cbuf_AddText ("deathmatch 0\n"); + Cbuf_AddText ("coop 0\n"); + if (gamemode == GAME_TRANSFUSION) + { + key_dest = key_menu; + M_Menu_Transfusion_Episode_f (); + break; + } + Cbuf_AddText ("startmap_sp\n"); + break; + + case 1: + M_Menu_Load_f (); + break; + + case 2: + M_Menu_Save_f (); + break; + } + } +} + +//============================================================================= +/* LOAD/SAVE MENU */ + +static int load_cursor; ///< 0 < load_cursor < MAX_SAVEGAMES + +static char m_filenames[MAX_SAVEGAMES][SAVEGAME_COMMENT_LENGTH+1]; +static int loadable[MAX_SAVEGAMES]; + +static void M_ScanSaves (void) +{ + int i, j; + size_t len; + char name[MAX_OSPATH]; + char buf[SAVEGAME_COMMENT_LENGTH + 256]; + const char *t; + qfile_t *f; +// int version; + + for (i=0 ; iwidth)/2, 4, "gfx/p_load" ); + + for (i=0 ; i< MAX_SAVEGAMES; i++) + M_Print(16, 32 + 8*i, m_filenames[i]); + +// line cursor + M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1)); +} + + +static void M_Save_Draw (void) +{ + int i; + cachepic_t *p; + + M_Background(320, 200); + + p = Draw_CachePic ("gfx/p_save"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/p_save"); + + for (i=0 ; i= MAX_SAVEGAMES) + load_cursor = 0; + break; + } +} + + +static void M_Save_Key (int k, int ascii) +{ + char vabuf[1024]; + switch (k) + { + case K_ESCAPE: + if (gamemode == GAME_TRANSFUSION) + M_Menu_Main_f (); + else + M_Menu_SinglePlayer_f (); + break; + case K_MOUSE1: + case K_ENTER: + m_state = m_none; + key_dest = key_game; + jni_BigScreenMode(0); + Cbuf_AddText (va(vabuf, sizeof(vabuf), "save s%i\n", load_cursor)); + return; + + case K_UPARROW: + case K_LEFTARROW: + case 'a': + S_LocalSound ("sound/misc/menu1.wav"); + load_cursor--; + if (load_cursor < 0) + load_cursor = MAX_SAVEGAMES-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + case 'd': + S_LocalSound ("sound/misc/menu1.wav"); + load_cursor++; + if (load_cursor >= MAX_SAVEGAMES) + load_cursor = 0; + break; + } +} + +//============================================================================= +/* Transfusion Single Player Episode Menu */ + +static int m_episode_cursor; +#define EPISODE_ITEMS 6 + +void M_Menu_Transfusion_Episode_f (void) +{ + m_entersound = true; + m_state = m_transfusion_episode; + key_dest = key_menu; +} + +static void M_Transfusion_Episode_Draw (void) +{ + int y; + cachepic_t *p; + char vabuf[1024]; + M_Background(640, 480); + + p = Draw_CachePic ("gfx/menu/tb-episodes"); + M_DrawPic (640/2 - p->width/2, 40, "gfx/menu/tb-episodes"); + for (y = 0; y < EPISODE_ITEMS; y++){ + M_DrawPic (0, 160 + y * 40, va(vabuf, sizeof(vabuf), "gfx/menu/episode%i", y+1)); + } + + M_DrawPic (0, 120 + (m_episode_cursor + 1) * 40, va(vabuf, sizeof(vabuf), "gfx/menu/episode%iselected", m_episode_cursor + 1)); +} + +static void M_Transfusion_Episode_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + m_episode_cursor++; + if (m_episode_cursor >= EPISODE_ITEMS) + m_episode_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + m_episode_cursor--; + if (m_episode_cursor < 0) + m_episode_cursor = EPISODE_ITEMS - 1; + break; + case K_MOUSE1: + case K_ENTER: + Cbuf_AddText ("deathmatch 0\n"); + m_entersound = true; + M_Menu_Transfusion_Skill_f (); + } +} + +//============================================================================= +/* Transfusion Single Player Skill Menu */ + +static int m_skill_cursor = 2; +#define SKILL_ITEMS 5 + +void M_Menu_Transfusion_Skill_f (void) +{ + m_entersound = true; + m_state = m_transfusion_skill; + key_dest = key_menu; +} + +static void M_Transfusion_Skill_Draw (void) +{ + int y; + cachepic_t *p; + char vabuf[1024]; + M_Background(640, 480); + + p = Draw_CachePic ("gfx/menu/tb-difficulty"); + M_DrawPic(640/2 - p->width/2, 40, "gfx/menu/tb-difficulty"); + + for (y = 0; y < SKILL_ITEMS; y++) + { + M_DrawPic (0, 180 + y * 40, va(vabuf, sizeof(vabuf), "gfx/menu/difficulty%i", y+1)); + } + M_DrawPic (0, 140 + (m_skill_cursor + 1) *40, va(vabuf, sizeof(vabuf), "gfx/menu/difficulty%iselected", m_skill_cursor + 1)); +} + +static void M_Transfusion_Skill_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Transfusion_Episode_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + m_skill_cursor++; + if (m_skill_cursor >= SKILL_ITEMS) + m_skill_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + m_skill_cursor--; + if (m_skill_cursor < 0) + m_skill_cursor = SKILL_ITEMS - 1; + break; + case K_MOUSE1: + case K_ENTER: + m_entersound = true; + switch (m_skill_cursor) + { + case 0: + Cbuf_AddText ("skill 1\n"); + break; + case 1: + Cbuf_AddText ("skill 2\n"); + break; + case 2: + Cbuf_AddText ("skill 3\n"); + break; + case 3: + Cbuf_AddText ("skill 4\n"); + break; + case 4: + Cbuf_AddText ("skill 5\n"); + break; + } + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("maxplayers 1\n"); + Cbuf_AddText ("deathmatch 0\n"); + Cbuf_AddText ("coop 0\n"); + switch (m_episode_cursor) + { + case 0: + Cbuf_AddText ("map e1m1\n"); + break; + case 1: + Cbuf_AddText ("map e2m1\n"); + break; + case 2: + Cbuf_AddText ("map e3m1\n"); + break; + case 3: + Cbuf_AddText ("map e4m1\n"); + break; + case 4: + Cbuf_AddText ("map e6m1\n"); + break; + case 5: + Cbuf_AddText ("map cp01\n"); + break; + } + } +} +//============================================================================= +/* MULTIPLAYER MENU */ + +static int m_multiplayer_cursor; +#define MULTIPLAYER_ITEMS 3 + + +void M_Menu_MultiPlayer_f (void) +{ + key_dest = key_menu; + m_state = m_multiplayer; + m_entersound = true; +} + + +static void M_MultiPlayer_Draw (void) +{ + int f; + cachepic_t *p; + char vabuf[1024]; + + if (gamemode == GAME_TRANSFUSION) + { + M_Background(640, 480); + p = Draw_CachePic ("gfx/menu/tb-online"); + M_DrawPic (640/2 - p->width/2, 140, "gfx/menu/tb-online"); + for (f = 1; f <= MULTIPLAYER_ITEMS; f++) + M_DrawPic (0, 180 + f*40, va(vabuf, sizeof(vabuf), "gfx/menu/online%i", f)); + M_DrawPic (0, 220 + m_multiplayer_cursor * 40, va(vabuf, sizeof(vabuf), "gfx/menu/online%iselected", m_multiplayer_cursor + 1)); + return; + } + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_multi"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); + M_DrawPic (72, 32, "gfx/mp_menu"); + + f = (int)(realtime * 10)%6; + + M_DrawPic (54, 32 + m_multiplayer_cursor * 20, va(vabuf, sizeof(vabuf), "gfx/menudot%i", f+1)); +} + + +static void M_MultiPlayer_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (++m_multiplayer_cursor >= MULTIPLAYER_ITEMS) + m_multiplayer_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (--m_multiplayer_cursor < 0) + m_multiplayer_cursor = MULTIPLAYER_ITEMS - 1; + break; + case K_MOUSE1: + case K_ENTER: + m_entersound = true; + switch (m_multiplayer_cursor) + { + case 0: + case 1: + M_Menu_LanConfig_f (); + break; + + case 2: + M_Menu_Setup_f (); + break; + } + } +} + +//============================================================================= +/* SETUP MENU */ + +static int setup_cursor = 4; +static int setup_cursor_table[] = {40, 64, 88, 124, 140}; + +static char setup_myname[MAX_SCOREBOARDNAME]; +static int setup_oldtop; +static int setup_oldbottom; +static int setup_top; +static int setup_bottom; +static int setup_rate; +static int setup_oldrate; + +#define NUM_SETUP_CMDS 5 + +void M_Menu_Setup_f (void) +{ + key_dest = key_menu; + m_state = m_setup; + m_entersound = true; + strlcpy(setup_myname, cl_name.string, sizeof(setup_myname)); + setup_top = setup_oldtop = cl_color.integer >> 4; + setup_bottom = setup_oldbottom = cl_color.integer & 15; + setup_rate = cl_rate.integer; +} + +static int menuplyr_width, menuplyr_height, menuplyr_top, menuplyr_bottom, menuplyr_load; +static unsigned char *menuplyr_pixels; +static unsigned int *menuplyr_translated; + +typedef struct ratetable_s +{ + int rate; + const char *name; +} +ratetable_t; + +#define RATES ((int)(sizeof(setup_ratetable)/sizeof(setup_ratetable[0]))) +static ratetable_t setup_ratetable[] = +{ + {1000, "28.8 bad"}, + {1500, "28.8 mediocre"}, + {2000, "28.8 good"}, + {2500, "33.6 mediocre"}, + {3000, "33.6 good"}, + {3500, "56k bad"}, + {4000, "56k mediocre"}, + {4500, "56k adequate"}, + {5000, "56k good"}, + {7000, "64k ISDN"}, + {15000, "128k ISDN"}, + {25000, "broadband"} +}; + +static int setup_rateindex(int rate) +{ + int i; + for (i = 0;i < RATES;i++) + if (setup_ratetable[i].rate > setup_rate) + break; + return bound(1, i, RATES) - 1; +} + +static void M_Setup_Draw (void) +{ + int i, j; + cachepic_t *p; + char vabuf[1024]; + + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_multi"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); + + M_Print(64, 40, "Your name"); + M_DrawTextBox (160, 32, 16, 1); + M_PrintColored(168, 40, setup_myname); + + if (gamemode != GAME_GOODVSBAD2) + { + M_Print(64, 64, "Shirt color"); + M_Print(64, 88, "Pants color"); + } + + M_Print(64, 124-8, "Network speed limit"); + M_Print(168, 124, va(vabuf, sizeof(vabuf), "%i (%s)", setup_rate, setup_ratetable[setup_rateindex(setup_rate)].name)); + + M_DrawTextBox (64, 140-8, 14, 1); + M_Print(72, 140, "Accept Changes"); + + // LordHavoc: rewrote this code greatly + if (menuplyr_load) + { + unsigned char *f; + fs_offset_t filesize; + menuplyr_load = false; + menuplyr_top = -1; + menuplyr_bottom = -1; + f = FS_LoadFile("gfx/menuplyr.lmp", tempmempool, true, &filesize); + if (f && filesize >= 9) + { + int width, height; + width = f[0] + f[1] * 256 + f[2] * 65536 + f[3] * 16777216; + height = f[4] + f[5] * 256 + f[6] * 65536 + f[7] * 16777216; + if (filesize >= 8 + width * height) + { + menuplyr_width = width; + menuplyr_height = height; + menuplyr_pixels = (unsigned char *)Mem_Alloc(cls.permanentmempool, width * height); + menuplyr_translated = (unsigned int *)Mem_Alloc(cls.permanentmempool, width * height * 4); + memcpy(menuplyr_pixels, f + 8, width * height); + } + } + if (f) + Mem_Free(f); + } + + if (menuplyr_pixels) + { + if (menuplyr_top != setup_top || menuplyr_bottom != setup_bottom) + { + menuplyr_top = setup_top; + menuplyr_bottom = setup_bottom; + + for (i = 0;i < menuplyr_width * menuplyr_height;i++) + { + j = menuplyr_pixels[i]; + if (j >= TOP_RANGE && j < TOP_RANGE + 16) + { + if (menuplyr_top < 8 || menuplyr_top == 14) + j = menuplyr_top * 16 + (j - TOP_RANGE); + else + j = menuplyr_top * 16 + 15-(j - TOP_RANGE); + } + else if (j >= BOTTOM_RANGE && j < BOTTOM_RANGE + 16) + { + if (menuplyr_bottom < 8 || menuplyr_bottom == 14) + j = menuplyr_bottom * 16 + (j - BOTTOM_RANGE); + else + j = menuplyr_bottom * 16 + 15-(j - BOTTOM_RANGE); + } + menuplyr_translated[i] = palette_bgra_transparent[j]; + } + Draw_NewPic("gfx/menuplyr", menuplyr_width, menuplyr_height, true, (unsigned char *)menuplyr_translated); + } + M_DrawPic(160, 48, "gfx/bigbox"); + M_DrawPic(172, 56, "gfx/menuplyr"); + } + + if (setup_cursor == 0) + M_DrawCharacter (168 + 8*strlen(setup_myname), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1)); + else + M_DrawCharacter (56, setup_cursor_table [setup_cursor], 12+((int)(realtime*4)&1)); +} + + +static void M_Setup_Key (int k, int ascii) +{ + int l; + char vabuf[1024]; + + switch (k) + { + case K_ESCAPE: + M_Menu_MultiPlayer_f (); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + setup_cursor--; + if (setup_cursor < 0) + setup_cursor = NUM_SETUP_CMDS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + setup_cursor++; + if (setup_cursor >= NUM_SETUP_CMDS) + setup_cursor = 0; + break; + + case K_LEFTARROW: + case 'a': + if (setup_cursor < 1) + return; + S_LocalSound ("sound/misc/menu3.wav"); + if (setup_cursor == 1) + setup_top = setup_top - 1; + if (setup_cursor == 2) + setup_bottom = setup_bottom - 1; + if (setup_cursor == 3) + { + l = setup_rateindex(setup_rate) - 1; + if (l < 0) + l = RATES - 1; + setup_rate = setup_ratetable[l].rate; + } + break; + case K_RIGHTARROW: + case 'd': + if (setup_cursor < 1) + return; +forward: + S_LocalSound ("sound/misc/menu3.wav"); + if (setup_cursor == 1) + setup_top = setup_top + 1; + if (setup_cursor == 2) + setup_bottom = setup_bottom + 1; + if (setup_cursor == 3) + { + l = setup_rateindex(setup_rate) + 1; + if (l >= RATES) + l = 0; + setup_rate = setup_ratetable[l].rate; + } + break; + case K_MOUSE1: + case K_ENTER: + if (setup_cursor == 0) + return; + + if (setup_cursor == 1 || setup_cursor == 2 || setup_cursor == 3) + goto forward; + + // setup_cursor == 4 (Accept changes) + if (strcmp(cl_name.string, setup_myname) != 0) + Cbuf_AddText(va(vabuf, sizeof(vabuf), "name \"%s\"\n", setup_myname) ); + if (setup_top != setup_oldtop || setup_bottom != setup_oldbottom) + Cbuf_AddText(va(vabuf, sizeof(vabuf), "color %i %i\n", setup_top, setup_bottom) ); + if (setup_rate != setup_oldrate) + Cbuf_AddText(va(vabuf, sizeof(vabuf), "rate %i\n", setup_rate)); + + m_entersound = true; + M_Menu_MultiPlayer_f (); + break; + + case K_BACKSPACE: + if (setup_cursor == 0) + { + if (strlen(setup_myname)) + setup_myname[strlen(setup_myname)-1] = 0; + } + break; + + default: + if (ascii < 32) + break; + if (setup_cursor == 0) + { + l = (int)strlen(setup_myname); + if (l < 15) + { + setup_myname[l+1] = 0; + setup_myname[l] = ascii; + } + } + } + + if (setup_top > 15) + setup_top = 0; + if (setup_top < 0) + setup_top = 15; + if (setup_bottom > 15) + setup_bottom = 0; + if (setup_bottom < 0) + setup_bottom = 15; +} + +//============================================================================= +/* OPTIONS MENU */ + +#define SLIDER_RANGE 10 + +static void M_DrawSlider (int x, int y, float num, float rangemin, float rangemax) +{ + char text[16]; + int i; + float range; + range = bound(0, (num - rangemin) / (rangemax - rangemin), 1); + M_DrawCharacter (x-8, y, 128); + for (i = 0;i < SLIDER_RANGE;i++) + M_DrawCharacter (x + i*8, y, 129); + M_DrawCharacter (x+i*8, y, 130); + M_DrawCharacter (x + (SLIDER_RANGE-1)*8 * range, y, 131); + if (fabs((int)num - num) < 0.01) + dpsnprintf(text, sizeof(text), "%i", (int)num); + else + dpsnprintf(text, sizeof(text), "%.3f", num); + M_Print(x + (SLIDER_RANGE+2) * 8, y, text); +} + +static void M_DrawCheckbox (int x, int y, int on) +{ + if (on) + M_Print(x, y, "on"); + else + M_Print(x, y, "off"); +} + + +#define OPTIONS_ITEMS 25 + +static int options_cursor; + +void M_Menu_Options_f (void) +{ + key_dest = key_menu; + m_state = m_options; + m_entersound = true; +} + +extern cvar_t slowmo; +extern dllhandle_t jpeg_dll; +extern cvar_t gl_texture_anisotropy; +extern cvar_t r_textshadow; +extern cvar_t r_hdr_scenebrightness; + +static void M_Menu_Options_AdjustSliders (int dir) +{ + int optnum; + double f; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 0; + if (options_cursor == optnum++) { + bigScreen = (bigScreen == 2 ? -1 : 2); + jni_BigScreenMode(bigScreen); + } + else if (options_cursor == optnum++) { + stereoMode = 1 - stereoMode; + jni_SwitchStereoMode(stereoMode); + } + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) + { + if (vrMode == 2) { + if (dir == 1) { + v_eyebufferresolution.integer *= 2; + if (v_eyebufferresolution.integer > 2048) + v_eyebufferresolution.integer = 256; + } + else { + v_eyebufferresolution.integer /= 2; + if (v_eyebufferresolution.integer < 256) + v_eyebufferresolution.integer = 2048; + } + + Cvar_SetValueQuick(&v_eyebufferresolution, v_eyebufferresolution.integer); + } + } + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) Cvar_SetValueQuick(&crosshair, bound(0, crosshair.integer + dir, 7)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&scr_fov, bound(1, scr_fov.integer + dir * 1, 170)); + else if (options_cursor == optnum++) + { + cl_forwardspeed.value += dir * 10; + if (cl_forwardspeed.value > 500) + cl_forwardspeed.value = 500; + if (cl_forwardspeed.value < 10) + cl_forwardspeed.value = 10; + + cl_backspeed.value += dir * 10; + if (cl_backspeed.value > 500) + cl_backspeed.value = 500; + if (cl_backspeed.value < 10) + cl_backspeed.value = 10; + + cl_sidespeed.value += dir * 10; + if (cl_sidespeed.value > 500) + cl_sidespeed.value = 500; + if (cl_sidespeed.value < 10) + cl_sidespeed.value = 10; + + Cvar_SetValueQuick (&cl_forwardspeed, cl_forwardspeed.value); + Cvar_SetValueQuick (&cl_backspeed, cl_backspeed.value); + Cvar_SetValueQuick (&cl_sidespeed, cl_sidespeed.value); + } + else if (options_cursor == optnum++) Cvar_SetValueQuick(&showfps, !showfps.integer); + else if (options_cursor == optnum++) ; + else if (options_cursor == optnum++) Cvar_SetValueQuick(&r_hdr_scenebrightness, bound(1, r_hdr_scenebrightness.value + dir * 0.0625, 4)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&v_contrast, bound(1, v_contrast.value + dir * 0.0625, 4)); + else if (options_cursor == optnum++) Cvar_SetValueQuick(&v_gamma, bound(0.5, v_gamma.value + dir * 0.0625, 3)); + else if (options_cursor == optnum++) + { + if (r_worldscale.value < 200.0f) + { + Cvar_SetValueQuick (&r_worldscale, 400.0f); + Cvar_SetValueQuick (&chase_active, 1); + } + else + { + Cvar_SetValueQuick (&r_worldscale, 40.0f); + Cvar_SetValueQuick (&chase_active, 0); + } + } +} + +static int optnum; +static int opty; +static int optcursor; + +static void M_Options_PrintCommand(const char *s, int enabled) +{ + if (opty >= 32) + { + if (optnum == optcursor) + DrawQ_Fill(menu_x + 48, menu_y + opty, 320, 8, optnum == optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_ItemPrint(0 + 48, opty, s, enabled); + } + opty += 8; + optnum++; +} + +static void M_Options_PrintCheckbox(const char *s, int enabled, int yes) +{ + if (opty >= 32) + { + if (optnum == optcursor) + DrawQ_Fill(menu_x + 48, menu_y + opty, 320, 8, optnum == optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_ItemPrint(0 + 48, opty, s, enabled); + M_DrawCheckbox(0 + 48 + (int)strlen(s) * 8 + 8, opty, yes); + } + opty += 8; + optnum++; +} + +static void M_Options_PrintSlider(const char *s, int enabled, float value, float minvalue, float maxvalue) +{ + if (opty >= 32) + { + if (optnum == optcursor) + DrawQ_Fill(menu_x + 48, menu_y + opty, 320, 8, optnum == optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_ItemPrint(0 + 48, opty, s, enabled); + M_DrawSlider(0 + 48 + (int)strlen(s) * 8 + 8, opty, value, minvalue, maxvalue); + } + opty += 8; + optnum++; +} + +static void M_Options_Draw (void) +{ + int visible; + cachepic_t *p; + + M_Background(320, bound(200, 32 + OPTIONS_ITEMS * 8, vid_conheight.integer)); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optnum = 0; + optcursor = options_cursor; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, OPTIONS_ITEMS - visible)) * 8; + + if (bigScreen == 2) + M_Options_PrintCommand( " Big Screen Mode Enabled", true); + else + M_Options_PrintCommand( " Big Screen Mode Disabled", true); + + switch (stereoMode) + { + case 0: + M_Options_PrintCommand( " Stereo Mode: MONO", true); + break; + case 1: + M_Options_PrintCommand( " Stereo Mode: STEREO", true); + break; + } + + M_Options_PrintCommand( " Controller Settings", true); + M_Options_PrintCommand( " Open Quake Console", true); + M_Options_PrintCommand( " Reset to defaults", true); + if (vrMode == 2) { + if (v_eyebufferresolution.integer == 0) + v_eyebufferresolution.integer = andrw; + M_Options_PrintSlider(" Eye Buffer Resolution", true, v_eyebufferresolution.integer, 256, + 2048); + } + else + M_Options_PrintCommand( " Eye Buffer Resolution n/a", false); + char buf[356]; + M_Options_PrintCommand( " Key/Button Bindings", true); + M_Options_PrintSlider( " Crosshair", true, crosshair.value, 0, 7); + M_Options_PrintSlider( " Field of View", true, scr_fov.integer, 1, 170); + M_Options_PrintSlider( " Player Movement Speed", true, cl_forwardspeed.value, 10, 500); + M_Options_PrintCheckbox(" Show Framerate", true, showfps.integer); + M_Options_PrintCommand( " Custom Brightness", true); + M_Options_PrintSlider( " Game Brightness", true, r_hdr_scenebrightness.value, 1, 4); + M_Options_PrintSlider( " Brightness", true, v_contrast.value, 1, 2); + M_Options_PrintSlider( " Gamma", true, v_gamma.value, 0.5, 3); + M_Options_PrintCheckbox(" Toy Soldier Mode", true, r_worldscale.value > 200.0f); + M_Options_PrintCommand( " Customize Effects", true); + M_Options_PrintCommand( " Effects: Quake", true); + M_Options_PrintCommand( " Effects: Normal", true); + M_Options_PrintCommand( " Effects: High", true); + M_Options_PrintCommand( " Customize Graphics", true); + M_Options_PrintCommand( " Lighting: Flares", true); + M_Options_PrintCommand( " Lighting: Normal", true); + M_Options_PrintCommand( " Lighting: High", true); + M_Options_PrintCommand( " Lighting: Full", true); + M_Options_PrintCommand( " Browse Mods", true); +} + + +static void M_Options_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_MOUSE1: + case K_ENTER: + m_entersound = true; + switch (options_cursor) + { + case 0: + bigScreen = (bigScreen == 2 ? -1 : 2); + jni_BigScreenMode(bigScreen); + break; + case 1: + stereoMode = 1 - stereoMode; + jni_SwitchStereoMode(stereoMode); + break; + case 2: + M_Menu_YawPitchControl_f (); + break; + case 3: + m_state = m_none; + key_dest = key_game; + Con_ToggleConsole_f (); + break; + case 4: + M_Menu_Reset_f (); + break; + case 6: + M_Menu_Keys_f (); + break; + case 11: + M_Menu_Options_ColorControl_f (); + break; + case 15: // Toy Soldier Mode + if (r_worldscale.value < 200.0f) + { + Cvar_SetValueQuick (&r_worldscale, 400.0f); + Cvar_SetValueQuick (&chase_active, 1); + } + else + { + Cvar_SetValueQuick (&r_worldscale, 40.0f); + Cvar_SetValueQuick (&chase_active, 0); + } + break; + case 16: // Customize Effects + M_Menu_Options_Effects_f (); + break; + case 17: // Effects: Quake + Cbuf_AddText("cl_particles 1;cl_particles_quake 1;cl_particles_quality 1;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 0;cl_stainmaps_clearonload 1;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 0;cl_beams_polygons 0;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); + break; + case 18: // Effects: Normal + Cbuf_AddText("cl_particles 1;cl_particles_quake 0;cl_particles_quality 1;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 0;cl_stainmaps_clearonload 1;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 1;cl_beams_polygons 1;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); + break; + case 19: // Effects: High + Cbuf_AddText("cl_particles 1;cl_particles_quake 0;cl_particles_quality 2;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 1;cl_stainmaps_clearonload 1;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 1;cl_beams_polygons 1;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); + break; + case 20: + M_Menu_Options_Graphics_f (); + break; + case 21: // Lighting: Flares + Cbuf_AddText("r_coronas 1;gl_flashblend 1;r_shadow_gloss 0;r_shadow_realtime_dlight 0;r_shadow_realtime_dlight_shadows 0;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 0"); + break; + case 22: // Lighting: Normal + Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 0;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 0"); + break; + case 23: // Lighting: High + Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 1;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 1"); + break; + case 24: // Lighting: Full + Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 1;r_shadow_realtime_world 1;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 1"); + break; + case 25: + M_Menu_ModList_f (); + break; + default: + M_Menu_Options_AdjustSliders (1); + break; + } + return; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_cursor--; + if (options_cursor < 0) + options_cursor = OPTIONS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_cursor++; + if (options_cursor >= OPTIONS_ITEMS) + options_cursor = 0; + break; + + case K_LEFTARROW: + case 'a': + M_Menu_Options_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + case 'd': + M_Menu_Options_AdjustSliders (1); + break; + } +} + +#define OPTIONS_EFFECTS_ITEMS 35 + +static int options_effects_cursor; + +void M_Menu_Options_Effects_f (void) +{ + key_dest = key_menu; + m_state = m_options_effects; + m_entersound = true; +} + + +extern cvar_t cl_stainmaps; +extern cvar_t cl_stainmaps_clearonload; +extern cvar_t r_explosionclip; +extern cvar_t r_coronas; +extern cvar_t gl_flashblend; +extern cvar_t cl_beams_polygons; +extern cvar_t cl_beams_quakepositionhack; +extern cvar_t cl_beams_instantaimhack; +extern cvar_t cl_beams_lightatend; +extern cvar_t r_lightningbeam_thickness; +extern cvar_t r_lightningbeam_scroll; +extern cvar_t r_lightningbeam_repeatdistance; +extern cvar_t r_lightningbeam_color_red; +extern cvar_t r_lightningbeam_color_green; +extern cvar_t r_lightningbeam_color_blue; +extern cvar_t r_lightningbeam_qmbtexture; + +static void M_Menu_Options_Effects_AdjustSliders (int dir) +{ + int optnum; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 0; + if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles, !cl_particles.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_quake, !cl_particles_quake.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_quality, bound(1, cl_particles_quality.value + dir * 0.5, 4)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_explosions_shell, !cl_particles_explosions_shell.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_explosionclip, !r_explosionclip.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_stainmaps, !cl_stainmaps.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_stainmaps_clearonload, !cl_stainmaps_clearonload.integer); + else if (options_effects_cursor == optnum++) ;//Cvar_SetValueQuick (&cl_decals, !cl_decals.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_bulletimpacts, !cl_particles_bulletimpacts.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_smoke, !cl_particles_smoke.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_sparks, !cl_particles_sparks.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_bubbles, !cl_particles_bubbles.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood, !cl_particles_blood.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood_alpha, bound(0.2, cl_particles_blood_alpha.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood_bloodhack, !cl_particles_blood_bloodhack.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_polygons, !cl_beams_polygons.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_instantaimhack, !cl_beams_instantaimhack.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_quakepositionhack, !cl_beams_quakepositionhack.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_lightatend, !cl_beams_lightatend.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_thickness, bound(1, r_lightningbeam_thickness.integer + dir, 10)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_scroll, bound(0, r_lightningbeam_scroll.integer + dir, 10)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_repeatdistance, bound(64, r_lightningbeam_repeatdistance.integer + dir * 64, 1024)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_red, bound(0, r_lightningbeam_color_red.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_green, bound(0, r_lightningbeam_color_green.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_blue, bound(0, r_lightningbeam_color_blue.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_qmbtexture, !r_lightningbeam_qmbtexture.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerpmodels, !r_lerpmodels.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerpsprites, !r_lerpsprites.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerplightstyles, !r_lerplightstyles.integer); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&gl_polyblend, bound(0, gl_polyblend.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_skyscroll1, bound(-8, r_skyscroll1.value + dir * 0.1, 8)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_skyscroll2, bound(-8, r_skyscroll2.value + dir * 0.1, 8)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_waterwarp, bound(0, r_waterwarp.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_wateralpha, bound(0, r_wateralpha.value + dir * 0.1, 1)); + else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_waterscroll, bound(0, r_waterscroll.value + dir * 0.5, 10)); +} + +static void M_Options_Effects_Draw (void) +{ + int visible; + cachepic_t *p; + + M_Background(320, bound(200, 32 + OPTIONS_EFFECTS_ITEMS * 8, vid_conheight.integer)); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optcursor = options_effects_cursor; + optnum = 0; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, OPTIONS_EFFECTS_ITEMS - visible)) * 8; + + M_Options_PrintCheckbox(" Particles", true, cl_particles.integer); + M_Options_PrintCheckbox(" Quake-style Particles", true, cl_particles_quake.integer); + M_Options_PrintSlider( " Particles Quality", true, cl_particles_quality.value, 1, 4); + M_Options_PrintCheckbox(" Explosion Shell", true, cl_particles_explosions_shell.integer); + M_Options_PrintCheckbox(" Explosion Shell Clip", true, r_explosionclip.integer); + M_Options_PrintCheckbox(" Stainmaps", true, cl_stainmaps.integer); + M_Options_PrintCheckbox("Onload Clear Stainmaps", true, cl_stainmaps_clearonload.integer); + M_Options_PrintCheckbox(" Decals", false, cl_decals.integer); + M_Options_PrintCheckbox(" Bullet Impacts", true, cl_particles_bulletimpacts.integer); + M_Options_PrintCheckbox(" Smoke", true, cl_particles_smoke.integer); + M_Options_PrintCheckbox(" Sparks", true, cl_particles_sparks.integer); + M_Options_PrintCheckbox(" Bubbles", true, cl_particles_bubbles.integer); + M_Options_PrintCheckbox(" Blood", true, cl_particles_blood.integer); + M_Options_PrintSlider( " Blood Opacity", true, cl_particles_blood_alpha.value, 0.2, 1); + M_Options_PrintCheckbox("Force New Blood Effect", true, cl_particles_blood_bloodhack.integer); + M_Options_PrintCheckbox(" Polygon Lightning", true, cl_beams_polygons.integer); + M_Options_PrintCheckbox("Smooth Sweep Lightning", true, cl_beams_instantaimhack.integer); + M_Options_PrintCheckbox(" Waist-level Lightning", true, cl_beams_quakepositionhack.integer); + M_Options_PrintCheckbox(" Lightning End Light", true, cl_beams_lightatend.integer); + M_Options_PrintSlider( " Lightning Thickness", cl_beams_polygons.integer, r_lightningbeam_thickness.integer, 1, 10); + M_Options_PrintSlider( " Lightning Scroll", cl_beams_polygons.integer, r_lightningbeam_scroll.integer, 0, 10); + M_Options_PrintSlider( " Lightning Repeat Dist", cl_beams_polygons.integer, r_lightningbeam_repeatdistance.integer, 64, 1024); + M_Options_PrintSlider( " Lightning Color Red", cl_beams_polygons.integer, r_lightningbeam_color_red.value, 0, 1); + M_Options_PrintSlider( " Lightning Color Green", cl_beams_polygons.integer, r_lightningbeam_color_green.value, 0, 1); + M_Options_PrintSlider( " Lightning Color Blue", cl_beams_polygons.integer, r_lightningbeam_color_blue.value, 0, 1); + M_Options_PrintCheckbox(" Lightning QMB Texture", cl_beams_polygons.integer, r_lightningbeam_qmbtexture.integer); + M_Options_PrintCheckbox(" Model Interpolation", true, r_lerpmodels.integer); + M_Options_PrintCheckbox(" Sprite Interpolation", true, r_lerpsprites.integer); + M_Options_PrintCheckbox(" Flicker Interpolation", true, r_lerplightstyles.integer); + M_Options_PrintSlider( " View Blend", true, gl_polyblend.value, 0, 1); + M_Options_PrintSlider( "Upper Sky Scroll Speed", true, r_skyscroll1.value, -8, 8); + M_Options_PrintSlider( "Lower Sky Scroll Speed", true, r_skyscroll2.value, -8, 8); + M_Options_PrintSlider( " Underwater View Warp", true, r_waterwarp.value, 0, 1); + M_Options_PrintSlider( " Water Alpha (opacity)", true, r_wateralpha.value, 0, 1); + M_Options_PrintSlider( " Water Movement", true, r_waterscroll.value, 0, 10); +} + + +static void M_Options_Effects_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Options_f (); + break; + case K_MOUSE1: + case K_ENTER: + M_Menu_Options_Effects_AdjustSliders (1); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_effects_cursor--; + if (options_effects_cursor < 0) + options_effects_cursor = OPTIONS_EFFECTS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_effects_cursor++; + if (options_effects_cursor >= OPTIONS_EFFECTS_ITEMS) + options_effects_cursor = 0; + break; + + case K_LEFTARROW: + case 'a': + M_Menu_Options_Effects_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + case 'd': + M_Menu_Options_Effects_AdjustSliders (1); + break; + } +} + + +#define OPTIONS_GRAPHICS_ITEMS 21 + +static int options_graphics_cursor; + +void M_Menu_Options_Graphics_f (void) +{ + key_dest = key_menu; + m_state = m_options_graphics; + m_entersound = true; +} + +extern cvar_t r_shadow_gloss; +extern cvar_t r_shadow_realtime_dlight; +extern cvar_t r_shadow_realtime_dlight_shadows; +extern cvar_t r_shadow_realtime_world; +extern cvar_t r_shadow_realtime_world_lightmaps; +extern cvar_t r_shadow_realtime_world_shadows; +extern cvar_t r_bloom; +extern cvar_t r_bloom_colorscale; +extern cvar_t r_bloom_colorsubtract; +extern cvar_t r_bloom_colorexponent; +extern cvar_t r_bloom_blur; +extern cvar_t r_bloom_brighten; +extern cvar_t r_bloom_resolution; +extern cvar_t r_hdr_scenebrightness; +extern cvar_t r_hdr_glowintensity; +extern cvar_t gl_picmip; + +static void M_Menu_Options_Graphics_AdjustSliders (int dir) +{ + int optnum; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 0; + + if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&cl_autocentreoffset, bound(-200, cl_autocentreoffset.integer + dir * 5, 200)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_coronas, bound(0, r_coronas.value + dir * 0.125, 4)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&gl_flashblend, !gl_flashblend.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_gloss, bound(0, r_shadow_gloss.integer + dir, 2)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_dlight, !r_shadow_realtime_dlight.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_dlight_shadows, !r_shadow_realtime_dlight_shadows.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world, !r_shadow_realtime_world.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world_lightmaps, bound(0, r_shadow_realtime_world_lightmaps.value + dir * 0.1, 1)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world_shadows, !r_shadow_realtime_world_shadows.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom, !r_bloom.integer); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_scenebrightness, bound(0.25, r_hdr_scenebrightness.value + dir * 0.125, 4)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_glowintensity, bound(0, r_hdr_glowintensity.value + dir * 0.25, 4)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorscale, bound(0.0625, r_bloom_colorscale.value + dir * 0.0625, 1)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorsubtract, bound(0, r_bloom_colorsubtract.value + dir * 0.0625, 1-0.0625)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorexponent, bound(1, r_bloom_colorexponent.value * (dir > 0 ? 2.0 : 0.5), 8)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_brighten, bound(1, r_bloom_brighten.value + dir * 0.0625, 4)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_blur, bound(1, r_bloom_blur.value + dir * 1, 16)); + else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_resolution, bound(64, r_bloom_resolution.value + dir * 64, 2048)); + else if (options_graphics_cursor == optnum++) Cbuf_AddText ("r_restart\n"); +} + + +static void M_Options_Graphics_Draw (void) +{ + int visible; + cachepic_t *p; + + M_Background(320, bound(200, 32 + OPTIONS_GRAPHICS_ITEMS * 8, vid_conheight.integer)); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optcursor = options_graphics_cursor; + optnum = 0; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, OPTIONS_GRAPHICS_ITEMS - visible)) * 8; + + M_Options_PrintSlider( " Lens Centre Offset", true, cl_autocentreoffset.integer, -200, 200); + M_Options_PrintSlider( " Corona Intensity", true, r_coronas.value, 0, 4); + M_Options_PrintCheckbox(" Use Only Coronas", true, gl_flashblend.integer); + M_Options_PrintSlider( " Gloss Mode", true, r_shadow_gloss.integer, 0, 2); + M_Options_PrintCheckbox(" RT DLights", !gl_flashblend.integer, r_shadow_realtime_dlight.integer); + M_Options_PrintCheckbox(" RT DLight Shadows", !gl_flashblend.integer, r_shadow_realtime_dlight_shadows.integer); + M_Options_PrintCheckbox(" RT World", true, r_shadow_realtime_world.integer); + M_Options_PrintSlider( " RT World Lightmaps", true, r_shadow_realtime_world_lightmaps.value, 0, 1); + M_Options_PrintCheckbox(" RT World Shadow", true, r_shadow_realtime_world_shadows.integer); + M_Options_PrintCheckbox(" Bloom Effect", true, r_bloom.integer); + M_Options_PrintSlider( " Scene Brightness", true, r_hdr_scenebrightness.value, 0.25, 4); + M_Options_PrintSlider( " Glow Brightness", true, r_hdr_glowintensity.value, 0, 4); + M_Options_PrintSlider( " Bloom Color Scale", r_bloom.integer, r_bloom_colorscale.value, 0.0625, 1); + M_Options_PrintSlider( " Bloom Color Subtract", r_bloom.integer, r_bloom_colorsubtract.value, 0, 1-0.0625); + M_Options_PrintSlider( " Bloom Color Exponent", r_bloom.integer, r_bloom_colorexponent.value, 1, 8); + M_Options_PrintSlider( " Bloom Intensity", r_bloom.integer, r_bloom_brighten.value, 1, 4); + M_Options_PrintSlider( " Bloom Blur", r_bloom.integer, r_bloom_blur.value, 1, 16); + M_Options_PrintSlider( " Bloom Resolution", r_bloom.integer, r_bloom_resolution.value, 64, 2048); + M_Options_PrintCommand( " Restart Renderer", true); +} + + +static void M_Options_Graphics_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Options_f (); + break; + case K_MOUSE1: + case K_ENTER: + M_Menu_Options_Graphics_AdjustSliders (1); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_graphics_cursor--; + if (options_graphics_cursor < 0) + options_graphics_cursor = OPTIONS_GRAPHICS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_graphics_cursor++; + if (options_graphics_cursor >= OPTIONS_GRAPHICS_ITEMS) + options_graphics_cursor = 0; + break; + + case K_LEFTARROW: + case 'a': + M_Menu_Options_Graphics_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + case 'd': + M_Menu_Options_Graphics_AdjustSliders (1); + break; + } +} + + +#define OPTIONS_COLORCONTROL_ITEMS 18 + +static int options_colorcontrol_cursor; + +// intensity value to match up to 50% dither to 'correct' quake +static cvar_t menu_options_colorcontrol_correctionvalue = {0, "menu_options_colorcontrol_correctionvalue", "0.5", "intensity value that matches up to white/black dither pattern, should be 0.5 for linear color"}; + +void M_Menu_Options_ColorControl_f (void) +{ + key_dest = key_menu; + m_state = m_options_colorcontrol; + m_entersound = true; +} + + +static void M_Menu_Options_ColorControl_AdjustSliders (int dir) +{ + int optnum; + float f; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 1; + if (options_colorcontrol_cursor == optnum++) + Cvar_SetValueQuick (&v_hwgamma, !v_hwgamma.integer); + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 0); + Cvar_SetValueQuick (&v_gamma, bound(1, v_gamma.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 0); + Cvar_SetValueQuick (&v_contrast, bound(1, v_contrast.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 0); + Cvar_SetValueQuick (&v_brightness, bound(0, v_brightness.value + dir * 0.05, 0.8)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, !v_color_enable.integer); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_black_r, bound(0, v_color_black_r.value + dir * 0.0125, 0.8)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_black_g, bound(0, v_color_black_g.value + dir * 0.0125, 0.8)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_black_b, bound(0, v_color_black_b.value + dir * 0.0125, 0.8)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + f = bound(0, (v_color_black_r.value + v_color_black_g.value + v_color_black_b.value) / 3 + dir * 0.0125, 0.8); + Cvar_SetValueQuick (&v_color_black_r, f); + Cvar_SetValueQuick (&v_color_black_g, f); + Cvar_SetValueQuick (&v_color_black_b, f); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_grey_r, bound(0, v_color_grey_r.value + dir * 0.0125, 0.95)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_grey_g, bound(0, v_color_grey_g.value + dir * 0.0125, 0.95)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_grey_b, bound(0, v_color_grey_b.value + dir * 0.0125, 0.95)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + f = bound(0, (v_color_grey_r.value + v_color_grey_g.value + v_color_grey_b.value) / 3 + dir * 0.0125, 0.95); + Cvar_SetValueQuick (&v_color_grey_r, f); + Cvar_SetValueQuick (&v_color_grey_g, f); + Cvar_SetValueQuick (&v_color_grey_b, f); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_white_r, bound(1, v_color_white_r.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_white_g, bound(1, v_color_white_g.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + Cvar_SetValueQuick (&v_color_white_b, bound(1, v_color_white_b.value + dir * 0.125, 5)); + } + else if (options_colorcontrol_cursor == optnum++) + { + Cvar_SetValueQuick (&v_color_enable, 1); + f = bound(1, (v_color_white_r.value + v_color_white_g.value + v_color_white_b.value) / 3 + dir * 0.125, 5); + Cvar_SetValueQuick (&v_color_white_r, f); + Cvar_SetValueQuick (&v_color_white_g, f); + Cvar_SetValueQuick (&v_color_white_b, f); + } +} + +static void M_Options_ColorControl_Draw (void) +{ + int visible; + float x, c, s, t, u, v; + cachepic_t *p, *dither; + + dither = Draw_CachePic_Flags ("gfx/colorcontrol/ditherpattern", CACHEPICFLAG_NOCLAMP); + + M_Background(320, 256); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optcursor = options_colorcontrol_cursor; + optnum = 0; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, OPTIONS_COLORCONTROL_ITEMS - visible)) * 8; + + M_Options_PrintCommand( " Reset to defaults", true); + M_Options_PrintCheckbox("Hardware Gamma Control", vid_hardwaregammasupported.integer, v_hwgamma.integer); + M_Options_PrintSlider( " Gamma", !v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_gamma.value, 1, 5); + M_Options_PrintSlider( " Contrast", !v_color_enable.integer, v_contrast.value, 1, 5); + M_Options_PrintSlider( " Brightness", !v_color_enable.integer, v_brightness.value, 0, 0.8); + M_Options_PrintCheckbox(" Color Level Controls", true, v_color_enable.integer); + M_Options_PrintSlider( " Black: Red ", v_color_enable.integer, v_color_black_r.value, 0, 0.8); + M_Options_PrintSlider( " Black: Green", v_color_enable.integer, v_color_black_g.value, 0, 0.8); + M_Options_PrintSlider( " Black: Blue ", v_color_enable.integer, v_color_black_b.value, 0, 0.8); + M_Options_PrintSlider( " Black: Grey ", v_color_enable.integer, (v_color_black_r.value + v_color_black_g.value + v_color_black_b.value) / 3, 0, 0.8); + M_Options_PrintSlider( " Grey: Red ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_r.value, 0, 0.95); + M_Options_PrintSlider( " Grey: Green", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_g.value, 0, 0.95); + M_Options_PrintSlider( " Grey: Blue ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_b.value, 0, 0.95); + M_Options_PrintSlider( " Grey: Grey ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, (v_color_grey_r.value + v_color_grey_g.value + v_color_grey_b.value) / 3, 0, 0.95); + M_Options_PrintSlider( " White: Red ", v_color_enable.integer, v_color_white_r.value, 1, 5); + M_Options_PrintSlider( " White: Green", v_color_enable.integer, v_color_white_g.value, 1, 5); + M_Options_PrintSlider( " White: Blue ", v_color_enable.integer, v_color_white_b.value, 1, 5); + M_Options_PrintSlider( " White: Grey ", v_color_enable.integer, (v_color_white_r.value + v_color_white_g.value + v_color_white_b.value) / 3, 1, 5); + + opty += 4; + DrawQ_Fill(menu_x, menu_y + opty, 320, 4 + 64 + 8 + 64 + 4, 0, 0, 0, 1, 0);opty += 4; + s = (float) 312 / 2 * vid.width / vid_conwidth.integer; + t = (float) 4 / 2 * vid.height / vid_conheight.integer; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, dither, 312, 4, 0,0, 1,0,0,1, s,0, 1,0,0,1, 0,t, 1,0,0,1, s,t, 1,0,0,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 1,0,0,1, 0,1, 0,0,0,1, 1,1, 1,0,0,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, dither, 312, 4, 0,0, 0,1,0,1, s,0, 0,1,0,1, 0,t, 0,1,0,1, s,t, 0,1,0,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 0,1,0,1, 0,1, 0,0,0,1, 1,1, 0,1,0,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, dither, 312, 4, 0,0, 0,0,1,1, s,0, 0,0,1,1, 0,t, 0,0,1,1, s,t, 0,0,1,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 0,0,1,1, 0,1, 0,0,0,1, 1,1, 0,0,1,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, dither, 312, 4, 0,0, 1,1,1,1, s,0, 1,1,1,1, 0,t, 1,1,1,1, s,t, 1,1,1,1, 0);opty += 4; + DrawQ_SuperPic(menu_x + 4, menu_y + opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 1,1,1,1, 0,1, 0,0,0,1, 1,1, 1,1,1,1, 0);opty += 4; + + c = menu_options_colorcontrol_correctionvalue.value; // intensity value that should be matched up to a 50% dither to 'correct' quake + s = (float) 48 / 2 * vid.width / vid_conwidth.integer; + t = (float) 48 / 2 * vid.height / vid_conheight.integer; + u = s * 0.5; + v = t * 0.5; + opty += 8; + x = 4; + DrawQ_Fill(menu_x + x, menu_y + opty, 64, 48, c, 0, 0, 1, 0); + DrawQ_SuperPic(menu_x + x + 16, menu_y + opty + 16, dither, 16, 16, 0,0, 1,0,0,1, s,0, 1,0,0,1, 0,t, 1,0,0,1, s,t, 1,0,0,1, 0); + DrawQ_SuperPic(menu_x + x + 32, menu_y + opty + 16, dither, 16, 16, 0,0, 1,0,0,1, u,0, 1,0,0,1, 0,v, 1,0,0,1, u,v, 1,0,0,1, 0); + x += 80; + DrawQ_Fill(menu_x + x, menu_y + opty, 64, 48, 0, c, 0, 1, 0); + DrawQ_SuperPic(menu_x + x + 16, menu_y + opty + 16, dither, 16, 16, 0,0, 0,1,0,1, s,0, 0,1,0,1, 0,t, 0,1,0,1, s,t, 0,1,0,1, 0); + DrawQ_SuperPic(menu_x + x + 32, menu_y + opty + 16, dither, 16, 16, 0,0, 0,1,0,1, u,0, 0,1,0,1, 0,v, 0,1,0,1, u,v, 0,1,0,1, 0); + x += 80; + DrawQ_Fill(menu_x + x, menu_y + opty, 64, 48, 0, 0, c, 1, 0); + DrawQ_SuperPic(menu_x + x + 16, menu_y + opty + 16, dither, 16, 16, 0,0, 0,0,1,1, s,0, 0,0,1,1, 0,t, 0,0,1,1, s,t, 0,0,1,1, 0); + DrawQ_SuperPic(menu_x + x + 32, menu_y + opty + 16, dither, 16, 16, 0,0, 0,0,1,1, u,0, 0,0,1,1, 0,v, 0,0,1,1, u,v, 0,0,1,1, 0); + x += 80; + DrawQ_Fill(menu_x + x, menu_y + opty, 64, 48, c, c, c, 1, 0); + DrawQ_SuperPic(menu_x + x + 16, menu_y + opty + 16, dither, 16, 16, 0,0, 1,1,1,1, s,0, 1,1,1,1, 0,t, 1,1,1,1, s,t, 1,1,1,1, 0); + DrawQ_SuperPic(menu_x + x + 32, menu_y + opty + 16, dither, 16, 16, 0,0, 1,1,1,1, u,0, 1,1,1,1, 0,v, 1,1,1,1, u,v, 1,1,1,1, 0); +} + + +static void M_Options_ColorControl_Key (int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + M_Menu_Options_f (); + break; + case K_MOUSE1: + case K_ENTER: + m_entersound = true; + switch (options_colorcontrol_cursor) + { + case 0: + Cvar_SetValueQuick(&v_hwgamma, 1); + Cvar_SetValueQuick(&v_gamma, 1); + Cvar_SetValueQuick(&v_contrast, 1); + Cvar_SetValueQuick(&v_brightness, 0); + Cvar_SetValueQuick(&v_color_enable, 0); + Cvar_SetValueQuick(&v_color_black_r, 0); + Cvar_SetValueQuick(&v_color_black_g, 0); + Cvar_SetValueQuick(&v_color_black_b, 0); + Cvar_SetValueQuick(&v_color_grey_r, 0); + Cvar_SetValueQuick(&v_color_grey_g, 0); + Cvar_SetValueQuick(&v_color_grey_b, 0); + Cvar_SetValueQuick(&v_color_white_r, 1); + Cvar_SetValueQuick(&v_color_white_g, 1); + Cvar_SetValueQuick(&v_color_white_b, 1); + break; + default: + M_Menu_Options_ColorControl_AdjustSliders (1); + break; + } + return; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_colorcontrol_cursor--; + if (options_colorcontrol_cursor < 0) + options_colorcontrol_cursor = OPTIONS_COLORCONTROL_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + options_colorcontrol_cursor++; + if (options_colorcontrol_cursor >= OPTIONS_COLORCONTROL_ITEMS) + options_colorcontrol_cursor = 0; + break; + + case K_LEFTARROW: + case 'a': + M_Menu_Options_ColorControl_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + case 'd': + M_Menu_Options_ColorControl_AdjustSliders (1); + break; + } +} + + +//============================================================================= +/* KEYS MENU */ + +static const char *quakebindnames[][2] = +{ +{"+attack", "attack"}, +{"impulse 10", "next weapon"}, +{"impulse 12", "previous weapon"}, +{"+jump", "jump / swim up"}, +{"+forward", "walk forward"}, +{"+back", "backpedal"}, +{"+left", "turn left"}, +{"+right", "turn right"}, +{"+speed", "run"}, +{"+moveleft", "step left"}, +{"+moveright", "step right"}, +{"+strafe", "sidestep"}, +{"+lookup", "look up"}, +{"+lookdown", "look down"}, +{"centerview", "center view"}, +{"+mlook", "mouse look"}, +{"+klook", "keyboard look"}, +{"+moveup", "swim up"}, +{"+movedown", "swim down"} +}; + +static const char *transfusionbindnames[][2] = +{ +{"", "Movement"}, // Movement commands +{"+forward", "walk forward"}, +{"+back", "backpedal"}, +{"+left", "turn left"}, +{"+right", "turn right"}, +{"+moveleft", "step left"}, +{"+moveright", "step right"}, +{"+jump", "jump / swim up"}, +{"+movedown", "swim down"}, +{"", "Combat"}, // Combat commands +{"impulse 1", "Pitch Fork"}, +{"impulse 2", "Flare Gun"}, +{"impulse 3", "Shotgun"}, +{"impulse 4", "Machine Gun"}, +{"impulse 5", "Incinerator"}, +{"impulse 6", "Bombs (TNT)"}, +{"impulse 35", "Proximity Bomb"}, +{"impulse 36", "Remote Detonator"}, +{"impulse 7", "Aerosol Can"}, +{"impulse 8", "Tesla Cannon"}, +{"impulse 9", "Life Leech"}, +{"impulse 10", "Voodoo Doll"}, +{"impulse 21", "next weapon"}, +{"impulse 22", "previous weapon"}, +{"+attack", "attack"}, +{"+button3", "altfire"}, +{"", "Inventory"}, // Inventory commands +{"impulse 40", "Dr.'s Bag"}, +{"impulse 41", "Crystal Ball"}, +{"impulse 42", "Beast Vision"}, +{"impulse 43", "Jump Boots"}, +{"impulse 23", "next item"}, +{"impulse 24", "previous item"}, +{"impulse 25", "use item"}, +{"", "Misc"}, // Misc commands +{"+button4", "use"}, +{"impulse 50", "add bot (red)"}, +{"impulse 51", "add bot (blue)"}, +{"impulse 52", "kick a bot"}, +{"impulse 26", "next armor type"}, +{"impulse 27", "identify player"}, +{"impulse 55", "voting menu"}, +{"impulse 56", "observer mode"}, +{"", "Taunts"}, // Taunts +{"impulse 70", "taunt 0"}, +{"impulse 71", "taunt 1"}, +{"impulse 72", "taunt 2"}, +{"impulse 73", "taunt 3"}, +{"impulse 74", "taunt 4"}, +{"impulse 75", "taunt 5"}, +{"impulse 76", "taunt 6"}, +{"impulse 77", "taunt 7"}, +{"impulse 78", "taunt 8"}, +{"impulse 79", "taunt 9"} +}; + +static const char *goodvsbad2bindnames[][2] = +{ +{"impulse 69", "Power 1"}, +{"impulse 70", "Power 2"}, +{"impulse 71", "Power 3"}, +{"+jump", "jump / swim up"}, +{"+forward", "walk forward"}, +{"+back", "backpedal"}, +{"+left", "turn left"}, +{"+right", "turn right"}, +{"+speed", "run"}, +{"+moveleft", "step left"}, +{"+moveright", "step right"}, +{"+strafe", "sidestep"}, +{"+lookup", "look up"}, +{"+lookdown", "look down"}, +{"centerview", "center view"}, +{"+mlook", "mouse look"}, +{"kill", "kill yourself"}, +{"+moveup", "swim up"}, +{"+movedown", "swim down"} +}; + +static int numcommands; +static const char *(*bindnames)[2]; + +/* +typedef struct binditem_s +{ + char *command, *description; + struct binditem_s *next; +} +binditem_t; + +typedef struct bindcategory_s +{ + char *name; + binditem_t *binds; + struct bindcategory_s *next; +} +bindcategory_t; + +static bindcategory_t *bindcategories = NULL; + +static void M_ClearBinds (void) +{ + for (c = bindcategories;c;c = cnext) + { + cnext = c->next; + for (b = c->binds;b;b = bnext) + { + bnext = b->next; + Z_Free(b); + } + Z_Free(c); + } + bindcategories = NULL; +} + +static void M_AddBindToCategory(bindcategory_t *c, char *command, char *description) +{ + for (b = &c->binds;*b;*b = &(*b)->next); + *b = Z_Alloc(sizeof(binditem_t) + strlen(command) + 1 + strlen(description) + 1); + *b->command = (char *)((*b) + 1); + *b->description = *b->command + strlen(command) + 1; + strlcpy(*b->command, command, strlen(command) + 1); + strlcpy(*b->description, description, strlen(description) + 1); +} + +static void M_AddBind (char *category, char *command, char *description) +{ + for (c = &bindcategories;*c;c = &(*c)->next) + { + if (!strcmp(category, (*c)->name)) + { + M_AddBindToCategory(*c, command, description); + return; + } + } + *c = Z_Alloc(sizeof(bindcategory_t)); + M_AddBindToCategory(*c, command, description); +} + +static void M_DefaultBinds (void) +{ + M_ClearBinds(); + M_AddBind("movement", "+jump", "jump / swim up"); + M_AddBind("movement", "+forward", "walk forward"); + M_AddBind("movement", "+back", "backpedal"); + M_AddBind("movement", "+left", "turn left"); + M_AddBind("movement", "+right", "turn right"); + M_AddBind("movement", "+speed", "run"); + M_AddBind("movement", "+moveleft", "step left"); + M_AddBind("movement", "+moveright", "step right"); + M_AddBind("movement", "+strafe", "sidestep"); + M_AddBind("movement", "+lookup", "look up"); + M_AddBind("movement", "+lookdown", "look down"); + M_AddBind("movement", "centerview", "center view"); + M_AddBind("movement", "+mlook", "mouse look"); + M_AddBind("movement", "+klook", "keyboard look"); + M_AddBind("movement", "+moveup", "swim up"); + M_AddBind("movement", "+movedown", "swim down"); + M_AddBind("weapons", "+attack", "attack"); + M_AddBind("weapons", "impulse 10", "next weapon"); + M_AddBind("weapons", "impulse 12", "previous weapon"); + M_AddBind("weapons", "impulse 1", "select weapon 1 (axe)"); + M_AddBind("weapons", "impulse 2", "select weapon 2 (shotgun)"); + M_AddBind("weapons", "impulse 3", "select weapon 3 (super )"); + M_AddBind("weapons", "impulse 4", "select weapon 4 (nailgun)"); + M_AddBind("weapons", "impulse 5", "select weapon 5 (super nailgun)"); + M_AddBind("weapons", "impulse 6", "select weapon 6 (grenade launcher)"); + M_AddBind("weapons", "impulse 7", "select weapon 7 (rocket launcher)"); + M_AddBind("weapons", "impulse 8", "select weapon 8 (lightning gun)"); +} +*/ + + +static int keys_cursor; +static int bind_grab; + +void M_Menu_Keys_f (void) +{ + key_dest = key_menu_grabbed; + m_state = m_keys; + m_entersound = true; + + if (gamemode == GAME_TRANSFUSION) + { + numcommands = sizeof(transfusionbindnames) / sizeof(transfusionbindnames[0]); + bindnames = transfusionbindnames; + } + else if (gamemode == GAME_GOODVSBAD2) + { + numcommands = sizeof(goodvsbad2bindnames) / sizeof(goodvsbad2bindnames[0]); + bindnames = goodvsbad2bindnames; + } + else + { + numcommands = sizeof(quakebindnames) / sizeof(quakebindnames[0]); + bindnames = quakebindnames; + } + + // Make sure "keys_cursor" doesn't start on a section in the binding list + keys_cursor = 0; + while (bindnames[keys_cursor][0][0] == '\0') + { + keys_cursor++; + + // Only sections? There may be a problem somewhere... + if (keys_cursor >= numcommands) + Sys_Error ("M_Init: The key binding list only contains sections"); + } +} + +#define NUMKEYS 5 + +static void M_UnbindCommand (const char *command) +{ + int j; + const char *b; + + for (j = 0; j < (int)sizeof (keybindings[0]) / (int)sizeof (keybindings[0][0]); j++) + { + b = keybindings[0][j]; + if (!b) + continue; + if (!strcmp (b, command)) + Key_SetBinding (j, 0, ""); + } +} + + +static void M_Keys_Draw (void) +{ + int i, j; + int keys[NUMKEYS]; + int y; + cachepic_t *p; + char keystring[MAX_INPUTLINE]; + + M_Background(320, 48 + 8 * numcommands); + + p = Draw_CachePic ("gfx/ttl_cstm"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_cstm"); + + if (bind_grab) + M_Print(12, 32, "Press a key or button for this action"); + else + M_Print(18, 32, "Enter to change, backspace to clear"); + +// search for known bindings + for (i=0 ; i 0) + strlcat(keystring, " or ", sizeof(keystring)); + strlcat(keystring, Key_KeynumToString (keys[j], tinystr, sizeof(tinystr)), sizeof(keystring)); + } + } + } + M_Print(150, y, keystring); + } + + if (bind_grab) + M_DrawCharacter (140, 48 + keys_cursor*8, '='); + else + M_DrawCharacter (140, 48 + keys_cursor*8, 12+((int)(realtime*4)&1)); +} + + +static void M_Keys_Key (int k, int ascii) +{ + char cmd[80]; + int keys[NUMKEYS]; + char tinystr[2]; + + if (bind_grab) + { // defining a key + S_LocalSound ("sound/misc/menu1.wav"); + if (k == K_ESCAPE) + { + bind_grab = false; + } + else //if (k != '`') + { + dpsnprintf (cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString (k, tinystr, sizeof(tinystr)), bindnames[keys_cursor][0]); + Cbuf_InsertText (cmd); + } + + bind_grab = false; + return; + } + + switch (k) + { + case K_ESCAPE: + M_Menu_Options_f (); + break; + + case K_LEFTARROW: + case 'a': + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + do + { + keys_cursor--; + if (keys_cursor < 0) + keys_cursor = numcommands-1; + } + while (bindnames[keys_cursor][0][0] == '\0'); // skip sections + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + case 'd': + S_LocalSound ("sound/misc/menu1.wav"); + do + { + keys_cursor++; + if (keys_cursor >= numcommands) + keys_cursor = 0; + } + while (bindnames[keys_cursor][0][0] == '\0'); // skip sections + break; + case K_MOUSE1: + case K_ENTER: // go into bind mode + Key_FindKeysForCommand (bindnames[keys_cursor][0], keys, NUMKEYS, 0); + S_LocalSound ("sound/misc/menu2.wav"); + if (keys[NUMKEYS - 1] != -1) + M_UnbindCommand (bindnames[keys_cursor][0]); + bind_grab = true; + break; + + case K_BACKSPACE: // delete bindings + case K_DEL: // delete bindings + S_LocalSound ("sound/misc/menu2.wav"); + M_UnbindCommand (bindnames[keys_cursor][0]); + break; + } +} + +void M_Menu_Reset_f (void) +{ + key_dest = key_menu; + m_state = m_reset; + m_entersound = true; +} + + +static void M_Reset_Key (int key, int ascii) +{ + switch (key) + { + case 'Y': + case 'y': + jni_BigScreenMode(0); + Cbuf_AddText ("cvar_resettodefaults_all;exec default.cfg\n"); + // no break here since we also exit the menu + + case K_ESCAPE: + case 'n': + case 'N': + m_state = m_options; + m_entersound = true; + break; + + default: + break; + } +} + +static void M_Reset_Draw (void) +{ + int lines = 2, linelength = 20; + M_Background(linelength * 8 + 16, lines * 8 + 16); + M_DrawTextBox(0, 0, linelength, lines); + M_Print(8 + 4 * (linelength - 19), 8, "Really wanna reset?"); + M_Print(8 + 4 * (linelength - 11), 16, "Press y / n"); +} + +#define YAWCONTROL_ITEMS 5 + +static int yawpitchcontrol_cursor; + +void M_Menu_YawPitchControl_f (void) +{ + key_dest = key_menu; + m_state = m_yawpitchcontrol; + m_entersound = true; +} + +static void M_Menu_YawPitchControl_AdjustSliders (int dir) +{ + int optnum; + S_LocalSound ("sound/misc/menu3.wav"); + + optnum = 0; + + if (yawpitchcontrol_cursor == optnum++) ; + else if (yawpitchcontrol_cursor == optnum++) ; + else if (yawpitchcontrol_cursor == optnum++) ; + else if (yawpitchcontrol_cursor == optnum++ && cl_yawmode.integer == 1) + { + float value = 45.0f; + if (dir == 1) + { + if (cl_comfort.value == 30.0f) + value = 45.0f; + else if (cl_comfort.value == 45.0f) + value = 60.0f; + else if (cl_comfort.value == 60.0f) + value = 90.0f; + else if (cl_comfort.value == 90.0f) + value = 180.0f; + else if (cl_comfort.value == 180.0f) + value = 30.0f; + } + else + { + if (cl_comfort.value == 30.0f) + value = 180.0f; + else if (cl_comfort.value == 180.0f) + value = 90.0f; + else if (cl_comfort.value == 90.0f) + value = 60.0f; + else if (cl_comfort.value == 60.0f) + value = 45.0f; + else if (cl_comfort.value == 45.0f) + value = 30.0f; + } + + Cvar_SetValueQuick (&cl_comfort, value); + } + else if (yawpitchcontrol_cursor == optnum++ && cl_yawmode.integer == 2) + Cvar_SetValueQuick (&sensitivity, bound(1, (sensitivity.value + (dir * 0.25)), 10)); +} + +static void M_Menu_YawPitchControl_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (++yawpitchcontrol_cursor >= YAWCONTROL_ITEMS) + yawpitchcontrol_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + if (--yawpitchcontrol_cursor < 0) + yawpitchcontrol_cursor = YAWCONTROL_ITEMS - 1; + break; + + case 'a': + case K_LEFTARROW: + if (yawpitchcontrol_cursor == 0) + { + headtracking = !headtracking; + } + else if (yawpitchcontrol_cursor == 1) + { + int newPitchMode = cl_pitchmode.integer; + if (--newPitchMode < 0) + newPitchMode = 2; + Cvar_SetValueQuick (&cl_pitchmode, newPitchMode); + } + else if (yawpitchcontrol_cursor == 2) + { + int newYawMode = cl_yawmode.integer; + if (--newYawMode < 0) + newYawMode = 3; + + Cvar_SetValueQuick (&cl_yawmode, newYawMode); + } + else + M_Menu_YawPitchControl_AdjustSliders(-1); + break; + + case 'd': + case K_RIGHTARROW: + if (yawpitchcontrol_cursor == 0) + { + headtracking = !headtracking; + } + else if (yawpitchcontrol_cursor == 1) + { + int newPitchMode = cl_pitchmode.integer; + if (++newPitchMode > 2) + newPitchMode = 0; + Cvar_SetValueQuick (&cl_pitchmode, newPitchMode); + } + else if (yawpitchcontrol_cursor == 2) + { + int newYawMode = cl_yawmode.integer; + if (++newYawMode > 3) + newYawMode = 0; + + Cvar_SetValueQuick (&cl_yawmode, newYawMode); + } + else + M_Menu_YawPitchControl_AdjustSliders(1); + break; + + default: + break; + } +} + +static void M_Menu_YawPitchControl_Draw (void) +{ + int visible; + cachepic_t *p; + + M_Background(320, bound(200, 32 + OPTIONS_ITEMS * 8, vid_conheight.integer)); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); + + optnum = 0; + optcursor = yawpitchcontrol_cursor; + visible = (int)((menu_height - 32) / 8); + opty = 32 - bound(0, optcursor - (visible >> 1), max(0, YAWCONTROL_ITEMS - visible)) * 8; + + if (!headtracking) + M_Options_PrintCommand(" Sensor Headtracking: Disabled", true); + else + M_Options_PrintCommand(" Sensor Headtracking: Enabled", true); + + if (cl_pitchmode.integer == 0) + M_Options_PrintCommand(" Pitch Mode: Head-tracked Only (default)", true); + else if (cl_pitchmode.integer == 1) + M_Options_PrintCommand(" Pitch Mode: Free", true); + else if (cl_pitchmode.integer == 2) + M_Options_PrintCommand(" Pitch Mode: Free (inverted)", true); + + if (cl_yawmode.integer == 0) + M_Options_PrintCommand(" Yaw Mode: Swivel-Chair", true); + else if (cl_yawmode.integer == 1) + M_Options_PrintCommand(" Yaw Mode: Comfort-Mode", true); + else if (cl_yawmode.integer == 2) + M_Options_PrintCommand(" Yaw Mode: Stick-Yaw", true); + else + M_Options_PrintCommand(" Yaw Mode: Look-To-Turn", true); + + M_Options_PrintSlider( "Comfort Mode Turn Angle", cl_yawmode.integer == 1, cl_comfort.value, 30, 180); + M_Options_PrintSlider( " Stick Yaw Turn Speed", cl_yawmode.integer == 2, sensitivity.value, 1, 10); + if (cl_yawmode.integer >= 2) + { + M_Options_PrintCommand(" ", true); + M_Options_PrintCommand(" ", true); + M_Options_PrintCommand("WARNING: Yaw rotation", true); + M_Options_PrintCommand("can induce severe nausea in ", true); + M_Options_PrintCommand("those not used to it.", true); + M_Options_PrintCommand(" ", true); + M_Options_PrintCommand("* Use this mode at your own risk! *", true); + } +} + +//============================================================================= +/* VIDEO MENU */ + +video_resolution_t video_resolutions_hardcoded[] = +{ +{"Standard 4x3" , 320, 240, 320, 240, 1 }, +{"Standard 4x3" , 400, 300, 400, 300, 1 }, +{"Standard 4x3" , 512, 384, 512, 384, 1 }, +{"Standard 4x3" , 640, 480, 640, 480, 1 }, +{"Standard 4x3" , 800, 600, 640, 480, 1 }, +{"Standard 4x3" , 1024, 768, 640, 480, 1 }, +{"Standard 4x3" , 1152, 864, 640, 480, 1 }, +{"Standard 4x3" , 1280, 960, 640, 480, 1 }, +{"Standard 4x3" , 1400,1050, 640, 480, 1 }, +{"Standard 4x3" , 1600,1200, 640, 480, 1 }, +{"Standard 4x3" , 1792,1344, 640, 480, 1 }, +{"Standard 4x3" , 1856,1392, 640, 480, 1 }, +{"Standard 4x3" , 1920,1440, 640, 480, 1 }, +{"Standard 4x3" , 2048,1536, 640, 480, 1 }, +{"Short Pixel (CRT) 5x4" , 320, 256, 320, 256, 0.9375}, +{"Short Pixel (CRT) 5x4" , 640, 512, 640, 512, 0.9375}, +{"Short Pixel (CRT) 5x4" , 1280,1024, 640, 512, 0.9375}, +{"Tall Pixel (CRT) 8x5" , 320, 200, 320, 200, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 640, 400, 640, 400, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 840, 525, 640, 400, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 960, 600, 640, 400, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 1680,1050, 640, 400, 1.2 }, +{"Tall Pixel (CRT) 8x5" , 1920,1200, 640, 400, 1.2 }, +{"Square Pixel (LCD) 5x4" , 320, 256, 320, 256, 1 }, +{"Square Pixel (LCD) 5x4" , 640, 512, 640, 512, 1 }, +{"Square Pixel (LCD) 5x4" , 1280,1024, 640, 512, 1 }, +{"WideScreen 5x3" , 640, 384, 640, 384, 1 }, +{"WideScreen 5x3" , 1280, 768, 640, 384, 1 }, +{"WideScreen 8x5" , 320, 200, 320, 200, 1 }, +{"WideScreen 8x5" , 640, 400, 640, 400, 1 }, +{"WideScreen 8x5" , 720, 450, 720, 450, 1 }, +{"WideScreen 8x5" , 840, 525, 640, 400, 1 }, +{"WideScreen 8x5" , 960, 600, 640, 400, 1 }, +{"WideScreen 8x5" , 1280, 800, 640, 400, 1 }, +{"WideScreen 8x5" , 1440, 900, 720, 450, 1 }, +{"WideScreen 8x5" , 1680,1050, 640, 400, 1 }, +{"WideScreen 8x5" , 1920,1200, 640, 400, 1 }, +{"WideScreen 8x5" , 2560,1600, 640, 400, 1 }, +{"WideScreen 8x5" , 3840,2400, 640, 400, 1 }, +{"WideScreen 14x9" , 840, 540, 640, 400, 1 }, +{"WideScreen 14x9" , 1680,1080, 640, 400, 1 }, +{"WideScreen 16x9" , 640, 360, 640, 360, 1 }, +{"WideScreen 16x9" , 683, 384, 683, 384, 1 }, +{"WideScreen 16x9" , 960, 540, 640, 360, 1 }, +{"WideScreen 16x9" , 1280, 720, 640, 360, 1 }, +{"WideScreen 16x9" , 1360, 768, 680, 384, 1 }, +{"WideScreen 16x9" , 1366, 768, 683, 384, 1 }, +{"WideScreen 16x9" , 1920,1080, 640, 360, 1 }, +{"WideScreen 16x9" , 2560,1440, 640, 360, 1 }, +{"WideScreen 16x9" , 3840,2160, 640, 360, 1 }, +{"NTSC 3x2" , 360, 240, 360, 240, 1.125 }, +{"NTSC 3x2" , 720, 480, 720, 480, 1.125 }, +{"PAL 14x11" , 360, 283, 360, 283, 0.9545}, +{"PAL 14x11" , 720, 566, 720, 566, 0.9545}, +{"NES 8x7" , 256, 224, 256, 224, 1.1667}, +{"SNES 8x7" , 512, 448, 512, 448, 1.1667}, +{NULL, 0, 0, 0, 0, 0} +}; +// this is the number of the default mode (640x480) in the list above +int video_resolutions_hardcoded_count = sizeof(video_resolutions_hardcoded) / sizeof(*video_resolutions_hardcoded) - 1; + +#define VIDEO_ITEMS 11 +static int video_cursor = 0; +static int video_cursor_table[VIDEO_ITEMS] = {68, 88, 96, 104, 112, 120, 128, 136, 144, 152, 168}; +static int menu_video_resolution; + +video_resolution_t *video_resolutions; +int video_resolutions_count; + +static video_resolution_t *menu_video_resolutions; +static int menu_video_resolutions_count; +static qboolean menu_video_resolutions_forfullscreen; + +static void M_Menu_Video_FindResolution(int w, int h, float a) +{ + int i; + + if(menu_video_resolutions_forfullscreen) + { + menu_video_resolutions = video_resolutions; + menu_video_resolutions_count = video_resolutions_count; + } + else + { + menu_video_resolutions = video_resolutions_hardcoded; + menu_video_resolutions_count = video_resolutions_hardcoded_count; + } + + // Look for the closest match to the current resolution + menu_video_resolution = 0; + for (i = 1;i < menu_video_resolutions_count;i++) + { + // if the new mode would be a worse match in width, skip it + if (abs(menu_video_resolutions[i].width - w) > abs(menu_video_resolutions[menu_video_resolution].width - w)) + continue; + // if it is equal in width, check height + if (menu_video_resolutions[i].width == w && menu_video_resolutions[menu_video_resolution].width == w) + { + // if the new mode would be a worse match in height, skip it + if (abs(menu_video_resolutions[i].height - h) > abs(menu_video_resolutions[menu_video_resolution].height - h)) + continue; + // if it is equal in width and height, check pixel aspect + if (menu_video_resolutions[i].height == h && menu_video_resolutions[menu_video_resolution].height == h) + { + // if the new mode would be a worse match in pixel aspect, skip it + if (abs(menu_video_resolutions[i].pixelheight - a) > abs(menu_video_resolutions[menu_video_resolution].pixelheight - a)) + continue; + // if it is equal in everything, skip it (prefer earlier modes) + if (menu_video_resolutions[i].pixelheight == a && menu_video_resolutions[menu_video_resolution].pixelheight == a) + continue; + // better match for width, height, and pixel aspect + menu_video_resolution = i; + } + else // better match for width and height + menu_video_resolution = i; + } + else // better match for width + menu_video_resolution = i; + } +} + +void M_Menu_Video_f (void) +{ + key_dest = key_menu; + m_state = m_video; + m_entersound = true; + + M_Menu_Video_FindResolution(vid.width, vid.height, vid_pixelheight.value); +} + + +static void M_Video_Draw (void) +{ + int t; + cachepic_t *p; + char vabuf[1024]; + + if(!!vid_fullscreen.integer != menu_video_resolutions_forfullscreen) + { + video_resolution_t *res = &menu_video_resolutions[menu_video_resolution]; + menu_video_resolutions_forfullscreen = !!vid_fullscreen.integer; + M_Menu_Video_FindResolution(res->width, res->height, res->pixelheight); + } + + M_Background(320, 200); + + M_DrawPic(16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/vidmodes"); + M_DrawPic((320-p->width)/2, 4, "gfx/vidmodes"); + + t = 0; + + // Current and Proposed Resolution + M_Print(16, video_cursor_table[t] - 12, " Current Resolution"); + if (vid_supportrefreshrate && vid.userefreshrate && vid.fullscreen) + M_Print(220, video_cursor_table[t] - 12, va(vabuf, sizeof(vabuf), "%dx%d %.2fhz", vid.width, vid.height, vid.refreshrate)); + else + M_Print(220, video_cursor_table[t] - 12, va(vabuf, sizeof(vabuf), "%dx%d", vid.width, vid.height)); + M_Print(16, video_cursor_table[t], " New Resolution"); + M_Print(220, video_cursor_table[t], va(vabuf, sizeof(vabuf), "%dx%d", menu_video_resolutions[menu_video_resolution].width, menu_video_resolutions[menu_video_resolution].height)); + M_Print(96, video_cursor_table[t] + 8, va(vabuf, sizeof(vabuf), "Type: %s", menu_video_resolutions[menu_video_resolution].type)); + t++; + + // Bits per pixel + M_Print(16, video_cursor_table[t], " Bits per pixel"); + M_Print(220, video_cursor_table[t], (vid_bitsperpixel.integer == 32) ? "32" : "16"); + t++; + + // Bits per pixel + M_Print(16, video_cursor_table[t], " Antialiasing"); + M_DrawSlider(220, video_cursor_table[t], vid_samples.value, 1, 32); + t++; + + // Refresh Rate + M_ItemPrint(16, video_cursor_table[t], " Use Refresh Rate", vid_supportrefreshrate); + M_DrawCheckbox(220, video_cursor_table[t], vid_userefreshrate.integer); + t++; + + // Refresh Rate + M_ItemPrint(16, video_cursor_table[t], " Refresh Rate", vid_supportrefreshrate && vid_userefreshrate.integer); + M_DrawSlider(220, video_cursor_table[t], vid_refreshrate.value, 50, 150); + t++; + + // Fullscreen + M_Print(16, video_cursor_table[t], " Fullscreen"); + M_DrawCheckbox(220, video_cursor_table[t], vid_fullscreen.integer); + t++; + + // Vertical Sync + M_ItemPrint(16, video_cursor_table[t], " Vertical Sync", true); + M_DrawCheckbox(220, video_cursor_table[t], vid_vsync.integer); + t++; + + M_ItemPrint(16, video_cursor_table[t], " Anisotropic Filter", vid.support.ext_texture_filter_anisotropic); + M_DrawSlider(220, video_cursor_table[t], gl_texture_anisotropy.integer, 1, vid.max_anisotropy); + t++; + + M_ItemPrint(16, video_cursor_table[t], " Texture Quality", true); + M_DrawSlider(220, video_cursor_table[t], gl_picmip.value, 3, 0); + t++; + + M_ItemPrint(16, video_cursor_table[t], " Texture Compression", vid.support.arb_texture_compression); + M_DrawCheckbox(220, video_cursor_table[t], gl_texturecompression.integer); + t++; + + // "Apply" button + M_Print(220, video_cursor_table[t], "Apply"); + t++; + + // Cursor + M_DrawCharacter(200, video_cursor_table[video_cursor], 12+((int)(realtime*4)&1)); +} + + +static void M_Menu_Video_AdjustSliders (int dir) +{ + int t; + + S_LocalSound ("sound/misc/menu3.wav"); + + t = 0; + if (video_cursor == t++) + { + // Resolution + int r; + for(r = 0;r < menu_video_resolutions_count;r++) + { + menu_video_resolution += dir; + if (menu_video_resolution >= menu_video_resolutions_count) + menu_video_resolution = 0; + if (menu_video_resolution < 0) + menu_video_resolution = menu_video_resolutions_count - 1; + if (menu_video_resolutions[menu_video_resolution].width >= vid_minwidth.integer && menu_video_resolutions[menu_video_resolution].height >= vid_minheight.integer) + break; + } + } + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_bitsperpixel, (vid_bitsperpixel.integer == 32) ? 16 : 32); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_samples, bound(1, vid_samples.value * (dir > 0 ? 2 : 0.5), 32)); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_userefreshrate, !vid_userefreshrate.integer); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_refreshrate, bound(50, vid_refreshrate.value + dir, 150)); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_fullscreen, !vid_fullscreen.integer); + else if (video_cursor == t++) + Cvar_SetValueQuick (&vid_vsync, !vid_vsync.integer); + else if (video_cursor == t++) + Cvar_SetValueQuick (&gl_texture_anisotropy, bound(1, gl_texture_anisotropy.value * (dir < 0 ? 0.5 : 2.0), vid.max_anisotropy)); + else if (video_cursor == t++) + Cvar_SetValueQuick (&gl_picmip, bound(0, gl_picmip.value - dir, 3)); + else if (video_cursor == t++) + Cvar_SetValueQuick (&gl_texturecompression, !gl_texturecompression.integer); +} + + +static void M_Video_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + // vid_shared.c has a copy of the current video config. We restore it + Cvar_SetValueQuick(&vid_fullscreen, vid.fullscreen); + Cvar_SetValueQuick(&vid_bitsperpixel, vid.bitsperpixel); + Cvar_SetValueQuick(&vid_samples, vid.samples); + if (vid_supportrefreshrate) + Cvar_SetValueQuick(&vid_refreshrate, vid.refreshrate); + Cvar_SetValueQuick(&vid_userefreshrate, vid.userefreshrate); + + S_LocalSound ("sound/misc/menu1.wav"); + M_Menu_Options_f (); + break; + case K_MOUSE1: + case K_ENTER: + m_entersound = true; + switch (video_cursor) + { + case (VIDEO_ITEMS - 1): + Cvar_SetValueQuick (&vid_width, menu_video_resolutions[menu_video_resolution].width); + Cvar_SetValueQuick (&vid_height, menu_video_resolutions[menu_video_resolution].height); + Cvar_SetValueQuick (&vid_conwidth, menu_video_resolutions[menu_video_resolution].conwidth); + Cvar_SetValueQuick (&vid_conheight, menu_video_resolutions[menu_video_resolution].conheight); + Cvar_SetValueQuick (&vid_pixelheight, menu_video_resolutions[menu_video_resolution].pixelheight); + Cbuf_AddText ("vid_restart\n"); + M_Menu_Options_f (); + break; + default: + M_Menu_Video_AdjustSliders (1); + } + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + video_cursor--; + if (video_cursor < 0) + video_cursor = VIDEO_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + video_cursor++; + if (video_cursor >= VIDEO_ITEMS) + video_cursor = 0; + break; + case 'a': + case K_LEFTARROW: + M_Menu_Video_AdjustSliders (-1); + break; + case 'd': + case K_RIGHTARROW: + M_Menu_Video_AdjustSliders (1); + break; + } +} + +//============================================================================= +/* HELP MENU */ + +static int help_page; +#define NUM_HELP_PAGES 6 + + +void M_Menu_Help_f (void) +{ + key_dest = key_menu; + m_state = m_help; + m_entersound = true; + help_page = 0; +} + + + +static void M_Help_Draw (void) +{ + char vabuf[1024]; + M_Background(320, 200); + M_DrawPic (0, 0, va(vabuf, sizeof(vabuf), "gfx/help%i", help_page)); +} + + +static void M_Help_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + M_Menu_Main_f (); + break; + + case K_UPARROW: + case K_RIGHTARROW: + case 'd': + m_entersound = true; + if (++help_page >= NUM_HELP_PAGES) + help_page = 0; + break; + + case K_DOWNARROW: + case K_LEFTARROW: + case 'a': + m_entersound = true; + if (--help_page < 0) + help_page = NUM_HELP_PAGES-1; + break; + } + +} + +//============================================================================= +/* CEDITS MENU */ + +void M_Menu_Credits_f (void) +{ + key_dest = key_menu; + m_state = m_credits; + m_entersound = true; +} + + +static const char *m_credits_message[11]; +static int M_CreditsMessage(const char *line1, const char *line2, + const char *line3, const char *line4, + const char *line5, const char *line6, + const char *line7, const char *line8) +{ + int line = 0; + m_credits_message[line++] = line1; + m_credits_message[line++] = line2; + m_credits_message[line++] = line3; + m_credits_message[line++] = line4; + m_credits_message[line++] = line5; + m_credits_message[line++] = line6; + m_credits_message[line++] = line7; + m_credits_message[line++] = line8; + m_credits_message[line++] = NULL; + return 1; +} + +static void M_Credits_Draw (void) +{ + M_CreditsMessage( + " -= QVR "QVR_VERSION" =- ", + "", + " DarkPlaces Engine - LordHavoc ", + " Cardboard Port - Dr. Beef", + "", + "", + "", + " ** Please Press Any Button ** "); + + int i, l, linelength, firstline, lastline, lines; + for (i = 0, linelength = 0, firstline = 9999, lastline = -1;m_credits_message[i];i++) + { + if ((l = (int)strlen(m_credits_message[i]))) + { + if (firstline > i) + firstline = i; + if (lastline < i) + lastline = i; + if (linelength < l) + linelength = l; + } + } + lines = (lastline - firstline) + 1; + M_Background(linelength * 8 + 16, lines * 8 + 16); + M_DrawTextBox(0, -48, linelength, lines); //this is less obtrusive than hacking up the M_DrawTextBox function + for (i = 0, l = firstline;i < lines;i++, l++) + M_Print(8 + 4 * (linelength - strlen(m_credits_message[l])), -40 + 8 * i, m_credits_message[l]); +} + + +static void M_Credits_Key (int key, int ascii) +{ + M_Menu_Main_f (); +} + +//============================================================================= +/* QUIT MENU */ + +static const char *m_quit_message[9]; +static int m_quit_prevstate; +static qboolean wasInMenus; + + +static int M_QuitMessage(const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6, const char *line7, const char *line8) +{ + m_quit_message[0] = line1; + m_quit_message[1] = line2; + m_quit_message[2] = line3; + m_quit_message[3] = line4; + m_quit_message[4] = line5; + m_quit_message[5] = line6; + m_quit_message[6] = line7; + m_quit_message[7] = line8; + m_quit_message[8] = NULL; + return 1; +} + +static int M_ChooseQuitMessage(int request) +{ + switch (gamemode) + { + case GAME_NORMAL: + case GAME_HIPNOTIC: + case GAME_ROGUE: + case GAME_QUOTH: + case GAME_NEHAHRA: + case GAME_DEFEATINDETAIL2: + if (request-- == 0) return M_QuitMessage("Are you gonna quit","this game just like","everything else?",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Milord, methinks that","thou art a lowly","quitter. Is this true?",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Do I need to bust your","face open for trying","to quit?",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Man, I oughta smack you","for trying to quit!","Press Y to get","smacked out.",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Press Y to quit like a","big loser in life.","Press N to stay proud","and successful!",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("If you press Y to","quit, I will summon","Satan all over your","hard drive!",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Um, Asmodeus dislikes","his children trying to","quit. Press Y to return","to your Tinkertoys.",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("If you quit now, I'll","throw a blanket-party","for you next time!",NULL,NULL,NULL,NULL,NULL); + break; + case GAME_GOODVSBAD2: + if (request-- == 0) return M_QuitMessage("Press Yes To Quit","...","Yes",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Do you really want to","Quit?","Play Good vs bad 3!",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("All your quit are","belong to long duck","dong",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Press Y to quit","","But are you too legit?",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("This game was made by","e@chip-web.com","It is by far the best","game ever made.",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Even I really dont","know of a game better","Press Y to quit","like rougue chedder",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("After you stop playing","tell the guys who made","counterstrike to just","kill themselves now",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Press Y to exit to DOS","","SSH login as user Y","to exit to Linux",NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Press Y like you","were waanderers","from Ys'",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("This game was made in","Nippon like the SS","announcer's saying ipon",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("you","want to quit?",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Please stop playing","this stupid game",NULL,NULL,NULL,NULL,NULL,NULL); + break; + case GAME_BATTLEMECH: + if (request-- == 0) return M_QuitMessage("? WHY ?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Leave now and your mech is scrap!","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Accept Defeat?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Wait! There are more mechs to destroy!","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Where's your bloodlust?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Your mech here is way more impressive","than your car out there...","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Quitting won't reduce your debt","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); + break; + case GAME_OPENQUARTZ: + if (request-- == 0) return M_QuitMessage("There is nothing like free beer!","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("GNU is not Unix!","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("You prefer free beer over free speech?","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Is OpenQuartz Propaganda?","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); + break; + default: + if (request-- == 0) return M_QuitMessage("Tired of fragging already?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Quit now and forfeit your bodycount?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Are you sure you want to quit?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); + if (request-- == 0) return M_QuitMessage("Off to do something constructive?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); + break; + } + return 0; +} + +void M_Menu_Quit_f (void) +{ + int n; + if (m_state == m_quit) + return; + wasInMenus = (key_dest == key_menu || key_dest == key_menu_grabbed); + key_dest = key_menu; + m_quit_prevstate = m_state; + m_state = m_quit; + m_entersound = true; + // count how many there are + for (n = 1;M_ChooseQuitMessage(n);n++); + // choose one + M_ChooseQuitMessage(rand() % n); +} + + +static void M_Quit_Key (int key, int ascii) +{ + switch (key) + { + case K_ESCAPE: + case 'n': + case 'N': + if (wasInMenus) + { + m_state = (enum m_state_e)m_quit_prevstate; + m_entersound = true; + } + else + { + key_dest = key_game; + m_state = m_none; + } + break; + + default: + { + Host_Quit_f (); + key_dest = key_game; + m_state = m_none; + } + break; + } +} + +static void M_Quit_Draw (void) +{ + int i, l, linelength, firstline, lastline, lines; + for (i = 0, linelength = 0, firstline = 9999, lastline = -1;m_quit_message[i];i++) + { + if ((l = (int)strlen(m_quit_message[i]))) + { + if (firstline > i) + firstline = i; + if (lastline < i) + lastline = i; + if (linelength < l) + linelength = l; + } + } + lines = (lastline - firstline) + 1; + M_Background(linelength * 8 + 16, lines * 8 + 16); + if (!m_missingdata) //since this is a fallback menu for missing data, it is very hard to read with the box + M_DrawTextBox(0, 0, linelength, lines); //this is less obtrusive than hacking up the M_DrawTextBox function + for (i = 0, l = firstline;i < lines;i++, l++) + M_Print(8 + 4 * (linelength - strlen(m_quit_message[l])), 8 + 8 * i, m_quit_message[l]); +} + +//============================================================================= +/* LAN CONFIG MENU */ + +static int lanConfig_cursor = -1; +static int lanConfig_cursor_table [] = {56, 76, 84, 120}; +#define NUM_LANCONFIG_CMDS 4 + +static int lanConfig_port; +static char lanConfig_portname[6]; +static char lanConfig_joinname[40]; + +void M_Menu_LanConfig_f (void) +{ + key_dest = key_menu; + m_state = m_lanconfig; + m_entersound = true; + if (lanConfig_cursor == -1) + { + if (JoiningGame) + lanConfig_cursor = 1; + } + if (StartingGame) + lanConfig_cursor = 1; + lanConfig_port = 26000; + dpsnprintf(lanConfig_portname, sizeof(lanConfig_portname), "%u", (unsigned int) lanConfig_port); + + M_Update_Return_Reason(""); +} + + +static void M_LanConfig_Draw (void) +{ + cachepic_t *p; + int basex; + const char *startJoin; + const char *protocol; + char vabuf[1024]; + + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_multi"); + basex = (320-p->width)/2; + M_DrawPic (basex, 4, "gfx/p_multi"); + + if (StartingGame) + startJoin = "New Game"; + else + startJoin = "Join Game"; + protocol = "TCP/IP"; + M_Print(basex, 32, va(vabuf, sizeof(vabuf), "%s - %s", startJoin, protocol)); + basex += 8; + + M_Print(basex, lanConfig_cursor_table[0], "Port"); + M_DrawTextBox (basex+8*8, lanConfig_cursor_table[0]-8, sizeof(lanConfig_portname), 1); + M_Print(basex+9*8, lanConfig_cursor_table[0], lanConfig_portname); + + if (JoiningGame) + { + M_Print(basex, lanConfig_cursor_table[1], "Search for DarkPlaces games..."); + M_Print(basex, lanConfig_cursor_table[2], "Search for QuakeWorld games..."); + M_Print(basex, lanConfig_cursor_table[3]-16, "Join game at:"); + M_DrawTextBox (basex+8, lanConfig_cursor_table[3]-8, sizeof(lanConfig_joinname), 1); + M_Print(basex+16, lanConfig_cursor_table[3], lanConfig_joinname); + } + else + { + M_DrawTextBox (basex, lanConfig_cursor_table[1]-8, 2, 1); + M_Print(basex+8, lanConfig_cursor_table[1], "OK"); + } + + M_DrawCharacter (basex-8, lanConfig_cursor_table [lanConfig_cursor], 12+((int)(realtime*4)&1)); + + if (lanConfig_cursor == 0) + M_DrawCharacter (basex+9*8 + 8*strlen(lanConfig_portname), lanConfig_cursor_table [lanConfig_cursor], 10+((int)(realtime*4)&1)); + + if (lanConfig_cursor == 3) + M_DrawCharacter (basex+16 + 8*strlen(lanConfig_joinname), lanConfig_cursor_table [lanConfig_cursor], 10+((int)(realtime*4)&1)); + + if (*m_return_reason) + M_Print(basex, 168, m_return_reason); +} + + +static void M_LanConfig_Key (int key, int ascii) +{ + int l; + char vabuf[1024]; + + switch (key) + { + case K_ESCAPE: + M_Menu_MultiPlayer_f (); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + lanConfig_cursor--; + if (lanConfig_cursor < 0) + lanConfig_cursor = NUM_LANCONFIG_CMDS-1; + // when in start game menu, skip the unused search qw servers item + if (StartingGame && lanConfig_cursor == 2) + lanConfig_cursor = 1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + lanConfig_cursor++; + if (lanConfig_cursor >= NUM_LANCONFIG_CMDS) + lanConfig_cursor = 0; + // when in start game menu, skip the unused search qw servers item + if (StartingGame && lanConfig_cursor == 1) + lanConfig_cursor = 2; + break; + case K_MOUSE1: + case K_ENTER: + if (lanConfig_cursor == 0) + break; + + m_entersound = true; + + Cbuf_AddText ("stopdemo\n"); + + Cvar_SetValue("port", lanConfig_port); + + if (lanConfig_cursor == 1 || lanConfig_cursor == 2) + { + if (StartingGame) + { + M_Menu_GameOptions_f (); + break; + } + M_Menu_ServerList_f(); + break; + } + + if (lanConfig_cursor == 3) + Cbuf_AddText(va(vabuf, sizeof(vabuf), "connect \"%s\"\n", lanConfig_joinname) ); + break; + + case K_BACKSPACE: + if (lanConfig_cursor == 0) + { + if (strlen(lanConfig_portname)) + lanConfig_portname[strlen(lanConfig_portname)-1] = 0; + } + + if (lanConfig_cursor == 3) + { + if (strlen(lanConfig_joinname)) + lanConfig_joinname[strlen(lanConfig_joinname)-1] = 0; + } + break; + + default: + if (ascii < 32) + break; + + if (lanConfig_cursor == 3) + { + l = (int)strlen(lanConfig_joinname); + if (l < (int)sizeof(lanConfig_joinname) - 1) + { + lanConfig_joinname[l+1] = 0; + lanConfig_joinname[l] = ascii; + } + } + + if (ascii < '0' || ascii > '9') + break; + if (lanConfig_cursor == 0) + { + l = (int)strlen(lanConfig_portname); + if (l < (int)sizeof(lanConfig_portname) - 1) + { + lanConfig_portname[l+1] = 0; + lanConfig_portname[l] = ascii; + } + } + } + + if (StartingGame && lanConfig_cursor == 3) + { + if (key == K_UPARROW) + lanConfig_cursor = 1; + else + lanConfig_cursor = 0; + } + + l = atoi(lanConfig_portname); + if (l <= 65535) + lanConfig_port = l; + dpsnprintf(lanConfig_portname, sizeof(lanConfig_portname), "%u", (unsigned int) lanConfig_port); +} + +//============================================================================= +/* GAME OPTIONS MENU */ + +typedef struct level_s +{ + const char *name; + const char *description; +} level_t; + +typedef struct episode_s +{ + const char *description; + int firstLevel; + int levels; +} episode_t; + +typedef struct gamelevels_s +{ + const char *gamename; + level_t *levels; + episode_t *episodes; + int numepisodes; +} +gamelevels_t; + +static level_t quakelevels[] = +{ + {"start", "Entrance"}, // 0 + + {"e1m1", "Slipgate Complex"}, // 1 + {"e1m2", "Castle of the Damned"}, + {"e1m3", "The Necropolis"}, + {"e1m4", "The Grisly Grotto"}, + {"e1m5", "Gloom Keep"}, + {"e1m6", "The Door To Chthon"}, + {"e1m7", "The House of Chthon"}, + {"e1m8", "Ziggurat Vertigo"}, + + {"e2m1", "The Installation"}, // 9 + {"e2m2", "Ogre Citadel"}, + {"e2m3", "Crypt of Decay"}, + {"e2m4", "The Ebon Fortress"}, + {"e2m5", "The Wizard's Manse"}, + {"e2m6", "The Dismal Oubliette"}, + {"e2m7", "Underearth"}, + + {"e3m1", "Termination Central"}, // 16 + {"e3m2", "The Vaults of Zin"}, + {"e3m3", "The Tomb of Terror"}, + {"e3m4", "Satan's Dark Delight"}, + {"e3m5", "Wind Tunnels"}, + {"e3m6", "Chambers of Torment"}, + {"e3m7", "The Haunted Halls"}, + + {"e4m1", "The Sewage System"}, // 23 + {"e4m2", "The Tower of Despair"}, + {"e4m3", "The Elder God Shrine"}, + {"e4m4", "The Palace of Hate"}, + {"e4m5", "Hell's Atrium"}, + {"e4m6", "The Pain Maze"}, + {"e4m7", "Azure Agony"}, + {"e4m8", "The Nameless City"}, + + {"end", "Shub-Niggurath's Pit"}, // 31 + + {"dm1", "Place of Two Deaths"}, // 32 + {"dm2", "Claustrophobopolis"}, + {"dm3", "The Abandoned Base"}, + {"dm4", "The Bad Place"}, + {"dm5", "The Cistern"}, + {"dm6", "The Dark Zone"} +}; + +static episode_t quakeepisodes[] = +{ + {"Welcome to Quake", 0, 1}, + {"Doomed Dimension", 1, 8}, + {"Realm of Black Magic", 9, 7}, + {"Netherworld", 16, 7}, + {"The Elder World", 23, 8}, + {"Final Level", 31, 1}, + {"Deathmatch Arena", 32, 6} +}; + + //MED 01/06/97 added hipnotic levels +static level_t hipnoticlevels[] = +{ + {"start", "Command HQ"}, // 0 + + {"hip1m1", "The Pumping Station"}, // 1 + {"hip1m2", "Storage Facility"}, + {"hip1m3", "The Lost Mine"}, + {"hip1m4", "Research Facility"}, + {"hip1m5", "Military Complex"}, + + {"hip2m1", "Ancient Realms"}, // 6 + {"hip2m2", "The Black Cathedral"}, + {"hip2m3", "The Catacombs"}, + {"hip2m4", "The Crypt"}, + {"hip2m5", "Mortum's Keep"}, + {"hip2m6", "The Gremlin's Domain"}, + + {"hip3m1", "Tur Torment"}, // 12 + {"hip3m2", "Pandemonium"}, + {"hip3m3", "Limbo"}, + {"hip3m4", "The Gauntlet"}, + + {"hipend", "Armagon's Lair"}, // 16 + + {"hipdm1", "The Edge of Oblivion"} // 17 +}; + +//MED 01/06/97 added hipnotic episodes +static episode_t hipnoticepisodes[] = +{ + {"Scourge of Armagon", 0, 1}, + {"Fortress of the Dead", 1, 5}, + {"Dominion of Darkness", 6, 6}, + {"The Rift", 12, 4}, + {"Final Level", 16, 1}, + {"Deathmatch Arena", 17, 1} +}; + +//PGM 01/07/97 added rogue levels +//PGM 03/02/97 added dmatch level +static level_t roguelevels[] = +{ + {"start", "Split Decision"}, + {"r1m1", "Deviant's Domain"}, + {"r1m2", "Dread Portal"}, + {"r1m3", "Judgement Call"}, + {"r1m4", "Cave of Death"}, + {"r1m5", "Towers of Wrath"}, + {"r1m6", "Temple of Pain"}, + {"r1m7", "Tomb of the Overlord"}, + {"r2m1", "Tempus Fugit"}, + {"r2m2", "Elemental Fury I"}, + {"r2m3", "Elemental Fury II"}, + {"r2m4", "Curse of Osiris"}, + {"r2m5", "Wizard's Keep"}, + {"r2m6", "Blood Sacrifice"}, + {"r2m7", "Last Bastion"}, + {"r2m8", "Source of Evil"}, + {"ctf1", "Division of Change"} +}; + +//PGM 01/07/97 added rogue episodes +//PGM 03/02/97 added dmatch episode +static episode_t rogueepisodes[] = +{ + {"Introduction", 0, 1}, + {"Hell's Fortress", 1, 7}, + {"Corridors of Time", 8, 8}, + {"Deathmatch Arena", 16, 1} +}; + +static level_t nehahralevels[] = +{ + {"nehstart", "Welcome to Nehahra"}, + {"neh1m1", "Forge City1: Slipgates"}, + {"neh1m2", "Forge City2: Boiler"}, + {"neh1m3", "Forge City3: Escape"}, + {"neh1m4", "Grind Core"}, + {"neh1m5", "Industrial Silence"}, + {"neh1m6", "Locked-Up Anger"}, + {"neh1m7", "Wanderer of the Wastes"}, + {"neh1m8", "Artemis System Net"}, + {"neh1m9", "To the Death"}, + {"neh2m1", "The Gates of Ghoro"}, + {"neh2m2", "Sacred Trinity"}, + {"neh2m3", "Realm of the Ancients"}, + {"neh2m4", "Temple of the Ancients"}, + {"neh2m5", "Dreams Made Flesh"}, + {"neh2m6", "Your Last Cup of Sorrow"}, + {"nehsec", "Ogre's Bane"}, + {"nehahra", "Nehahra's Den"}, + {"nehend", "Quintessence"} +}; + +static episode_t nehahraepisodes[] = +{ + {"Welcome to Nehahra", 0, 1}, + {"The Fall of Forge", 1, 9}, + {"The Outlands", 10, 7}, + {"Dimension of the Lost", 17, 2} +}; + +// Map list for Transfusion +static level_t transfusionlevels[] = +{ + {"e1m1", "Cradle to Grave"}, + {"e1m2", "Wrong Side of the Tracks"}, + {"e1m3", "Phantom Express"}, + {"e1m4", "Dark Carnival"}, + {"e1m5", "Hallowed Grounds"}, + {"e1m6", "The Great Temple"}, + {"e1m7", "Altar of Stone"}, + {"e1m8", "House of Horrors"}, + + {"e2m1", "Shipwrecked"}, + {"e2m2", "The Lumber Mill"}, + {"e2m3", "Rest for the Wicked"}, + {"e2m4", "The Overlooked Hotel"}, + {"e2m5", "The Haunting"}, + {"e2m6", "The Cold Rush"}, + {"e2m7", "Bowels of the Earth"}, + {"e2m8", "The Lair of Shial"}, + {"e2m9", "Thin Ice"}, + + {"e3m1", "Ghost Town"}, + {"e3m2", "The Siege"}, + {"e3m3", "Raw Sewage"}, + {"e3m4", "The Sick Ward"}, + {"e3m5", "Spare Parts"}, + {"e3m6", "Monster Bait"}, + {"e3m7", "The Pit of Cerberus"}, + {"e3m8", "Catacombs"}, + + {"e4m1", "Butchery Loves Company"}, + {"e4m2", "Breeding Grounds"}, + {"e4m3", "Charnel House"}, + {"e4m4", "Crystal Lake"}, + {"e4m5", "Fire and Brimstone"}, + {"e4m6", "The Ganglion Depths"}, + {"e4m7", "In the Flesh"}, + {"e4m8", "The Hall of the Epiphany"}, + {"e4m9", "Mall of the Dead"}, + + {"bb1", "The Stronghold"}, + {"bb2", "Winter Wonderland"}, + {"bb3", "Bodies"}, + {"bb4", "The Tower"}, + {"bb5", "Click!"}, + {"bb6", "Twin Fortress"}, + {"bb7", "Midgard"}, + {"bb8", "Fun With Heads"}, + {"dm1", "Monolith Building 11"}, + {"dm2", "Power!"}, + {"dm3", "Area 15"}, + + {"e6m1", "Welcome to Your Life"}, + {"e6m2", "They Are Here"}, + {"e6m3", "Public Storage"}, + {"e6m4", "Aqueducts"}, + {"e6m5", "The Ruined Temple"}, + {"e6m6", "Forbidden Rituals"}, + {"e6m7", "The Dungeon"}, + {"e6m8", "Beauty and the Beast"}, + {"e6m9", "Forgotten Catacombs"}, + + {"cp01", "Boat Docks"}, + {"cp02", "Old Opera House"}, + {"cp03", "Gothic Library"}, + {"cp04", "Lost Monastery"}, + {"cp05", "Steamboat"}, + {"cp06", "Graveyard"}, + {"cp07", "Mountain Pass"}, + {"cp08", "Abysmal Mine"}, + {"cp09", "Castle"}, + {"cps1", "Boggy Creek"}, + + {"cpbb01", "Crypt of Despair"}, + {"cpbb02", "Pits of Blood"}, + {"cpbb03", "Unholy Cathedral"}, + {"cpbb04", "Deadly Inspirations"}, + + {"b2a15", "Area 15 (B2)"}, + {"b2bodies", "BB_Bodies (B2)"}, + {"b2cabana", "BB_Cabana"}, + {"b2power", "BB_Power"}, + {"barena", "Blood Arena"}, + {"bkeep", "Blood Keep"}, + {"bstar", "Brown Star"}, + {"crypt", "The Crypt"}, + + {"bb3_2k1", "Bodies Infusion"}, + {"captasao", "Captasao"}, + {"curandero", "Curandero"}, + {"dcamp", "DeathCamp"}, + {"highnoon", "HighNoon"}, + {"qbb1", "The Confluence"}, + {"qbb2", "KathartiK"}, + {"qbb3", "Caleb's Woodland Retreat"}, + {"zoo", "Zoo"}, + + {"dranzbb6", "Black Coffee"}, + {"fragm", "Frag'M"}, + {"maim", "Maim"}, + {"qe1m7", "The House of Chthon"}, + {"qdm1", "Place of Two Deaths"}, + {"qdm4", "The Bad Place"}, + {"qdm5", "The Cistern"}, + {"qmorbias", "DM-Morbias"}, + {"simple", "Dead Simple"} +}; + +static episode_t transfusionepisodes[] = +{ + {"The Way of All Flesh", 0, 8}, + {"Even Death May Die", 8, 9}, + {"Farewell to Arms", 17, 8}, + {"Dead Reckoning", 25, 9}, + {"BloodBath", 34, 11}, + {"Post Mortem", 45, 9}, + {"Cryptic Passage", 54, 10}, + {"Cryptic BloodBath", 64, 4}, + {"Blood 2", 68, 8}, + {"Transfusion", 76, 9}, + {"Conversions", 85, 9} +}; + +static level_t goodvsbad2levels[] = +{ + {"rts", "Many Paths"}, // 0 + {"chess", "Chess, Scott Hess"}, // 1 + {"dot", "Big Wall"}, + {"city2", "The Big City"}, + {"bwall", "0 G like Psychic TV"}, + {"snow", "Wireframed"}, + {"telep", "Infinite Falling"}, + {"faces", "Facing Bases"}, + {"island", "Adventure Islands"}, +}; + +static episode_t goodvsbad2episodes[] = +{ + {"Levels? Bevels!", 0, 8}, +}; + +static level_t battlemechlevels[] = +{ + {"start", "Parking Level"}, + {"dm1", "Hot Dump"}, // 1 + {"dm2", "The Pits"}, + {"dm3", "Dimber Died"}, + {"dm4", "Fire in the Hole"}, + {"dm5", "Clubhouses"}, + {"dm6", "Army go Underground"}, +}; + +static episode_t battlemechepisodes[] = +{ + {"Time for Battle", 0, 7}, +}; + +static level_t openquartzlevels[] = +{ + {"start", "Welcome to Openquartz"}, + + {"void1", "The center of nowhere"}, // 1 + {"void2", "The place with no name"}, + {"void3", "The lost supply base"}, + {"void4", "Past the outer limits"}, + {"void5", "Into the nonexistance"}, + {"void6", "Void walk"}, + + {"vtest", "Warp Central"}, + {"box", "The deathmatch box"}, + {"bunkers", "Void command"}, + {"house", "House of chaos"}, + {"office", "Overnight office kill"}, + {"am1", "The nameless chambers"}, +}; + +static episode_t openquartzepisodes[] = +{ + {"Single Player", 0, 1}, + {"Void Deathmatch", 1, 6}, + {"Contrib", 7, 6}, +}; + +static level_t defeatindetail2levels[] = +{ + {"atac3", "River Crossing"}, + {"atac4", "Canyon Chaos"}, + {"atac7", "Desert Stormer"}, +}; + +static episode_t defeatindetail2episodes[] = +{ + {"ATAC Campaign", 0, 3}, +}; + +static level_t prydonlevels[] = +{ + {"curig2", "Capel Curig"}, // 0 + + {"tdastart", "Gateway"}, // 1 +}; + +static episode_t prydonepisodes[] = +{ + {"Prydon Gate", 0, 1}, + {"The Dark Age", 1, 1} +}; + +static gamelevels_t sharewarequakegame = {"Shareware Quake", quakelevels, quakeepisodes, 2}; +static gamelevels_t registeredquakegame = {"Quake", quakelevels, quakeepisodes, 7}; +static gamelevels_t hipnoticgame = {"Scourge of Armagon", hipnoticlevels, hipnoticepisodes, 6}; +static gamelevels_t roguegame = {"Dissolution of Eternity", roguelevels, rogueepisodes, 4}; +static gamelevels_t nehahragame = {"Nehahra", nehahralevels, nehahraepisodes, 4}; +static gamelevels_t transfusiongame = {"Transfusion", transfusionlevels, transfusionepisodes, 11}; +static gamelevels_t goodvsbad2game = {"Good Vs. Bad 2", goodvsbad2levels, goodvsbad2episodes, 1}; +static gamelevels_t battlemechgame = {"Battlemech", battlemechlevels, battlemechepisodes, 1}; +static gamelevels_t openquartzgame = {"OpenQuartz", openquartzlevels, openquartzepisodes, 3}; +static gamelevels_t defeatindetail2game = {"Defeat In Detail 2", defeatindetail2levels, defeatindetail2episodes, 1}; +static gamelevels_t prydongame = {"Prydon Gate", prydonlevels, prydonepisodes, 2}; + +typedef struct gameinfo_s +{ + gamemode_t gameid; + gamelevels_t *notregistered; + gamelevels_t *registered; +} +gameinfo_t; + +static gameinfo_t gamelist[] = +{ + {GAME_NORMAL, &sharewarequakegame, ®isteredquakegame}, + {GAME_HIPNOTIC, &hipnoticgame, &hipnoticgame}, + {GAME_ROGUE, &roguegame, &roguegame}, + {GAME_QUOTH, &sharewarequakegame, ®isteredquakegame}, + {GAME_NEHAHRA, &nehahragame, &nehahragame}, + {GAME_TRANSFUSION, &transfusiongame, &transfusiongame}, + {GAME_GOODVSBAD2, &goodvsbad2game, &goodvsbad2game}, + {GAME_BATTLEMECH, &battlemechgame, &battlemechgame}, + {GAME_OPENQUARTZ, &openquartzgame, &openquartzgame}, + {GAME_DEFEATINDETAIL2, &defeatindetail2game, &defeatindetail2game}, + {GAME_PRYDON, &prydongame, &prydongame}, +}; + +static gamelevels_t *gameoptions_levels = NULL; + +static int startepisode; +static int startlevel; +static int maxplayers; +static qboolean m_serverInfoMessage = false; +static double m_serverInfoMessageTime; + +void M_Menu_GameOptions_f (void) +{ + int i; + key_dest = key_menu; + m_state = m_gameoptions; + m_entersound = true; + if (maxplayers == 0) + maxplayers = svs.maxclients; + if (maxplayers < 2) + maxplayers = min(8, MAX_SCOREBOARD); + // pick game level list based on gamemode (use GAME_NORMAL if no matches) + gameoptions_levels = registered.integer ? gamelist[0].registered : gamelist[0].notregistered; + for (i = 0;i < (int)(sizeof(gamelist)/sizeof(gamelist[0]));i++) + if (gamelist[i].gameid == gamemode) + gameoptions_levels = registered.integer ? gamelist[i].registered : gamelist[i].notregistered; +} + + +static int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 104, 112, 140, 160, 168}; +#define NUM_GAMEOPTIONS 12 +static int gameoptions_cursor; + +void M_GameOptions_Draw (void) +{ + cachepic_t *p; + int x; + char vabuf[1024]; + + M_Background(320, 200); + + M_DrawPic (16, 4, "gfx/qplaque"); + p = Draw_CachePic ("gfx/p_multi"); + M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); + + M_DrawTextBox (152, 32, 10, 1); + M_Print(160, 40, "begin game"); + + M_Print(0, 56, " Max players"); + M_Print(160, 56, va(vabuf, sizeof(vabuf), "%i", maxplayers) ); + + if (gamemode != GAME_GOODVSBAD2) + { + M_Print(0, 64, " Game Type"); + if (gamemode == GAME_TRANSFUSION) + { + if (!coop.integer && !deathmatch.integer) + Cvar_SetValue("deathmatch", 1); + if (deathmatch.integer == 0) + M_Print(160, 64, "Cooperative"); + else if (deathmatch.integer == 2) + M_Print(160, 64, "Capture the Flag"); + else + M_Print(160, 64, "Blood Bath"); + } + else if (gamemode == GAME_BATTLEMECH) + { + if (!deathmatch.integer) + Cvar_SetValue("deathmatch", 1); + if (deathmatch.integer == 2) + M_Print(160, 64, "Rambo Match"); + else + M_Print(160, 64, "Deathmatch"); + } + else + { + if (!coop.integer && !deathmatch.integer) + Cvar_SetValue("deathmatch", 1); + if (coop.integer) + M_Print(160, 64, "Cooperative"); + else + M_Print(160, 64, "Deathmatch"); + } + + M_Print(0, 72, " Teamplay"); + if (gamemode == GAME_ROGUE) + { + const char *msg; + + switch((int)teamplay.integer) + { + case 1: msg = "No Friendly Fire"; break; + case 2: msg = "Friendly Fire"; break; + case 3: msg = "Tag"; break; + case 4: msg = "Capture the Flag"; break; + case 5: msg = "One Flag CTF"; break; + case 6: msg = "Three Team CTF"; break; + default: msg = "Off"; break; + } + M_Print(160, 72, msg); + } + else + { + const char *msg; + + switch (teamplay.integer) + { + case 0: msg = "Off"; break; + case 2: msg = "Friendly Fire"; break; + default: msg = "No Friendly Fire"; break; + } + M_Print(160, 72, msg); + } + M_Print(0, 80, " Skill"); + if (gamemode == GAME_TRANSFUSION) + { + if (skill.integer == 1) + M_Print(160, 80, "Still Kicking"); + else if (skill.integer == 2) + M_Print(160, 80, "Pink On The Inside"); + else if (skill.integer == 3) + M_Print(160, 80, "Lightly Broiled"); + else if (skill.integer == 4) + M_Print(160, 80, "Well Done"); + else + M_Print(160, 80, "Extra Crispy"); + } + else + { + if (skill.integer == 0) + M_Print(160, 80, "Easy difficulty"); + else if (skill.integer == 1) + M_Print(160, 80, "Normal difficulty"); + else if (skill.integer == 2) + M_Print(160, 80, "Hard difficulty"); + else + M_Print(160, 80, "Nightmare difficulty"); + } + M_Print(0, 88, " Frag Limit"); + if (fraglimit.integer == 0) + M_Print(160, 88, "none"); + else + M_Print(160, 88, va(vabuf, sizeof(vabuf), "%i frags", fraglimit.integer)); + + M_Print(0, 96, " Time Limit"); + if (timelimit.integer == 0) + M_Print(160, 96, "none"); + else + M_Print(160, 96, va(vabuf, sizeof(vabuf), "%i minutes", timelimit.integer)); + } + + M_Print(0, 104, " Public server"); + M_Print(160, 104, (sv_public.integer == 0) ? "no" : "yes"); + + M_Print(0, 112, " Server maxrate"); + M_Print(160, 112, va(vabuf, sizeof(vabuf), "%i", sv_maxrate.integer)); + + M_Print(0, 128, " Server name"); + M_DrawTextBox (0, 132, 38, 1); + M_Print(8, 140, hostname.string); + + if (gamemode != GAME_GOODVSBAD2) + { + M_Print(0, 160, " Episode"); + M_Print(160, 160, gameoptions_levels->episodes[startepisode].description); + } + + M_Print(0, 168, " Level"); + M_Print(160, 168, gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].description); + M_Print(160, 176, gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].name); + +// line cursor + if (gameoptions_cursor == 9) + M_DrawCharacter (8 + 8 * strlen(hostname.string), gameoptions_cursor_table[gameoptions_cursor], 10+((int)(realtime*4)&1)); + else + M_DrawCharacter (144, gameoptions_cursor_table[gameoptions_cursor], 12+((int)(realtime*4)&1)); + + if (m_serverInfoMessage) + { + if ((realtime - m_serverInfoMessageTime) < 5.0) + { + x = (320-26*8)/2; + M_DrawTextBox (x, 138, 24, 4); + x += 8; + M_Print(x, 146, " More than 255 players??"); + M_Print(x, 154, " First, question your "); + M_Print(x, 162, " sanity, then email "); + M_Print(x, 170, " lordhavoc@ghdigital.com"); + } + else + m_serverInfoMessage = false; + } +} + + +static void M_NetStart_Change (int dir) +{ + int count; + + switch (gameoptions_cursor) + { + case 1: + maxplayers += dir; + if (maxplayers > MAX_SCOREBOARD) + { + maxplayers = MAX_SCOREBOARD; + m_serverInfoMessage = true; + m_serverInfoMessageTime = realtime; + } + if (maxplayers < 2) + maxplayers = 2; + break; + + case 2: + if (gamemode == GAME_GOODVSBAD2) + break; + if (gamemode == GAME_TRANSFUSION) + { + switch (deathmatch.integer) + { + // From Cooperative to BloodBath + case 0: + Cvar_SetValueQuick (&coop, 0); + Cvar_SetValueQuick (&deathmatch, 1); + break; + + // From BloodBath to CTF + case 1: + Cvar_SetValueQuick (&coop, 0); + Cvar_SetValueQuick (&deathmatch, 2); + break; + + // From CTF to Cooperative + //case 2: + default: + Cvar_SetValueQuick (&coop, 1); + Cvar_SetValueQuick (&deathmatch, 0); + } + } + else if (gamemode == GAME_BATTLEMECH) + { + if (deathmatch.integer == 2) // changing from Rambo to Deathmatch + Cvar_SetValueQuick (&deathmatch, 0); + else // changing from Deathmatch to Rambo + Cvar_SetValueQuick (&deathmatch, 2); + } + else + { + if (deathmatch.integer) // changing from deathmatch to coop + { + Cvar_SetValueQuick (&coop, 1); + Cvar_SetValueQuick (&deathmatch, 0); + } + else // changing from coop to deathmatch + { + Cvar_SetValueQuick (&coop, 0); + Cvar_SetValueQuick (&deathmatch, 1); + } + } + break; + + case 3: + if (gamemode == GAME_GOODVSBAD2) + break; + if (gamemode == GAME_ROGUE) + count = 6; + else + count = 2; + + Cvar_SetValueQuick (&teamplay, teamplay.integer + dir); + if (teamplay.integer > count) + Cvar_SetValueQuick (&teamplay, 0); + else if (teamplay.integer < 0) + Cvar_SetValueQuick (&teamplay, count); + break; + + case 4: + if (gamemode == GAME_GOODVSBAD2) + break; + Cvar_SetValueQuick (&skill, skill.integer + dir); + if (gamemode == GAME_TRANSFUSION) + { + if (skill.integer > 5) + Cvar_SetValueQuick (&skill, 1); + if (skill.integer < 1) + Cvar_SetValueQuick (&skill, 5); + } + else + { + if (skill.integer > 3) + Cvar_SetValueQuick (&skill, 0); + if (skill.integer < 0) + Cvar_SetValueQuick (&skill, 3); + } + break; + + case 5: + if (gamemode == GAME_GOODVSBAD2) + break; + Cvar_SetValueQuick (&fraglimit, fraglimit.integer + dir*10); + if (fraglimit.integer > 100) + Cvar_SetValueQuick (&fraglimit, 0); + if (fraglimit.integer < 0) + Cvar_SetValueQuick (&fraglimit, 100); + break; + + case 6: + if (gamemode == GAME_GOODVSBAD2) + break; + Cvar_SetValueQuick (&timelimit, timelimit.value + dir*5); + if (timelimit.value > 60) + Cvar_SetValueQuick (&timelimit, 0); + if (timelimit.value < 0) + Cvar_SetValueQuick (&timelimit, 60); + break; + + case 7: + Cvar_SetValueQuick (&sv_public, !sv_public.integer); + break; + + case 8: + Cvar_SetValueQuick (&sv_maxrate, sv_maxrate.integer + dir*500); + if (sv_maxrate.integer < NET_MINRATE) + Cvar_SetValueQuick (&sv_maxrate, NET_MINRATE); + break; + + case 9: + break; + + case 10: + if (gamemode == GAME_GOODVSBAD2) + break; + startepisode += dir; + + if (startepisode < 0) + startepisode = gameoptions_levels->numepisodes - 1; + + if (startepisode >= gameoptions_levels->numepisodes) + startepisode = 0; + + startlevel = 0; + break; + + case 11: + startlevel += dir; + + if (startlevel < 0) + startlevel = gameoptions_levels->episodes[startepisode].levels - 1; + + if (startlevel >= gameoptions_levels->episodes[startepisode].levels) + startlevel = 0; + break; + } +} + +static void M_GameOptions_Key (int key, int ascii) +{ + int l; + char hostnamebuf[128]; + char vabuf[1024]; + + switch (key) + { + case K_ESCAPE: + M_Menu_MultiPlayer_f (); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + gameoptions_cursor--; + if (gameoptions_cursor < 0) + gameoptions_cursor = NUM_GAMEOPTIONS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + gameoptions_cursor++; + if (gameoptions_cursor >= NUM_GAMEOPTIONS) + gameoptions_cursor = 0; + break; + case 'a': + case K_LEFTARROW: + if (gameoptions_cursor == 0) + break; + S_LocalSound ("sound/misc/menu3.wav"); + M_NetStart_Change (-1); + break; + + case K_RIGHTARROW: + case 'd': + if (gameoptions_cursor == 0) + break; + S_LocalSound ("sound/misc/menu3.wav"); + M_NetStart_Change (1); + break; + case K_MOUSE1: + case K_ENTER: + S_LocalSound ("sound/misc/menu2.wav"); + if (gameoptions_cursor == 0) + { + if (sv.active) + Cbuf_AddText("disconnect\n"); + Cbuf_AddText(va(vabuf, sizeof(vabuf), "maxplayers %u\n", maxplayers) ); + + Cbuf_AddText(va(vabuf, sizeof(vabuf), "map %s\n", gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].name) ); + return; + } + + M_NetStart_Change (1); + break; + + case K_BACKSPACE: + if (gameoptions_cursor == 9) + { + l = (int)strlen(hostname.string); + if (l) + { + l = min(l - 1, 37); + memcpy(hostnamebuf, hostname.string, l); + hostnamebuf[l] = 0; + Cvar_Set("hostname", hostnamebuf); + } + } + break; + + default: + if (ascii < 32) + break; + if (gameoptions_cursor == 9) + { + l = (int)strlen(hostname.string); + if (l < 37) + { + memcpy(hostnamebuf, hostname.string, l); + hostnamebuf[l] = ascii; + hostnamebuf[l+1] = 0; + Cvar_Set("hostname", hostnamebuf); + } + } + } +} + +//============================================================================= +/* SLIST MENU */ + +static int slist_cursor; + +void M_Menu_ServerList_f (void) +{ + key_dest = key_menu; + m_state = m_slist; + m_entersound = true; + slist_cursor = 0; + M_Update_Return_Reason(""); + if (lanConfig_cursor == 2) + Net_SlistQW_f(); + else + Net_Slist_f(); +} + + +static void M_ServerList_Draw (void) +{ + int n, y, visible, start, end, numplayers, maxplayers; + cachepic_t *p; + const char *s; + char vabuf[1024]; + + // use as much vertical space as available + if (gamemode == GAME_TRANSFUSION) + M_Background(640, vid_conheight.integer - 80); + else + M_Background(640, vid_conheight.integer); + // scroll the list as the cursor moves + ServerList_GetPlayerStatistics(&numplayers, &maxplayers); + s = va(vabuf, sizeof(vabuf), "%i/%i masters %i/%i servers %i/%i players", masterreplycount, masterquerycount, serverreplycount, serverquerycount, numplayers, maxplayers); + M_PrintRed((640 - strlen(s) * 8) / 2, 32, s); + if (*m_return_reason) + M_Print(16, menu_height - 8, m_return_reason); + y = 48; + visible = (int)((menu_height - 16 - y) / 8 / 2); + start = bound(0, slist_cursor - (visible >> 1), serverlist_viewcount - visible); + end = min(start + visible, serverlist_viewcount); + + p = Draw_CachePic ("gfx/p_multi"); + M_DrawPic((640 - p->width) / 2, 4, "gfx/p_multi"); + if (end > start) + { + for (n = start;n < end;n++) + { + serverlist_entry_t *entry = ServerList_GetViewEntry(n); + DrawQ_Fill(menu_x, menu_y + y, 640, 16, n == slist_cursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_PrintColored(0, y, entry->line1);y += 8; + M_PrintColored(0, y, entry->line2);y += 8; + } + } + else if (realtime - masterquerytime > 10) + { + if (masterquerycount) + M_Print(0, y, "No servers found"); + else + M_Print(0, y, "No master servers found (network problem?)"); + } + else + { + if (serverquerycount) + M_Print(0, y, "Querying servers"); + else + M_Print(0, y, "Querying master servers"); + } +} + + +static void M_ServerList_Key(int k, int ascii) +{ + char vabuf[1024]; + switch (k) + { + case K_ESCAPE: + M_Menu_LanConfig_f(); + break; + + case K_SPACE: + if (lanConfig_cursor == 2) + Net_SlistQW_f(); + else + Net_Slist_f(); + break; + + case K_UPARROW: + case K_LEFTARROW: + case 'a': + S_LocalSound ("sound/misc/menu1.wav"); + slist_cursor--; + if (slist_cursor < 0) + slist_cursor = serverlist_viewcount - 1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + case 'd': + S_LocalSound ("sound/misc/menu1.wav"); + slist_cursor++; + if (slist_cursor >= serverlist_viewcount) + slist_cursor = 0; + break; + case K_MOUSE1: + case K_ENTER: + S_LocalSound ("sound/misc/menu2.wav"); + if (serverlist_viewcount) + Cbuf_AddText(va(vabuf, sizeof(vabuf), "connect \"%s\"\n", ServerList_GetViewEntry(slist_cursor)->info.cname)); + break; + + default: + break; + } + +} + +//============================================================================= +/* MODLIST MENU */ +// same limit of mod dirs as in fs.c +#define MODLIST_MAXDIRS 16 +static int modlist_enabled [MODLIST_MAXDIRS]; //array of indexs to modlist +static int modlist_numenabled; //number of enabled (or in process to be..) mods + +typedef struct modlist_entry_s +{ + qboolean loaded; // used to determine whether this entry is loaded and running + int enabled; // index to array of modlist_enabled + + // name of the modification, this is (will...be) displayed on the menu entry + char name[128]; + // directory where we will find it + char dir[MAX_QPATH]; +} modlist_entry_t; + +static int modlist_cursor; +//static int modlist_viewcount; + +static int modlist_count = 0; +static modlist_entry_t modlist[MODLIST_TOTALSIZE]; + +static void ModList_RebuildList(void) +{ + int i,j; + stringlist_t list; + + stringlistinit(&list); + listdirectory(&list, fs_basedir, ""); + stringlistsort(&list, true); + modlist_count = 0; + modlist_numenabled = fs_numgamedirs; + for (i = 0;i < list.numstrings;i++) + { + if (modlist_count >= MODLIST_TOTALSIZE) break; + // check all dirs to see if they "appear" to be mods + // reject any dirs that are part of the base game + if (gamedirname1 && !strcasecmp(gamedirname1, list.strings[i])) continue; + //if (gamedirname2 && !strcasecmp(gamedirname2, list.strings[i])) continue; + if (FS_CheckNastyPath (list.strings[i], true)) continue; + if (!FS_CheckGameDir(list.strings[i])) continue; + + strlcpy (modlist[modlist_count].dir, list.strings[i], sizeof(modlist[modlist_count].dir)); + //check currently loaded mods + modlist[modlist_count].loaded = false; + if (fs_numgamedirs) + for (j = 0; j < fs_numgamedirs; j++) + if (!strcasecmp(fs_gamedirs[j], modlist[modlist_count].dir)) + { + modlist[modlist_count].loaded = true; + modlist[modlist_count].enabled = j; + modlist_enabled[j] = modlist_count; + break; + } + modlist_count ++; + } + stringlistfreecontents(&list); +} + +static void ModList_Enable (void) +{ + int i; + int numgamedirs; + char gamedirs[MODLIST_MAXDIRS][MAX_QPATH]; + + // copy our mod list into an array for FS_ChangeGameDirs + numgamedirs = modlist_numenabled; + for (i = 0; i < modlist_numenabled; i++) + strlcpy (gamedirs[i], modlist[modlist_enabled[i]].dir,sizeof (gamedirs[i])); + + // this code snippet is from FS_ChangeGameDirs + if (fs_numgamedirs == numgamedirs) + { + for (i = 0;i < numgamedirs;i++) + if (strcasecmp(fs_gamedirs[i], gamedirs[i])) + break; + if (i == numgamedirs) + return; // already using this set of gamedirs, do nothing + } + + // this part is basically the same as the FS_GameDir_f function + if ((cls.state == ca_connected && !cls.demoplayback) || sv.active) + { + // actually, changing during game would work fine, but would be stupid + Con_Printf("Can not change gamedir while client is connected or server is running!\n"); + return; + } + + FS_ChangeGameDirs (modlist_numenabled, gamedirs, true, true); +} + +void M_Menu_ModList_f (void) +{ + key_dest = key_menu; + m_state = m_modlist; + m_entersound = true; + modlist_cursor = 0; + M_Update_Return_Reason(""); + ModList_RebuildList(); +} + +static void M_Menu_ModList_AdjustSliders (int dir) +{ + int i; + S_LocalSound ("sound/misc/menu3.wav"); + + // stop adding mods, we reach the limit + if (!modlist[modlist_cursor].loaded && (modlist_numenabled == MODLIST_MAXDIRS)) return; + modlist[modlist_cursor].loaded = !modlist[modlist_cursor].loaded; + if (modlist[modlist_cursor].loaded) + { + modlist[modlist_cursor].enabled = modlist_numenabled; + //push the value on the enabled list + modlist_enabled[modlist_numenabled++] = modlist_cursor; + } + else + { + //eliminate the value from the enabled list + for (i = modlist[modlist_cursor].enabled; i < modlist_numenabled; i++) + { + modlist_enabled[i] = modlist_enabled[i+1]; + modlist[modlist_enabled[i]].enabled--; + } + modlist_numenabled--; + } +} + +static void M_ModList_Draw (void) +{ + int n, y, visible, start, end; + cachepic_t *p; + const char *s_available = "Available Mods"; + const char *s_enabled = "Enabled Mods"; + + // use as much vertical space as available + if (gamemode == GAME_TRANSFUSION) + M_Background(640, vid_conheight.integer - 80); + else + M_Background(640, vid_conheight.integer); + + M_PrintRed(48 + 32, 32, s_available); + M_PrintRed(432, 32, s_enabled); + // Draw a list box with all enabled mods + DrawQ_Pic(menu_x + 432, menu_y + 48, NULL, 172, 8 * modlist_numenabled, 0, 0, 0, 0.5, 0); + for (y = 0; y < modlist_numenabled; y++) + M_PrintRed(432, 48 + y * 8, modlist[modlist_enabled[y]].dir); + + if (*m_return_reason) + M_Print(16, menu_height - 8, m_return_reason); + // scroll the list as the cursor moves + y = 48; + visible = (int)((menu_height - 16 - y) / 8 / 2); + start = bound(0, modlist_cursor - (visible >> 1), modlist_count - visible); + end = min(start + visible, modlist_count); + + p = Draw_CachePic ("gfx/p_option"); + M_DrawPic((640 - p->width) / 2, 4, "gfx/p_option"); + if (end > start) + { + for (n = start;n < end;n++) + { + DrawQ_Pic(menu_x + 40, menu_y + y, NULL, 360, 8, n == modlist_cursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); + M_ItemPrint(80, y, modlist[n].dir, true); + M_DrawCheckbox(48, y, modlist[n].loaded); + y +=8; + } + } + else + { + M_Print(80, y, "No Mods found"); + } +} + +static void M_ModList_Key(int k, int ascii) +{ + switch (k) + { + case K_ESCAPE: + ModList_Enable (); + M_Menu_Options_f(); + break; + + case K_SPACE: + S_LocalSound ("sound/misc/menu2.wav"); + ModList_RebuildList(); + break; + + case K_UPARROW: + S_LocalSound ("sound/misc/menu1.wav"); + modlist_cursor--; + if (modlist_cursor < 0) + modlist_cursor = modlist_count - 1; + break; + + case K_LEFTARROW: + case 'a': + M_Menu_ModList_AdjustSliders (-1); + break; + + case K_DOWNARROW: + S_LocalSound ("sound/misc/menu1.wav"); + modlist_cursor++; + if (modlist_cursor >= modlist_count) + modlist_cursor = 0; + break; + + case K_RIGHTARROW: + case 'd': + M_Menu_ModList_AdjustSliders (1); + break; + case K_MOUSE1: + case K_ENTER: + S_LocalSound ("sound/misc/menu2.wav"); + ModList_Enable (); + break; + + default: + break; + } + +} + +//============================================================================= +/* Menu Subsystem */ + +static void M_KeyEvent(int key, int ascii, qboolean downevent); +static void M_Draw(void); +void M_ToggleMenu(int mode); +static void M_Shutdown(void); + +static void M_Init (void) +{ + menuplyr_load = true; + menuplyr_pixels = NULL; + + Cmd_AddCommand ("menu_main", M_Menu_Main_f, "open the main menu"); + Cmd_AddCommand ("menu_singleplayer", M_Menu_SinglePlayer_f, "open the singleplayer menu"); + Cmd_AddCommand ("menu_load", M_Menu_Load_f, "open the loadgame menu"); + Cmd_AddCommand ("menu_save", M_Menu_Save_f, "open the savegame menu"); + Cmd_AddCommand ("menu_multiplayer", M_Menu_MultiPlayer_f, "open the multiplayer menu"); + Cmd_AddCommand ("menu_setup", M_Menu_Setup_f, "open the player setup menu"); + Cmd_AddCommand ("menu_options", M_Menu_Options_f, "open the options menu"); + Cmd_AddCommand ("menu_options_effects", M_Menu_Options_Effects_f, "open the effects options menu"); + Cmd_AddCommand ("menu_options_graphics", M_Menu_Options_Graphics_f, "open the graphics options menu"); + Cmd_AddCommand ("menu_options_colorcontrol", M_Menu_Options_ColorControl_f, "open the color control menu"); + Cmd_AddCommand ("menu_keys", M_Menu_Keys_f, "open the key binding menu"); + Cmd_AddCommand ("menu_video", M_Menu_Video_f, "open the video options menu"); + Cmd_AddCommand ("menu_reset", M_Menu_Reset_f, "open the reset to defaults menu"); + Cmd_AddCommand ("menu_reset", M_Menu_YawPitchControl_f, "open the yaw/pitch control menu"); + Cmd_AddCommand ("menu_mods", M_Menu_ModList_f, "open the mods browser menu"); + Cmd_AddCommand ("help", M_Menu_Help_f, "open the help menu"); + Cmd_AddCommand ("menu_quit", M_Menu_Quit_f, "open the quit menu"); + Cmd_AddCommand ("menu_transfusion_episode", M_Menu_Transfusion_Episode_f, "open the transfusion episode select menu"); + Cmd_AddCommand ("menu_transfusion_skill", M_Menu_Transfusion_Skill_f, "open the transfusion skill select menu"); + Cmd_AddCommand ("menu_credits", M_Menu_Credits_f, "open the credits menu"); +} + +void M_Draw (void) +{ + char vabuf[1024]; + if (key_dest != key_menu && key_dest != key_menu_grabbed) { + m_state = m_none; + jni_BigScreenMode(0); + } + + if (m_state == m_none) + return; + + switch (m_state) + { + case m_none: + break; + + case m_main: + M_Main_Draw (); + break; + + case m_demo: + M_Demo_Draw (); + break; + + case m_singleplayer: + M_SinglePlayer_Draw (); + break; + + case m_transfusion_episode: + M_Transfusion_Episode_Draw (); + break; + + case m_transfusion_skill: + M_Transfusion_Skill_Draw (); + break; + + case m_load: + M_Load_Draw (); + break; + + case m_save: + M_Save_Draw (); + break; + + case m_multiplayer: + M_MultiPlayer_Draw (); + break; + + case m_setup: + M_Setup_Draw (); + break; + + case m_options: + M_Options_Draw (); + break; + + case m_options_effects: + M_Options_Effects_Draw (); + break; + + case m_options_graphics: + M_Options_Graphics_Draw (); + break; + + case m_options_colorcontrol: + M_Options_ColorControl_Draw (); + break; + + case m_keys: + M_Keys_Draw (); + break; + + case m_reset: + M_Reset_Draw (); + break; + + case m_video: + M_Video_Draw (); + break; + + case m_yawpitchcontrol: + M_Menu_YawPitchControl_Draw (); + break; + + case m_help: + M_Help_Draw (); + break; + + case m_credits: + M_Credits_Draw (); + break; + + case m_quit: + M_Quit_Draw (); + break; + + case m_lanconfig: + M_LanConfig_Draw (); + break; + + case m_gameoptions: + M_GameOptions_Draw (); + break; + + case m_slist: + M_ServerList_Draw (); + break; + + case m_modlist: + M_ModList_Draw (); + break; + } + + if (gamemode == GAME_TRANSFUSION && !m_missingdata) { + if (m_state != m_credits) { + cachepic_t *p, *drop1, *drop2, *drop3; + int g, scale_x, scale_y, scale_y_repeat, top_offset; + float scale_y_rate; + scale_y_repeat = vid_conheight.integer * 2; + g = (int)(realtime * 64)%96; + scale_y_rate = (float)(g+1) / 96; + top_offset = (g+12)/12; + p = Draw_CachePic (va(vabuf, sizeof(vabuf), "gfx/menu/blooddrip%i", top_offset)); + drop1 = Draw_CachePic ("gfx/menu/blooddrop1"); + drop2 = Draw_CachePic ("gfx/menu/blooddrop2"); + drop3 = Draw_CachePic ("gfx/menu/blooddrop3"); + for (scale_x = 0; scale_x <= vid_conwidth.integer; scale_x += p->width) { + for (scale_y = -scale_y_repeat; scale_y <= vid_conheight.integer; scale_y += scale_y_repeat) { + DrawQ_Pic (scale_x + 21, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 116, scale_y_repeat + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 180, scale_y_repeat * .275 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 242, scale_y_repeat * .75 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 304, scale_y_repeat * .25 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 362, scale_y_repeat * .46125 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 402, scale_y_repeat * .1725 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 438, scale_y_repeat * .9 + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 484, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 557, scale_y_repeat * .9425 + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); + DrawQ_Pic (scale_x + 606, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop2, 0, 0, 1, 1, 1, 1, 0); + } + DrawQ_Pic (scale_x, -1, Draw_CachePic (va(vabuf, sizeof(vabuf), "gfx/menu/blooddrip%i", top_offset)), 0, 0, 1, 1, 1, 1, 0); + } + } + } + + if (m_entersound) + { + S_LocalSound ("sound/misc/menu2.wav"); + m_entersound = false; + } + + S_ExtraUpdate (); +} + + +void M_KeyEvent (int key, int ascii, qboolean downevent) +{ + if (!downevent) + return; + switch (m_state) + { + case m_none: + return; + + case m_main: + M_Main_Key (key, ascii); + return; + + case m_demo: + M_Demo_Key (key, ascii); + return; + + case m_singleplayer: + M_SinglePlayer_Key (key, ascii); + return; + + case m_transfusion_episode: + M_Transfusion_Episode_Key (key, ascii); + return; + + case m_transfusion_skill: + M_Transfusion_Skill_Key (key, ascii); + return; + + case m_load: + M_Load_Key (key, ascii); + return; + + case m_save: + M_Save_Key (key, ascii); + return; + + case m_multiplayer: + M_MultiPlayer_Key (key, ascii); + return; + + case m_setup: + M_Setup_Key (key, ascii); + return; + + case m_options: + M_Options_Key (key, ascii); + return; + + case m_options_effects: + M_Options_Effects_Key (key, ascii); + return; + + case m_options_graphics: + M_Options_Graphics_Key (key, ascii); + return; + + case m_options_colorcontrol: + M_Options_ColorControl_Key (key, ascii); + return; + + case m_keys: + M_Keys_Key (key, ascii); + return; + + case m_reset: + M_Reset_Key (key, ascii); + return; + + case m_video: + M_Video_Key (key, ascii); + return; + + case m_yawpitchcontrol: + M_Menu_YawPitchControl_Key (key, ascii); + return; + + case m_help: + M_Help_Key (key, ascii); + return; + + case m_credits: + M_Credits_Key (key, ascii); + return; + + case m_quit: + M_Quit_Key (key, ascii); + return; + + case m_lanconfig: + M_LanConfig_Key (key, ascii); + return; + + case m_gameoptions: + M_GameOptions_Key (key, ascii); + return; + + case m_slist: + M_ServerList_Key (key, ascii); + return; + + case m_modlist: + M_ModList_Key (key, ascii); + return; + } + +} + +static void M_NewMap(void) +{ +} + +void M_Shutdown(void) +{ + // reset key_dest + key_dest = key_game; +} + +//============================================================================ +// Menu prog handling + +static const char *m_required_func[] = { +"m_init", +"m_keydown", +"m_draw", +"m_toggle", +"m_shutdown", +}; + +static int m_numrequiredfunc = sizeof(m_required_func) / sizeof(char*); + +static prvm_required_field_t m_required_fields[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_menufieldvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_menufieldstring(x) {ev_string, #x}, +#define PRVM_DECLARE_menufieldedict(x) {ev_entity, #x}, +#define PRVM_DECLARE_menufieldfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +static int m_numrequiredfields = sizeof(m_required_fields) / sizeof(m_required_fields[0]); + +static prvm_required_field_t m_required_globals[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_menuglobalvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_menuglobalstring(x) {ev_string, #x}, +#define PRVM_DECLARE_menuglobaledict(x) {ev_entity, #x}, +#define PRVM_DECLARE_menuglobalfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +static int m_numrequiredglobals = sizeof(m_required_globals) / sizeof(m_required_globals[0]); + +void MR_SetRouting (qboolean forceold); + +void MVM_error_cmd(const char *format, ...) DP_FUNC_PRINTF(1); +void MVM_error_cmd(const char *format, ...) +{ + prvm_prog_t *prog = MVM_prog; + static qboolean processingError = false; + char errorstring[MAX_INPUTLINE]; + va_list argptr; + + va_start (argptr, format); + dpvsnprintf (errorstring, sizeof(errorstring), format, argptr); + va_end (argptr); + Con_Printf( "Menu_Error: %s\n", errorstring ); + + if( !processingError ) { + processingError = true; + PRVM_Crash(prog); + processingError = false; + } else { + Con_Printf( "Menu_Error: Recursive call to MVM_error_cmd (from PRVM_Crash)!\n" ); + } + + // fall back to the normal menu + + // say it + Con_Print("Falling back to normal menu\n"); + + key_dest = key_game; + + // init the normal menu now -> this will also correct the menu router pointers + MR_SetRouting (TRUE); + + // reset the active scene, too (to be on the safe side ;)) + R_SelectScene( RST_CLIENT ); + + Host_AbortCurrentFrame(); +} + +static void MVM_begin_increase_edicts(prvm_prog_t *prog) +{ +} + +static void MVM_end_increase_edicts(prvm_prog_t *prog) +{ +} + +static void MVM_init_edict(prvm_prog_t *prog, prvm_edict_t *edict) +{ +} + +static void MVM_free_edict(prvm_prog_t *prog, prvm_edict_t *ed) +{ +} + +static void MVM_count_edicts(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *ent; + int active; + + active = 0; + for (i=0 ; inum_edicts ; i++) + { + ent = PRVM_EDICT_NUM(i); + if (ent->priv.required->free) + continue; + active++; + } + + Con_Printf("num_edicts:%3i\n", prog->num_edicts); + Con_Printf("active :%3i\n", active); +} + +static qboolean MVM_load_edict(prvm_prog_t *prog, prvm_edict_t *ent) +{ + return true; +} + +static void MP_KeyEvent (int key, int ascii, qboolean downevent) +{ + prvm_prog_t *prog = MVM_prog; + + // pass key + prog->globals.fp[OFS_PARM0] = (prvm_vec_t) key; + prog->globals.fp[OFS_PARM1] = (prvm_vec_t) ascii; + if (downevent) + prog->ExecuteProgram(prog, PRVM_menufunction(m_keydown),"m_keydown(float key, float ascii) required"); + else if (PRVM_menufunction(m_keyup)) + prog->ExecuteProgram(prog, PRVM_menufunction(m_keyup),"m_keyup(float key, float ascii) required"); +} + +static void MP_Draw (void) +{ + prvm_prog_t *prog = MVM_prog; + // declarations that are needed right now + + float oldquality; + + R_SelectScene( RST_MENU ); + + // reset the temp entities each frame + r_refdef.scene.numtempentities = 0; + + // menu scenes do not use reduced rendering quality + oldquality = r_refdef.view.quality; + r_refdef.view.quality = 1; + // TODO: this needs to be exposed to R_SetView (or something similar) ASAP [2/5/2008 Andreas] + r_refdef.scene.time = realtime; + + // FIXME: this really shouldnt error out lest we have a very broken refdef state...? + // or does it kill the server too? + prog->ExecuteProgram(prog, PRVM_menufunction(m_draw),"m_draw() required"); + + // TODO: imo this should be moved into scene, too [1/27/2008 Andreas] + r_refdef.view.quality = oldquality; + + R_SelectScene( RST_CLIENT ); +} + +static void MP_ToggleMenu(int mode) +{ + prvm_prog_t *prog = MVM_prog; + + prog->globals.fp[OFS_PARM0] = (prvm_vec_t) mode; + prog->ExecuteProgram(prog, PRVM_menufunction(m_toggle),"m_toggle(float mode) required"); +} + +static void MP_NewMap(void) +{ + prvm_prog_t *prog = MVM_prog; + if (PRVM_menufunction(m_newmap)) + prog->ExecuteProgram(prog, PRVM_menufunction(m_newmap),"m_newmap() required"); +} + +static void MP_Shutdown (void) +{ + prvm_prog_t *prog = MVM_prog; + + prog->ExecuteProgram(prog, PRVM_menufunction(m_shutdown),"m_shutdown() required"); + + // reset key_dest + key_dest = key_game; + + // AK not using this cause Im not sure whether this is useful at all instead : + PRVM_Prog_Reset(prog); +} + +static void MP_Init (void) +{ + prvm_prog_t *prog = MVM_prog; + PRVM_Prog_Init(prog); + + prog->edictprivate_size = 0; // no private struct used + prog->name = "menu"; + prog->num_edicts = 1; + prog->limit_edicts = M_MAX_EDICTS; + prog->extensionstring = vm_m_extensions; + prog->builtins = vm_m_builtins; + prog->numbuiltins = vm_m_numbuiltins; + + // all callbacks must be defined (pointers are not checked before calling) + prog->begin_increase_edicts = MVM_begin_increase_edicts; + prog->end_increase_edicts = MVM_end_increase_edicts; + prog->init_edict = MVM_init_edict; + prog->free_edict = MVM_free_edict; + prog->count_edicts = MVM_count_edicts; + prog->load_edict = MVM_load_edict; + prog->init_cmd = MVM_init_cmd; + prog->reset_cmd = MVM_reset_cmd; + prog->error_cmd = MVM_error_cmd; + prog->ExecuteProgram = MVM_ExecuteProgram; + + // allocate the mempools + prog->progs_mempool = Mem_AllocPool(menu_progs.string, 0, NULL); + + PRVM_Prog_Load(prog, menu_progs.string, NULL, 0, m_numrequiredfunc, m_required_func, m_numrequiredfields, m_required_fields, m_numrequiredglobals, m_required_globals); + + // note: OP_STATE is not supported by menu qc, we don't even try to detect + // it here + + in_client_mouse = true; + + // call the prog init + prog->ExecuteProgram(prog, PRVM_menufunction(m_init),"m_init() required"); +} + +//============================================================================ +// Menu router + +void (*MR_KeyEvent) (int key, int ascii, qboolean downevent); +void (*MR_Draw) (void); +void (*MR_ToggleMenu) (int mode); +void (*MR_Shutdown) (void); +void (*MR_NewMap) (void); + +void MR_SetRouting(qboolean forceold) +{ + // if the menu prog isnt available or forceqmenu ist set, use the old menu + if(!FS_FileExists(menu_progs.string) || forceqmenu.integer || forceold) + { + // set menu router function pointers + MR_KeyEvent = M_KeyEvent; + MR_Draw = M_Draw; + MR_ToggleMenu = M_ToggleMenu; + MR_Shutdown = M_Shutdown; + MR_NewMap = M_NewMap; + M_Init(); + } + else + { + // set menu router function pointers + MR_KeyEvent = MP_KeyEvent; + MR_Draw = MP_Draw; + MR_ToggleMenu = MP_ToggleMenu; + MR_Shutdown = MP_Shutdown; + MR_NewMap = MP_NewMap; + MP_Init(); + } +} + +void MR_Restart(void) +{ + if(MR_Shutdown) + MR_Shutdown (); + MR_SetRouting (FALSE); +} + +static void Call_MR_ToggleMenu_f(void) +{ + int m; + m = ((Cmd_Argc() < 2) ? -1 : atoi(Cmd_Argv(1))); + Host_StartVideo(); + if(MR_ToggleMenu) + MR_ToggleMenu(m); +} + +void MR_Init_Commands(void) +{ + // set router console commands + Cvar_RegisterVariable (&forceqmenu); + Cvar_RegisterVariable (&menu_options_colorcontrol_correctionvalue); + Cvar_RegisterVariable (&menu_progs); + Cmd_AddCommand ("menu_restart",MR_Restart, "restart menu system (reloads menu.dat)"); + Cmd_AddCommand ("togglemenu", Call_MR_ToggleMenu_f, "opens or closes menu"); +} + +void MR_Init(void) +{ + vid_mode_t res[1024]; + size_t res_count, i; + + res_count = VID_ListModes(res, sizeof(res) / sizeof(*res)); + res_count = VID_SortModes(res, res_count, false, false, true); + if(res_count) + { + video_resolutions_count = res_count; + video_resolutions = (video_resolution_t *) Mem_Alloc(cls.permanentmempool, sizeof(*video_resolutions) * (video_resolutions_count + 1)); + memset(&video_resolutions[video_resolutions_count], 0, sizeof(video_resolutions[video_resolutions_count])); + for(i = 0; i < res_count; ++i) + { + int n, d, t; + video_resolutions[i].type = "Detected mode"; // FIXME make this more dynamic + video_resolutions[i].width = res[i].width; + video_resolutions[i].height = res[i].height; + video_resolutions[i].pixelheight = res[i].pixelheight_num / (double) res[i].pixelheight_denom; + n = res[i].pixelheight_denom * video_resolutions[i].width; + d = res[i].pixelheight_num * video_resolutions[i].height; + while(d) + { + t = n; + n = d; + d = t % d; + } + d = (res[i].pixelheight_num * video_resolutions[i].height) / n; + n = (res[i].pixelheight_denom * video_resolutions[i].width) / n; + switch(n * 0x10000 | d) + { + case 0x00040003: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 480; + video_resolutions[i].type = "Standard 4x3"; + break; + case 0x00050004: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 512; + if(res[i].pixelheight_denom == res[i].pixelheight_num) + video_resolutions[i].type = "Square Pixel (LCD) 5x4"; + else + video_resolutions[i].type = "Short Pixel (CRT) 5x4"; + break; + case 0x00080005: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 400; + if(res[i].pixelheight_denom == res[i].pixelheight_num) + video_resolutions[i].type = "Widescreen 8x5"; + else + video_resolutions[i].type = "Tall Pixel (CRT) 8x5"; + + break; + case 0x00050003: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 384; + video_resolutions[i].type = "Widescreen 5x3"; + break; + case 0x000D0009: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 400; + video_resolutions[i].type = "Widescreen 14x9"; + break; + case 0x00100009: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 480; + video_resolutions[i].type = "Widescreen 16x9"; + break; + case 0x00030002: + video_resolutions[i].conwidth = 720; + video_resolutions[i].conheight = 480; + video_resolutions[i].type = "NTSC 3x2"; + break; + case 0x000D000B: + video_resolutions[i].conwidth = 720; + video_resolutions[i].conheight = 566; + video_resolutions[i].type = "PAL 14x11"; + break; + case 0x00080007: + if(video_resolutions[i].width >= 512) + { + video_resolutions[i].conwidth = 512; + video_resolutions[i].conheight = 448; + video_resolutions[i].type = "SNES 8x7"; + } + else + { + video_resolutions[i].conwidth = 256; + video_resolutions[i].conheight = 224; + video_resolutions[i].type = "NES 8x7"; + } + break; + default: + video_resolutions[i].conwidth = 640; + video_resolutions[i].conheight = 640 * d / n; + video_resolutions[i].type = "Detected mode"; + break; + } + if(video_resolutions[i].conwidth > video_resolutions[i].width || video_resolutions[i].conheight > video_resolutions[i].height) + { + int f1, f2; + f1 = video_resolutions[i].conwidth > video_resolutions[i].width; + f2 = video_resolutions[i].conheight > video_resolutions[i].height; + if(f1 > f2) + { + video_resolutions[i].conwidth = video_resolutions[i].width; + video_resolutions[i].conheight = video_resolutions[i].conheight / f1; + } + else + { + video_resolutions[i].conwidth = video_resolutions[i].conwidth / f2; + video_resolutions[i].conheight = video_resolutions[i].height; + } + } + } + } + else + { + video_resolutions = video_resolutions_hardcoded; + video_resolutions_count = sizeof(video_resolutions_hardcoded) / sizeof(*video_resolutions_hardcoded) - 1; + } + + menu_video_resolutions_forfullscreen = !!vid_fullscreen.integer; + M_Menu_Video_FindResolution(vid.width, vid.height, vid_pixelheight.value); + + // use -forceqmenu to use always the normal quake menu (it sets forceqmenu to 1) +// COMMANDLINEOPTION: Client: -forceqmenu disables menu.dat (same as +forceqmenu 1) + if(COM_CheckParm("-forceqmenu")) + Cvar_SetValueQuick(&forceqmenu,1); + // use -useqmenu for debugging proposes, cause it starts + // the normal quake menu only the first time +// COMMANDLINEOPTION: Client: -useqmenu causes the first time you open the menu to use the quake menu, then reverts to menu.dat (if forceqmenu is 0) + if(COM_CheckParm("-useqmenu")) + MR_SetRouting (TRUE); + else + MR_SetRouting (FALSE); +} diff --git a/app/jni/menu.h b/app/jni/menu.h new file mode 100644 index 0000000..baab931 --- /dev/null +++ b/app/jni/menu.h @@ -0,0 +1,99 @@ +/* +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. + +*/ + +#ifndef MENU_H +#define MENU_H + +enum m_state_e { + m_none, + m_main, + m_demo, + m_singleplayer, + m_transfusion_episode, + m_transfusion_skill, + m_load, + m_save, + m_multiplayer, + m_setup, + m_options, + m_video, + m_yawpitchcontrol, + m_keys, + m_help, + m_credits, + m_quit, + m_lanconfig, + m_gameoptions, + m_slist, + m_options_effects, + m_options_graphics, + m_options_colorcontrol, + m_reset, + m_modlist +}; + +extern enum m_state_e m_state; +extern char m_return_reason[128]; +void M_Update_Return_Reason(const char *s); + +/* +// hard-coded menus +// +void M_Init (void); +void M_KeyEvent (int key); +void M_Draw (void); +void M_ToggleMenu (int mode); + +// +// menu prog menu +// +void MP_Init (void); +void MP_KeyEvent (int key); +void MP_Draw (void); +void MP_ToggleMenu (int mode); +void MP_Shutdown (void);*/ + +// +// menu router +// + +void MR_Init_Commands (void); +void MR_Init (void); +void MR_Restart (void); +extern void (*MR_KeyEvent) (int key, int ascii, qboolean downevent); +extern void (*MR_Draw) (void); +extern void (*MR_ToggleMenu) (int mode); +extern void (*MR_Shutdown) (void); +extern void (*MR_NewMap) (void); + +typedef struct video_resolution_s +{ + const char *type; + int width, height; + int conwidth, conheight; + double pixelheight; ///< pixel aspect +} +video_resolution_t; +extern video_resolution_t *video_resolutions; +extern int video_resolutions_count; +extern video_resolution_t video_resolutions_hardcoded[]; +extern int video_resolutions_hardcoded_count; +#endif + diff --git a/app/jni/meshqueue.c b/app/jni/meshqueue.c new file mode 100644 index 0000000..2bcf720 --- /dev/null +++ b/app/jni/meshqueue.c @@ -0,0 +1,151 @@ + +#include "quakedef.h" +#include "meshqueue.h" + +typedef struct meshqueue_s +{ + struct meshqueue_s *next; + void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfaceindices); + const entity_render_t *ent; + int surfacenumber; + const rtlight_t *rtlight; + float dist; + dptransparentsortcategory_t category; +} +meshqueue_t; + +int trans_sortarraysize; +meshqueue_t **trans_hash = NULL; +meshqueue_t ***trans_hashpointer = NULL; + +float mqt_viewplanedist; +float mqt_viewmaxdist; +meshqueue_t *mqt_array; +int mqt_count; +int mqt_total; + +void R_MeshQueue_BeginScene(void) +{ + mqt_count = 0; + mqt_viewplanedist = DotProduct(r_refdef.view.origin, r_refdef.view.forward); + mqt_viewmaxdist = 0; +} + +void R_MeshQueue_AddTransparent(dptransparentsortcategory_t category, const vec3_t center, void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist), const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight) +{ + meshqueue_t *mq; + if (mqt_count >= mqt_total || !mqt_array) + { + int newtotal = max(1024, mqt_total * 2); + meshqueue_t *newarray = (meshqueue_t *)Mem_Alloc(cls.permanentmempool, newtotal * sizeof(meshqueue_t)); + if (mqt_array) + { + memcpy(newarray, mqt_array, mqt_total * sizeof(meshqueue_t)); + Mem_Free(mqt_array); + } + mqt_array = newarray; + mqt_total = newtotal; + } + mq = &mqt_array[mqt_count++]; + mq->callback = callback; + mq->ent = ent; + mq->surfacenumber = surfacenumber; + mq->rtlight = rtlight; + mq->category = category; + if (r_transparent_useplanardistance.integer) + mq->dist = DotProduct(center, r_refdef.view.forward) - mqt_viewplanedist; + else + mq->dist = VectorDistance(center, r_refdef.view.origin); + mq->next = NULL; + mqt_viewmaxdist = max(mqt_viewmaxdist, mq->dist); +} + +void R_MeshQueue_RenderTransparent(void) +{ + int i, hashindex, maxhashindex, batchnumsurfaces; + float distscale; + const entity_render_t *ent; + const rtlight_t *rtlight; + void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfaceindices); + int batchsurfaceindex[MESHQUEUE_TRANSPARENT_BATCHSIZE]; + meshqueue_t *mqt; + + if (!mqt_count) + return; + + // check for bad cvars + if (r_transparent_sortarraysize.integer < 1 || r_transparent_sortarraysize.integer > 32768) + Cvar_SetValueQuick(&r_transparent_sortarraysize, bound(1, r_transparent_sortarraysize.integer, 32768)); + if (r_transparent_sortmindist.integer < 1 || r_transparent_sortmindist.integer >= r_transparent_sortmaxdist.integer) + Cvar_SetValueQuick(&r_transparent_sortmindist, 1); + if (r_transparent_sortmaxdist.integer < r_transparent_sortmindist.integer || r_transparent_sortmaxdist.integer > 32768) + Cvar_SetValueQuick(&r_transparent_sortmaxdist, bound(r_transparent_sortmindist.integer, r_transparent_sortmaxdist.integer, 32768)); + + // update hash array + if (trans_sortarraysize != r_transparent_sortarraysize.integer) + { + trans_sortarraysize = r_transparent_sortarraysize.integer; + if (trans_hash) + Mem_Free(trans_hash); + trans_hash = (meshqueue_t **)Mem_Alloc(cls.permanentmempool, sizeof(trans_hash) * trans_sortarraysize); + if (trans_hashpointer) + Mem_Free(trans_hashpointer); + trans_hashpointer = (meshqueue_t ***)Mem_Alloc(cls.permanentmempool, sizeof(trans_hashpointer) * trans_sortarraysize); + } + + // build index + memset(trans_hash, 0, sizeof(trans_hash) * trans_sortarraysize); + for (i = 0; i < trans_sortarraysize; i++) + trans_hashpointer[i] = &trans_hash[i]; + distscale = (trans_sortarraysize - 1) / min(mqt_viewmaxdist, r_transparent_sortmaxdist.integer); + maxhashindex = trans_sortarraysize - 1; + for (i = 0, mqt = mqt_array; i < mqt_count; i++, mqt++) + { + switch(mqt->category) + { + default: + case TRANSPARENTSORT_HUD: + hashindex = 0; + break; + case TRANSPARENTSORT_DISTANCE: + // this could use a reduced range if we need more categories + hashindex = bound(0, (int)(bound(0, mqt->dist - r_transparent_sortmindist.integer, r_transparent_sortmaxdist.integer) * distscale), maxhashindex); + break; + case TRANSPARENTSORT_SKY: + hashindex = maxhashindex; + break; + } + // link to tail of hash chain (to preserve render order) + mqt->next = NULL; + *trans_hashpointer[hashindex] = mqt; + trans_hashpointer[hashindex] = &mqt->next; + } + callback = NULL; + ent = NULL; + rtlight = NULL; + batchnumsurfaces = 0; + + // draw + for (i = maxhashindex; i >= 0; i--) + { + if (trans_hash[i]) + { + for (mqt = trans_hash[i]; mqt; mqt = mqt->next) + { + if (ent != mqt->ent || rtlight != mqt->rtlight || callback != mqt->callback || batchnumsurfaces >= MESHQUEUE_TRANSPARENT_BATCHSIZE) + { + if (batchnumsurfaces) + callback(ent, rtlight, batchnumsurfaces, batchsurfaceindex); + batchnumsurfaces = 0; + ent = mqt->ent; + rtlight = mqt->rtlight; + callback = mqt->callback; + } + batchsurfaceindex[batchnumsurfaces++] = mqt->surfacenumber; + } + } + } + if (batchnumsurfaces) + callback(ent, rtlight, batchnumsurfaces, batchsurfaceindex); + mqt_count = 0; +} diff --git a/app/jni/meshqueue.h b/app/jni/meshqueue.h new file mode 100644 index 0000000..68fa4d0 --- /dev/null +++ b/app/jni/meshqueue.h @@ -0,0 +1,12 @@ + +#ifndef MESHQUEUE_H +#define MESHQUEUE_H + +// VorteX: seems this value is hardcoded in other several defines as it's changing makes mess +#define MESHQUEUE_TRANSPARENT_BATCHSIZE 256 + +void R_MeshQueue_BeginScene(void); +void R_MeshQueue_AddTransparent(dptransparentsortcategory_t category, const vec3_t center, void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist), const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight); +void R_MeshQueue_RenderTransparent(void); + +#endif diff --git a/app/jni/mod_skeletal_animatevertices_generic.c b/app/jni/mod_skeletal_animatevertices_generic.c new file mode 100644 index 0000000..4356345 --- /dev/null +++ b/app/jni/mod_skeletal_animatevertices_generic.c @@ -0,0 +1,136 @@ +#include "mod_skeletal_animatevertices_generic.h" + +void Mod_Skeletal_AnimateVertices_Generic(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + // vertex weighted skeletal + int i, k; + float *bonepose; + float *boneposerelative; + const blendweights_t * RESTRICT weights; + + //unsigned long long ts = rdtsc(); + bonepose = (float *) Mod_Skeletal_AnimateVertices_AllocBuffers(sizeof(float[12]) * (model->num_bones*2 + model->surfmesh.num_blends)); + boneposerelative = bonepose + model->num_bones * 12; + + Mod_Skeletal_BuildTransforms(model, frameblend, skeleton, bonepose, boneposerelative); + + // generate matrices for all blend combinations + weights = model->surfmesh.data_blendweights; + for (i = 0;i < model->surfmesh.num_blends;i++, weights++) + { + float * RESTRICT b = boneposerelative + 12 * (model->num_bones + i); + const float * RESTRICT m = boneposerelative + 12 * (unsigned int)weights->index[0]; + float f = weights->influence[0] * (1.0f / 255.0f); + b[ 0] = f*m[ 0]; b[ 1] = f*m[ 1]; b[ 2] = f*m[ 2]; b[ 3] = f*m[ 3]; + b[ 4] = f*m[ 4]; b[ 5] = f*m[ 5]; b[ 6] = f*m[ 6]; b[ 7] = f*m[ 7]; + b[ 8] = f*m[ 8]; b[ 9] = f*m[ 9]; b[10] = f*m[10]; b[11] = f*m[11]; + for (k = 1;k < 4 && weights->influence[k];k++) + { + m = boneposerelative + 12 * (unsigned int)weights->index[k]; + f = weights->influence[k] * (1.0f / 255.0f); + b[ 0] += f*m[ 0]; b[ 1] += f*m[ 1]; b[ 2] += f*m[ 2]; b[ 3] += f*m[ 3]; + b[ 4] += f*m[ 4]; b[ 5] += f*m[ 5]; b[ 6] += f*m[ 6]; b[ 7] += f*m[ 7]; + b[ 8] += f*m[ 8]; b[ 9] += f*m[ 9]; b[10] += f*m[10]; b[11] += f*m[11]; + } + } + +#define LOAD_MATRIX_SCALAR() const float * RESTRICT m = boneposerelative + 12 * (unsigned int)*b + +#define LOAD_MATRIX3() \ + LOAD_MATRIX_SCALAR() +#define LOAD_MATRIX4() \ + LOAD_MATRIX_SCALAR() + +#define TRANSFORM_POSITION_SCALAR(in, out) \ + (out)[0] = ((in)[0] * m[0] + (in)[1] * m[1] + (in)[2] * m[ 2] + m[3]); \ + (out)[1] = ((in)[0] * m[4] + (in)[1] * m[5] + (in)[2] * m[ 6] + m[7]); \ + (out)[2] = ((in)[0] * m[8] + (in)[1] * m[9] + (in)[2] * m[10] + m[11]); +#define TRANSFORM_VECTOR_SCALAR(in, out) \ + (out)[0] = ((in)[0] * m[0] + (in)[1] * m[1] + (in)[2] * m[ 2]); \ + (out)[1] = ((in)[0] * m[4] + (in)[1] * m[5] + (in)[2] * m[ 6]); \ + (out)[2] = ((in)[0] * m[8] + (in)[1] * m[9] + (in)[2] * m[10]); + +#define TRANSFORM_POSITION(in, out) \ + TRANSFORM_POSITION_SCALAR(in, out) +#define TRANSFORM_VECTOR(in, out) \ + TRANSFORM_VECTOR_SCALAR(in, out) + + // transform vertex attributes by blended matrices + if (vertex3f) + { + const float * RESTRICT v = model->surfmesh.data_vertex3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + // special case common combinations of attributes to avoid repeated loading of matrices + if (normal3f) + { + const float * RESTRICT n = model->surfmesh.data_normal3f; + if (svector3f && tvector3f) + { + const float * RESTRICT sv = model->surfmesh.data_svector3f; + const float * RESTRICT tv = model->surfmesh.data_tvector3f; + + // Note that for SSE each iteration stores one element past end, so we break one vertex short + // and handle that with scalars in that case + for (i = 0; i < model->surfmesh.num_vertices; i++, v += 3, n += 3, sv += 3, tv += 3, b++, + vertex3f += 3, normal3f += 3, svector3f += 3, tvector3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + TRANSFORM_VECTOR(n, normal3f); + TRANSFORM_VECTOR(sv, svector3f); + TRANSFORM_VECTOR(tv, tvector3f); + } + + return; + } + + for (i = 0;i < model->surfmesh.num_vertices; i++, v += 3, n += 3, b++, vertex3f += 3, normal3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + TRANSFORM_VECTOR(n, normal3f); + } + } + else + { + for (i = 0;i < model->surfmesh.num_vertices; i++, v += 3, b++, vertex3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + } + } + } + + else if (normal3f) + { + const float * RESTRICT n = model->surfmesh.data_normal3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < model->surfmesh.num_vertices; i++, n += 3, b++, normal3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(n, normal3f); + } + } + + if (svector3f) + { + const float * RESTRICT sv = model->surfmesh.data_svector3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < model->surfmesh.num_vertices; i++, sv += 3, b++, svector3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(sv, svector3f); + } + } + + if (tvector3f) + { + const float * RESTRICT tv = model->surfmesh.data_tvector3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < model->surfmesh.num_vertices; i++, tv += 3, b++, tvector3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(tv, tvector3f); + } + } +} diff --git a/app/jni/mod_skeletal_animatevertices_generic.h b/app/jni/mod_skeletal_animatevertices_generic.h new file mode 100644 index 0000000..2ad97eb --- /dev/null +++ b/app/jni/mod_skeletal_animatevertices_generic.h @@ -0,0 +1,8 @@ +#ifndef MOD_SKELETAL_ANIMATEVERTICES_GENERIC_H +#define MOD_H + +#include "quakedef.h" + +void Mod_Skeletal_AnimateVertices_Generic(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); + +#endif diff --git a/app/jni/mod_skeletal_animatevertices_sse.c b/app/jni/mod_skeletal_animatevertices_sse.c new file mode 100644 index 0000000..648ab31 --- /dev/null +++ b/app/jni/mod_skeletal_animatevertices_sse.c @@ -0,0 +1,446 @@ +#include "mod_skeletal_animatevertices_sse.h" + +#ifdef SSE_POSSIBLE + +#ifdef MATRIX4x4_OPENGLORIENTATION +#error "SSE skeletal requires D3D matrix layout" +#endif + +#include + +void Mod_Skeletal_AnimateVertices_SSE(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + // vertex weighted skeletal + int i, k; + int blends; + matrix4x4_t *bonepose; + matrix4x4_t *boneposerelative; + float m[12]; + const blendweights_t * RESTRICT weights; + int num_vertices_minus_one; + + num_vertices_minus_one = model->surfmesh.num_vertices - 1; + + //unsigned long long ts = rdtsc(); + bonepose = (matrix4x4_t *) Mod_Skeletal_AnimateVertices_AllocBuffers(sizeof(matrix4x4_t) * (model->num_bones*2 + model->surfmesh.num_blends)); + boneposerelative = bonepose + model->num_bones; + + if (skeleton && !skeleton->relativetransforms) + skeleton = NULL; + + // interpolate matrices + if (skeleton) + { + for (i = 0;i < model->num_bones;i++) + { + const float * RESTRICT n = model->data_baseboneposeinverse + i * 12; + matrix4x4_t * RESTRICT s = &skeleton->relativetransforms[i]; + matrix4x4_t * RESTRICT b = &bonepose[i]; + matrix4x4_t * RESTRICT r = &boneposerelative[i]; + __m128 b0, b1, b2, b3, r0, r1, r2, r3, nr; + if (model->data_bones[i].parent >= 0) + { + const matrix4x4_t * RESTRICT p = &bonepose[model->data_bones[i].parent]; + __m128 s0 = _mm_loadu_ps(s->m[0]), s1 = _mm_loadu_ps(s->m[1]), s2 = _mm_loadu_ps(s->m[2]); +#ifdef OPENGLORIENTATION + __m128 s3 = _mm_loadu_ps(s->m[3]); +#define SKELETON_MATRIX(r, c) _mm_shuffle_ps(s##c, s##c, _MM_SHUFFLE(r, r, r, r)) +#else +#define SKELETON_MATRIX(r, c) _mm_shuffle_ps(s##r, s##r, _MM_SHUFFLE(c, c, c, c)) +#endif + __m128 pr = _mm_load_ps(p->m[0]); + b0 = _mm_mul_ps(pr, SKELETON_MATRIX(0, 0)); + b1 = _mm_mul_ps(pr, SKELETON_MATRIX(0, 1)); + b2 = _mm_mul_ps(pr, SKELETON_MATRIX(0, 2)); + b3 = _mm_mul_ps(pr, SKELETON_MATRIX(0, 3)); + pr = _mm_load_ps(p->m[1]); + b0 = _mm_add_ps(b0, _mm_mul_ps(pr, SKELETON_MATRIX(1, 0))); + b1 = _mm_add_ps(b1, _mm_mul_ps(pr, SKELETON_MATRIX(1, 1))); + b2 = _mm_add_ps(b2, _mm_mul_ps(pr, SKELETON_MATRIX(1, 2))); + b3 = _mm_add_ps(b3, _mm_mul_ps(pr, SKELETON_MATRIX(1, 3))); + pr = _mm_load_ps(p->m[2]); + b0 = _mm_add_ps(b0, _mm_mul_ps(pr, SKELETON_MATRIX(2, 0))); + b1 = _mm_add_ps(b1, _mm_mul_ps(pr, SKELETON_MATRIX(2, 1))); + b2 = _mm_add_ps(b2, _mm_mul_ps(pr, SKELETON_MATRIX(2, 2))); + b3 = _mm_add_ps(b3, _mm_mul_ps(pr, SKELETON_MATRIX(2, 3))); + b3 = _mm_add_ps(b3, _mm_load_ps(p->m[3])); + } + else + { + b0 = _mm_loadu_ps(s->m[0]); + b1 = _mm_loadu_ps(s->m[1]); + b2 = _mm_loadu_ps(s->m[2]); + b3 = _mm_loadu_ps(s->m[3]); +#ifndef OPENGLORIENTATION + _MM_TRANSPOSE4_PS(b0, b1, b2, b3); +#endif + } + _mm_store_ps(b->m[0], b0); + _mm_store_ps(b->m[1], b1); + _mm_store_ps(b->m[2], b2); + _mm_store_ps(b->m[3], b3); + nr = _mm_loadu_ps(n); + r0 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0))); + r1 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1))); + r2 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2))); + r3 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3))); + nr = _mm_loadu_ps(n+4); + r0 = _mm_add_ps(r0, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0)))); + r1 = _mm_add_ps(r1, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1)))); + r2 = _mm_add_ps(r2, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2)))); + r3 = _mm_add_ps(r3, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3)))); + nr = _mm_loadu_ps(n+8); + r0 = _mm_add_ps(r0, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0)))); + r1 = _mm_add_ps(r1, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1)))); + r2 = _mm_add_ps(r2, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2)))); + r3 = _mm_add_ps(r3, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3)))); + r3 = _mm_add_ps(r3, b3); + _mm_store_ps(r->m[0], r0); + _mm_store_ps(r->m[1], r1); + _mm_store_ps(r->m[2], r2); + _mm_store_ps(r->m[3], r3); + } + } + else + { + for (i = 0;i < model->num_bones;i++) + { + const short * RESTRICT pose7s = model->data_poses7s + 7 * (frameblend[0].subframe * model->num_bones + i); + float lerp = frameblend[0].lerp, + tx = pose7s[0], ty = pose7s[1], tz = pose7s[2], + rx = pose7s[3] * lerp, + ry = pose7s[4] * lerp, + rz = pose7s[5] * lerp, + rw = pose7s[6] * lerp, + dx = tx*rw + ty*rz - tz*ry, + dy = -tx*rz + ty*rw + tz*rx, + dz = tx*ry - ty*rx + tz*rw, + dw = -tx*rx - ty*ry - tz*rz, + scale, sx, sy, sz, sw; + for (blends = 1;blends < MAX_FRAMEBLENDS && frameblend[blends].lerp > 0;blends++) + { + const short * RESTRICT pose7s = model->data_poses7s + 7 * (frameblend[blends].subframe * model->num_bones + i); + float lerp = frameblend[blends].lerp, + tx = pose7s[0], ty = pose7s[1], tz = pose7s[2], + qx = pose7s[3], qy = pose7s[4], qz = pose7s[5], qw = pose7s[6]; + if(rx*qx + ry*qy + rz*qz + rw*qw < 0) lerp = -lerp; + qx *= lerp; + qy *= lerp; + qz *= lerp; + qw *= lerp; + rx += qx; + ry += qy; + rz += qz; + rw += qw; + dx += tx*qw + ty*qz - tz*qy; + dy += -tx*qz + ty*qw + tz*qx; + dz += tx*qy - ty*qx + tz*qw; + dw += -tx*qx - ty*qy - tz*qz; + } + scale = 1.0f / (rx*rx + ry*ry + rz*rz + rw*rw); + sx = rx * scale; + sy = ry * scale; + sz = rz * scale; + sw = rw * scale; + m[0] = sw*rw + sx*rx - sy*ry - sz*rz; + m[1] = 2*(sx*ry - sw*rz); + m[2] = 2*(sx*rz + sw*ry); + m[3] = model->num_posescale*(dx*sw - dy*sz + dz*sy - dw*sx); + m[4] = 2*(sx*ry + sw*rz); + m[5] = sw*rw + sy*ry - sx*rx - sz*rz; + m[6] = 2*(sy*rz - sw*rx); + m[7] = model->num_posescale*(dx*sz + dy*sw - dz*sx - dw*sy); + m[8] = 2*(sx*rz - sw*ry); + m[9] = 2*(sy*rz + sw*rx); + m[10] = sw*rw + sz*rz - sx*rx - sy*ry; + m[11] = model->num_posescale*(dy*sx + dz*sw - dx*sy - dw*sz); + if (i == r_skeletal_debugbone.integer) + m[r_skeletal_debugbonecomponent.integer % 12] += r_skeletal_debugbonevalue.value; + m[3] *= r_skeletal_debugtranslatex.value; + m[7] *= r_skeletal_debugtranslatey.value; + m[11] *= r_skeletal_debugtranslatez.value; + { + const float * RESTRICT n = model->data_baseboneposeinverse + i * 12; + matrix4x4_t * RESTRICT b = &bonepose[i]; + matrix4x4_t * RESTRICT r = &boneposerelative[i]; + __m128 b0, b1, b2, b3, r0, r1, r2, r3, nr; + if (model->data_bones[i].parent >= 0) + { + const matrix4x4_t * RESTRICT p = &bonepose[model->data_bones[i].parent]; + __m128 pr = _mm_load_ps(p->m[0]); + b0 = _mm_mul_ps(pr, _mm_set1_ps(m[0])); + b1 = _mm_mul_ps(pr, _mm_set1_ps(m[1])); + b2 = _mm_mul_ps(pr, _mm_set1_ps(m[2])); + b3 = _mm_mul_ps(pr, _mm_set1_ps(m[3])); + pr = _mm_load_ps(p->m[1]); + b0 = _mm_add_ps(b0, _mm_mul_ps(pr, _mm_set1_ps(m[4]))); + b1 = _mm_add_ps(b1, _mm_mul_ps(pr, _mm_set1_ps(m[5]))); + b2 = _mm_add_ps(b2, _mm_mul_ps(pr, _mm_set1_ps(m[6]))); + b3 = _mm_add_ps(b3, _mm_mul_ps(pr, _mm_set1_ps(m[7]))); + pr = _mm_load_ps(p->m[2]); + b0 = _mm_add_ps(b0, _mm_mul_ps(pr, _mm_set1_ps(m[8]))); + b1 = _mm_add_ps(b1, _mm_mul_ps(pr, _mm_set1_ps(m[9]))); + b2 = _mm_add_ps(b2, _mm_mul_ps(pr, _mm_set1_ps(m[10]))); + b3 = _mm_add_ps(b3, _mm_mul_ps(pr, _mm_set1_ps(m[11]))); + b3 = _mm_add_ps(b3, _mm_load_ps(p->m[3])); + } + else + { + b0 = _mm_setr_ps(m[0], m[4], m[8], 0.0f); + b1 = _mm_setr_ps(m[1], m[5], m[9], 0.0f); + b2 = _mm_setr_ps(m[2], m[6], m[10], 0.0f); + b3 = _mm_setr_ps(m[3], m[7], m[11], 1.0f); + } + _mm_store_ps(b->m[0], b0); + _mm_store_ps(b->m[1], b1); + _mm_store_ps(b->m[2], b2); + _mm_store_ps(b->m[3], b3); + nr = _mm_loadu_ps(n); + r0 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0))); + r1 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1))); + r2 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2))); + r3 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3))); + nr = _mm_loadu_ps(n+4); + r0 = _mm_add_ps(r0, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0)))); + r1 = _mm_add_ps(r1, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1)))); + r2 = _mm_add_ps(r2, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2)))); + r3 = _mm_add_ps(r3, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3)))); + nr = _mm_loadu_ps(n+8); + r0 = _mm_add_ps(r0, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0)))); + r1 = _mm_add_ps(r1, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1)))); + r2 = _mm_add_ps(r2, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2)))); + r3 = _mm_add_ps(r3, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3)))); + r3 = _mm_add_ps(r3, b3); + _mm_store_ps(r->m[0], r0); + _mm_store_ps(r->m[1], r1); + _mm_store_ps(r->m[2], r2); + _mm_store_ps(r->m[3], r3); + } + } + } + + // generate matrices for all blend combinations + weights = model->surfmesh.data_blendweights; + for (i = 0;i < model->surfmesh.num_blends;i++, weights++) + { + float * RESTRICT b = &boneposerelative[model->num_bones + i].m[0][0]; + const float * RESTRICT m = &boneposerelative[weights->index[0]].m[0][0]; + float f = weights->influence[0] * (1.0f / 255.0f); + __m128 fv = _mm_set_ps1(f); + __m128 b0 = _mm_load_ps(m); + __m128 b1 = _mm_load_ps(m+4); + __m128 b2 = _mm_load_ps(m+8); + __m128 b3 = _mm_load_ps(m+12); + __m128 m0, m1, m2, m3; + b0 = _mm_mul_ps(b0, fv); + b1 = _mm_mul_ps(b1, fv); + b2 = _mm_mul_ps(b2, fv); + b3 = _mm_mul_ps(b3, fv); + for (k = 1;k < 4 && weights->influence[k];k++) + { + m = &boneposerelative[weights->index[k]].m[0][0]; + f = weights->influence[k] * (1.0f / 255.0f); + fv = _mm_set_ps1(f); + m0 = _mm_load_ps(m); + m1 = _mm_load_ps(m+4); + m2 = _mm_load_ps(m+8); + m3 = _mm_load_ps(m+12); + m0 = _mm_mul_ps(m0, fv); + m1 = _mm_mul_ps(m1, fv); + m2 = _mm_mul_ps(m2, fv); + m3 = _mm_mul_ps(m3, fv); + b0 = _mm_add_ps(m0, b0); + b1 = _mm_add_ps(m1, b1); + b2 = _mm_add_ps(m2, b2); + b3 = _mm_add_ps(m3, b3); + } + _mm_store_ps(b, b0); + _mm_store_ps(b+4, b1); + _mm_store_ps(b+8, b2); + _mm_store_ps(b+12, b3); + } + +#define LOAD_MATRIX_SCALAR() const float * RESTRICT m = &boneposerelative[*b].m[0][0] + +#define LOAD_MATRIX3() \ + const float * RESTRICT m = &boneposerelative[*b].m[0][0]; \ + /* bonepose array is 16 byte aligned */ \ + __m128 m1 = _mm_load_ps((m)); \ + __m128 m2 = _mm_load_ps((m)+4); \ + __m128 m3 = _mm_load_ps((m)+8); +#define LOAD_MATRIX4() \ + const float * RESTRICT m = &boneposerelative[*b].m[0][0]; \ + /* bonepose array is 16 byte aligned */ \ + __m128 m1 = _mm_load_ps((m)); \ + __m128 m2 = _mm_load_ps((m)+4); \ + __m128 m3 = _mm_load_ps((m)+8); \ + __m128 m4 = _mm_load_ps((m)+12) + + /* Note that matrix is 4x4 and transposed compared to non-USE_SSE codepath */ +#define TRANSFORM_POSITION_SCALAR(in, out) \ + (out)[0] = ((in)[0] * m[0] + (in)[1] * m[4] + (in)[2] * m[ 8] + m[12]); \ + (out)[1] = ((in)[0] * m[1] + (in)[1] * m[5] + (in)[2] * m[ 9] + m[13]); \ + (out)[2] = ((in)[0] * m[2] + (in)[1] * m[6] + (in)[2] * m[10] + m[14]); +#define TRANSFORM_VECTOR_SCALAR(in, out) \ + (out)[0] = ((in)[0] * m[0] + (in)[1] * m[4] + (in)[2] * m[ 8]); \ + (out)[1] = ((in)[0] * m[1] + (in)[1] * m[5] + (in)[2] * m[ 9]); \ + (out)[2] = ((in)[0] * m[2] + (in)[1] * m[6] + (in)[2] * m[10]); + +#define TRANSFORM_POSITION(in, out) { \ + __m128 pin = _mm_loadu_ps(in); /* we ignore the value in the last element (x from the next vertex) */ \ + __m128 x = _mm_shuffle_ps(pin, pin, 0x0); \ + __m128 t1 = _mm_mul_ps(x, m1); \ + \ + /* y, + x */ \ + __m128 y = _mm_shuffle_ps(pin, pin, 0x55); \ + __m128 t2 = _mm_mul_ps(y, m2); \ + __m128 t3 = _mm_add_ps(t1, t2); \ + \ + /* z, + (y+x) */ \ + __m128 z = _mm_shuffle_ps(pin, pin, 0xaa); \ + __m128 t4 = _mm_mul_ps(z, m3); \ + __m128 t5 = _mm_add_ps(t3, t4); \ + \ + /* + m3 */ \ + __m128 pout = _mm_add_ps(t5, m4); \ + _mm_storeu_ps((out), pout); \ + } + +#define TRANSFORM_VECTOR(in, out) { \ + __m128 vin = _mm_loadu_ps(in); \ + \ + /* x */ \ + __m128 x = _mm_shuffle_ps(vin, vin, 0x0); \ + __m128 t1 = _mm_mul_ps(x, m1); \ + \ + /* y, + x */ \ + __m128 y = _mm_shuffle_ps(vin, vin, 0x55); \ + __m128 t2 = _mm_mul_ps(y, m2); \ + __m128 t3 = _mm_add_ps(t1, t2); \ + \ + /* nz, + (ny + nx) */ \ + __m128 z = _mm_shuffle_ps(vin, vin, 0xaa); \ + __m128 t4 = _mm_mul_ps(z, m3); \ + __m128 vout = _mm_add_ps(t3, t4); \ + _mm_storeu_ps((out), vout); \ + } + + // transform vertex attributes by blended matrices + if (vertex3f) + { + const float * RESTRICT v = model->surfmesh.data_vertex3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + // special case common combinations of attributes to avoid repeated loading of matrices + if (normal3f) + { + const float * RESTRICT n = model->surfmesh.data_normal3f; + if (svector3f && tvector3f) + { + const float * RESTRICT sv = model->surfmesh.data_svector3f; + const float * RESTRICT tv = model->surfmesh.data_tvector3f; + + // Note that for SSE each iteration stores one element past end, so we break one vertex short + // and handle that with scalars in that case + for (i = 0; i < num_vertices_minus_one; i++, v += 3, n += 3, sv += 3, tv += 3, b++, + vertex3f += 3, normal3f += 3, svector3f += 3, tvector3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + TRANSFORM_VECTOR(n, normal3f); + TRANSFORM_VECTOR(sv, svector3f); + TRANSFORM_VECTOR(tv, tvector3f); + } + + // Last vertex needs to be done with scalars to avoid reading/writing 1 word past end of arrays + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_POSITION_SCALAR(v, vertex3f); + TRANSFORM_VECTOR_SCALAR(n, normal3f); + TRANSFORM_VECTOR_SCALAR(sv, svector3f); + TRANSFORM_VECTOR_SCALAR(tv, tvector3f); + } + //printf("elapsed ticks: %llu\n", rdtsc() - ts); // XXX + return; + } + + for (i = 0;i < num_vertices_minus_one; i++, v += 3, n += 3, b++, vertex3f += 3, normal3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + TRANSFORM_VECTOR(n, normal3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_POSITION_SCALAR(v, vertex3f); + TRANSFORM_VECTOR_SCALAR(n, normal3f); + } + } + else + { + for (i = 0;i < num_vertices_minus_one; i++, v += 3, b++, vertex3f += 3) + { + LOAD_MATRIX4(); + TRANSFORM_POSITION(v, vertex3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_POSITION_SCALAR(v, vertex3f); + } + } + } + + else if (normal3f) + { + const float * RESTRICT n = model->surfmesh.data_normal3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < num_vertices_minus_one; i++, n += 3, b++, normal3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(n, normal3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_VECTOR_SCALAR(n, normal3f); + } + } + + if (svector3f) + { + const float * RESTRICT sv = model->surfmesh.data_svector3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < num_vertices_minus_one; i++, sv += 3, b++, svector3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(sv, svector3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_VECTOR_SCALAR(sv, svector3f); + } + } + + if (tvector3f) + { + const float * RESTRICT tv = model->surfmesh.data_tvector3f; + const unsigned short * RESTRICT b = model->surfmesh.blends; + for (i = 0; i < num_vertices_minus_one; i++, tv += 3, b++, tvector3f += 3) + { + LOAD_MATRIX3(); + TRANSFORM_VECTOR(tv, tvector3f); + } + { + LOAD_MATRIX_SCALAR(); + TRANSFORM_VECTOR_SCALAR(tv, tvector3f); + } + } + +#undef LOAD_MATRIX3 +#undef LOAD_MATRIX4 +#undef TRANSFORM_POSITION +#undef TRANSFORM_VECTOR +#undef LOAD_MATRIX_SCALAR +#undef TRANSFORM_POSITION_SCALAR +#undef TRANSFORM_VECTOR_SCALAR +} + +#endif diff --git a/app/jni/mod_skeletal_animatevertices_sse.h b/app/jni/mod_skeletal_animatevertices_sse.h new file mode 100644 index 0000000..7de55ca --- /dev/null +++ b/app/jni/mod_skeletal_animatevertices_sse.h @@ -0,0 +1,10 @@ +#ifndef MOD_SKELTAL_ANIMATEVERTICES_SSE_H +#define MOD_SKELTAL_ANIMATEVERTICES_SSE_H + +#include "quakedef.h" + +#ifdef SSE_POSSIBLE +void Mod_Skeletal_AnimateVertices_SSE(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); +#endif + +#endif diff --git a/app/jni/model_alias.c b/app/jni/model_alias.c new file mode 100644 index 0000000..ddcd849 --- /dev/null +++ b/app/jni/model_alias.c @@ -0,0 +1,4044 @@ +/* +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 "image.h" +#include "r_shadow.h" +#include "mod_skeletal_animatevertices_generic.h" +#ifdef SSE_POSSIBLE +#include "mod_skeletal_animatevertices_sse.h" +#endif + +#ifdef SSE_POSSIBLE +static qboolean r_skeletal_use_sse_defined = false; +cvar_t r_skeletal_use_sse = {0, "r_skeletal_use_sse", "1", "use SSE for skeletal model animation"}; +#endif +cvar_t r_skeletal_debugbone = {0, "r_skeletal_debugbone", "-1", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugbonecomponent = {0, "r_skeletal_debugbonecomponent", "3", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugbonevalue = {0, "r_skeletal_debugbonevalue", "100", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugtranslatex = {0, "r_skeletal_debugtranslatex", "1", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugtranslatey = {0, "r_skeletal_debugtranslatey", "1", "development cvar for testing skeletal model code"}; +cvar_t r_skeletal_debugtranslatez = {0, "r_skeletal_debugtranslatez", "1", "development cvar for testing skeletal model code"}; +cvar_t mod_alias_supporttagscale = {0, "mod_alias_supporttagscale", "1", "support scaling factors in bone/tag attachment matrices as supported by MD3"}; +cvar_t mod_alias_force_animated = {0, "mod_alias_force_animated", "", "if set to an non-empty string, overrides the is-animated flag of any alias models (for benchmarking)"}; + +float mod_md3_sin[320]; + +static size_t Mod_Skeletal_AnimateVertices_maxbonepose = 0; +static void *Mod_Skeletal_AnimateVertices_bonepose = NULL; +void Mod_Skeletal_FreeBuffers(void) +{ + if(Mod_Skeletal_AnimateVertices_bonepose) + Mem_Free(Mod_Skeletal_AnimateVertices_bonepose); + Mod_Skeletal_AnimateVertices_maxbonepose = 0; + Mod_Skeletal_AnimateVertices_bonepose = NULL; +} +void *Mod_Skeletal_AnimateVertices_AllocBuffers(size_t nbytes) +{ + if(Mod_Skeletal_AnimateVertices_maxbonepose < nbytes) + { + if(Mod_Skeletal_AnimateVertices_bonepose) + Mem_Free(Mod_Skeletal_AnimateVertices_bonepose); + Mod_Skeletal_AnimateVertices_bonepose = Z_Malloc(nbytes); + Mod_Skeletal_AnimateVertices_maxbonepose = nbytes; + } + return Mod_Skeletal_AnimateVertices_bonepose; +} + +void Mod_Skeletal_BuildTransforms(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT bonepose, float * RESTRICT boneposerelative) +{ + int i, blends; + float m[12]; + + if (!bonepose) + bonepose = (float * RESTRICT) Mod_Skeletal_AnimateVertices_AllocBuffers(sizeof(float[12]) * model->num_bones); + + if (skeleton && !skeleton->relativetransforms) + skeleton = NULL; + + // interpolate matrices + if (skeleton) + { + for (i = 0;i < model->num_bones;i++) + { + Matrix4x4_ToArray12FloatD3D(&skeleton->relativetransforms[i], m); + if (model->data_bones[i].parent >= 0) + R_ConcatTransforms(bonepose + model->data_bones[i].parent * 12, m, bonepose + i * 12); + else + memcpy(bonepose + i * 12, m, sizeof(m)); + + // create a relative deformation matrix to describe displacement + // from the base mesh, which is used by the actual weighting + R_ConcatTransforms(bonepose + i * 12, model->data_baseboneposeinverse + i * 12, boneposerelative + i * 12); + } + } + else + { + for (i = 0;i < model->num_bones;i++) + { + // blend by transform each quaternion/translation into a dual-quaternion first, then blending + const short * RESTRICT pose7s = model->data_poses7s + 7 * (frameblend[0].subframe * model->num_bones + i); + float lerp = frameblend[0].lerp, + tx = pose7s[0], ty = pose7s[1], tz = pose7s[2], + rx = pose7s[3] * lerp, + ry = pose7s[4] * lerp, + rz = pose7s[5] * lerp, + rw = pose7s[6] * lerp, + dx = tx*rw + ty*rz - tz*ry, + dy = -tx*rz + ty*rw + tz*rx, + dz = tx*ry - ty*rx + tz*rw, + dw = -tx*rx - ty*ry - tz*rz, + scale, sx, sy, sz, sw; + for (blends = 1;blends < MAX_FRAMEBLENDS && frameblend[blends].lerp > 0;blends++) + { + const short * RESTRICT pose7s = model->data_poses7s + 7 * (frameblend[blends].subframe * model->num_bones + i); + float lerp = frameblend[blends].lerp, + tx = pose7s[0], ty = pose7s[1], tz = pose7s[2], + qx = pose7s[3], qy = pose7s[4], qz = pose7s[5], qw = pose7s[6]; + if(rx*qx + ry*qy + rz*qz + rw*qw < 0) lerp = -lerp; + qx *= lerp; + qy *= lerp; + qz *= lerp; + qw *= lerp; + rx += qx; + ry += qy; + rz += qz; + rw += qw; + dx += tx*qw + ty*qz - tz*qy; + dy += -tx*qz + ty*qw + tz*qx; + dz += tx*qy - ty*qx + tz*qw; + dw += -tx*qx - ty*qy - tz*qz; + } + // generate a matrix from the dual-quaternion, implicitly normalizing it in the process + scale = 1.0f / (rx*rx + ry*ry + rz*rz + rw*rw); + sx = rx * scale; + sy = ry * scale; + sz = rz * scale; + sw = rw * scale; + m[0] = sw*rw + sx*rx - sy*ry - sz*rz; + m[1] = 2*(sx*ry - sw*rz); + m[2] = 2*(sx*rz + sw*ry); + m[3] = model->num_posescale*(dx*sw - dy*sz + dz*sy - dw*sx); + m[4] = 2*(sx*ry + sw*rz); + m[5] = sw*rw + sy*ry - sx*rx - sz*rz; + m[6] = 2*(sy*rz - sw*rx); + m[7] = model->num_posescale*(dx*sz + dy*sw - dz*sx - dw*sy); + m[8] = 2*(sx*rz - sw*ry); + m[9] = 2*(sy*rz + sw*rx); + m[10] = sw*rw + sz*rz - sx*rx - sy*ry; + m[11] = model->num_posescale*(dy*sx + dz*sw - dx*sy - dw*sz); + if (i == r_skeletal_debugbone.integer) + m[r_skeletal_debugbonecomponent.integer % 12] += r_skeletal_debugbonevalue.value; + m[3] *= r_skeletal_debugtranslatex.value; + m[7] *= r_skeletal_debugtranslatey.value; + m[11] *= r_skeletal_debugtranslatez.value; + if (model->data_bones[i].parent >= 0) + R_ConcatTransforms(bonepose + model->data_bones[i].parent * 12, m, bonepose + i * 12); + else + memcpy(bonepose + i * 12, m, sizeof(m)); + // create a relative deformation matrix to describe displacement + // from the base mesh, which is used by the actual weighting + R_ConcatTransforms(bonepose + i * 12, model->data_baseboneposeinverse + i * 12, boneposerelative + i * 12); + } + } +} + +static void Mod_Skeletal_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + + if (!model->surfmesh.num_vertices) + return; + + if (!model->num_bones) + { + if (vertex3f) memcpy(vertex3f, model->surfmesh.data_vertex3f, model->surfmesh.num_vertices*sizeof(float[3])); + if (normal3f) memcpy(normal3f, model->surfmesh.data_normal3f, model->surfmesh.num_vertices*sizeof(float[3])); + if (svector3f) memcpy(svector3f, model->surfmesh.data_svector3f, model->surfmesh.num_vertices*sizeof(float[3])); + if (tvector3f) memcpy(tvector3f, model->surfmesh.data_tvector3f, model->surfmesh.num_vertices*sizeof(float[3])); + return; + } + +#ifdef SSE_POSSIBLE + if(r_skeletal_use_sse_defined) + if(r_skeletal_use_sse.integer) + { + Mod_Skeletal_AnimateVertices_SSE(model, frameblend, skeleton, vertex3f, normal3f, svector3f, tvector3f); + return; + } +#endif + Mod_Skeletal_AnimateVertices_Generic(model, frameblend, skeleton, vertex3f, normal3f, svector3f, tvector3f); +} + +void Mod_AliasInit (void) +{ + int i; + Cvar_RegisterVariable(&r_skeletal_debugbone); + Cvar_RegisterVariable(&r_skeletal_debugbonecomponent); + Cvar_RegisterVariable(&r_skeletal_debugbonevalue); + Cvar_RegisterVariable(&r_skeletal_debugtranslatex); + Cvar_RegisterVariable(&r_skeletal_debugtranslatey); + Cvar_RegisterVariable(&r_skeletal_debugtranslatez); + Cvar_RegisterVariable(&mod_alias_supporttagscale); + Cvar_RegisterVariable(&mod_alias_force_animated); + for (i = 0;i < 320;i++) + mod_md3_sin[i] = sin(i * M_PI * 2.0f / 256.0); +#ifdef SSE_POSSIBLE + if(Sys_HaveSSE()) + { + Con_Printf("Skeletal animation uses SSE code path\n"); + r_skeletal_use_sse_defined = true; + Cvar_RegisterVariable(&r_skeletal_use_sse); + } + else + Con_Printf("Skeletal animation uses generic code path (SSE disabled or not detected)\n"); +#else + Con_Printf("Skeletal animation uses generic code path (SSE not compiled in)\n"); +#endif +} + +static int Mod_Skeletal_AddBlend(dp_model_t *model, const blendweights_t *newweights) +{ + int i; + blendweights_t *weights; + if(!newweights->influence[1]) + return newweights->index[0]; + weights = model->surfmesh.data_blendweights; + for (i = 0;i < model->surfmesh.num_blends;i++, weights++) + { + if (!memcmp(weights, newweights, sizeof(blendweights_t))) + return model->num_bones + i; + } + model->surfmesh.num_blends++; + memcpy(weights, newweights, sizeof(blendweights_t)); + return model->num_bones + i; +} + +static int Mod_Skeletal_CompressBlend(dp_model_t *model, const int *newindex, const float *newinfluence) +{ + int i, total; + float scale; + blendweights_t newweights; + if(!newinfluence[1]) + return newindex[0]; + scale = 0; + for (i = 0;i < 4;i++) + scale += newinfluence[i]; + scale = 255.0f / scale; + total = 0; + for (i = 0;i < 4;i++) + { + newweights.index[i] = newindex[i]; + newweights.influence[i] = (unsigned char)(newinfluence[i] * scale); + total += newweights.influence[i]; + } + while (total > 255) + { + for (i = 0;i < 4;i++) + { + if(newweights.influence[i] > 0 && total > 255) + { + newweights.influence[i]--; + total--; + } + } + } + while (total < 255) + { + for (i = 0; i < 4;i++) + { + if(newweights.influence[i] < 255 && total < 255) + { + newweights.influence[i]++; + total++; + } + } + } + return Mod_Skeletal_AddBlend(model, &newweights); +} + +static void Mod_MD3_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + // vertex morph + int i, numblends, blendnum; + int numverts = model->surfmesh.num_vertices; + numblends = 0; + for (blendnum = 0;blendnum < MAX_FRAMEBLENDS;blendnum++) + { + //VectorMA(translate, model->surfmesh.num_morphmdlframetranslate, frameblend[blendnum].lerp, translate); + if (frameblend[blendnum].lerp > 0) + numblends = blendnum + 1; + } + // special case for the first blend because it avoids some adds and the need to memset the arrays first + for (blendnum = 0;blendnum < numblends;blendnum++) + { + const md3vertex_t *verts = model->surfmesh.data_morphmd3vertex + numverts * frameblend[blendnum].subframe; + if (vertex3f) + { + float scale = frameblend[blendnum].lerp * (1.0f / 64.0f); + if (blendnum == 0) + { + for (i = 0;i < numverts;i++) + { + vertex3f[i * 3 + 0] = verts[i].origin[0] * scale; + vertex3f[i * 3 + 1] = verts[i].origin[1] * scale; + vertex3f[i * 3 + 2] = verts[i].origin[2] * scale; + } + } + else + { + for (i = 0;i < numverts;i++) + { + vertex3f[i * 3 + 0] += verts[i].origin[0] * scale; + vertex3f[i * 3 + 1] += verts[i].origin[1] * scale; + vertex3f[i * 3 + 2] += verts[i].origin[2] * scale; + } + } + } + // the yaw and pitch stored in md3 models are 8bit quantized angles + // (0-255), and as such a lookup table is very well suited to + // decoding them, and since cosine is equivalent to sine with an + // extra 45 degree rotation, this uses one lookup table for both + // sine and cosine with a +64 bias to get cosine. + if (normal3f) + { + float lerp = frameblend[blendnum].lerp; + if (blendnum == 0) + { + for (i = 0;i < numverts;i++) + { + normal3f[i * 3 + 0] = mod_md3_sin[verts[i].yaw + 64] * mod_md3_sin[verts[i].pitch ] * lerp; + normal3f[i * 3 + 1] = mod_md3_sin[verts[i].yaw ] * mod_md3_sin[verts[i].pitch ] * lerp; + normal3f[i * 3 + 2] = mod_md3_sin[verts[i].pitch + 64] * lerp; + } + } + else + { + for (i = 0;i < numverts;i++) + { + normal3f[i * 3 + 0] += mod_md3_sin[verts[i].yaw + 64] * mod_md3_sin[verts[i].pitch ] * lerp; + normal3f[i * 3 + 1] += mod_md3_sin[verts[i].yaw ] * mod_md3_sin[verts[i].pitch ] * lerp; + normal3f[i * 3 + 2] += mod_md3_sin[verts[i].pitch + 64] * lerp; + } + } + } + if (svector3f) + { + const texvecvertex_t *texvecvert = model->surfmesh.data_morphtexvecvertex + numverts * frameblend[blendnum].subframe; + float f = frameblend[blendnum].lerp * (1.0f / 127.0f); + if (blendnum == 0) + { + for (i = 0;i < numverts;i++, texvecvert++) + { + VectorScale(texvecvert->svec, f, svector3f + i*3); + VectorScale(texvecvert->tvec, f, tvector3f + i*3); + } + } + else + { + for (i = 0;i < numverts;i++, texvecvert++) + { + VectorMA(svector3f + i*3, f, texvecvert->svec, svector3f + i*3); + VectorMA(tvector3f + i*3, f, texvecvert->tvec, tvector3f + i*3); + } + } + } + } +} +static void Mod_MDL_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) +{ + // vertex morph + int i, numblends, blendnum; + int numverts = model->surfmesh.num_vertices; + float translate[3]; + VectorClear(translate); + numblends = 0; + // blend the frame translates to avoid redundantly doing so on each vertex + // (a bit of a brain twister but it works) + for (blendnum = 0;blendnum < MAX_FRAMEBLENDS;blendnum++) + { + if (model->surfmesh.data_morphmd2framesize6f) + VectorMA(translate, frameblend[blendnum].lerp, model->surfmesh.data_morphmd2framesize6f + frameblend[blendnum].subframe * 6 + 3, translate); + else + VectorMA(translate, frameblend[blendnum].lerp, model->surfmesh.num_morphmdlframetranslate, translate); + if (frameblend[blendnum].lerp > 0) + numblends = blendnum + 1; + } + // special case for the first blend because it avoids some adds and the need to memset the arrays first + for (blendnum = 0;blendnum < numblends;blendnum++) + { + const trivertx_t *verts = model->surfmesh.data_morphmdlvertex + numverts * frameblend[blendnum].subframe; + if (vertex3f) + { + float scale[3]; + if (model->surfmesh.data_morphmd2framesize6f) + VectorScale(model->surfmesh.data_morphmd2framesize6f + frameblend[blendnum].subframe * 6, frameblend[blendnum].lerp, scale); + else + VectorScale(model->surfmesh.num_morphmdlframescale, frameblend[blendnum].lerp, scale); + if (blendnum == 0) + { + for (i = 0;i < numverts;i++) + { + vertex3f[i * 3 + 0] = translate[0] + verts[i].v[0] * scale[0]; + vertex3f[i * 3 + 1] = translate[1] + verts[i].v[1] * scale[1]; + vertex3f[i * 3 + 2] = translate[2] + verts[i].v[2] * scale[2]; + } + } + else + { + for (i = 0;i < numverts;i++) + { + vertex3f[i * 3 + 0] += verts[i].v[0] * scale[0]; + vertex3f[i * 3 + 1] += verts[i].v[1] * scale[1]; + vertex3f[i * 3 + 2] += verts[i].v[2] * scale[2]; + } + } + } + // the vertex normals in mdl models are an index into a table of + // 162 unique values, this very crude quantization reduces the + // vertex normal to only one byte, which saves a lot of space but + // also makes lighting pretty coarse + if (normal3f) + { + float lerp = frameblend[blendnum].lerp; + if (blendnum == 0) + { + for (i = 0;i < numverts;i++) + { + const float *vn = m_bytenormals[verts[i].lightnormalindex]; + VectorScale(vn, lerp, normal3f + i*3); + } + } + else + { + for (i = 0;i < numverts;i++) + { + const float *vn = m_bytenormals[verts[i].lightnormalindex]; + VectorMA(normal3f + i*3, lerp, vn, normal3f + i*3); + } + } + } + if (svector3f) + { + const texvecvertex_t *texvecvert = model->surfmesh.data_morphtexvecvertex + numverts * frameblend[blendnum].subframe; + float f = frameblend[blendnum].lerp * (1.0f / 127.0f); + if (blendnum == 0) + { + for (i = 0;i < numverts;i++, texvecvert++) + { + VectorScale(texvecvert->svec, f, svector3f + i*3); + VectorScale(texvecvert->tvec, f, tvector3f + i*3); + } + } + else + { + for (i = 0;i < numverts;i++, texvecvert++) + { + VectorMA(svector3f + i*3, f, texvecvert->svec, svector3f + i*3); + VectorMA(tvector3f + i*3, f, texvecvert->tvec, tvector3f + i*3); + } + } + } + } +} + +int Mod_Alias_GetTagMatrix(const dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, int tagindex, matrix4x4_t *outmatrix) +{ + matrix4x4_t temp; + matrix4x4_t parentbonematrix; + matrix4x4_t tempbonematrix; + matrix4x4_t bonematrix; + matrix4x4_t blendmatrix; + int blendindex; + int parenttagindex; + int k; + float lerp; + const float *input; + float blendtag[12]; + *outmatrix = identitymatrix; + if (skeleton && skeleton->relativetransforms) + { + if (tagindex < 0 || tagindex >= skeleton->model->num_bones) + return 4; + *outmatrix = skeleton->relativetransforms[tagindex]; + while ((tagindex = model->data_bones[tagindex].parent) >= 0) + { + temp = *outmatrix; + Matrix4x4_Concat(outmatrix, &skeleton->relativetransforms[tagindex], &temp); + } + } + else if (model->num_bones) + { + if (tagindex < 0 || tagindex >= model->num_bones) + return 4; + Matrix4x4_Clear(&blendmatrix); + for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) + { + lerp = frameblend[blendindex].lerp; + Matrix4x4_FromBonePose7s(&bonematrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + tagindex)); + parenttagindex = tagindex; + while ((parenttagindex = model->data_bones[parenttagindex].parent) >= 0) + { + Matrix4x4_FromBonePose7s(&parentbonematrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + parenttagindex)); + tempbonematrix = bonematrix; + Matrix4x4_Concat(&bonematrix, &parentbonematrix, &tempbonematrix); + } + Matrix4x4_Accumulate(&blendmatrix, &bonematrix, lerp); + } + *outmatrix = blendmatrix; + } + else if (model->num_tags) + { + if (tagindex < 0 || tagindex >= model->num_tags) + return 4; + for (k = 0;k < 12;k++) + blendtag[k] = 0; + for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) + { + lerp = frameblend[blendindex].lerp; + input = model->data_tags[frameblend[blendindex].subframe * model->num_tags + tagindex].matrixgl; + for (k = 0;k < 12;k++) + blendtag[k] += input[k] * lerp; + } + Matrix4x4_FromArray12FloatGL(outmatrix, blendtag); + } + + if(!mod_alias_supporttagscale.integer) + Matrix4x4_Normalize3(outmatrix, outmatrix); + + return 0; +} + +int Mod_Alias_GetExtendedTagInfoForIndex(const dp_model_t *model, unsigned int skin, const frameblend_t *frameblend, const skeleton_t *skeleton, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) +{ + int blendindex; + int k; + float lerp; + matrix4x4_t bonematrix; + matrix4x4_t blendmatrix; + const float *input; + float blendtag[12]; + + if (skeleton && skeleton->relativetransforms) + { + if (tagindex < 0 || tagindex >= skeleton->model->num_bones) + return 1; + *parentindex = skeleton->model->data_bones[tagindex].parent; + *tagname = skeleton->model->data_bones[tagindex].name; + *tag_localmatrix = skeleton->relativetransforms[tagindex]; + return 0; + } + else if (model->num_bones) + { + if (tagindex < 0 || tagindex >= model->num_bones) + return 1; + *parentindex = model->data_bones[tagindex].parent; + *tagname = model->data_bones[tagindex].name; + Matrix4x4_Clear(&blendmatrix); + for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) + { + lerp = frameblend[blendindex].lerp; + Matrix4x4_FromBonePose7s(&bonematrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + tagindex)); + Matrix4x4_Accumulate(&blendmatrix, &bonematrix, lerp); + } + *tag_localmatrix = blendmatrix; + return 0; + } + else if (model->num_tags) + { + if (tagindex < 0 || tagindex >= model->num_tags) + return 1; + *parentindex = -1; + *tagname = model->data_tags[tagindex].name; + for (k = 0;k < 12;k++) + blendtag[k] = 0; + for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) + { + lerp = frameblend[blendindex].lerp; + input = model->data_tags[frameblend[blendindex].subframe * model->num_tags + tagindex].matrixgl; + for (k = 0;k < 12;k++) + blendtag[k] += input[k] * lerp; + } + Matrix4x4_FromArray12FloatGL(tag_localmatrix, blendtag); + return 0; + } + + return 2; +} + +int Mod_Alias_GetTagIndexForName(const dp_model_t *model, unsigned int skin, const char *tagname) +{ + int i; + if(skin >= (unsigned int)model->numskins) + skin = 0; + if (model->num_bones) + for (i = 0;i < model->num_bones;i++) + if (!strcasecmp(tagname, model->data_bones[i].name)) + return i + 1; + if (model->num_tags) + for (i = 0;i < model->num_tags;i++) + if (!strcasecmp(tagname, model->data_tags[i].name)) + return i + 1; + return 0; +} + +static void Mod_BuildBaseBonePoses(void) +{ + int boneindex; + matrix4x4_t *basebonepose; + float *outinvmatrix = loadmodel->data_baseboneposeinverse; + matrix4x4_t bonematrix; + matrix4x4_t tempbonematrix; + if (!loadmodel->num_bones) + return; + basebonepose = (matrix4x4_t *)Mem_Alloc(tempmempool, loadmodel->num_bones * sizeof(matrix4x4_t)); + for (boneindex = 0;boneindex < loadmodel->num_bones;boneindex++) + { + Matrix4x4_FromBonePose7s(&bonematrix, loadmodel->num_posescale, loadmodel->data_poses7s + 7 * boneindex); + if (loadmodel->data_bones[boneindex].parent >= 0) + { + tempbonematrix = bonematrix; + Matrix4x4_Concat(&bonematrix, basebonepose + loadmodel->data_bones[boneindex].parent, &tempbonematrix); + } + basebonepose[boneindex] = bonematrix; + Matrix4x4_Invert_Simple(&tempbonematrix, basebonepose + boneindex); + Matrix4x4_ToArray12FloatD3D(&tempbonematrix, outinvmatrix + 12*boneindex); + } + Mem_Free(basebonepose); +} + +static qboolean Mod_Alias_CalculateBoundingBox(void) +{ + int vnum; + qboolean firstvertex = true; + float dist, yawradius, radius; + float *v; + qboolean isanimated = false; + VectorClear(loadmodel->normalmins); + VectorClear(loadmodel->normalmaxs); + yawradius = 0; + radius = 0; + if (loadmodel->AnimateVertices) + { + float *vertex3f, *refvertex3f; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + memset(frameblend, 0, sizeof(frameblend)); + frameblend[0].lerp = 1; + vertex3f = (float *) Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(float[3]) * 2); + refvertex3f = NULL; + for (frameblend[0].subframe = 0;frameblend[0].subframe < loadmodel->num_poses;frameblend[0].subframe++) + { + loadmodel->AnimateVertices(loadmodel, frameblend, NULL, vertex3f, NULL, NULL, NULL); + if (!refvertex3f) + { + // make a copy of the first frame for comparing all others + refvertex3f = vertex3f + loadmodel->surfmesh.num_vertices * 3; + memcpy(refvertex3f, vertex3f, loadmodel->surfmesh.num_vertices * sizeof(float[3])); + } + else + { + if (!isanimated && memcmp(refvertex3f, vertex3f, loadmodel->surfmesh.num_vertices * sizeof(float[3]))) + isanimated = true; + } + for (vnum = 0, v = vertex3f;vnum < loadmodel->surfmesh.num_vertices;vnum++, v += 3) + { + if (firstvertex) + { + firstvertex = false; + VectorCopy(v, loadmodel->normalmins); + VectorCopy(v, loadmodel->normalmaxs); + } + else + { + if (loadmodel->normalmins[0] > v[0]) loadmodel->normalmins[0] = v[0]; + if (loadmodel->normalmins[1] > v[1]) loadmodel->normalmins[1] = v[1]; + if (loadmodel->normalmins[2] > v[2]) loadmodel->normalmins[2] = v[2]; + if (loadmodel->normalmaxs[0] < v[0]) loadmodel->normalmaxs[0] = v[0]; + if (loadmodel->normalmaxs[1] < v[1]) loadmodel->normalmaxs[1] = v[1]; + if (loadmodel->normalmaxs[2] < v[2]) loadmodel->normalmaxs[2] = v[2]; + } + dist = v[0] * v[0] + v[1] * v[1]; + if (yawradius < dist) + yawradius = dist; + dist += v[2] * v[2]; + if (radius < dist) + radius = dist; + } + } + if (vertex3f) + Mem_Free(vertex3f); + } + else + { + for (vnum = 0, v = loadmodel->surfmesh.data_vertex3f;vnum < loadmodel->surfmesh.num_vertices;vnum++, v += 3) + { + if (firstvertex) + { + firstvertex = false; + VectorCopy(v, loadmodel->normalmins); + VectorCopy(v, loadmodel->normalmaxs); + } + else + { + if (loadmodel->normalmins[0] > v[0]) loadmodel->normalmins[0] = v[0]; + if (loadmodel->normalmins[1] > v[1]) loadmodel->normalmins[1] = v[1]; + if (loadmodel->normalmins[2] > v[2]) loadmodel->normalmins[2] = v[2]; + if (loadmodel->normalmaxs[0] < v[0]) loadmodel->normalmaxs[0] = v[0]; + if (loadmodel->normalmaxs[1] < v[1]) loadmodel->normalmaxs[1] = v[1]; + if (loadmodel->normalmaxs[2] < v[2]) loadmodel->normalmaxs[2] = v[2]; + } + dist = v[0] * v[0] + v[1] * v[1]; + if (yawradius < dist) + yawradius = dist; + dist += v[2] * v[2]; + if (radius < dist) + radius = dist; + } + } + radius = sqrt(radius); + yawradius = sqrt(yawradius); + loadmodel->yawmins[0] = loadmodel->yawmins[1] = -yawradius; + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = yawradius; + loadmodel->yawmins[2] = loadmodel->normalmins[2]; + loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; + loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -radius; + loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = radius; + loadmodel->radius = radius; + loadmodel->radius2 = radius * radius; + return isanimated; +} + +static void Mod_Alias_MorphMesh_CompileFrames(void) +{ + int i, j; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + unsigned char *datapointer; + memset(frameblend, 0, sizeof(frameblend)); + frameblend[0].lerp = 1; + datapointer = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * (sizeof(float[3]) * 4 + loadmodel->surfmesh.num_morphframes * sizeof(texvecvertex_t))); + loadmodel->surfmesh.data_vertex3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_morphtexvecvertex = (texvecvertex_t *)datapointer;datapointer += loadmodel->surfmesh.num_morphframes * loadmodel->surfmesh.num_vertices * sizeof(texvecvertex_t); + // this counts down from the last frame to the first so that the final data in surfmesh is for frame zero (which is what the renderer expects to be there) + for (i = loadmodel->surfmesh.num_morphframes-1;i >= 0;i--) + { + frameblend[0].subframe = i; + loadmodel->AnimateVertices(loadmodel, frameblend, NULL, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_normal3f, NULL, NULL); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + // encode the svector and tvector in 3 byte format for permanent storage + for (j = 0;j < loadmodel->surfmesh.num_vertices;j++) + { + VectorScaleCast(loadmodel->surfmesh.data_svector3f + j * 3, 127.0f, signed char, loadmodel->surfmesh.data_morphtexvecvertex[i*loadmodel->surfmesh.num_vertices+j].svec); + VectorScaleCast(loadmodel->surfmesh.data_tvector3f + j * 3, 127.0f, signed char, loadmodel->surfmesh.data_morphtexvecvertex[i*loadmodel->surfmesh.num_vertices+j].tvec); + } + } +} + +static void Mod_MDLMD2MD3_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + int i; + float segmentmins[3], segmentmaxs[3]; + msurface_t *surface; + float vertex3fbuf[1024*3]; + float *vertex3f = vertex3fbuf; + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + if (model->surfmesh.num_vertices > 1024) + vertex3f = (float *)Mem_Alloc(tempmempool, model->surfmesh.num_vertices * sizeof(float[3])); + segmentmins[0] = min(start[0], end[0]) - 1; + segmentmins[1] = min(start[1], end[1]) - 1; + segmentmins[2] = min(start[2], end[2]) - 1; + segmentmaxs[0] = max(start[0], end[0]) + 1; + segmentmaxs[1] = max(start[1], end[1]) + 1; + segmentmaxs[2] = max(start[2], end[2]) + 1; + model->AnimateVertices(model, frameblend, skeleton, vertex3f, NULL, NULL, NULL); + for (i = 0, surface = model->data_surfaces;i < model->num_surfaces;i++, surface++) + Collision_TraceLineTriangleMeshFloat(trace, start, end, model->surfmesh.num_triangles, model->surfmesh.data_element3i, vertex3f, 0, NULL, SUPERCONTENTS_SOLID | (surface->texture->basematerialflags & MATERIALFLAGMASK_TRANSLUCENT ? 0 : SUPERCONTENTS_OPAQUE), 0, surface->texture, segmentmins, segmentmaxs); + if (vertex3f != vertex3fbuf) + Mem_Free(vertex3f); +} + +static void Mod_MDLMD2MD3_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask) +{ + int i; + vec3_t shiftstart, shiftend; + float segmentmins[3], segmentmaxs[3]; + msurface_t *surface; + float vertex3fbuf[1024*3]; + float *vertex3f = vertex3fbuf; + colboxbrushf_t thisbrush_start, thisbrush_end; + vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; + + if (VectorCompare(boxmins, boxmaxs)) + { + VectorAdd(start, boxmins, shiftstart); + VectorAdd(end, boxmins, shiftend); + Mod_MDLMD2MD3_TraceLine(model, frameblend, skeleton, trace, shiftstart, shiftend, hitsupercontentsmask); + VectorSubtract(trace->endpos, boxmins, trace->endpos); + return; + } + + // box trace, performed as brush trace + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + if (model->surfmesh.num_vertices > 1024) + vertex3f = (float *)Mem_Alloc(tempmempool, model->surfmesh.num_vertices * sizeof(float[3])); + segmentmins[0] = min(start[0], end[0]) + boxmins[0] - 1; + segmentmins[1] = min(start[1], end[1]) + boxmins[1] - 1; + segmentmins[2] = min(start[2], end[2]) + boxmins[2] - 1; + segmentmaxs[0] = max(start[0], end[0]) + boxmaxs[0] + 1; + segmentmaxs[1] = max(start[1], end[1]) + boxmaxs[1] + 1; + segmentmaxs[2] = max(start[2], end[2]) + boxmaxs[2] + 1; + VectorAdd(start, boxmins, boxstartmins); + VectorAdd(start, boxmaxs, boxstartmaxs); + VectorAdd(end, boxmins, boxendmins); + VectorAdd(end, boxmaxs, boxendmaxs); + Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); + model->AnimateVertices(model, frameblend, skeleton, vertex3f, NULL, NULL, NULL); + for (i = 0, surface = model->data_surfaces;i < model->num_surfaces;i++, surface++) + Collision_TraceBrushTriangleMeshFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, model->surfmesh.num_triangles, model->surfmesh.data_element3i, vertex3f, 0, NULL, SUPERCONTENTS_SOLID | (surface->texture->basematerialflags & MATERIALFLAGMASK_TRANSLUCENT ? 0 : SUPERCONTENTS_OPAQUE), 0, surface->texture, segmentmins, segmentmaxs); + if (vertex3f != vertex3fbuf) + Mem_Free(vertex3f); +} + +static void Mod_ConvertAliasVerts (int inverts, trivertx_t *v, trivertx_t *out, int *vertremap) +{ + int i, j; + for (i = 0;i < inverts;i++) + { + if (vertremap[i] < 0 && vertremap[i+inverts] < 0) // only used vertices need apply... + continue; + j = vertremap[i]; // not onseam + if (j >= 0) + out[j] = v[i]; + j = vertremap[i+inverts]; // onseam + if (j >= 0) + out[j] = v[i]; + } +} + +static void Mod_MDL_LoadFrames (unsigned char* datapointer, int inverts, int *vertremap) +{ + int i, f, pose, groupframes; + float interval; + daliasframetype_t *pframetype; + daliasframe_t *pinframe; + daliasgroup_t *group; + daliasinterval_t *intervals; + animscene_t *scene; + pose = 0; + scene = loadmodel->animscenes; + for (f = 0;f < loadmodel->numframes;f++) + { + pframetype = (daliasframetype_t *)datapointer; + datapointer += sizeof(daliasframetype_t); + if (LittleLong (pframetype->type) == ALIAS_SINGLE) + { + // a single frame is still treated as a group + interval = 0.1f; + groupframes = 1; + } + else + { + // read group header + group = (daliasgroup_t *)datapointer; + datapointer += sizeof(daliasgroup_t); + groupframes = LittleLong (group->numframes); + + // intervals (time per frame) + intervals = (daliasinterval_t *)datapointer; + datapointer += sizeof(daliasinterval_t) * groupframes; + + interval = LittleFloat (intervals->interval); // FIXME: support variable framerate groups + if (interval < 0.01f) + { + Con_Printf("%s has an invalid interval %f, changing to 0.1\n", loadmodel->name, interval); + interval = 0.1f; + } + } + + // get scene name from first frame + pinframe = (daliasframe_t *)datapointer; + + strlcpy(scene->name, pinframe->name, sizeof(scene->name)); + scene->firstframe = pose; + scene->framecount = groupframes; + scene->framerate = 1.0f / interval; + scene->loop = true; + scene++; + + // read frames + for (i = 0;i < groupframes;i++) + { + datapointer += sizeof(daliasframe_t); + Mod_ConvertAliasVerts(inverts, (trivertx_t *)datapointer, loadmodel->surfmesh.data_morphmdlvertex + pose * loadmodel->surfmesh.num_vertices, vertremap); + datapointer += sizeof(trivertx_t) * inverts; + pose++; + } + } +} + +static void Mod_BuildAliasSkinFromSkinFrame(texture_t *texture, skinframe_t *skinframe) +{ + if (cls.state == ca_dedicated) + return; + // hack + if (!skinframe) + skinframe = R_SkinFrame_LoadMissing(); + memset(texture, 0, sizeof(*texture)); + texture->currentframe = texture; + //texture->animated = false; + texture->numskinframes = 1; + texture->skinframerate = 1; + texture->skinframes[0] = skinframe; + texture->currentskinframe = skinframe; + //texture->backgroundnumskinframes = 0; + //texture->customblendfunc[0] = 0; + //texture->customblendfunc[1] = 0; + //texture->surfaceflags = 0; + //texture->supercontents = 0; + //texture->surfaceparms = 0; + //texture->textureflags = 0; + + texture->basematerialflags = MATERIALFLAG_WALL; + if (texture->currentskinframe->hasalpha) + texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + texture->currentmaterialflags = texture->basematerialflags; + texture->offsetmapping = OFFSETMAPPING_DEFAULT; + texture->offsetscale = 1; + texture->offsetbias = 0; + texture->specularscalemod = 1; + texture->specularpowermod = 1; + texture->surfaceflags = 0; + texture->supercontents = SUPERCONTENTS_SOLID; + if (!(texture->basematerialflags & MATERIALFLAG_BLENDED)) + texture->supercontents |= SUPERCONTENTS_OPAQUE; + texture->transparentsort = TRANSPARENTSORT_DISTANCE; + // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS + // JUST GREP FOR "specularscalemod = 1". +} + +void Mod_BuildAliasSkinsFromSkinFiles(texture_t *skin, skinfile_t *skinfile, const char *meshname, const char *shadername) +{ + int i; + char stripbuf[MAX_QPATH]; + skinfileitem_t *skinfileitem; + if(developer_extra.integer) + Con_DPrintf("Looking up texture for %s (default: %s)\n", meshname, shadername); + if (skinfile) + { + // the skin += loadmodel->num_surfaces part of this is because data_textures on alias models is arranged as [numskins][numsurfaces] + for (i = 0;skinfile;skinfile = skinfile->next, i++, skin += loadmodel->num_surfaces) + { + memset(skin, 0, sizeof(*skin)); + // see if a mesh + for (skinfileitem = skinfile->items;skinfileitem;skinfileitem = skinfileitem->next) + { + // leave the skin unitialized (nodraw) if the replacement is "common/nodraw" or "textures/common/nodraw" + if (!strcmp(skinfileitem->name, meshname)) + { + Image_StripImageExtension(skinfileitem->replacement, stripbuf, sizeof(stripbuf)); + if(developer_extra.integer) + Con_DPrintf("--> got %s from skin file\n", stripbuf); + Mod_LoadTextureFromQ3Shader(skin, stripbuf, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); + break; + } + } + if (!skinfileitem) + { + // don't render unmentioned meshes + Mod_BuildAliasSkinFromSkinFrame(skin, NULL); + if(developer_extra.integer) + Con_DPrintf("--> skipping\n"); + skin->basematerialflags = skin->currentmaterialflags = MATERIALFLAG_NOSHADOW | MATERIALFLAG_NODRAW; + } + } + } + else + { + if(developer_extra.integer) + Con_DPrintf("--> using default\n"); + Image_StripImageExtension(shadername, stripbuf, sizeof(stripbuf)); + Mod_LoadTextureFromQ3Shader(skin, stripbuf, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); + } +} + +#define BOUNDI(VALUE,MIN,MAX) if (VALUE < MIN || VALUE >= MAX) Host_Error("model %s has an invalid ##VALUE (%d exceeds %d - %d)", loadmodel->name, VALUE, MIN, MAX); +#define BOUNDF(VALUE,MIN,MAX) if (VALUE < MIN || VALUE >= MAX) Host_Error("model %s has an invalid ##VALUE (%f exceeds %f - %f)", loadmodel->name, VALUE, MIN, MAX); +void Mod_IDP0_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, version, totalskins, skinwidth, skinheight, groupframes, groupskins, numverts; + float scales, scalet, interval; + msurface_t *surface; + unsigned char *data; + mdl_t *pinmodel; + stvert_t *pinstverts; + dtriangle_t *pintriangles; + daliasskintype_t *pinskintype; + daliasskingroup_t *pinskingroup; + daliasskininterval_t *pinskinintervals; + daliasframetype_t *pinframetype; + daliasgroup_t *pinframegroup; + unsigned char *datapointer, *startframes, *startskins; + char name[MAX_QPATH]; + skinframe_t *tempskinframe; + animscene_t *tempskinscenes; + texture_t *tempaliasskins; + float *vertst; + int *vertonseam, *vertremap; + skinfile_t *skinfiles; + char vabuf[1024]; + + datapointer = (unsigned char *)buffer; + pinmodel = (mdl_t *)datapointer; + datapointer += sizeof(mdl_t); + + version = LittleLong (pinmodel->version); + if (version != ALIAS_VERSION) + Host_Error ("%s has wrong version number (%i should be %i)", + loadmodel->name, version, ALIAS_VERSION); + + loadmodel->modeldatatypestring = "MDL"; + + loadmodel->type = mod_alias; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + // FIXME add TraceBrush! + loadmodel->PointSuperContents = NULL; + loadmodel->AnimateVertices = Mod_MDL_AnimateVertices; + + loadmodel->num_surfaces = 1; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->sortedmodelsurfaces[0] = 0; + + loadmodel->numskins = LittleLong(pinmodel->numskins); + BOUNDI(loadmodel->numskins,0,65536); + skinwidth = LittleLong (pinmodel->skinwidth); + BOUNDI(skinwidth,0,65536); + skinheight = LittleLong (pinmodel->skinheight); + BOUNDI(skinheight,0,65536); + numverts = LittleLong(pinmodel->numverts); + BOUNDI(numverts,0,65536); + loadmodel->surfmesh.num_triangles = LittleLong(pinmodel->numtris); + BOUNDI(loadmodel->surfmesh.num_triangles,0,65536); + loadmodel->numframes = LittleLong(pinmodel->numframes); + BOUNDI(loadmodel->numframes,0,65536); + loadmodel->synctype = (synctype_t)LittleLong (pinmodel->synctype); + BOUNDI((int)loadmodel->synctype,0,2); + // convert model flags to EF flags (MF_ROCKET becomes EF_ROCKET, etc) + i = LittleLong (pinmodel->flags); + loadmodel->effects = ((i & 255) << 24) | (i & 0x00FFFF00); + + for (i = 0;i < 3;i++) + { + loadmodel->surfmesh.num_morphmdlframescale[i] = LittleFloat (pinmodel->scale[i]); + loadmodel->surfmesh.num_morphmdlframetranslate[i] = LittleFloat (pinmodel->scale_origin[i]); + } + + startskins = datapointer; + totalskins = 0; + for (i = 0;i < loadmodel->numskins;i++) + { + pinskintype = (daliasskintype_t *)datapointer; + datapointer += sizeof(daliasskintype_t); + if (LittleLong(pinskintype->type) == ALIAS_SKIN_SINGLE) + groupskins = 1; + else + { + pinskingroup = (daliasskingroup_t *)datapointer; + datapointer += sizeof(daliasskingroup_t); + groupskins = LittleLong(pinskingroup->numskins); + datapointer += sizeof(daliasskininterval_t) * groupskins; + } + + for (j = 0;j < groupskins;j++) + { + datapointer += skinwidth * skinheight; + totalskins++; + } + } + + pinstverts = (stvert_t *)datapointer; + datapointer += sizeof(stvert_t) * numverts; + + pintriangles = (dtriangle_t *)datapointer; + datapointer += sizeof(dtriangle_t) * loadmodel->surfmesh.num_triangles; + + startframes = datapointer; + loadmodel->surfmesh.num_morphframes = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + pinframetype = (daliasframetype_t *)datapointer; + datapointer += sizeof(daliasframetype_t); + if (LittleLong (pinframetype->type) == ALIAS_SINGLE) + groupframes = 1; + else + { + pinframegroup = (daliasgroup_t *)datapointer; + datapointer += sizeof(daliasgroup_t); + groupframes = LittleLong(pinframegroup->numframes); + datapointer += sizeof(daliasinterval_t) * groupframes; + } + + for (j = 0;j < groupframes;j++) + { + datapointer += sizeof(daliasframe_t); + datapointer += sizeof(trivertx_t) * numverts; + loadmodel->surfmesh.num_morphframes++; + } + } + loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; + + // store texture coordinates into temporary array, they will be stored + // after usage is determined (triangle data) + vertst = (float *)Mem_Alloc(tempmempool, numverts * 2 * sizeof(float[2])); + vertremap = (int *)Mem_Alloc(tempmempool, numverts * 3 * sizeof(int)); + vertonseam = vertremap + numverts * 2; + + scales = 1.0 / skinwidth; + scalet = 1.0 / skinheight; + for (i = 0;i < numverts;i++) + { + vertonseam[i] = LittleLong(pinstverts[i].onseam); + vertst[i*2+0] = LittleLong(pinstverts[i].s) * scales; + vertst[i*2+1] = LittleLong(pinstverts[i].t) * scalet; + vertst[(i+numverts)*2+0] = vertst[i*2+0] + 0.5; + vertst[(i+numverts)*2+1] = vertst[i*2+1]; + } + +// load triangle data + loadmodel->surfmesh.data_element3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * loadmodel->surfmesh.num_triangles); + + // read the triangle elements + for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) + for (j = 0;j < 3;j++) + loadmodel->surfmesh.data_element3i[i*3+j] = LittleLong(pintriangles[i].vertindex[j]); + // validate (note numverts is used because this is the original data) + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, numverts, __FILE__, __LINE__); + // now butcher the elements according to vertonseam and tri->facesfront + // and then compact the vertex set to remove duplicates + for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) + if (!LittleLong(pintriangles[i].facesfront)) // backface + for (j = 0;j < 3;j++) + if (vertonseam[loadmodel->surfmesh.data_element3i[i*3+j]]) + loadmodel->surfmesh.data_element3i[i*3+j] += numverts; + // count the usage + // (this uses vertremap to count usage to save some memory) + for (i = 0;i < numverts*2;i++) + vertremap[i] = 0; + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + vertremap[loadmodel->surfmesh.data_element3i[i]]++; + // build remapping table and compact array + loadmodel->surfmesh.num_vertices = 0; + for (i = 0;i < numverts*2;i++) + { + if (vertremap[i]) + { + vertremap[i] = loadmodel->surfmesh.num_vertices; + vertst[loadmodel->surfmesh.num_vertices*2+0] = vertst[i*2+0]; + vertst[loadmodel->surfmesh.num_vertices*2+1] = vertst[i*2+1]; + loadmodel->surfmesh.num_vertices++; + } + else + vertremap[i] = -1; // not used at all + } + // remap the elements to the new vertex set + for (i = 0;i < loadmodel->surfmesh.num_triangles * 3;i++) + loadmodel->surfmesh.data_element3i[i] = vertremap[loadmodel->surfmesh.data_element3i[i]]; + // store the texture coordinates + loadmodel->surfmesh.data_texcoordtexture2f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[2]) * loadmodel->surfmesh.num_vertices); + for (i = 0;i < loadmodel->surfmesh.num_vertices;i++) + { + loadmodel->surfmesh.data_texcoordtexture2f[i*2+0] = vertst[i*2+0]; + loadmodel->surfmesh.data_texcoordtexture2f[i*2+1] = vertst[i*2+1]; + } + + // generate ushort elements array if possible + if (loadmodel->surfmesh.num_vertices <= 65536) + loadmodel->surfmesh.data_element3s = (unsigned short *)Mem_Alloc(loadmodel->mempool, sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles); + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + +// load the frames + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); + loadmodel->surfmesh.data_morphmdlvertex = (trivertx_t *)Mem_Alloc(loadmodel->mempool, sizeof(trivertx_t) * loadmodel->surfmesh.num_morphframes * loadmodel->surfmesh.num_vertices); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_triangles * sizeof(int[3])); + } + Mod_MDL_LoadFrames (startframes, numverts, vertremap); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); + Mod_Alias_MorphMesh_CompileFrames(); + + Mem_Free(vertst); + Mem_Free(vertremap); + + // load the skins + skinfiles = Mod_LoadSkinFiles(); + if (skinfiles) + { + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numskins * sizeof(animscene_t)); + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures, skinfiles, "default", ""); + Mod_FreeSkinFiles(skinfiles); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + } + else + { + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numskins * sizeof(animscene_t)); + loadmodel->num_textures = loadmodel->num_surfaces * totalskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * totalskins * sizeof(texture_t)); + totalskins = 0; + datapointer = startskins; + for (i = 0;i < loadmodel->numskins;i++) + { + pinskintype = (daliasskintype_t *)datapointer; + datapointer += sizeof(daliasskintype_t); + + if (pinskintype->type == ALIAS_SKIN_SINGLE) + { + groupskins = 1; + interval = 0.1f; + } + else + { + pinskingroup = (daliasskingroup_t *)datapointer; + datapointer += sizeof(daliasskingroup_t); + + groupskins = LittleLong (pinskingroup->numskins); + + pinskinintervals = (daliasskininterval_t *)datapointer; + datapointer += sizeof(daliasskininterval_t) * groupskins; + + interval = LittleFloat(pinskinintervals[0].interval); + if (interval < 0.01f) + { + Con_Printf("%s has an invalid interval %f, changing to 0.1\n", loadmodel->name, interval); + interval = 0.1f; + } + } + + dpsnprintf(loadmodel->skinscenes[i].name, sizeof(loadmodel->skinscenes[i].name), "skin %i", i); + loadmodel->skinscenes[i].firstframe = totalskins; + loadmodel->skinscenes[i].framecount = groupskins; + loadmodel->skinscenes[i].framerate = 1.0f / interval; + loadmodel->skinscenes[i].loop = true; + + for (j = 0;j < groupskins;j++) + { + if (groupskins > 1) + dpsnprintf (name, sizeof(name), "%s_%i_%i", loadmodel->name, i, j); + else + dpsnprintf (name, sizeof(name), "%s_%i", loadmodel->name, i); + if (!Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, name, false, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS)) + Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, R_SkinFrame_LoadInternalQuake(name, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_PICMIP, true, r_fullbrights.integer, (unsigned char *)datapointer, skinwidth, skinheight)); + datapointer += skinwidth * skinheight; + totalskins++; + } + } + // check for skins that don't exist in the model, but do exist as external images + // (this was added because yummyluv kept pestering me about support for it) + // TODO: support shaders here? + while ((tempskinframe = R_SkinFrame_LoadExternal(va(vabuf, sizeof(vabuf), "%s_%i", loadmodel->name, loadmodel->numskins), (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS, false))) + { + // expand the arrays to make room + tempskinscenes = loadmodel->skinscenes; + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, (loadmodel->numskins + 1) * sizeof(animscene_t)); + memcpy(loadmodel->skinscenes, tempskinscenes, loadmodel->numskins * sizeof(animscene_t)); + Mem_Free(tempskinscenes); + + tempaliasskins = loadmodel->data_textures; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * (totalskins + 1) * sizeof(texture_t)); + memcpy(loadmodel->data_textures, tempaliasskins, loadmodel->num_surfaces * totalskins * sizeof(texture_t)); + Mem_Free(tempaliasskins); + + // store the info about the new skin + Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, tempskinframe); + strlcpy(loadmodel->skinscenes[loadmodel->numskins].name, name, sizeof(loadmodel->skinscenes[loadmodel->numskins].name)); + loadmodel->skinscenes[loadmodel->numskins].firstframe = totalskins; + loadmodel->skinscenes[loadmodel->numskins].framecount = 1; + loadmodel->skinscenes[loadmodel->numskins].framerate = 10.0f; + loadmodel->skinscenes[loadmodel->numskins].loop = true; + + //increase skin counts + loadmodel->numskins++; + totalskins++; + + // fix up the pointers since they are pointing at the old textures array + // FIXME: this is a hack! + for (j = 0;j < loadmodel->numskins * loadmodel->num_surfaces;j++) + loadmodel->data_textures[j].currentframe = &loadmodel->data_textures[j]; + } + } + + surface = loadmodel->data_surfaces; + surface->texture = loadmodel->data_textures; + surface->num_firsttriangle = 0; + surface->num_triangles = loadmodel->surfmesh.num_triangles; + surface->num_firstvertex = 0; + surface->num_vertices = loadmodel->surfmesh.num_vertices; + + if(mod_alias_force_animated.string[0]) + loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } + + // because shaders can do somewhat unexpected things, check for unusual features now + for (i = 0;i < loadmodel->num_textures;i++) + { + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) + mod->DrawSky = R_Q1BSP_DrawSky; + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + } +} + +void Mod_IDP2_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, hashindex, numxyz, numst, xyz, st, skinwidth, skinheight, *vertremap, version, end; + float iskinwidth, iskinheight; + unsigned char *data; + msurface_t *surface; + md2_t *pinmodel; + unsigned char *base, *datapointer; + md2frame_t *pinframe; + char *inskin; + md2triangle_t *intri; + unsigned short *inst; + struct md2verthash_s + { + struct md2verthash_s *next; + unsigned short xyz; + unsigned short st; + } + *hash, **md2verthash, *md2verthashdata; + skinfile_t *skinfiles; + + pinmodel = (md2_t *)buffer; + base = (unsigned char *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD2ALIAS_VERSION) + Host_Error ("%s has wrong version number (%i should be %i)", + loadmodel->name, version, MD2ALIAS_VERSION); + + loadmodel->modeldatatypestring = "MD2"; + + loadmodel->type = mod_alias; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + loadmodel->AnimateVertices = Mod_MDL_AnimateVertices; + + if (LittleLong(pinmodel->num_tris) < 1 || LittleLong(pinmodel->num_tris) > 65536) + Host_Error ("%s has invalid number of triangles: %i", loadmodel->name, LittleLong(pinmodel->num_tris)); + if (LittleLong(pinmodel->num_xyz) < 1 || LittleLong(pinmodel->num_xyz) > 65536) + Host_Error ("%s has invalid number of vertices: %i", loadmodel->name, LittleLong(pinmodel->num_xyz)); + if (LittleLong(pinmodel->num_frames) < 1 || LittleLong(pinmodel->num_frames) > 65536) + Host_Error ("%s has invalid number of frames: %i", loadmodel->name, LittleLong(pinmodel->num_frames)); + if (LittleLong(pinmodel->num_skins) < 0 || LittleLong(pinmodel->num_skins) > 256) + Host_Error ("%s has invalid number of skins: %i", loadmodel->name, LittleLong(pinmodel->num_skins)); + + end = LittleLong(pinmodel->ofs_end); + if (LittleLong(pinmodel->num_skins) >= 1 && (LittleLong(pinmodel->ofs_skins) <= 0 || LittleLong(pinmodel->ofs_skins) >= end)) + Host_Error ("%s is not a valid model", loadmodel->name); + if (LittleLong(pinmodel->ofs_st) <= 0 || LittleLong(pinmodel->ofs_st) >= end) + Host_Error ("%s is not a valid model", loadmodel->name); + if (LittleLong(pinmodel->ofs_tris) <= 0 || LittleLong(pinmodel->ofs_tris) >= end) + Host_Error ("%s is not a valid model", loadmodel->name); + if (LittleLong(pinmodel->ofs_frames) <= 0 || LittleLong(pinmodel->ofs_frames) >= end) + Host_Error ("%s is not a valid model", loadmodel->name); + if (LittleLong(pinmodel->ofs_glcmds) <= 0 || LittleLong(pinmodel->ofs_glcmds) >= end) + Host_Error ("%s is not a valid model", loadmodel->name); + + loadmodel->numskins = LittleLong(pinmodel->num_skins); + numxyz = LittleLong(pinmodel->num_xyz); + numst = LittleLong(pinmodel->num_st); + loadmodel->surfmesh.num_triangles = LittleLong(pinmodel->num_tris); + loadmodel->numframes = LittleLong(pinmodel->num_frames); + loadmodel->surfmesh.num_morphframes = loadmodel->numframes; + loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; + skinwidth = LittleLong(pinmodel->skinwidth); + skinheight = LittleLong(pinmodel->skinheight); + iskinwidth = 1.0f / skinwidth; + iskinheight = 1.0f / skinheight; + + loadmodel->num_surfaces = 1; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->numframes * sizeof(animscene_t) + loadmodel->numframes * sizeof(float[6]) + loadmodel->surfmesh.num_triangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? loadmodel->surfmesh.num_triangles * sizeof(int[3]) : 0)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->sortedmodelsurfaces[0] = 0; + loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); + loadmodel->surfmesh.data_morphmd2framesize6f = (float *)data;data += loadmodel->numframes * sizeof(float[6]); + loadmodel->surfmesh.data_element3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); + } + + loadmodel->synctype = ST_RAND; + + // load the skins + inskin = (char *)(base + LittleLong(pinmodel->ofs_skins)); + skinfiles = Mod_LoadSkinFiles(); + if (skinfiles) + { + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures, skinfiles, "default", ""); + Mod_FreeSkinFiles(skinfiles); + } + else if (loadmodel->numskins) + { + // skins found (most likely not a player model) + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); + for (i = 0;i < loadmodel->numskins;i++, inskin += MD2_SKINNAME) + Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i * loadmodel->num_surfaces, inskin, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); + } + else + { + // no skins (most likely a player model) + loadmodel->numskins = 1; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); + Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures, NULL); + } + + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // load the triangles and stvert data + inst = (unsigned short *)(base + LittleLong(pinmodel->ofs_st)); + intri = (md2triangle_t *)(base + LittleLong(pinmodel->ofs_tris)); + md2verthash = (struct md2verthash_s **)Mem_Alloc(tempmempool, 65536 * sizeof(hash)); + md2verthashdata = (struct md2verthash_s *)Mem_Alloc(tempmempool, loadmodel->surfmesh.num_triangles * 3 * sizeof(*hash)); + // swap the triangle list + loadmodel->surfmesh.num_vertices = 0; + for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) + { + for (j = 0;j < 3;j++) + { + xyz = (unsigned short) LittleShort (intri[i].index_xyz[j]); + st = (unsigned short) LittleShort (intri[i].index_st[j]); + if (xyz >= numxyz) + { + Con_Printf("%s has an invalid xyz index (%i) on triangle %i, resetting to 0\n", loadmodel->name, xyz, i); + xyz = 0; + } + if (st >= numst) + { + Con_Printf("%s has an invalid st index (%i) on triangle %i, resetting to 0\n", loadmodel->name, st, i); + st = 0; + } + hashindex = (xyz * 256 + st) & 65535; + for (hash = md2verthash[hashindex];hash;hash = hash->next) + if (hash->xyz == xyz && hash->st == st) + break; + if (hash == NULL) + { + hash = md2verthashdata + loadmodel->surfmesh.num_vertices++; + hash->xyz = xyz; + hash->st = st; + hash->next = md2verthash[hashindex]; + md2verthash[hashindex] = hash; + } + loadmodel->surfmesh.data_element3i[i*3+j] = (hash - md2verthashdata); + } + } + + vertremap = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(int)); + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(float[2]) + loadmodel->surfmesh.num_vertices * loadmodel->surfmesh.num_morphframes * sizeof(trivertx_t)); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[2]); + loadmodel->surfmesh.data_morphmdlvertex = (trivertx_t *)data;data += loadmodel->surfmesh.num_vertices * loadmodel->surfmesh.num_morphframes * sizeof(trivertx_t); + for (i = 0;i < loadmodel->surfmesh.num_vertices;i++) + { + int sts, stt; + hash = md2verthashdata + i; + vertremap[i] = hash->xyz; + sts = LittleShort(inst[hash->st*2+0]); + stt = LittleShort(inst[hash->st*2+1]); + if (sts < 0 || sts >= skinwidth || stt < 0 || stt >= skinheight) + { + Con_Printf("%s has an invalid skin coordinate (%i %i) on vert %i, changing to 0 0\n", loadmodel->name, sts, stt, i); + sts = 0; + stt = 0; + } + loadmodel->surfmesh.data_texcoordtexture2f[i*2+0] = sts * iskinwidth; + loadmodel->surfmesh.data_texcoordtexture2f[i*2+1] = stt * iskinheight; + } + + Mem_Free(md2verthash); + Mem_Free(md2verthashdata); + + // generate ushort elements array if possible + if (loadmodel->surfmesh.num_vertices <= 65536) + loadmodel->surfmesh.data_element3s = (unsigned short *)Mem_Alloc(loadmodel->mempool, sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles); + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + + // load the frames + datapointer = (base + LittleLong(pinmodel->ofs_frames)); + for (i = 0;i < loadmodel->surfmesh.num_morphframes;i++) + { + int k; + trivertx_t *v; + trivertx_t *out; + pinframe = (md2frame_t *)datapointer; + datapointer += sizeof(md2frame_t); + // store the frame scale/translate into the appropriate array + for (j = 0;j < 3;j++) + { + loadmodel->surfmesh.data_morphmd2framesize6f[i*6+j] = LittleFloat(pinframe->scale[j]); + loadmodel->surfmesh.data_morphmd2framesize6f[i*6+3+j] = LittleFloat(pinframe->translate[j]); + } + // convert the vertices + v = (trivertx_t *)datapointer; + out = loadmodel->surfmesh.data_morphmdlvertex + i * loadmodel->surfmesh.num_vertices; + for (k = 0;k < loadmodel->surfmesh.num_vertices;k++) + out[k] = v[vertremap[k]]; + datapointer += numxyz * sizeof(trivertx_t); + + strlcpy(loadmodel->animscenes[i].name, pinframe->name, sizeof(loadmodel->animscenes[i].name)); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].framerate = 10; + loadmodel->animscenes[i].loop = true; + } + + Mem_Free(vertremap); + + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); + Mod_Alias_MorphMesh_CompileFrames(); + if(mod_alias_force_animated.string[0]) + loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; + + surface = loadmodel->data_surfaces; + surface->texture = loadmodel->data_textures; + surface->num_firsttriangle = 0; + surface->num_triangles = loadmodel->surfmesh.num_triangles; + surface->num_firstvertex = 0; + surface->num_vertices = loadmodel->surfmesh.num_vertices; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } + + // because shaders can do somewhat unexpected things, check for unusual features now + for (i = 0;i < loadmodel->num_textures;i++) + { + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) + mod->DrawSky = R_Q1BSP_DrawSky; + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + } +} + +void Mod_IDP3_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, k, version, meshvertices, meshtriangles; + unsigned char *data; + msurface_t *surface; + md3modelheader_t *pinmodel; + md3frameinfo_t *pinframe; + md3mesh_t *pinmesh; + md3tag_t *pintag; + skinfile_t *skinfiles; + + pinmodel = (md3modelheader_t *)buffer; + + if (memcmp(pinmodel->identifier, "IDP3", 4)) + Host_Error ("%s is not a MD3 (IDP3) file", loadmodel->name); + version = LittleLong (pinmodel->version); + if (version != MD3VERSION) + Host_Error ("%s has wrong version number (%i should be %i)", + loadmodel->name, version, MD3VERSION); + + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + loadmodel->modeldatatypestring = "MD3"; + + loadmodel->type = mod_alias; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + loadmodel->AnimateVertices = Mod_MD3_AnimateVertices; + loadmodel->synctype = ST_RAND; + // convert model flags to EF flags (MF_ROCKET becomes EF_ROCKET, etc) + i = LittleLong (pinmodel->flags); + loadmodel->effects = ((i & 255) << 24) | (i & 0x00FFFF00); + + // set up some global info about the model + loadmodel->numframes = LittleLong(pinmodel->num_frames); + loadmodel->num_surfaces = LittleLong(pinmodel->num_meshes); + + // make skinscenes for the skins (no groups) + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // load frameinfo + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numframes * sizeof(animscene_t)); + for (i = 0, pinframe = (md3frameinfo_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_frameinfo));i < loadmodel->numframes;i++, pinframe++) + { + strlcpy(loadmodel->animscenes[i].name, pinframe->name, sizeof(loadmodel->animscenes[i].name)); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].framerate = 10; + loadmodel->animscenes[i].loop = true; + } + + // load tags + loadmodel->num_tagframes = loadmodel->numframes; + loadmodel->num_tags = LittleLong(pinmodel->num_tags); + loadmodel->data_tags = (aliastag_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_tagframes * loadmodel->num_tags * sizeof(aliastag_t)); + for (i = 0, pintag = (md3tag_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_tags));i < loadmodel->num_tagframes * loadmodel->num_tags;i++, pintag++) + { + strlcpy(loadmodel->data_tags[i].name, pintag->name, sizeof(loadmodel->data_tags[i].name)); + for (j = 0;j < 9;j++) + loadmodel->data_tags[i].matrixgl[j] = LittleFloat(pintag->rotationmatrix[j]); + for (j = 0;j < 3;j++) + loadmodel->data_tags[i].matrixgl[9+j] = LittleFloat(pintag->origin[j]); + //Con_Printf("model \"%s\" frame #%i tag #%i \"%s\"\n", loadmodel->name, i / loadmodel->num_tags, i % loadmodel->num_tags, loadmodel->data_tags[i].name); + } + + // load meshes + meshvertices = 0; + meshtriangles = 0; + for (i = 0, pinmesh = (md3mesh_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_meshes));i < loadmodel->num_surfaces;i++, pinmesh = (md3mesh_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_end))) + { + if (memcmp(pinmesh->identifier, "IDP3", 4)) + Host_Error("Mod_IDP3_Load: invalid mesh identifier (not IDP3)"); + if (LittleLong(pinmesh->num_frames) != loadmodel->numframes) + Host_Error("Mod_IDP3_Load: mesh numframes differs from header"); + meshvertices += LittleLong(pinmesh->num_vertices); + meshtriangles += LittleLong(pinmesh->num_triangles); + } + + loadmodel->nummodelsurfaces = loadmodel->num_surfaces; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + meshvertices * sizeof(float[2]) + meshvertices * loadmodel->numframes * sizeof(md3vertex_t)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + loadmodel->surfmesh.num_morphframes = loadmodel->numframes; // TODO: remove? + loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; + loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); + loadmodel->surfmesh.data_morphmd3vertex = (md3vertex_t *)data;data += meshvertices * loadmodel->numframes * sizeof(md3vertex_t); + if (meshvertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); + } + + meshvertices = 0; + meshtriangles = 0; + for (i = 0, pinmesh = (md3mesh_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_meshes));i < loadmodel->num_surfaces;i++, pinmesh = (md3mesh_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_end))) + { + if (memcmp(pinmesh->identifier, "IDP3", 4)) + Host_Error("Mod_IDP3_Load: invalid mesh identifier (not IDP3)"); + loadmodel->sortedmodelsurfaces[i] = i; + surface = loadmodel->data_surfaces + i; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = meshtriangles; + surface->num_triangles = LittleLong(pinmesh->num_triangles); + surface->num_firstvertex = meshvertices; + surface->num_vertices = LittleLong(pinmesh->num_vertices); + meshvertices += surface->num_vertices; + meshtriangles += surface->num_triangles; + + for (j = 0;j < surface->num_triangles * 3;j++) + loadmodel->surfmesh.data_element3i[j + surface->num_firsttriangle * 3] = surface->num_firstvertex + LittleLong(((int *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_elements)))[j]); + for (j = 0;j < surface->num_vertices;j++) + { + loadmodel->surfmesh.data_texcoordtexture2f[(j + surface->num_firstvertex) * 2 + 0] = LittleFloat(((float *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_texcoords)))[j * 2 + 0]); + loadmodel->surfmesh.data_texcoordtexture2f[(j + surface->num_firstvertex) * 2 + 1] = LittleFloat(((float *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_texcoords)))[j * 2 + 1]); + } + for (j = 0;j < loadmodel->numframes;j++) + { + const md3vertex_t *in = (md3vertex_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_framevertices)) + j * surface->num_vertices; + md3vertex_t *out = loadmodel->surfmesh.data_morphmd3vertex + surface->num_firstvertex + j * loadmodel->surfmesh.num_vertices; + for (k = 0;k < surface->num_vertices;k++, in++, out++) + { + out->origin[0] = LittleShort(in->origin[0]); + out->origin[1] = LittleShort(in->origin[1]); + out->origin[2] = LittleShort(in->origin[2]); + out->pitch = in->pitch; + out->yaw = in->yaw; + } + } + + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, pinmesh->name, LittleLong(pinmesh->num_shaders) >= 1 ? ((md3shader_t *)((unsigned char *) pinmesh + LittleLong(pinmesh->lump_shaders)))->name : ""); + + Mod_ValidateElements(loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3, surface->num_triangles, surface->num_firstvertex, surface->num_vertices, __FILE__, __LINE__); + } + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + Mod_Alias_MorphMesh_CompileFrames(); + loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); + Mod_FreeSkinFiles(skinfiles); + Mod_MakeSortedSurfaces(loadmodel); + if(mod_alias_force_animated.string[0]) + loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } + + // because shaders can do somewhat unexpected things, check for unusual features now + for (i = 0;i < loadmodel->num_textures;i++) + { + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) + mod->DrawSky = R_Q1BSP_DrawSky; + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + } +} + +void Mod_ZYMOTICMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + zymtype1header_t *pinmodel, *pheader; + unsigned char *pbase; + int i, j, k, numposes, meshvertices, meshtriangles, *bonecount, *vertbonecounts, count, *renderlist, *renderlistend, *outelements; + float modelradius, corner[2], *poses, *intexcoord2f, *outtexcoord2f, *bonepose, f, biggestorigin, tempvec[3], modelscale; + zymvertex_t *verts, *vertdata; + zymscene_t *scene; + zymbone_t *bone; + char *shadername; + skinfile_t *skinfiles; + unsigned char *data; + msurface_t *surface; + + pinmodel = (zymtype1header_t *)buffer; + pbase = (unsigned char *)buffer; + if (memcmp(pinmodel->id, "ZYMOTICMODEL", 12)) + Host_Error ("Mod_ZYMOTICMODEL_Load: %s is not a zymotic model", loadmodel->name); + if (BigLong(pinmodel->type) != 1) + Host_Error ("Mod_ZYMOTICMODEL_Load: only type 1 (skeletal pose) models are currently supported (name = %s)", loadmodel->name); + + loadmodel->modeldatatypestring = "ZYM"; + + loadmodel->type = mod_alias; + loadmodel->synctype = ST_RAND; + + // byteswap header + pheader = pinmodel; + pheader->type = BigLong(pinmodel->type); + pheader->filesize = BigLong(pinmodel->filesize); + pheader->mins[0] = BigFloat(pinmodel->mins[0]); + pheader->mins[1] = BigFloat(pinmodel->mins[1]); + pheader->mins[2] = BigFloat(pinmodel->mins[2]); + pheader->maxs[0] = BigFloat(pinmodel->maxs[0]); + pheader->maxs[1] = BigFloat(pinmodel->maxs[1]); + pheader->maxs[2] = BigFloat(pinmodel->maxs[2]); + pheader->radius = BigFloat(pinmodel->radius); + pheader->numverts = BigLong(pinmodel->numverts); + pheader->numtris = BigLong(pinmodel->numtris); + pheader->numshaders = BigLong(pinmodel->numshaders); + pheader->numbones = BigLong(pinmodel->numbones); + pheader->numscenes = BigLong(pinmodel->numscenes); + pheader->lump_scenes.start = BigLong(pinmodel->lump_scenes.start); + pheader->lump_scenes.length = BigLong(pinmodel->lump_scenes.length); + pheader->lump_poses.start = BigLong(pinmodel->lump_poses.start); + pheader->lump_poses.length = BigLong(pinmodel->lump_poses.length); + pheader->lump_bones.start = BigLong(pinmodel->lump_bones.start); + pheader->lump_bones.length = BigLong(pinmodel->lump_bones.length); + pheader->lump_vertbonecounts.start = BigLong(pinmodel->lump_vertbonecounts.start); + pheader->lump_vertbonecounts.length = BigLong(pinmodel->lump_vertbonecounts.length); + pheader->lump_verts.start = BigLong(pinmodel->lump_verts.start); + pheader->lump_verts.length = BigLong(pinmodel->lump_verts.length); + pheader->lump_texcoords.start = BigLong(pinmodel->lump_texcoords.start); + pheader->lump_texcoords.length = BigLong(pinmodel->lump_texcoords.length); + pheader->lump_render.start = BigLong(pinmodel->lump_render.start); + pheader->lump_render.length = BigLong(pinmodel->lump_render.length); + pheader->lump_shaders.start = BigLong(pinmodel->lump_shaders.start); + pheader->lump_shaders.length = BigLong(pinmodel->lump_shaders.length); + pheader->lump_trizone.start = BigLong(pinmodel->lump_trizone.start); + pheader->lump_trizone.length = BigLong(pinmodel->lump_trizone.length); + + if (pheader->numtris < 1 || pheader->numverts < 3 || pheader->numshaders < 1) + { + Con_Printf("%s has no geometry\n", loadmodel->name); + return; + } + if (pheader->numscenes < 1 || pheader->lump_poses.length < (int)sizeof(float[3][4])) + { + Con_Printf("%s has no animations\n", loadmodel->name); + return; + } + + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; + + loadmodel->numframes = pheader->numscenes; + loadmodel->num_surfaces = pheader->numshaders; + + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + // make skinscenes for the skins (no groups) + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // model bbox + // LordHavoc: actually we blow this away later with Mod_Alias_CalculateBoundingBox() + modelradius = pheader->radius; + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = pheader->mins[i]; + loadmodel->normalmaxs[i] = pheader->maxs[i]; + loadmodel->rotatedmins[i] = -modelradius; + loadmodel->rotatedmaxs[i] = modelradius; + } + corner[0] = max(fabs(loadmodel->normalmins[0]), fabs(loadmodel->normalmaxs[0])); + corner[1] = max(fabs(loadmodel->normalmins[1]), fabs(loadmodel->normalmaxs[1])); + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); + if (loadmodel->yawmaxs[0] > modelradius) + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = modelradius; + loadmodel->yawmins[0] = loadmodel->yawmins[1] = -loadmodel->yawmaxs[0]; + loadmodel->yawmins[2] = loadmodel->normalmins[2]; + loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; + loadmodel->radius = modelradius; + loadmodel->radius2 = modelradius * modelradius; + + // go through the lumps, swapping things + + //zymlump_t lump_scenes; // zymscene_t scene[numscenes]; // name and other information for each scene (see zymscene struct) + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); + scene = (zymscene_t *) (pheader->lump_scenes.start + pbase); + numposes = pheader->lump_poses.length / pheader->numbones / sizeof(float[3][4]); + for (i = 0;i < pheader->numscenes;i++) + { + memcpy(loadmodel->animscenes[i].name, scene->name, 32); + loadmodel->animscenes[i].firstframe = BigLong(scene->start); + loadmodel->animscenes[i].framecount = BigLong(scene->length); + loadmodel->animscenes[i].framerate = BigFloat(scene->framerate); + loadmodel->animscenes[i].loop = (BigLong(scene->flags) & ZYMSCENEFLAG_NOLOOP) == 0; + if ((unsigned int) loadmodel->animscenes[i].firstframe >= (unsigned int) numposes) + Host_Error("%s scene->firstframe (%i) >= numposes (%i)", loadmodel->name, loadmodel->animscenes[i].firstframe, numposes); + if ((unsigned int) loadmodel->animscenes[i].firstframe + (unsigned int) loadmodel->animscenes[i].framecount > (unsigned int) numposes) + Host_Error("%s scene->firstframe (%i) + framecount (%i) >= numposes (%i)", loadmodel->name, loadmodel->animscenes[i].firstframe, loadmodel->animscenes[i].framecount, numposes); + if (loadmodel->animscenes[i].framerate < 0) + Host_Error("%s scene->framerate (%f) < 0", loadmodel->name, loadmodel->animscenes[i].framerate); + scene++; + } + + //zymlump_t lump_bones; // zymbone_t bone[numbones]; + loadmodel->num_bones = pheader->numbones; + loadmodel->data_bones = (aliasbone_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_bones * sizeof(aliasbone_t)); + bone = (zymbone_t *) (pheader->lump_bones.start + pbase); + for (i = 0;i < pheader->numbones;i++) + { + memcpy(loadmodel->data_bones[i].name, bone[i].name, sizeof(bone[i].name)); + loadmodel->data_bones[i].flags = BigLong(bone[i].flags); + loadmodel->data_bones[i].parent = BigLong(bone[i].parent); + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); + } + + //zymlump_t lump_vertbonecounts; // int vertbonecounts[numvertices]; // how many bones influence each vertex (separate mainly to make this compress better) + vertbonecounts = (int *)Mem_Alloc(loadmodel->mempool, pheader->numverts * sizeof(int)); + bonecount = (int *) (pheader->lump_vertbonecounts.start + pbase); + for (i = 0;i < pheader->numverts;i++) + { + vertbonecounts[i] = BigLong(bonecount[i]); + if (vertbonecounts[i] != 1) + Host_Error("%s bonecount[%i] != 1 (vertex weight support is impossible in this format)", loadmodel->name, i); + } + + loadmodel->num_poses = pheader->lump_poses.length / sizeof(float[3][4]) / loadmodel->num_bones; + + meshvertices = pheader->numverts; + meshtriangles = pheader->numtris; + + loadmodel->nummodelsurfaces = loadmodel->num_surfaces; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + meshvertices * (sizeof(float[14]) + sizeof(unsigned short) + sizeof(unsigned char[2][4])) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]) + loadmodel->num_bones * sizeof(float[12])); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); + loadmodel->surfmesh.data_skeletalindex4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); + loadmodel->surfmesh.data_skeletalweight4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); + loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); + loadmodel->surfmesh.num_blends = 0; + loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); + if (loadmodel->surfmesh.num_vertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); + } + loadmodel->data_poses7s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]); + loadmodel->surfmesh.data_blendweights = NULL; + + //zymlump_t lump_poses; // float pose[numposes][numbones][3][4]; // animation data + poses = (float *) (pheader->lump_poses.start + pbase); + // figure out scale of model from root bone, for compatibility with old zmodel versions + tempvec[0] = BigFloat(poses[0]); + tempvec[1] = BigFloat(poses[1]); + tempvec[2] = BigFloat(poses[2]); + modelscale = VectorLength(tempvec); + biggestorigin = 0; + for (i = 0;i < loadmodel->num_bones * numposes * 12;i++) + { + f = fabs(BigFloat(poses[i])); + biggestorigin = max(biggestorigin, f); + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + for (i = 0;i < numposes;i++) + { + const float *frameposes = (float *) (pheader->lump_poses.start + pbase) + 12*i*loadmodel->num_bones; + for (j = 0;j < loadmodel->num_bones;j++) + { + float pose[12]; + matrix4x4_t posematrix; + for (k = 0;k < 12;k++) + pose[k] = BigFloat(frameposes[j*12+k]); + //if (j < loadmodel->num_bones) + // Con_Printf("%s: bone %i = %f %f %f %f : %f %f %f %f : %f %f %f %f : scale = %f\n", loadmodel->name, j, pose[0], pose[1], pose[2], pose[3], pose[4], pose[5], pose[6], pose[7], pose[8], pose[9], pose[10], pose[11], VectorLength(pose)); + // scale child bones to match the root scale + if (loadmodel->data_bones[j].parent >= 0) + { + pose[3] *= modelscale; + pose[7] *= modelscale; + pose[11] *= modelscale; + } + // normalize rotation matrix + VectorNormalize(pose + 0); + VectorNormalize(pose + 4); + VectorNormalize(pose + 8); + Matrix4x4_FromArray12FloatD3D(&posematrix, pose); + Matrix4x4_ToBonePose7s(&posematrix, loadmodel->num_poseinvscale, loadmodel->data_poses7s + 7*(i*loadmodel->num_bones+j)); + } + } + + //zymlump_t lump_verts; // zymvertex_t vert[numvertices]; // see vertex struct + verts = (zymvertex_t *)Mem_Alloc(loadmodel->mempool, pheader->lump_verts.length); + vertdata = (zymvertex_t *) (pheader->lump_verts.start + pbase); + // reconstruct frame 0 matrices to allow reconstruction of the base mesh + // (converting from weight-blending skeletal animation to + // deformation-based skeletal animation) + bonepose = (float *)Z_Malloc(loadmodel->num_bones * sizeof(float[12])); + for (i = 0;i < loadmodel->num_bones;i++) + { + float m[12]; + for (k = 0;k < 12;k++) + m[k] = BigFloat(poses[i*12+k]); + if (loadmodel->data_bones[i].parent >= 0) + R_ConcatTransforms(bonepose + 12 * loadmodel->data_bones[i].parent, m, bonepose + 12 * i); + else + for (k = 0;k < 12;k++) + bonepose[12*i+k] = m[k]; + } + for (j = 0;j < pheader->numverts;j++) + { + // this format really should have had a per vertexweight weight value... + // but since it does not, the weighting is completely ignored and + // only one weight is allowed per vertex + int boneindex = BigLong(vertdata[j].bonenum); + const float *m = bonepose + 12 * boneindex; + float relativeorigin[3]; + relativeorigin[0] = BigFloat(vertdata[j].origin[0]); + relativeorigin[1] = BigFloat(vertdata[j].origin[1]); + relativeorigin[2] = BigFloat(vertdata[j].origin[2]); + // transform the vertex bone weight into the base mesh + loadmodel->surfmesh.data_vertex3f[j*3+0] = relativeorigin[0] * m[0] + relativeorigin[1] * m[1] + relativeorigin[2] * m[ 2] + m[ 3]; + loadmodel->surfmesh.data_vertex3f[j*3+1] = relativeorigin[0] * m[4] + relativeorigin[1] * m[5] + relativeorigin[2] * m[ 6] + m[ 7]; + loadmodel->surfmesh.data_vertex3f[j*3+2] = relativeorigin[0] * m[8] + relativeorigin[1] * m[9] + relativeorigin[2] * m[10] + m[11]; + // store the weight as the primary weight on this vertex + loadmodel->surfmesh.blends[j] = boneindex; + loadmodel->surfmesh.data_skeletalindex4ub[j*4 ] = boneindex; + loadmodel->surfmesh.data_skeletalindex4ub[j*4+1] = 0; + loadmodel->surfmesh.data_skeletalindex4ub[j*4+2] = 0; + loadmodel->surfmesh.data_skeletalindex4ub[j*4+3] = 0; + loadmodel->surfmesh.data_skeletalweight4ub[j*4 ] = 255; + loadmodel->surfmesh.data_skeletalweight4ub[j*4+1] = 0; + loadmodel->surfmesh.data_skeletalweight4ub[j*4+2] = 0; + loadmodel->surfmesh.data_skeletalweight4ub[j*4+3] = 0; + } + Z_Free(bonepose); + // normals and tangents are calculated after elements are loaded + + //zymlump_t lump_texcoords; // float texcoords[numvertices][2]; + outtexcoord2f = loadmodel->surfmesh.data_texcoordtexture2f; + intexcoord2f = (float *) (pheader->lump_texcoords.start + pbase); + for (i = 0;i < pheader->numverts;i++) + { + outtexcoord2f[i*2+0] = BigFloat(intexcoord2f[i*2+0]); + // flip T coordinate for OpenGL + outtexcoord2f[i*2+1] = 1 - BigFloat(intexcoord2f[i*2+1]); + } + + //zymlump_t lump_trizone; // byte trizone[numtris]; // see trizone explanation + //loadmodel->alias.zymdata_trizone = Mem_Alloc(loadmodel->mempool, pheader->numtris); + //memcpy(loadmodel->alias.zymdata_trizone, (void *) (pheader->lump_trizone.start + pbase), pheader->numtris); + + //zymlump_t lump_shaders; // char shadername[numshaders][32]; // shaders used on this model + //zymlump_t lump_render; // int renderlist[rendersize]; // sorted by shader with run lengths (int count), shaders are sequentially used, each run can be used with glDrawElements (each triangle is 3 int indices) + // byteswap, validate, and swap winding order of tris + count = pheader->numshaders * sizeof(int) + pheader->numtris * sizeof(int[3]); + if (pheader->lump_render.length != count) + Host_Error("%s renderlist is wrong size (%i bytes, should be %i bytes)", loadmodel->name, pheader->lump_render.length, count); + renderlist = (int *) (pheader->lump_render.start + pbase); + renderlistend = (int *) ((unsigned char *) renderlist + pheader->lump_render.length); + meshtriangles = 0; + for (i = 0;i < loadmodel->num_surfaces;i++) + { + int firstvertex, lastvertex; + if (renderlist >= renderlistend) + Host_Error("%s corrupt renderlist (wrong size)", loadmodel->name); + count = BigLong(*renderlist);renderlist++; + if (renderlist + count * 3 > renderlistend || (i == pheader->numshaders - 1 && renderlist + count * 3 != renderlistend)) + Host_Error("%s corrupt renderlist (wrong size)", loadmodel->name); + + loadmodel->sortedmodelsurfaces[i] = i; + surface = loadmodel->data_surfaces + i; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = meshtriangles; + surface->num_triangles = count; + meshtriangles += surface->num_triangles; + + // load the elements + outelements = loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3; + for (j = 0;j < surface->num_triangles;j++, renderlist += 3) + { + outelements[j*3+2] = BigLong(renderlist[0]); + outelements[j*3+1] = BigLong(renderlist[1]); + outelements[j*3+0] = BigLong(renderlist[2]); + } + // validate the elements and find the used vertex range + firstvertex = meshvertices; + lastvertex = 0; + for (j = 0;j < surface->num_triangles * 3;j++) + { + if ((unsigned int)outelements[j] >= (unsigned int)meshvertices) + Host_Error("%s corrupt renderlist (out of bounds index)", loadmodel->name); + firstvertex = min(firstvertex, outelements[j]); + lastvertex = max(lastvertex, outelements[j]); + } + surface->num_firstvertex = firstvertex; + surface->num_vertices = lastvertex + 1 - firstvertex; + + // since zym models do not have named sections, reuse their shader + // name as the section name + shadername = (char *) (pheader->lump_shaders.start + pbase) + i * 32; + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, shadername, shadername); + } + Mod_FreeSkinFiles(skinfiles); + Mem_Free(vertbonecounts); + Mem_Free(verts); + Mod_MakeSortedSurfaces(loadmodel); + + // compute all the mesh information that was not loaded from the file + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); + Mod_BuildBaseBonePoses(); + Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); + if(mod_alias_force_animated.string[0]) + loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } + + // because shaders can do somewhat unexpected things, check for unusual features now + for (i = 0;i < loadmodel->num_textures;i++) + { + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) + mod->DrawSky = R_Q1BSP_DrawSky; + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + } +} + +void Mod_DARKPLACESMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + dpmheader_t *pheader; + dpmframe_t *frames; + dpmbone_t *bone; + dpmmesh_t *dpmmesh; + unsigned char *pbase; + int i, j, k, meshvertices, meshtriangles; + skinfile_t *skinfiles; + unsigned char *data; + float *bonepose; + float biggestorigin, tempvec[3], modelscale; + float f; + float *poses; + + pheader = (dpmheader_t *)buffer; + pbase = (unsigned char *)buffer; + if (memcmp(pheader->id, "DARKPLACESMODEL\0", 16)) + Host_Error ("Mod_DARKPLACESMODEL_Load: %s is not a darkplaces model", loadmodel->name); + if (BigLong(pheader->type) != 2) + Host_Error ("Mod_DARKPLACESMODEL_Load: only type 2 (hierarchical skeletal pose) models are currently supported (name = %s)", loadmodel->name); + + loadmodel->modeldatatypestring = "DPM"; + + loadmodel->type = mod_alias; + loadmodel->synctype = ST_RAND; + + // byteswap header + pheader->type = BigLong(pheader->type); + pheader->filesize = BigLong(pheader->filesize); + pheader->mins[0] = BigFloat(pheader->mins[0]); + pheader->mins[1] = BigFloat(pheader->mins[1]); + pheader->mins[2] = BigFloat(pheader->mins[2]); + pheader->maxs[0] = BigFloat(pheader->maxs[0]); + pheader->maxs[1] = BigFloat(pheader->maxs[1]); + pheader->maxs[2] = BigFloat(pheader->maxs[2]); + pheader->yawradius = BigFloat(pheader->yawradius); + pheader->allradius = BigFloat(pheader->allradius); + pheader->num_bones = BigLong(pheader->num_bones); + pheader->num_meshs = BigLong(pheader->num_meshs); + pheader->num_frames = BigLong(pheader->num_frames); + pheader->ofs_bones = BigLong(pheader->ofs_bones); + pheader->ofs_meshs = BigLong(pheader->ofs_meshs); + pheader->ofs_frames = BigLong(pheader->ofs_frames); + + if (pheader->num_bones < 1 || pheader->num_meshs < 1) + { + Con_Printf("%s has no geometry\n", loadmodel->name); + return; + } + if (pheader->num_frames < 1) + { + Con_Printf("%s has no frames\n", loadmodel->name); + return; + } + + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; + + // model bbox + // LordHavoc: actually we blow this away later with Mod_Alias_CalculateBoundingBox() + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = pheader->mins[i]; + loadmodel->normalmaxs[i] = pheader->maxs[i]; + loadmodel->yawmins[i] = i != 2 ? -pheader->yawradius : pheader->mins[i]; + loadmodel->yawmaxs[i] = i != 2 ? pheader->yawradius : pheader->maxs[i]; + loadmodel->rotatedmins[i] = -pheader->allradius; + loadmodel->rotatedmaxs[i] = pheader->allradius; + } + loadmodel->radius = pheader->allradius; + loadmodel->radius2 = pheader->allradius * pheader->allradius; + + // load external .skin files if present + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + meshvertices = 0; + meshtriangles = 0; + + // gather combined statistics from the meshes + dpmmesh = (dpmmesh_t *) (pbase + pheader->ofs_meshs); + for (i = 0;i < (int)pheader->num_meshs;i++) + { + int numverts = BigLong(dpmmesh->num_verts); + meshvertices += numverts; + meshtriangles += BigLong(dpmmesh->num_tris); + dpmmesh++; + } + + loadmodel->numframes = pheader->num_frames; + loadmodel->num_bones = pheader->num_bones; + loadmodel->num_poses = loadmodel->numframes; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces = pheader->num_meshs; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + // do most allocations as one merged chunk + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + meshvertices * (sizeof(float[14]) + sizeof(unsigned short) + sizeof(unsigned char[2][4])) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); + loadmodel->surfmesh.data_skeletalindex4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); + loadmodel->surfmesh.data_skeletalweight4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); + loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); + loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); + loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); + loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); + loadmodel->surfmesh.num_blends = 0; + loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); + if (meshvertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); + } + loadmodel->data_poses7s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]); + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, meshvertices * sizeof(blendweights_t)); + + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // load the bone info + bone = (dpmbone_t *) (pbase + pheader->ofs_bones); + for (i = 0;i < loadmodel->num_bones;i++) + { + memcpy(loadmodel->data_bones[i].name, bone[i].name, sizeof(bone[i].name)); + loadmodel->data_bones[i].flags = BigLong(bone[i].flags); + loadmodel->data_bones[i].parent = BigLong(bone[i].parent); + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); + } + + // load the frames + frames = (dpmframe_t *) (pbase + pheader->ofs_frames); + // figure out scale of model from root bone, for compatibility with old dpmodel versions + poses = (float *) (pbase + BigLong(frames[0].ofs_bonepositions)); + tempvec[0] = BigFloat(poses[0]); + tempvec[1] = BigFloat(poses[1]); + tempvec[2] = BigFloat(poses[2]); + modelscale = VectorLength(tempvec); + biggestorigin = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + memcpy(loadmodel->animscenes[i].name, frames[i].name, sizeof(frames[i].name)); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].loop = true; + loadmodel->animscenes[i].framerate = 10; + // load the bone poses for this frame + poses = (float *) (pbase + BigLong(frames[i].ofs_bonepositions)); + for (j = 0;j < loadmodel->num_bones*12;j++) + { + f = fabs(BigFloat(poses[j])); + biggestorigin = max(biggestorigin, f); + } + // stuff not processed here: mins, maxs, yawradius, allradius + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + for (i = 0;i < loadmodel->numframes;i++) + { + const float *frameposes = (float *) (pbase + BigLong(frames[i].ofs_bonepositions)); + for (j = 0;j < loadmodel->num_bones;j++) + { + float pose[12]; + matrix4x4_t posematrix; + for (k = 0;k < 12;k++) + pose[k] = BigFloat(frameposes[j*12+k]); + // scale child bones to match the root scale + if (loadmodel->data_bones[j].parent >= 0) + { + pose[3] *= modelscale; + pose[7] *= modelscale; + pose[11] *= modelscale; + } + // normalize rotation matrix + VectorNormalize(pose + 0); + VectorNormalize(pose + 4); + VectorNormalize(pose + 8); + Matrix4x4_FromArray12FloatD3D(&posematrix, pose); + Matrix4x4_ToBonePose7s(&posematrix, loadmodel->num_poseinvscale, loadmodel->data_poses7s + 7*(i*loadmodel->num_bones+j)); + } + } + + // load the meshes now + dpmmesh = (dpmmesh_t *) (pbase + pheader->ofs_meshs); + meshvertices = 0; + meshtriangles = 0; + // reconstruct frame 0 matrices to allow reconstruction of the base mesh + // (converting from weight-blending skeletal animation to + // deformation-based skeletal animation) + poses = (float *) (pbase + BigLong(frames[0].ofs_bonepositions)); + bonepose = (float *)Z_Malloc(loadmodel->num_bones * sizeof(float[12])); + for (i = 0;i < loadmodel->num_bones;i++) + { + float m[12]; + for (k = 0;k < 12;k++) + m[k] = BigFloat(poses[i*12+k]); + if (loadmodel->data_bones[i].parent >= 0) + R_ConcatTransforms(bonepose + 12 * loadmodel->data_bones[i].parent, m, bonepose + 12 * i); + else + for (k = 0;k < 12;k++) + bonepose[12*i+k] = m[k]; + } + for (i = 0;i < loadmodel->num_surfaces;i++, dpmmesh++) + { + const int *inelements; + int *outelements; + const float *intexcoord; + msurface_t *surface; + + loadmodel->sortedmodelsurfaces[i] = i; + surface = loadmodel->data_surfaces + i; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = meshtriangles; + surface->num_triangles = BigLong(dpmmesh->num_tris); + surface->num_firstvertex = meshvertices; + surface->num_vertices = BigLong(dpmmesh->num_verts); + meshvertices += surface->num_vertices; + meshtriangles += surface->num_triangles; + + inelements = (int *) (pbase + BigLong(dpmmesh->ofs_indices)); + outelements = loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3; + for (j = 0;j < surface->num_triangles;j++) + { + // swap element order to flip triangles, because Quake uses clockwise (rare) and dpm uses counterclockwise (standard) + outelements[0] = surface->num_firstvertex + BigLong(inelements[2]); + outelements[1] = surface->num_firstvertex + BigLong(inelements[1]); + outelements[2] = surface->num_firstvertex + BigLong(inelements[0]); + inelements += 3; + outelements += 3; + } + + intexcoord = (float *) (pbase + BigLong(dpmmesh->ofs_texcoords)); + for (j = 0;j < surface->num_vertices*2;j++) + loadmodel->surfmesh.data_texcoordtexture2f[j + surface->num_firstvertex * 2] = BigFloat(intexcoord[j]); + + data = (unsigned char *) (pbase + BigLong(dpmmesh->ofs_verts)); + for (j = surface->num_firstvertex;j < surface->num_firstvertex + surface->num_vertices;j++) + { + int weightindex[4] = { 0, 0, 0, 0 }; + float weightinfluence[4] = { 0, 0, 0, 0 }; + int l; + int numweights = BigLong(((dpmvertex_t *)data)->numbones); + data += sizeof(dpmvertex_t); + for (k = 0;k < numweights;k++) + { + const dpmbonevert_t *vert = (dpmbonevert_t *) data; + int boneindex = BigLong(vert->bonenum); + const float *m = bonepose + 12 * boneindex; + float influence = BigFloat(vert->influence); + float relativeorigin[3], relativenormal[3]; + relativeorigin[0] = BigFloat(vert->origin[0]); + relativeorigin[1] = BigFloat(vert->origin[1]); + relativeorigin[2] = BigFloat(vert->origin[2]); + relativenormal[0] = BigFloat(vert->normal[0]); + relativenormal[1] = BigFloat(vert->normal[1]); + relativenormal[2] = BigFloat(vert->normal[2]); + // blend the vertex bone weights into the base mesh + loadmodel->surfmesh.data_vertex3f[j*3+0] += relativeorigin[0] * m[0] + relativeorigin[1] * m[1] + relativeorigin[2] * m[ 2] + influence * m[ 3]; + loadmodel->surfmesh.data_vertex3f[j*3+1] += relativeorigin[0] * m[4] + relativeorigin[1] * m[5] + relativeorigin[2] * m[ 6] + influence * m[ 7]; + loadmodel->surfmesh.data_vertex3f[j*3+2] += relativeorigin[0] * m[8] + relativeorigin[1] * m[9] + relativeorigin[2] * m[10] + influence * m[11]; + loadmodel->surfmesh.data_normal3f[j*3+0] += relativenormal[0] * m[0] + relativenormal[1] * m[1] + relativenormal[2] * m[ 2]; + loadmodel->surfmesh.data_normal3f[j*3+1] += relativenormal[0] * m[4] + relativenormal[1] * m[5] + relativenormal[2] * m[ 6]; + loadmodel->surfmesh.data_normal3f[j*3+2] += relativenormal[0] * m[8] + relativenormal[1] * m[9] + relativenormal[2] * m[10]; + if (!k) + { + // store the first (and often only) weight + weightinfluence[0] = influence; + weightindex[0] = boneindex; + } + else + { + // sort the new weight into this vertex's weight table + // (which only accepts up to 4 bones per vertex) + for (l = 0;l < 4;l++) + { + if (weightinfluence[l] < influence) + { + // move weaker influence weights out of the way first + int l2; + for (l2 = 3;l2 > l;l2--) + { + weightinfluence[l2] = weightinfluence[l2-1]; + weightindex[l2] = weightindex[l2-1]; + } + // store the new weight + weightinfluence[l] = influence; + weightindex[l] = boneindex; + break; + } + } + } + data += sizeof(dpmbonevert_t); + } + loadmodel->surfmesh.blends[j] = Mod_Skeletal_CompressBlend(loadmodel, weightindex, weightinfluence); + loadmodel->surfmesh.data_skeletalindex4ub[j*4 ] = weightindex[0]; + loadmodel->surfmesh.data_skeletalindex4ub[j*4+1] = weightindex[1]; + loadmodel->surfmesh.data_skeletalindex4ub[j*4+2] = weightindex[2]; + loadmodel->surfmesh.data_skeletalindex4ub[j*4+3] = weightindex[3]; + loadmodel->surfmesh.data_skeletalweight4ub[j*4 ] = (unsigned char)(weightinfluence[0]*255.0f); + loadmodel->surfmesh.data_skeletalweight4ub[j*4+1] = (unsigned char)(weightinfluence[1]*255.0f); + loadmodel->surfmesh.data_skeletalweight4ub[j*4+2] = (unsigned char)(weightinfluence[2]*255.0f); + loadmodel->surfmesh.data_skeletalweight4ub[j*4+3] = (unsigned char)(weightinfluence[3]*255.0f); + } + + // since dpm models do not have named sections, reuse their shader name as the section name + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, dpmmesh->shadername, dpmmesh->shadername); + + Mod_ValidateElements(loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3, surface->num_triangles, surface->num_firstvertex, surface->num_vertices, __FILE__, __LINE__); + } + if (loadmodel->surfmesh.num_blends < meshvertices) + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Realloc(loadmodel->mempool, loadmodel->surfmesh.data_blendweights, loadmodel->surfmesh.num_blends * sizeof(blendweights_t)); + Z_Free(bonepose); + Mod_FreeSkinFiles(skinfiles); + Mod_MakeSortedSurfaces(loadmodel); + + // compute all the mesh information that was not loaded from the file + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + Mod_BuildBaseBonePoses(); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); + if(mod_alias_force_animated.string[0]) + loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } + + // because shaders can do somewhat unexpected things, check for unusual features now + for (i = 0;i < loadmodel->num_textures;i++) + { + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) + mod->DrawSky = R_Q1BSP_DrawSky; + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + } +} + +// no idea why PSK/PSA files contain weird quaternions but they do... +#define PSKQUATNEGATIONS +void Mod_PSKMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, index, version, recordsize, numrecords, meshvertices, meshtriangles; + int numpnts, numvtxw, numfaces, nummatts, numbones, numrawweights, numanimbones, numanims, numanimkeys; + fs_offset_t filesize; + pskpnts_t *pnts; + pskvtxw_t *vtxw; + pskface_t *faces; + pskmatt_t *matts; + pskboneinfo_t *bones; + pskrawweights_t *rawweights; + //pskboneinfo_t *animbones; + pskaniminfo_t *anims; + pskanimkeys_t *animkeys; + void *animfilebuffer, *animbuffer, *animbufferend; + unsigned char *data; + pskchunk_t *pchunk; + skinfile_t *skinfiles; + char animname[MAX_QPATH]; + size_t size; + float biggestorigin; + + pchunk = (pskchunk_t *)buffer; + if (strcmp(pchunk->id, "ACTRHEAD")) + Host_Error ("Mod_PSKMODEL_Load: %s is not an Unreal Engine ActorX (.psk + .psa) model", loadmodel->name); + + loadmodel->modeldatatypestring = "PSK"; + + loadmodel->type = mod_alias; + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; + loadmodel->synctype = ST_RAND; + + FS_StripExtension(loadmodel->name, animname, sizeof(animname)); + strlcat(animname, ".psa", sizeof(animname)); + animbuffer = animfilebuffer = FS_LoadFile(animname, loadmodel->mempool, false, &filesize); + animbufferend = (void *)((unsigned char*)animbuffer + (int)filesize); + if (!animbuffer) + animbufferend = animbuffer; + + numpnts = 0; + pnts = NULL; + numvtxw = 0; + vtxw = NULL; + numfaces = 0; + faces = NULL; + nummatts = 0; + matts = NULL; + numbones = 0; + bones = NULL; + numrawweights = 0; + rawweights = NULL; + numanims = 0; + anims = NULL; + numanimkeys = 0; + animkeys = NULL; + + while (buffer < bufferend) + { + pchunk = (pskchunk_t *)buffer; + buffer = (void *)((unsigned char *)buffer + sizeof(pskchunk_t)); + version = LittleLong(pchunk->version); + recordsize = LittleLong(pchunk->recordsize); + numrecords = LittleLong(pchunk->numrecords); + if (developer_extra.integer) + Con_DPrintf("%s: %s %x: %i * %i = %i\n", loadmodel->name, pchunk->id, version, recordsize, numrecords, recordsize * numrecords); + if (version != 0x1e83b9 && version != 0x1e9179 && version != 0x2e && version != 0x12f2bc && version != 0x12f2f0) + Con_Printf ("%s: chunk %s has unknown version %x (0x1e83b9, 0x1e9179, 0x2e, 0x12f2bc, 0x12f2f0 are currently supported), trying to load anyway!\n", loadmodel->name, pchunk->id, version); + if (!strcmp(pchunk->id, "ACTRHEAD")) + { + // nothing to do + } + else if (!strcmp(pchunk->id, "PNTS0000")) + { + pskpnts_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numpnts = numrecords; + pnts = (pskpnts_t *)buffer; + for (index = 0, p = (pskpnts_t *)buffer;index < numrecords;index++, p++) + { + p->origin[0] = LittleFloat(p->origin[0]); + p->origin[1] = LittleFloat(p->origin[1]); + p->origin[2] = LittleFloat(p->origin[2]); + } + buffer = p; + } + else if (!strcmp(pchunk->id, "VTXW0000")) + { + pskvtxw_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numvtxw = numrecords; + vtxw = (pskvtxw_t *)buffer; + for (index = 0, p = (pskvtxw_t *)buffer;index < numrecords;index++, p++) + { + p->pntsindex = LittleShort(p->pntsindex); + p->texcoord[0] = LittleFloat(p->texcoord[0]); + p->texcoord[1] = LittleFloat(p->texcoord[1]); + if (p->pntsindex >= numpnts) + { + Con_Printf("%s: vtxw->pntsindex %i >= numpnts %i\n", loadmodel->name, p->pntsindex, numpnts); + p->pntsindex = 0; + } + } + buffer = p; + } + else if (!strcmp(pchunk->id, "FACE0000")) + { + pskface_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numfaces = numrecords; + faces = (pskface_t *)buffer; + for (index = 0, p = (pskface_t *)buffer;index < numrecords;index++, p++) + { + p->vtxwindex[0] = LittleShort(p->vtxwindex[0]); + p->vtxwindex[1] = LittleShort(p->vtxwindex[1]); + p->vtxwindex[2] = LittleShort(p->vtxwindex[2]); + p->group = LittleLong(p->group); + if (p->vtxwindex[0] >= numvtxw) + { + Con_Printf("%s: face->vtxwindex[0] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[0], numvtxw); + p->vtxwindex[0] = 0; + } + if (p->vtxwindex[1] >= numvtxw) + { + Con_Printf("%s: face->vtxwindex[1] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[1], numvtxw); + p->vtxwindex[1] = 0; + } + if (p->vtxwindex[2] >= numvtxw) + { + Con_Printf("%s: face->vtxwindex[2] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[2], numvtxw); + p->vtxwindex[2] = 0; + } + } + buffer = p; + } + else if (!strcmp(pchunk->id, "MATT0000")) + { + pskmatt_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + nummatts = numrecords; + matts = (pskmatt_t *)buffer; + for (index = 0, p = (pskmatt_t *)buffer;index < numrecords;index++, p++) + { + // nothing to do + } + buffer = p; + } + else if (!strcmp(pchunk->id, "REFSKELT")) + { + pskboneinfo_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numbones = numrecords; + bones = (pskboneinfo_t *)buffer; + for (index = 0, p = (pskboneinfo_t *)buffer;index < numrecords;index++, p++) + { + p->numchildren = LittleLong(p->numchildren); + p->parent = LittleLong(p->parent); + p->basepose.quat[0] = LittleFloat(p->basepose.quat[0]); + p->basepose.quat[1] = LittleFloat(p->basepose.quat[1]); + p->basepose.quat[2] = LittleFloat(p->basepose.quat[2]); + p->basepose.quat[3] = LittleFloat(p->basepose.quat[3]); + p->basepose.origin[0] = LittleFloat(p->basepose.origin[0]); + p->basepose.origin[1] = LittleFloat(p->basepose.origin[1]); + p->basepose.origin[2] = LittleFloat(p->basepose.origin[2]); + p->basepose.unknown = LittleFloat(p->basepose.unknown); + p->basepose.size[0] = LittleFloat(p->basepose.size[0]); + p->basepose.size[1] = LittleFloat(p->basepose.size[1]); + p->basepose.size[2] = LittleFloat(p->basepose.size[2]); +#ifdef PSKQUATNEGATIONS + if (index) + { + p->basepose.quat[0] *= -1; + p->basepose.quat[1] *= -1; + p->basepose.quat[2] *= -1; + } + else + { + p->basepose.quat[0] *= 1; + p->basepose.quat[1] *= -1; + p->basepose.quat[2] *= 1; + } +#endif + if (p->parent < 0 || p->parent >= numbones) + { + Con_Printf("%s: bone->parent %i >= numbones %i\n", loadmodel->name, p->parent, numbones); + p->parent = 0; + } + } + buffer = p; + } + else if (!strcmp(pchunk->id, "RAWWEIGHTS")) + { + pskrawweights_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); + // byteswap in place and keep the pointer + numrawweights = numrecords; + rawweights = (pskrawweights_t *)buffer; + for (index = 0, p = (pskrawweights_t *)buffer;index < numrecords;index++, p++) + { + p->weight = LittleFloat(p->weight); + p->pntsindex = LittleLong(p->pntsindex); + p->boneindex = LittleLong(p->boneindex); + if (p->pntsindex < 0 || p->pntsindex >= numpnts) + { + Con_Printf("%s: weight->pntsindex %i >= numpnts %i\n", loadmodel->name, p->pntsindex, numpnts); + p->pntsindex = 0; + } + if (p->boneindex < 0 || p->boneindex >= numbones) + { + Con_Printf("%s: weight->boneindex %i >= numbones %i\n", loadmodel->name, p->boneindex, numbones); + p->boneindex = 0; + } + } + buffer = p; + } + } + + while (animbuffer < animbufferend) + { + pchunk = (pskchunk_t *)animbuffer; + animbuffer = (void *)((unsigned char *)animbuffer + sizeof(pskchunk_t)); + version = LittleLong(pchunk->version); + recordsize = LittleLong(pchunk->recordsize); + numrecords = LittleLong(pchunk->numrecords); + if (developer_extra.integer) + Con_DPrintf("%s: %s %x: %i * %i = %i\n", animname, pchunk->id, version, recordsize, numrecords, recordsize * numrecords); + if (version != 0x1e83b9 && version != 0x1e9179 && version != 0x2e && version != 0x12f2bc && version != 0x12f2f0) + Con_Printf ("%s: chunk %s has unknown version %x (0x1e83b9, 0x1e9179, 0x2e, 0x12f2bc, 0x12f2f0 are currently supported), trying to load anyway!\n", animname, pchunk->id, version); + if (!strcmp(pchunk->id, "ANIMHEAD")) + { + // nothing to do + } + else if (!strcmp(pchunk->id, "BONENAMES")) + { + pskboneinfo_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); + // byteswap in place and keep the pointer + numanimbones = numrecords; + //animbones = (pskboneinfo_t *)animbuffer; + // NOTE: supposedly psa does not need to match the psk model, the + // bones missing from the psa would simply use their base + // positions from the psk, but this is hard for me to implement + // and people can easily make animations that match. + if (numanimbones != numbones) + Host_Error("%s: this loader only supports animations with the same bones as the mesh", loadmodel->name); + for (index = 0, p = (pskboneinfo_t *)animbuffer;index < numrecords;index++, p++) + { + p->numchildren = LittleLong(p->numchildren); + p->parent = LittleLong(p->parent); + p->basepose.quat[0] = LittleFloat(p->basepose.quat[0]); + p->basepose.quat[1] = LittleFloat(p->basepose.quat[1]); + p->basepose.quat[2] = LittleFloat(p->basepose.quat[2]); + p->basepose.quat[3] = LittleFloat(p->basepose.quat[3]); + p->basepose.origin[0] = LittleFloat(p->basepose.origin[0]); + p->basepose.origin[1] = LittleFloat(p->basepose.origin[1]); + p->basepose.origin[2] = LittleFloat(p->basepose.origin[2]); + p->basepose.unknown = LittleFloat(p->basepose.unknown); + p->basepose.size[0] = LittleFloat(p->basepose.size[0]); + p->basepose.size[1] = LittleFloat(p->basepose.size[1]); + p->basepose.size[2] = LittleFloat(p->basepose.size[2]); +#ifdef PSKQUATNEGATIONS + if (index) + { + p->basepose.quat[0] *= -1; + p->basepose.quat[1] *= -1; + p->basepose.quat[2] *= -1; + } + else + { + p->basepose.quat[0] *= 1; + p->basepose.quat[1] *= -1; + p->basepose.quat[2] *= 1; + } +#endif + if (p->parent < 0 || p->parent >= numanimbones) + { + Con_Printf("%s: bone->parent %i >= numanimbones %i\n", animname, p->parent, numanimbones); + p->parent = 0; + } + // check that bones are the same as in the base + if (strcmp(p->name, bones[index].name) || p->parent != bones[index].parent) + Host_Error("%s: this loader only supports animations with the same bones as the mesh", animname); + } + animbuffer = p; + } + else if (!strcmp(pchunk->id, "ANIMINFO")) + { + pskaniminfo_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); + // byteswap in place and keep the pointer + numanims = numrecords; + anims = (pskaniminfo_t *)animbuffer; + for (index = 0, p = (pskaniminfo_t *)animbuffer;index < numrecords;index++, p++) + { + p->numbones = LittleLong(p->numbones); + p->playtime = LittleFloat(p->playtime); + p->fps = LittleFloat(p->fps); + p->firstframe = LittleLong(p->firstframe); + p->numframes = LittleLong(p->numframes); + if (p->numbones != numbones) + Con_Printf("%s: animinfo->numbones != numbones, trying to load anyway!\n", animname); + } + animbuffer = p; + } + else if (!strcmp(pchunk->id, "ANIMKEYS")) + { + pskanimkeys_t *p; + if (recordsize != sizeof(*p)) + Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); + numanimkeys = numrecords; + animkeys = (pskanimkeys_t *)animbuffer; + for (index = 0, p = (pskanimkeys_t *)animbuffer;index < numrecords;index++, p++) + { + p->origin[0] = LittleFloat(p->origin[0]); + p->origin[1] = LittleFloat(p->origin[1]); + p->origin[2] = LittleFloat(p->origin[2]); + p->quat[0] = LittleFloat(p->quat[0]); + p->quat[1] = LittleFloat(p->quat[1]); + p->quat[2] = LittleFloat(p->quat[2]); + p->quat[3] = LittleFloat(p->quat[3]); + p->frametime = LittleFloat(p->frametime); +#ifdef PSKQUATNEGATIONS + if (index % numbones) + { + p->quat[0] *= -1; + p->quat[1] *= -1; + p->quat[2] *= -1; + } + else + { + p->quat[0] *= 1; + p->quat[1] *= -1; + p->quat[2] *= 1; + } +#endif + } + animbuffer = p; + // TODO: allocate bonepose stuff + } + else + Con_Printf("%s: unknown chunk ID \"%s\"\n", animname, pchunk->id); + } + + if (!numpnts || !pnts || !numvtxw || !vtxw || !numfaces || !faces || !nummatts || !matts || !numbones || !bones || !numrawweights || !rawweights) + Host_Error("%s: missing required chunks", loadmodel->name); + + if (numanims) + { + loadmodel->numframes = 0; + for (index = 0;index < numanims;index++) + loadmodel->numframes += anims[index].numframes; + if (numanimkeys != numbones * loadmodel->numframes) + Host_Error("%s: %s has incorrect number of animation keys", animname, pchunk->id); + } + else + loadmodel->numframes = loadmodel->num_poses = 1; + + meshvertices = numvtxw; + meshtriangles = numfaces; + + // load external .skin files if present + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + loadmodel->num_bones = numbones; + loadmodel->num_poses = loadmodel->numframes; + loadmodel->nummodelsurfaces = loadmodel->num_surfaces = nummatts; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + // do most allocations as one merged chunk + size = loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + loadmodel->surfmesh.num_triangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? loadmodel->surfmesh.num_triangles * sizeof(int[3]) : 0) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[2]) + loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]) + loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]) + loadmodel->surfmesh.num_vertices * sizeof(unsigned short) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t) + ((loadmodel->surfmesh.num_vertices <= 65536) ? (loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3])) : 0); + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, size); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.data_element3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_vertex3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[2]); + loadmodel->surfmesh.data_skeletalindex4ub = (unsigned char *)data;data += loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]); + loadmodel->surfmesh.data_skeletalweight4ub = (unsigned char *)data;data += loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]); + loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); + loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); + loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); + loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); + loadmodel->surfmesh.num_blends = 0; + loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); + if (loadmodel->surfmesh.num_vertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); + } + loadmodel->data_poses7s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]); + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(blendweights_t)); + + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // create surfaces + for (index = 0, i = 0;index < nummatts;index++) + { + // since psk models do not have named sections, reuse their shader name as the section name + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + index, skinfiles, matts[index].name, matts[index].name); + loadmodel->sortedmodelsurfaces[index] = index; + loadmodel->data_surfaces[index].texture = loadmodel->data_textures + index; + loadmodel->data_surfaces[index].num_firstvertex = 0; + loadmodel->data_surfaces[index].num_vertices = loadmodel->surfmesh.num_vertices; + } + + // copy over the vertex locations and texcoords + for (index = 0;index < numvtxw;index++) + { + loadmodel->surfmesh.data_vertex3f[index*3+0] = pnts[vtxw[index].pntsindex].origin[0]; + loadmodel->surfmesh.data_vertex3f[index*3+1] = pnts[vtxw[index].pntsindex].origin[1]; + loadmodel->surfmesh.data_vertex3f[index*3+2] = pnts[vtxw[index].pntsindex].origin[2]; + loadmodel->surfmesh.data_texcoordtexture2f[index*2+0] = vtxw[index].texcoord[0]; + loadmodel->surfmesh.data_texcoordtexture2f[index*2+1] = vtxw[index].texcoord[1]; + } + + // loading the faces is complicated because we need to sort them into surfaces by mattindex + for (index = 0;index < numfaces;index++) + loadmodel->data_surfaces[faces[index].mattindex].num_triangles++; + for (index = 0, i = 0;index < nummatts;index++) + { + loadmodel->data_surfaces[index].num_firsttriangle = i; + i += loadmodel->data_surfaces[index].num_triangles; + loadmodel->data_surfaces[index].num_triangles = 0; + } + for (index = 0;index < numfaces;index++) + { + i = (loadmodel->data_surfaces[faces[index].mattindex].num_firsttriangle + loadmodel->data_surfaces[faces[index].mattindex].num_triangles++)*3; + loadmodel->surfmesh.data_element3i[i+0] = faces[index].vtxwindex[0]; + loadmodel->surfmesh.data_element3i[i+1] = faces[index].vtxwindex[1]; + loadmodel->surfmesh.data_element3i[i+2] = faces[index].vtxwindex[2]; + } + + // copy over the bones + for (index = 0;index < numbones;index++) + { + strlcpy(loadmodel->data_bones[index].name, bones[index].name, sizeof(loadmodel->data_bones[index].name)); + loadmodel->data_bones[index].parent = (index || bones[index].parent > 0) ? bones[index].parent : -1; + if (loadmodel->data_bones[index].parent >= index) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, index, index); + } + + // convert the basepose data + if (loadmodel->num_bones) + { + int boneindex; + matrix4x4_t *basebonepose; + float *outinvmatrix = loadmodel->data_baseboneposeinverse; + matrix4x4_t bonematrix; + matrix4x4_t tempbonematrix; + basebonepose = (matrix4x4_t *)Mem_Alloc(tempmempool, loadmodel->num_bones * sizeof(matrix4x4_t)); + for (boneindex = 0;boneindex < loadmodel->num_bones;boneindex++) + { + Matrix4x4_FromOriginQuat(&bonematrix, bones[boneindex].basepose.origin[0], bones[boneindex].basepose.origin[1], bones[boneindex].basepose.origin[2], bones[boneindex].basepose.quat[0], bones[boneindex].basepose.quat[1], bones[boneindex].basepose.quat[2], bones[boneindex].basepose.quat[3]); + if (loadmodel->data_bones[boneindex].parent >= 0) + { + tempbonematrix = bonematrix; + Matrix4x4_Concat(&bonematrix, basebonepose + loadmodel->data_bones[boneindex].parent, &tempbonematrix); + } + basebonepose[boneindex] = bonematrix; + Matrix4x4_Invert_Simple(&tempbonematrix, basebonepose + boneindex); + Matrix4x4_ToArray12FloatD3D(&tempbonematrix, outinvmatrix + 12*boneindex); + } + Mem_Free(basebonepose); + } + + // sort the psk point weights into the vertex weight tables + // (which only accept up to 4 bones per vertex) + for (index = 0;index < numvtxw;index++) + { + int weightindex[4] = { 0, 0, 0, 0 }; + float weightinfluence[4] = { 0, 0, 0, 0 }; + int l; + for (j = 0;j < numrawweights;j++) + { + if (rawweights[j].pntsindex == vtxw[index].pntsindex) + { + int boneindex = rawweights[j].boneindex; + float influence = rawweights[j].weight; + for (l = 0;l < 4;l++) + { + if (weightinfluence[l] < influence) + { + // move lower influence weights out of the way first + int l2; + for (l2 = 3;l2 > l;l2--) + { + weightinfluence[l2] = weightinfluence[l2-1]; + weightindex[l2] = weightindex[l2-1]; + } + // store the new weight + weightinfluence[l] = influence; + weightindex[l] = boneindex; + break; + } + } + } + } + loadmodel->surfmesh.blends[index] = Mod_Skeletal_CompressBlend(loadmodel, weightindex, weightinfluence); + loadmodel->surfmesh.data_skeletalindex4ub[index*4 ] = weightindex[0]; + loadmodel->surfmesh.data_skeletalindex4ub[index*4+1] = weightindex[1]; + loadmodel->surfmesh.data_skeletalindex4ub[index*4+2] = weightindex[2]; + loadmodel->surfmesh.data_skeletalindex4ub[index*4+3] = weightindex[3]; + loadmodel->surfmesh.data_skeletalweight4ub[index*4 ] = (unsigned char)(weightinfluence[0]*255.0f); + loadmodel->surfmesh.data_skeletalweight4ub[index*4+1] = (unsigned char)(weightinfluence[1]*255.0f); + loadmodel->surfmesh.data_skeletalweight4ub[index*4+2] = (unsigned char)(weightinfluence[2]*255.0f); + loadmodel->surfmesh.data_skeletalweight4ub[index*4+3] = (unsigned char)(weightinfluence[3]*255.0f); + } + if (loadmodel->surfmesh.num_blends < loadmodel->surfmesh.num_vertices) + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Realloc(loadmodel->mempool, loadmodel->surfmesh.data_blendweights, loadmodel->surfmesh.num_blends * sizeof(blendweights_t)); + + // set up the animscenes based on the anims + if (numanims) + { + for (index = 0, i = 0;index < numanims;index++) + { + for (j = 0;j < anims[index].numframes;j++, i++) + { + dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "%s_%d", anims[index].name, j); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].loop = true; + loadmodel->animscenes[i].framerate = anims[index].fps; + } + } + // calculate the scaling value for bone origins so they can be compressed to short + biggestorigin = 0; + for (index = 0;index < numanimkeys;index++) + { + pskanimkeys_t *k = animkeys + index; + biggestorigin = max(biggestorigin, fabs(k->origin[0])); + biggestorigin = max(biggestorigin, fabs(k->origin[1])); + biggestorigin = max(biggestorigin, fabs(k->origin[2])); + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + + // load the poses from the animkeys + for (index = 0;index < numanimkeys;index++) + { + pskanimkeys_t *k = animkeys + index; + float quat[4]; + Vector4Copy(k->quat, quat); + if (quat[3] > 0) + Vector4Negate(quat, quat); + Vector4Normalize2(quat, quat); + // compress poses to the short[7] format for longterm storage + loadmodel->data_poses7s[index*7+0] = k->origin[0] * loadmodel->num_poseinvscale; + loadmodel->data_poses7s[index*7+1] = k->origin[1] * loadmodel->num_poseinvscale; + loadmodel->data_poses7s[index*7+2] = k->origin[2] * loadmodel->num_poseinvscale; + loadmodel->data_poses7s[index*7+3] = quat[0] * 32767.0f; + loadmodel->data_poses7s[index*7+4] = quat[1] * 32767.0f; + loadmodel->data_poses7s[index*7+5] = quat[2] * 32767.0f; + loadmodel->data_poses7s[index*7+6] = quat[3] * 32767.0f; + } + } + else + { + strlcpy(loadmodel->animscenes[0].name, "base", sizeof(loadmodel->animscenes[0].name)); + loadmodel->animscenes[0].firstframe = 0; + loadmodel->animscenes[0].framecount = 1; + loadmodel->animscenes[0].loop = true; + loadmodel->animscenes[0].framerate = 10; + + // calculate the scaling value for bone origins so they can be compressed to short + biggestorigin = 0; + for (index = 0;index < numbones;index++) + { + pskboneinfo_t *p = bones + index; + biggestorigin = max(biggestorigin, fabs(p->basepose.origin[0])); + biggestorigin = max(biggestorigin, fabs(p->basepose.origin[1])); + biggestorigin = max(biggestorigin, fabs(p->basepose.origin[2])); + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + + // load the basepose as a frame + for (index = 0;index < numbones;index++) + { + pskboneinfo_t *p = bones + index; + float quat[4]; + Vector4Copy(p->basepose.quat, quat); + if (quat[3] > 0) + Vector4Negate(quat, quat); + Vector4Normalize2(quat, quat); + // compress poses to the short[7] format for longterm storage + loadmodel->data_poses7s[index*7+0] = p->basepose.origin[0] * loadmodel->num_poseinvscale; + loadmodel->data_poses7s[index*7+1] = p->basepose.origin[1] * loadmodel->num_poseinvscale; + loadmodel->data_poses7s[index*7+2] = p->basepose.origin[2] * loadmodel->num_poseinvscale; + loadmodel->data_poses7s[index*7+3] = quat[0] * 32767.0f; + loadmodel->data_poses7s[index*7+4] = quat[1] * 32767.0f; + loadmodel->data_poses7s[index*7+5] = quat[2] * 32767.0f; + loadmodel->data_poses7s[index*7+6] = quat[3] * 32767.0f; + } + } + + Mod_FreeSkinFiles(skinfiles); + if (animfilebuffer) + Mem_Free(animfilebuffer); + Mod_MakeSortedSurfaces(loadmodel); + + // compute all the mesh information that was not loaded from the file + // TODO: honor smoothing groups somehow? + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); + Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); + if(mod_alias_force_animated.string[0]) + loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; + + if (!loadmodel->surfmesh.isanimated) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } + + // because shaders can do somewhat unexpected things, check for unusual features now + for (i = 0;i < loadmodel->num_textures;i++) + { + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) + mod->DrawSky = R_Q1BSP_DrawSky; + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + } +} + +void Mod_INTERQUAKEMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + unsigned char *data; + const char *text; + const unsigned char *pbase, *pend; + iqmheader_t header; + skinfile_t *skinfiles; + int i, j, k, meshvertices, meshtriangles; + float biggestorigin; + const unsigned int *inelements; + int *outelements; + const int *inneighbors; + int *outneighbors; + float *outvertex, *outnormal, *outtexcoord, *outsvector, *outtvector, *outcolor; + // this pointers into the file data are read only through Little* functions so they can be unaligned memory + const float *vnormal = NULL; + const float *vposition = NULL; + const float *vtangent = NULL; + const float *vtexcoord = NULL; + const float *vcolor4f = NULL; + const unsigned char *vblendindexes = NULL; + const unsigned char *vblendweights = NULL; + const unsigned char *vcolor4ub = NULL; + const unsigned short *framedata = NULL; + // temporary memory allocations (because the data in the file may be misaligned) + iqmanim_t *anims = NULL; + iqmbounds_t *bounds = NULL; + iqmjoint1_t *joint1 = NULL; + iqmjoint_t *joint = NULL; + iqmmesh_t *meshes = NULL; + iqmpose1_t *pose1 = NULL; + iqmpose_t *pose = NULL; + iqmvertexarray_t *vas = NULL; + + pbase = (unsigned char *)buffer; + pend = (unsigned char *)bufferend; + + if (pbase + sizeof(iqmheader_t) > pend) + Host_Error ("Mod_INTERQUAKEMODEL_Load: %s is not an Inter-Quake Model %d", loadmodel->name, (int)(pend - pbase)); + + // copy struct (otherwise it may be misaligned) + // LordHavoc: okay it's definitely not misaligned here, but for consistency... + memcpy(&header, pbase, sizeof(iqmheader_t)); + + if (memcmp(header.id, "INTERQUAKEMODEL", 16)) + Host_Error ("Mod_INTERQUAKEMODEL_Load: %s is not an Inter-Quake Model", loadmodel->name); + if (LittleLong(header.version) != 1 && LittleLong(header.version) != 2) + Host_Error ("Mod_INTERQUAKEMODEL_Load: only version 1 and 2 models are currently supported (name = %s)", loadmodel->name); + + loadmodel->modeldatatypestring = "IQM"; + + loadmodel->type = mod_alias; + loadmodel->synctype = ST_RAND; + + // byteswap header + header.version = LittleLong(header.version); + header.filesize = LittleLong(header.filesize); + header.flags = LittleLong(header.flags); + header.num_text = LittleLong(header.num_text); + header.ofs_text = LittleLong(header.ofs_text); + header.num_meshes = LittleLong(header.num_meshes); + header.ofs_meshes = LittleLong(header.ofs_meshes); + header.num_vertexarrays = LittleLong(header.num_vertexarrays); + header.num_vertexes = LittleLong(header.num_vertexes); + header.ofs_vertexarrays = LittleLong(header.ofs_vertexarrays); + header.num_triangles = LittleLong(header.num_triangles); + header.ofs_triangles = LittleLong(header.ofs_triangles); + header.ofs_neighbors = LittleLong(header.ofs_neighbors); + header.num_joints = LittleLong(header.num_joints); + header.ofs_joints = LittleLong(header.ofs_joints); + header.num_poses = LittleLong(header.num_poses); + header.ofs_poses = LittleLong(header.ofs_poses); + header.num_anims = LittleLong(header.num_anims); + header.ofs_anims = LittleLong(header.ofs_anims); + header.num_frames = LittleLong(header.num_frames); + header.num_framechannels = LittleLong(header.num_framechannels); + header.ofs_frames = LittleLong(header.ofs_frames); + header.ofs_bounds = LittleLong(header.ofs_bounds); + header.num_comment = LittleLong(header.num_comment); + header.ofs_comment = LittleLong(header.ofs_comment); + header.num_extensions = LittleLong(header.num_extensions); + header.ofs_extensions = LittleLong(header.ofs_extensions); + + if (header.version == 1) + { + if (pbase + header.ofs_joints + header.num_joints*sizeof(iqmjoint1_t) > pend || + pbase + header.ofs_poses + header.num_poses*sizeof(iqmpose1_t) > pend) + { + Con_Printf("%s has invalid size or offset information\n", loadmodel->name); + return; + } + } + else + { + if (pbase + header.ofs_joints + header.num_joints*sizeof(iqmjoint_t) > pend || + pbase + header.ofs_poses + header.num_poses*sizeof(iqmpose_t) > pend) + { + Con_Printf("%s has invalid size or offset information\n", loadmodel->name); + return; + } + } + if (pbase + header.ofs_text + header.num_text > pend || + pbase + header.ofs_meshes + header.num_meshes*sizeof(iqmmesh_t) > pend || + pbase + header.ofs_vertexarrays + header.num_vertexarrays*sizeof(iqmvertexarray_t) > pend || + pbase + header.ofs_triangles + header.num_triangles*sizeof(int[3]) > pend || + (header.ofs_neighbors && pbase + header.ofs_neighbors + header.num_triangles*sizeof(int[3]) > pend) || + pbase + header.ofs_anims + header.num_anims*sizeof(iqmanim_t) > pend || + pbase + header.ofs_frames + header.num_frames*header.num_framechannels*sizeof(unsigned short) > pend || + (header.ofs_bounds && pbase + header.ofs_bounds + header.num_frames*sizeof(iqmbounds_t) > pend) || + pbase + header.ofs_comment + header.num_comment > pend) + { + Con_Printf("%s has invalid size or offset information\n", loadmodel->name); + return; + } + + // copy structs to make them aligned in memory (otherwise we crash on Sparc and PowerPC and others) + if (header.num_vertexarrays) + vas = (iqmvertexarray_t *)(pbase + header.ofs_vertexarrays); + if (header.num_anims) + anims = (iqmanim_t *)(pbase + header.ofs_anims); + if (header.ofs_bounds) + bounds = (iqmbounds_t *)(pbase + header.ofs_bounds); + if (header.num_meshes) + meshes = (iqmmesh_t *)(pbase + header.ofs_meshes); + + for (i = 0;i < (int)header.num_vertexarrays;i++) + { + iqmvertexarray_t va; + size_t vsize; + va.type = LittleLong(vas[i].type); + va.flags = LittleLong(vas[i].flags); + va.format = LittleLong(vas[i].format); + va.size = LittleLong(vas[i].size); + va.offset = LittleLong(vas[i].offset); + vsize = header.num_vertexes*va.size; + switch (va.format) + { + case IQM_FLOAT: vsize *= sizeof(float); break; + case IQM_UBYTE: vsize *= sizeof(unsigned char); break; + default: continue; + } + if (pbase + va.offset + vsize > pend) + continue; + // no need to copy the vertex data for alignment because LittleLong/LittleShort will be invoked on reading them, and the destination is aligned + switch (va.type) + { + case IQM_POSITION: + if (va.format == IQM_FLOAT && va.size == 3) + vposition = (const float *)(pbase + va.offset); + break; + case IQM_TEXCOORD: + if (va.format == IQM_FLOAT && va.size == 2) + vtexcoord = (const float *)(pbase + va.offset); + break; + case IQM_NORMAL: + if (va.format == IQM_FLOAT && va.size == 3) + vnormal = (const float *)(pbase + va.offset); + break; + case IQM_TANGENT: + if (va.format == IQM_FLOAT && va.size == 4) + vtangent = (const float *)(pbase + va.offset); + break; + case IQM_BLENDINDEXES: + if (va.format == IQM_UBYTE && va.size == 4) + vblendindexes = (const unsigned char *)(pbase + va.offset); + break; + case IQM_BLENDWEIGHTS: + if (va.format == IQM_UBYTE && va.size == 4) + vblendweights = (const unsigned char *)(pbase + va.offset); + break; + case IQM_COLOR: + if (va.format == IQM_FLOAT && va.size == 4) + vcolor4f = (const float *)(pbase + va.offset); + if (va.format == IQM_UBYTE && va.size == 4) + vcolor4ub = (const unsigned char *)(pbase + va.offset); + break; + } + } + if (header.num_vertexes > 0 && (!vposition || !vtexcoord || ((header.num_frames > 0 || header.num_anims > 0) && (!vblendindexes || !vblendweights)))) + { + Con_Printf("%s is missing vertex array data\n", loadmodel->name); + return; + } + + text = header.num_text && header.ofs_text ? (const char *)(pbase + header.ofs_text) : ""; + + loadmodel->DrawSky = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; + loadmodel->PointSuperContents = NULL; + loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; + + // load external .skin files if present + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + loadmodel->numframes = max(header.num_anims, 1); + loadmodel->num_bones = header.num_joints; + loadmodel->num_poses = max(header.num_frames, 1); + loadmodel->nummodelsurfaces = loadmodel->num_surfaces = header.num_meshes; + loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; + loadmodel->num_texturesperskin = loadmodel->num_surfaces; + + meshvertices = header.num_vertexes; + meshtriangles = header.num_triangles; + + // do most allocations as one merged chunk + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + meshvertices * (sizeof(float[14]) + (vcolor4f || vcolor4ub ? sizeof(float[4]) : 0)) + (vblendindexes && vblendweights ? meshvertices * (sizeof(unsigned short) + sizeof(unsigned char[2][4])) : 0) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t)); + loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = meshvertices; + loadmodel->surfmesh.num_triangles = meshtriangles; + loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); + if (r_enableshadowvolumes.integer) + { + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); + } + loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); + if (vcolor4f || vcolor4ub) + { + loadmodel->surfmesh.data_lightmapcolor4f = (float *)data;data += meshvertices * sizeof(float[4]); + } + if (vblendindexes && vblendweights) + { + loadmodel->surfmesh.data_skeletalindex4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); + loadmodel->surfmesh.data_skeletalweight4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); + } + loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); + loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); + loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); + loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); + if (vblendindexes && vblendweights) + { + loadmodel->surfmesh.num_blends = 0; + loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); + } + if (meshvertices <= 65536) + { + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); + } + loadmodel->data_poses7s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]); + if (vblendindexes && vblendweights) + loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, meshvertices * sizeof(blendweights_t)); + + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + // load the bone info + if (header.version == 1) + { + iqmjoint1_t *injoint1 = (iqmjoint1_t *)(pbase + header.ofs_joints); + if (loadmodel->num_bones) + joint1 = (iqmjoint1_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_bones * sizeof(iqmjoint1_t)); + for (i = 0;i < loadmodel->num_bones;i++) + { + matrix4x4_t relbase, relinvbase, pinvbase, invbase; + joint1[i].name = LittleLong(injoint1[i].name); + joint1[i].parent = LittleLong(injoint1[i].parent); + for (j = 0;j < 3;j++) + { + joint1[i].origin[j] = LittleFloat(injoint1[i].origin[j]); + joint1[i].rotation[j] = LittleFloat(injoint1[i].rotation[j]); + joint1[i].scale[j] = LittleFloat(injoint1[i].scale[j]); + } + strlcpy(loadmodel->data_bones[i].name, &text[joint1[i].name], sizeof(loadmodel->data_bones[i].name)); + loadmodel->data_bones[i].parent = joint1[i].parent; + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); + Matrix4x4_FromDoom3Joint(&relbase, joint1[i].origin[0], joint1[i].origin[1], joint1[i].origin[2], joint1[i].rotation[0], joint1[i].rotation[1], joint1[i].rotation[2]); + Matrix4x4_Invert_Simple(&relinvbase, &relbase); + if (loadmodel->data_bones[i].parent >= 0) + { + Matrix4x4_FromArray12FloatD3D(&pinvbase, loadmodel->data_baseboneposeinverse + 12*loadmodel->data_bones[i].parent); + Matrix4x4_Concat(&invbase, &relinvbase, &pinvbase); + Matrix4x4_ToArray12FloatD3D(&invbase, loadmodel->data_baseboneposeinverse + 12*i); + } + else Matrix4x4_ToArray12FloatD3D(&relinvbase, loadmodel->data_baseboneposeinverse + 12*i); + } + } + else + { + iqmjoint_t *injoint = (iqmjoint_t *)(pbase + header.ofs_joints); + if (header.num_joints) + joint = (iqmjoint_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_bones * sizeof(iqmjoint_t)); + for (i = 0;i < loadmodel->num_bones;i++) + { + matrix4x4_t relbase, relinvbase, pinvbase, invbase; + joint[i].name = LittleLong(injoint[i].name); + joint[i].parent = LittleLong(injoint[i].parent); + for (j = 0;j < 3;j++) + { + joint[i].origin[j] = LittleFloat(injoint[i].origin[j]); + joint[i].rotation[j] = LittleFloat(injoint[i].rotation[j]); + joint[i].scale[j] = LittleFloat(injoint[i].scale[j]); + } + joint[i].rotation[3] = LittleFloat(injoint[i].rotation[3]); + strlcpy(loadmodel->data_bones[i].name, &text[joint[i].name], sizeof(loadmodel->data_bones[i].name)); + loadmodel->data_bones[i].parent = joint[i].parent; + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); + if (joint[i].rotation[3] > 0) + Vector4Negate(joint[i].rotation, joint[i].rotation); + Vector4Normalize2(joint[i].rotation, joint[i].rotation); + Matrix4x4_FromDoom3Joint(&relbase, joint[i].origin[0], joint[i].origin[1], joint[i].origin[2], joint[i].rotation[0], joint[i].rotation[1], joint[i].rotation[2]); + Matrix4x4_Invert_Simple(&relinvbase, &relbase); + if (loadmodel->data_bones[i].parent >= 0) + { + Matrix4x4_FromArray12FloatD3D(&pinvbase, loadmodel->data_baseboneposeinverse + 12*loadmodel->data_bones[i].parent); + Matrix4x4_Concat(&invbase, &relinvbase, &pinvbase); + Matrix4x4_ToArray12FloatD3D(&invbase, loadmodel->data_baseboneposeinverse + 12*i); + } + else Matrix4x4_ToArray12FloatD3D(&relinvbase, loadmodel->data_baseboneposeinverse + 12*i); + } + } + + // set up the animscenes based on the anims + for (i = 0;i < (int)header.num_anims;i++) + { + iqmanim_t anim; + anim.name = LittleLong(anims[i].name); + anim.first_frame = LittleLong(anims[i].first_frame); + anim.num_frames = LittleLong(anims[i].num_frames); + anim.framerate = LittleFloat(anims[i].framerate); + anim.flags = LittleLong(anims[i].flags); + strlcpy(loadmodel->animscenes[i].name, &text[anim.name], sizeof(loadmodel->animscenes[i].name)); + loadmodel->animscenes[i].firstframe = anim.first_frame; + loadmodel->animscenes[i].framecount = anim.num_frames; + loadmodel->animscenes[i].loop = ((anim.flags & IQM_LOOP) != 0); + loadmodel->animscenes[i].framerate = anim.framerate; + } + if (header.num_anims <= 0) + { + strlcpy(loadmodel->animscenes[0].name, "static", sizeof(loadmodel->animscenes[0].name)); + loadmodel->animscenes[0].firstframe = 0; + loadmodel->animscenes[0].framecount = 1; + loadmodel->animscenes[0].loop = true; + loadmodel->animscenes[0].framerate = 10; + } + + loadmodel->surfmesh.isanimated = loadmodel->num_bones > 1 || loadmodel->numframes > 1 || (loadmodel->animscenes && loadmodel->animscenes[0].framecount > 1); + if(mod_alias_force_animated.string[0]) + loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; + + biggestorigin = 0; + if (header.version == 1) + { + iqmpose1_t *inpose1 = (iqmpose1_t *)(pbase + header.ofs_poses); + if (header.num_poses) + pose1 = (iqmpose1_t *)Mem_Alloc(loadmodel->mempool, header.num_poses * sizeof(iqmpose1_t)); + for (i = 0;i < (int)header.num_poses;i++) + { + float f; + pose1[i].parent = LittleLong(inpose1[i].parent); + pose1[i].channelmask = LittleLong(inpose1[i].channelmask); + for (j = 0;j < 9;j++) + { + pose1[i].channeloffset[j] = LittleFloat(inpose1[i].channeloffset[j]); + pose1[i].channelscale[j] = LittleFloat(inpose1[i].channelscale[j]); + } + f = fabs(pose1[i].channeloffset[0]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[1]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[2]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[0] + 0xFFFF*pose1[i].channelscale[0]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[1] + 0xFFFF*pose1[i].channelscale[1]); biggestorigin = max(biggestorigin, f); + f = fabs(pose1[i].channeloffset[2] + 0xFFFF*pose1[i].channelscale[2]); biggestorigin = max(biggestorigin, f); + } + if (header.num_frames <= 0) + { + for (i = 0;i < loadmodel->num_bones;i++) + { + float f; + f = fabs(joint1[i].origin[0]); biggestorigin = max(biggestorigin, f); + f = fabs(joint1[i].origin[1]); biggestorigin = max(biggestorigin, f); + f = fabs(joint1[i].origin[2]); biggestorigin = max(biggestorigin, f); + } + } + } + else + { + iqmpose_t *inpose = (iqmpose_t *)(pbase + header.ofs_poses); + if (header.num_poses) + pose = (iqmpose_t *)Mem_Alloc(loadmodel->mempool, header.num_poses * sizeof(iqmpose_t)); + for (i = 0;i < (int)header.num_poses;i++) + { + float f; + pose[i].parent = LittleLong(inpose[i].parent); + pose[i].channelmask = LittleLong(inpose[i].channelmask); + for (j = 0;j < 10;j++) + { + pose[i].channeloffset[j] = LittleFloat(inpose[i].channeloffset[j]); + pose[i].channelscale[j] = LittleFloat(inpose[i].channelscale[j]); + } + f = fabs(pose[i].channeloffset[0]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[1]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[2]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[0] + 0xFFFF*pose[i].channelscale[0]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[1] + 0xFFFF*pose[i].channelscale[1]); biggestorigin = max(biggestorigin, f); + f = fabs(pose[i].channeloffset[2] + 0xFFFF*pose[i].channelscale[2]); biggestorigin = max(biggestorigin, f); + } + if (header.num_frames <= 0) + { + for (i = 0;i < loadmodel->num_bones;i++) + { + float f; + f = fabs(joint[i].origin[0]); biggestorigin = max(biggestorigin, f); + f = fabs(joint[i].origin[1]); biggestorigin = max(biggestorigin, f); + f = fabs(joint[i].origin[2]); biggestorigin = max(biggestorigin, f); + } + } + } + loadmodel->num_posescale = biggestorigin / 32767.0f; + loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; + + // load the pose data + // this unaligned memory access is safe (LittleShort reads as bytes) + framedata = (const unsigned short *)(pbase + header.ofs_frames); + if (header.version == 1) + { + for (i = 0, k = 0;i < (int)header.num_frames;i++) + { + for (j = 0;j < (int)header.num_poses;j++, k++) + { + float qx, qy, qz, qw; + loadmodel->data_poses7s[k*7 + 0] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[0] + (pose1[j].channelmask&1 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[0] : 0)); + loadmodel->data_poses7s[k*7 + 1] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[1] + (pose1[j].channelmask&2 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[1] : 0)); + loadmodel->data_poses7s[k*7 + 2] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[2] + (pose1[j].channelmask&4 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[2] : 0)); + qx = pose1[j].channeloffset[3] + (pose1[j].channelmask&8 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[3] : 0); + qy = pose1[j].channeloffset[4] + (pose1[j].channelmask&16 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[4] : 0); + qz = pose1[j].channeloffset[5] + (pose1[j].channelmask&32 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[5] : 0); + qw = 1.0f - (qx*qx + qy*qy + qz*qz); + qw = qw > 0.0f ? -sqrt(qw) : 0.0f; + loadmodel->data_poses7s[k*7 + 3] = 32767.0f * qx; + loadmodel->data_poses7s[k*7 + 4] = 32767.0f * qy; + loadmodel->data_poses7s[k*7 + 5] = 32767.0f * qz; + loadmodel->data_poses7s[k*7 + 6] = 32767.0f * qw; + // skip scale data for now + if(pose1[j].channelmask&64) framedata++; + if(pose1[j].channelmask&128) framedata++; + if(pose1[j].channelmask&256) framedata++; + } + } + if (header.num_frames <= 0) + { + for (i = 0;i < loadmodel->num_bones;i++) + { + float qx, qy, qz, qw; + loadmodel->data_poses7s[i*7 + 0] = loadmodel->num_poseinvscale * joint1[i].origin[0]; + loadmodel->data_poses7s[i*7 + 1] = loadmodel->num_poseinvscale * joint1[i].origin[1]; + loadmodel->data_poses7s[i*7 + 2] = loadmodel->num_poseinvscale * joint1[i].origin[2]; + qx = joint1[i].rotation[0]; + qy = joint1[i].rotation[1]; + qz = joint1[i].rotation[2]; + qw = 1.0f - (qx*qx + qy*qy + qz*qz); + qw = qw > 0.0f ? -sqrt(qw) : 0.0f; + loadmodel->data_poses7s[i*7 + 3] = 32767.0f * qx; + loadmodel->data_poses7s[i*7 + 4] = 32767.0f * qy; + loadmodel->data_poses7s[i*7 + 5] = 32767.0f * qz; + loadmodel->data_poses7s[i*7 + 6] = 32767.0f * qw; + } + } + } + else + { + for (i = 0, k = 0;i < (int)header.num_frames;i++) + { + for (j = 0;j < (int)header.num_poses;j++, k++) + { + float rot[4]; + loadmodel->data_poses7s[k*7 + 0] = loadmodel->num_poseinvscale * (pose[j].channeloffset[0] + (pose[j].channelmask&1 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[0] : 0)); + loadmodel->data_poses7s[k*7 + 1] = loadmodel->num_poseinvscale * (pose[j].channeloffset[1] + (pose[j].channelmask&2 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[1] : 0)); + loadmodel->data_poses7s[k*7 + 2] = loadmodel->num_poseinvscale * (pose[j].channeloffset[2] + (pose[j].channelmask&4 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[2] : 0)); + rot[0] = pose[j].channeloffset[3] + (pose[j].channelmask&8 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[3] : 0); + rot[1] = pose[j].channeloffset[4] + (pose[j].channelmask&16 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[4] : 0); + rot[2] = pose[j].channeloffset[5] + (pose[j].channelmask&32 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[5] : 0); + rot[3] = pose[j].channeloffset[6] + (pose[j].channelmask&64 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[6] : 0); + if (rot[3] > 0) + Vector4Negate(rot, rot); + Vector4Normalize2(rot, rot); + loadmodel->data_poses7s[k*7 + 3] = 32767.0f * rot[0]; + loadmodel->data_poses7s[k*7 + 4] = 32767.0f * rot[1]; + loadmodel->data_poses7s[k*7 + 5] = 32767.0f * rot[2]; + loadmodel->data_poses7s[k*7 + 6] = 32767.0f * rot[3]; + // skip scale data for now + if(pose[j].channelmask&128) framedata++; + if(pose[j].channelmask&256) framedata++; + if(pose[j].channelmask&512) framedata++; + } + } + if (header.num_frames <= 0) + { + for (i = 0;i < loadmodel->num_bones;i++) + { + loadmodel->data_poses7s[i*7 + 0] = loadmodel->num_poseinvscale * joint[i].origin[0]; + loadmodel->data_poses7s[i*7 + 1] = loadmodel->num_poseinvscale * joint[i].origin[1]; + loadmodel->data_poses7s[i*7 + 2] = loadmodel->num_poseinvscale * joint[i].origin[2]; + loadmodel->data_poses7s[i*7 + 3] = 32767.0f * joint[i].rotation[0]; + loadmodel->data_poses7s[i*7 + 4] = 32767.0f * joint[i].rotation[1]; + loadmodel->data_poses7s[i*7 + 5] = 32767.0f * joint[i].rotation[2]; + loadmodel->data_poses7s[i*7 + 6] = 32767.0f * joint[i].rotation[3]; + } + } + } + + // load bounding box data + if (header.ofs_bounds) + { + float xyradius = 0, radius = 0; + VectorClear(loadmodel->normalmins); + VectorClear(loadmodel->normalmaxs); + for (i = 0; i < (int)header.num_frames;i++) + { + iqmbounds_t bound; + bound.mins[0] = LittleFloat(bounds[i].mins[0]); + bound.mins[1] = LittleFloat(bounds[i].mins[1]); + bound.mins[2] = LittleFloat(bounds[i].mins[2]); + bound.maxs[0] = LittleFloat(bounds[i].maxs[0]); + bound.maxs[1] = LittleFloat(bounds[i].maxs[1]); + bound.maxs[2] = LittleFloat(bounds[i].maxs[2]); + bound.xyradius = LittleFloat(bounds[i].xyradius); + bound.radius = LittleFloat(bounds[i].radius); + if (!i) + { + VectorCopy(bound.mins, loadmodel->normalmins); + VectorCopy(bound.maxs, loadmodel->normalmaxs); + } + else + { + if (loadmodel->normalmins[0] > bound.mins[0]) loadmodel->normalmins[0] = bound.mins[0]; + if (loadmodel->normalmins[1] > bound.mins[1]) loadmodel->normalmins[1] = bound.mins[1]; + if (loadmodel->normalmins[2] > bound.mins[2]) loadmodel->normalmins[2] = bound.mins[2]; + if (loadmodel->normalmaxs[0] < bound.maxs[0]) loadmodel->normalmaxs[0] = bound.maxs[0]; + if (loadmodel->normalmaxs[1] < bound.maxs[1]) loadmodel->normalmaxs[1] = bound.maxs[1]; + if (loadmodel->normalmaxs[2] < bound.maxs[2]) loadmodel->normalmaxs[2] = bound.maxs[2]; + } + if (bound.xyradius > xyradius) + xyradius = bound.xyradius; + if (bound.radius > radius) + radius = bound.radius; + } + loadmodel->yawmins[0] = loadmodel->yawmins[1] = -xyradius; + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = xyradius; + loadmodel->yawmins[2] = loadmodel->normalmins[2]; + loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; + loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -radius; + loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = radius; + loadmodel->radius = radius; + loadmodel->radius2 = radius * radius; + } + + // load triangle data + // this unaligned memory access is safe (LittleLong reads as bytes) + inelements = (const unsigned int *)(pbase + header.ofs_triangles); + outelements = loadmodel->surfmesh.data_element3i; + for (i = 0;i < (int)header.num_triangles;i++) + { + outelements[0] = LittleLong(inelements[0]); + outelements[1] = LittleLong(inelements[1]); + outelements[2] = LittleLong(inelements[2]); + outelements += 3; + inelements += 3; + } + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, header.num_vertexes, __FILE__, __LINE__); + + if (header.ofs_neighbors && loadmodel->surfmesh.data_neighbor3i) + { + // this unaligned memory access is safe (LittleLong reads as bytes) + inneighbors = (const int *)(pbase + header.ofs_neighbors); + outneighbors = loadmodel->surfmesh.data_neighbor3i; + for (i = 0;i < (int)header.num_triangles;i++) + { + outneighbors[0] = LittleLong(inneighbors[0]); + outneighbors[1] = LittleLong(inneighbors[1]); + outneighbors[2] = LittleLong(inneighbors[2]); + outneighbors += 3; + inneighbors += 3; + } + } + + // load vertex data + // this unaligned memory access is safe (LittleFloat reads as bytes) + outvertex = loadmodel->surfmesh.data_vertex3f; + for (i = 0;i < (int)header.num_vertexes;i++) + { + outvertex[0] = LittleFloat(vposition[0]); + outvertex[1] = LittleFloat(vposition[1]); + outvertex[2] = LittleFloat(vposition[2]); + vposition += 3; + outvertex += 3; + } + + outtexcoord = loadmodel->surfmesh.data_texcoordtexture2f; + // this unaligned memory access is safe (LittleFloat reads as bytes) + for (i = 0;i < (int)header.num_vertexes;i++) + { + outtexcoord[0] = LittleFloat(vtexcoord[0]); + outtexcoord[1] = LittleFloat(vtexcoord[1]); + vtexcoord += 2; + outtexcoord += 2; + } + + // this unaligned memory access is safe (LittleFloat reads as bytes) + if(vnormal) + { + outnormal = loadmodel->surfmesh.data_normal3f; + for (i = 0;i < (int)header.num_vertexes;i++) + { + outnormal[0] = LittleFloat(vnormal[0]); + outnormal[1] = LittleFloat(vnormal[1]); + outnormal[2] = LittleFloat(vnormal[2]); + vnormal += 3; + outnormal += 3; + } + } + + // this unaligned memory access is safe (LittleFloat reads as bytes) + if(vnormal && vtangent) + { + outnormal = loadmodel->surfmesh.data_normal3f; + outsvector = loadmodel->surfmesh.data_svector3f; + outtvector = loadmodel->surfmesh.data_tvector3f; + for (i = 0;i < (int)header.num_vertexes;i++) + { + outsvector[0] = LittleFloat(vtangent[0]); + outsvector[1] = LittleFloat(vtangent[1]); + outsvector[2] = LittleFloat(vtangent[2]); + if(LittleFloat(vtangent[3]) < 0) + CrossProduct(outsvector, outnormal, outtvector); + else + CrossProduct(outnormal, outsvector, outtvector); + vtangent += 4; + outnormal += 3; + outsvector += 3; + outtvector += 3; + } + } + + // this unaligned memory access is safe (all bytes) + if (vblendindexes && vblendweights) + { + for (i = 0; i < (int)header.num_vertexes;i++) + { + blendweights_t weights; + memcpy(weights.index, vblendindexes + i*4, 4); + memcpy(weights.influence, vblendweights + i*4, 4); + loadmodel->surfmesh.blends[i] = Mod_Skeletal_AddBlend(loadmodel, &weights); + loadmodel->surfmesh.data_skeletalindex4ub[i*4 ] = weights.index[0]; + loadmodel->surfmesh.data_skeletalindex4ub[i*4+1] = weights.index[1]; + loadmodel->surfmesh.data_skeletalindex4ub[i*4+2] = weights.index[2]; + loadmodel->surfmesh.data_skeletalindex4ub[i*4+3] = weights.index[3]; + loadmodel->surfmesh.data_skeletalweight4ub[i*4 ] = weights.influence[0]; + loadmodel->surfmesh.data_skeletalweight4ub[i*4+1] = weights.influence[1]; + loadmodel->surfmesh.data_skeletalweight4ub[i*4+2] = weights.influence[2]; + loadmodel->surfmesh.data_skeletalweight4ub[i*4+3] = weights.influence[3]; + } + } + + if (vcolor4f) + { + outcolor = loadmodel->surfmesh.data_lightmapcolor4f; + // this unaligned memory access is safe (LittleFloat reads as bytes) + for (i = 0;i < (int)header.num_vertexes;i++) + { + outcolor[0] = LittleFloat(vcolor4f[0]); + outcolor[1] = LittleFloat(vcolor4f[1]); + outcolor[2] = LittleFloat(vcolor4f[2]); + outcolor[3] = LittleFloat(vcolor4f[3]); + vcolor4f += 4; + outcolor += 4; + } + } + else if (vcolor4ub) + { + outcolor = loadmodel->surfmesh.data_lightmapcolor4f; + // this unaligned memory access is safe (all bytes) + for (i = 0;i < (int)header.num_vertexes;i++) + { + outcolor[0] = vcolor4ub[0] * (1.0f / 255.0f); + outcolor[1] = vcolor4ub[1] * (1.0f / 255.0f); + outcolor[2] = vcolor4ub[2] * (1.0f / 255.0f); + outcolor[3] = vcolor4ub[3] * (1.0f / 255.0f); + vcolor4ub += 4; + outcolor += 4; + } + } + + // load meshes + for (i = 0;i < (int)header.num_meshes;i++) + { + iqmmesh_t mesh; + msurface_t *surface; + + mesh.name = LittleLong(meshes[i].name); + mesh.material = LittleLong(meshes[i].material); + mesh.first_vertex = LittleLong(meshes[i].first_vertex); + mesh.num_vertexes = LittleLong(meshes[i].num_vertexes); + mesh.first_triangle = LittleLong(meshes[i].first_triangle); + mesh.num_triangles = LittleLong(meshes[i].num_triangles); + + loadmodel->sortedmodelsurfaces[i] = i; + surface = loadmodel->data_surfaces + i; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = mesh.first_triangle; + surface->num_triangles = mesh.num_triangles; + surface->num_firstvertex = mesh.first_vertex; + surface->num_vertices = mesh.num_vertexes; + + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, &text[mesh.name], &text[mesh.material]); + } + + Mod_FreeSkinFiles(skinfiles); + Mod_MakeSortedSurfaces(loadmodel); + + // compute all the mesh information that was not loaded from the file + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + if (!vnormal) + Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + if (!vnormal || !vtangent) + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (!header.ofs_neighbors && loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + if (!header.ofs_bounds) + Mod_Alias_CalculateBoundingBox(); + + if (!loadmodel->surfmesh.isanimated && loadmodel->surfmesh.num_triangles >= 1) + { + Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + } + + if (joint ) Mem_Free(joint );joint = NULL; + if (joint1 ) Mem_Free(joint1 );joint1 = NULL; + if (pose ) Mem_Free(pose );pose = NULL; + if (pose1 ) Mem_Free(pose1 );pose1 = NULL; + + // because shaders can do somewhat unexpected things, check for unusual features now + for (i = 0;i < loadmodel->num_textures;i++) + { + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) + mod->DrawSky = R_Q1BSP_DrawSky; + if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + } +} diff --git a/app/jni/model_alias.h b/app/jni/model_alias.h new file mode 100644 index 0000000..61c1099 --- /dev/null +++ b/app/jni/model_alias.h @@ -0,0 +1,252 @@ +/* +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. + +*/ + +#ifndef MODEL_ALIAS_H +#define MODEL_ALIAS_H + +/* +============================================================================== + +ALIAS MODELS + +Alias models are position independent, so the cache manager can move them. +============================================================================== +*/ + +#include "modelgen.h" + +typedef struct daliashdr_s +{ + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + synctype_t synctype; + int flags; + float size; +} +daliashdr_t; + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +// LordHavoc: grabbed this from the Q2 utility source, +// renamed a things to avoid conflicts + +#define MD2ALIAS_VERSION 8 +#define MD2_SKINNAME 64 + +typedef struct md2stvert_s +{ + short s; + short t; +} md2stvert_t; + +typedef struct md2triangle_s +{ + short index_xyz[3]; + short index_st[3]; +} md2triangle_t; + +typedef struct md2frame_s +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing +} md2frame_t; + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct md2_s +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file +} md2_t; + +// all md3 ints, floats, and shorts, are little endian, and thus need to be +// passed through LittleLong/LittleFloat/LittleShort to avoid breaking on +// bigendian machines +#define MD3VERSION 15 +#define MD3NAME 64 +#define MD3FRAMENAME 16 + +// the origin is at 1/64th scale +// the pitch and yaw are encoded as 8 bits each +typedef struct md3vertex_s +{ + short origin[3]; + unsigned char pitch; + unsigned char yaw; +} +md3vertex_t; + +// one per frame +typedef struct md3frameinfo_s +{ + float mins[3]; + float maxs[3]; + float origin[3]; + float radius; + char name[MD3FRAMENAME]; +} +md3frameinfo_t; + +// one per tag per frame +typedef struct md3tag_s +{ + char name[MD3NAME]; + float origin[3]; + float rotationmatrix[9]; +} +md3tag_t; + +// one per shader per mesh +typedef struct md3shader_s +{ + char name[MD3NAME]; + // engine field (yes this empty int does exist in the file) + int shadernum; +} +md3shader_t; + +// one per mesh per model +// +// note that the lump_ offsets in this struct are relative to the beginning +// of the mesh struct +// +// to find the next mesh in the file, you must go to lump_end, which puts you +// at the beginning of the next mesh +typedef struct md3mesh_s +{ + char identifier[4]; // "IDP3" + char name[MD3NAME]; + int flags; + int num_frames; + int num_shaders; + int num_vertices; + int num_triangles; + int lump_elements; + int lump_shaders; + int lump_texcoords; + int lump_framevertices; + int lump_end; +} +md3mesh_t; + +// this struct is at the beginning of the md3 file +// +// note that the lump_ offsets in this struct are relative to the beginning +// of the header struct (which is the beginning of the file) +typedef struct md3modelheader_s +{ + char identifier[4]; // "IDP3" + int version; // 15 + char name[MD3NAME]; + int flags; + int num_frames; + int num_tags; + int num_meshes; + int num_skins; + int lump_frameinfo; + int lump_tags; + int lump_meshes; + int lump_end; +} +md3modelheader_t; + +typedef struct aliastag_s +{ + char name[MD3NAME]; + float matrixgl[12]; +} +aliastag_t; + +typedef struct aliasbone_s +{ + char name[MD3NAME]; + int flags; + int parent; // -1 for no parent +} +aliasbone_t; + +#include "model_zymotic.h" + +#include "model_dpmodel.h" + +#include "model_psk.h" + +#include "model_iqm.h" + +// for decoding md3 model latlong vertex normals +extern float mod_md3_sin[320]; + +extern cvar_t r_skeletal_debugbone; +extern cvar_t r_skeletal_debugbonecomponent; +extern cvar_t r_skeletal_debugbonevalue; +extern cvar_t r_skeletal_debugtranslatex; +extern cvar_t r_skeletal_debugtranslatey; +extern cvar_t r_skeletal_debugtranslatez; + +struct model_s; +struct frameblend_s; + +void *Mod_Skeletal_AnimateVertices_AllocBuffers(size_t nbytes); +void Mod_Skeletal_BuildTransforms(const struct model_s * RESTRICT model, const struct frameblend_s * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT bonepose, float * RESTRICT boneposerelative); + +#endif + diff --git a/app/jni/model_brush.c b/app/jni/model_brush.c new file mode 100644 index 0000000..b8e64da --- /dev/null +++ b/app/jni/model_brush.c @@ -0,0 +1,8071 @@ +/* +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 "image.h" +#include "r_shadow.h" +#include "polygon.h" +#include "curves.h" +#include "wad.h" + + +//cvar_t r_subdivide_size = {CVAR_SAVE, "r_subdivide_size", "128", "how large water polygons should be (smaller values produce more polygons which give better warping effects)"}; +cvar_t mod_bsp_portalize = {0, "mod_bsp_portalize", "1", "enables portal generation from BSP tree (may take several seconds per map), used by r_drawportals, r_useportalculling, r_shadow_realtime_world_compileportalculling, sv_cullentities_portal"}; +cvar_t r_novis = {0, "r_novis", "0", "draws whole level, see also sv_cullentities_pvs 0"}; +cvar_t r_nosurftextures = {0, "r_nosurftextures", "0", "pretends there was no texture lump found in the q1bsp/hlbsp loading (useful for debugging this rare case)"}; +cvar_t r_subdivisions_tolerance = {0, "r_subdivisions_tolerance", "4", "maximum error tolerance on curve subdivision for rendering purposes (in other words, the curves will be given as many polygons as necessary to represent curves at this quality)"}; +cvar_t r_subdivisions_mintess = {0, "r_subdivisions_mintess", "0", "minimum number of subdivisions (values above 0 will smooth curves that don't need it)"}; +cvar_t r_subdivisions_maxtess = {0, "r_subdivisions_maxtess", "1024", "maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing)"}; +cvar_t r_subdivisions_maxvertices = {0, "r_subdivisions_maxvertices", "65536", "maximum vertices allowed per subdivided curve"}; +cvar_t r_subdivisions_collision_tolerance = {0, "r_subdivisions_collision_tolerance", "15", "maximum error tolerance on curve subdivision for collision purposes (usually a larger error tolerance than for rendering)"}; +cvar_t r_subdivisions_collision_mintess = {0, "r_subdivisions_collision_mintess", "0", "minimum number of subdivisions (values above 0 will smooth curves that don't need it)"}; +cvar_t r_subdivisions_collision_maxtess = {0, "r_subdivisions_collision_maxtess", "1024", "maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing)"}; +cvar_t r_subdivisions_collision_maxvertices = {0, "r_subdivisions_collision_maxvertices", "4225", "maximum vertices allowed per subdivided curve"}; +cvar_t r_trippy = {0, "r_trippy", "0", "easter egg"}; +cvar_t mod_noshader_default_offsetmapping = {CVAR_SAVE, "mod_noshader_default_offsetmapping", "1", "use offsetmapping by default on all surfaces that are not using q3 shader files"}; +cvar_t mod_obj_orientation = {0, "mod_obj_orientation", "1", "fix orientation of OBJ models to the usual conventions (if zero, use coordinates as is)"}; +cvar_t mod_q3bsp_curves_collisions = {0, "mod_q3bsp_curves_collisions", "1", "enables collisions with curves (SLOW)"}; +cvar_t mod_q3bsp_curves_collisions_stride = {0, "mod_q3bsp_curves_collisions_stride", "16", "collisions against curves: optimize performance by doing a combined collision check for this triangle amount first (-1 avoids any box tests)"}; +cvar_t mod_q3bsp_curves_stride = {0, "mod_q3bsp_curves_stride", "16", "particle effect collisions against curves: optimize performance by doing a combined collision check for this triangle amount first (-1 avoids any box tests)"}; +cvar_t mod_q3bsp_optimizedtraceline = {0, "mod_q3bsp_optimizedtraceline", "1", "whether to use optimized traceline code for line traces (as opposed to tracebox code)"}; +cvar_t mod_q3bsp_debugtracebrush = {0, "mod_q3bsp_debugtracebrush", "0", "selects different tracebrush bsp recursion algorithms (for debugging purposes only)"}; +cvar_t mod_q3bsp_lightmapmergepower = {CVAR_SAVE, "mod_q3bsp_lightmapmergepower", "4", "merges the quake3 128x128 lightmap textures into larger lightmap group textures to speed up rendering, 1 = 256x256, 2 = 512x512, 3 = 1024x1024, 4 = 2048x2048, 5 = 4096x4096, ..."}; +cvar_t mod_q3bsp_nolightmaps = {CVAR_SAVE, "mod_q3bsp_nolightmaps", "0", "do not load lightmaps in Q3BSP maps (to save video RAM, but be warned: it looks ugly)"}; +cvar_t mod_q3bsp_tracelineofsight_brushes = {0, "mod_q3bsp_tracelineofsight_brushes", "0", "enables culling of entities behind detail brushes, curves, etc"}; +cvar_t mod_q3bsp_sRGBlightmaps = {0, "mod_q3bsp_sRGBlightmaps", "0", "treat lightmaps from Q3 maps as sRGB when vid_sRGB is active"}; +cvar_t mod_q3shader_default_offsetmapping = {CVAR_SAVE, "mod_q3shader_default_offsetmapping", "1", "use offsetmapping by default on all surfaces that are using q3 shader files"}; +cvar_t mod_q3shader_default_offsetmapping_scale = {CVAR_SAVE, "mod_q3shader_default_offsetmapping_scale", "1", "default scale used for offsetmapping"}; +cvar_t mod_q3shader_default_offsetmapping_bias = {CVAR_SAVE, "mod_q3shader_default_offsetmapping_bias", "0", "default bias used for offsetmapping"}; +cvar_t mod_q3shader_default_polygonfactor = {0, "mod_q3shader_default_polygonfactor", "0", "biases depth values of 'polygonoffset' shaders to prevent z-fighting artifacts"}; +cvar_t mod_q3shader_default_polygonoffset = {0, "mod_q3shader_default_polygonoffset", "-2", "biases depth values of 'polygonoffset' shaders to prevent z-fighting artifacts"}; +cvar_t mod_q3shader_force_addalpha = {0, "mod_q3shader_force_addalpha", "0", "treat GL_ONE GL_ONE (or add) blendfunc as GL_SRC_ALPHA GL_ONE for compatibility with older DarkPlaces releases"}; +cvar_t mod_q3shader_force_terrain_alphaflag = {0, "mod_q3shader_force_terrain_alphaflag", "0", "for multilayered terrain shaders force TEXF_ALPHA flag on both layers"}; + +cvar_t mod_q1bsp_polygoncollisions = {0, "mod_q1bsp_polygoncollisions", "0", "disables use of precomputed cliphulls and instead collides with polygons (uses Bounding Interval Hierarchy optimizations)"}; +cvar_t mod_collision_bih = {0, "mod_collision_bih", "1", "enables use of generated Bounding Interval Hierarchy tree instead of compiled bsp tree in collision code"}; +cvar_t mod_recalculatenodeboxes = {0, "mod_recalculatenodeboxes", "1", "enables use of generated node bounding boxes based on BSP tree portal reconstruction, rather than the node boxes supplied by the map compiler"}; + +static texture_t mod_q1bsp_texture_solid; +static texture_t mod_q1bsp_texture_sky; +static texture_t mod_q1bsp_texture_lava; +static texture_t mod_q1bsp_texture_slime; +static texture_t mod_q1bsp_texture_water; + +void Mod_BrushInit(void) +{ +// Cvar_RegisterVariable(&r_subdivide_size); + Cvar_RegisterVariable(&mod_bsp_portalize); + Cvar_RegisterVariable(&r_novis); + Cvar_RegisterVariable(&r_nosurftextures); + Cvar_RegisterVariable(&r_subdivisions_tolerance); + Cvar_RegisterVariable(&r_subdivisions_mintess); + Cvar_RegisterVariable(&r_subdivisions_maxtess); + Cvar_RegisterVariable(&r_subdivisions_maxvertices); + Cvar_RegisterVariable(&r_subdivisions_collision_tolerance); + Cvar_RegisterVariable(&r_subdivisions_collision_mintess); + Cvar_RegisterVariable(&r_subdivisions_collision_maxtess); + Cvar_RegisterVariable(&r_subdivisions_collision_maxvertices); + Cvar_RegisterVariable(&r_trippy); + Cvar_RegisterVariable(&mod_noshader_default_offsetmapping); + Cvar_RegisterVariable(&mod_obj_orientation); + Cvar_RegisterVariable(&mod_q3bsp_curves_collisions); + Cvar_RegisterVariable(&mod_q3bsp_curves_collisions_stride); + Cvar_RegisterVariable(&mod_q3bsp_curves_stride); + Cvar_RegisterVariable(&mod_q3bsp_optimizedtraceline); + Cvar_RegisterVariable(&mod_q3bsp_debugtracebrush); + Cvar_RegisterVariable(&mod_q3bsp_lightmapmergepower); + Cvar_RegisterVariable(&mod_q3bsp_nolightmaps); + Cvar_RegisterVariable(&mod_q3bsp_sRGBlightmaps); + Cvar_RegisterVariable(&mod_q3bsp_tracelineofsight_brushes); + Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping); + Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping_scale); + Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping_bias); + Cvar_RegisterVariable(&mod_q3shader_default_polygonfactor); + Cvar_RegisterVariable(&mod_q3shader_default_polygonoffset); + Cvar_RegisterVariable(&mod_q3shader_force_addalpha); + Cvar_RegisterVariable(&mod_q3shader_force_terrain_alphaflag); + Cvar_RegisterVariable(&mod_q1bsp_polygoncollisions); + Cvar_RegisterVariable(&mod_collision_bih); + Cvar_RegisterVariable(&mod_recalculatenodeboxes); + + // these games were made for older DP engines and are no longer + // maintained; use this hack to show their textures properly + if(gamemode == GAME_NEXUIZ) + Cvar_SetQuick(&mod_q3shader_force_addalpha, "1"); + + memset(&mod_q1bsp_texture_solid, 0, sizeof(mod_q1bsp_texture_solid)); + strlcpy(mod_q1bsp_texture_solid.name, "solid" , sizeof(mod_q1bsp_texture_solid.name)); + mod_q1bsp_texture_solid.surfaceflags = 0; + mod_q1bsp_texture_solid.supercontents = SUPERCONTENTS_SOLID; + + mod_q1bsp_texture_sky = mod_q1bsp_texture_solid; + strlcpy(mod_q1bsp_texture_sky.name, "sky", sizeof(mod_q1bsp_texture_sky.name)); + mod_q1bsp_texture_sky.surfaceflags = Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT | Q3SURFACEFLAG_NOMARKS | Q3SURFACEFLAG_NODLIGHT | Q3SURFACEFLAG_NOLIGHTMAP; + mod_q1bsp_texture_sky.supercontents = SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP; + + mod_q1bsp_texture_lava = mod_q1bsp_texture_solid; + strlcpy(mod_q1bsp_texture_lava.name, "*lava", sizeof(mod_q1bsp_texture_lava.name)); + mod_q1bsp_texture_lava.surfaceflags = Q3SURFACEFLAG_NOMARKS; + mod_q1bsp_texture_lava.supercontents = SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP; + + mod_q1bsp_texture_slime = mod_q1bsp_texture_solid; + strlcpy(mod_q1bsp_texture_slime.name, "*slime", sizeof(mod_q1bsp_texture_slime.name)); + mod_q1bsp_texture_slime.surfaceflags = Q3SURFACEFLAG_NOMARKS; + mod_q1bsp_texture_slime.supercontents = SUPERCONTENTS_SLIME; + + mod_q1bsp_texture_water = mod_q1bsp_texture_solid; + strlcpy(mod_q1bsp_texture_water.name, "*water", sizeof(mod_q1bsp_texture_water.name)); + mod_q1bsp_texture_water.surfaceflags = Q3SURFACEFLAG_NOMARKS; + mod_q1bsp_texture_water.supercontents = SUPERCONTENTS_WATER; +} + +static mleaf_t *Mod_Q1BSP_PointInLeaf(dp_model_t *model, const vec3_t p) +{ + mnode_t *node; + + if (model == NULL) + return NULL; + + // LordHavoc: modified to start at first clip node, + // in other words: first node of the (sub)model + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + while (node->plane) + node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist]; + + return (mleaf_t *)node; +} + +static void Mod_Q1BSP_AmbientSoundLevelsForPoint(dp_model_t *model, const vec3_t p, unsigned char *out, int outsize) +{ + int i; + mleaf_t *leaf; + leaf = Mod_Q1BSP_PointInLeaf(model, p); + if (leaf) + { + i = min(outsize, (int)sizeof(leaf->ambient_sound_level)); + if (i) + { + memcpy(out, leaf->ambient_sound_level, i); + out += i; + outsize -= i; + } + } + if (outsize) + memset(out, 0, outsize); +} + +static int Mod_Q1BSP_FindBoxClusters(dp_model_t *model, const vec3_t mins, const vec3_t maxs, int maxclusters, int *clusterlist) +{ + int numclusters = 0; + int nodestackindex = 0; + mnode_t *node, *nodestack[1024]; + if (!model->brush.num_pvsclusters) + return -1; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + for (;;) + { +#if 1 + if (node->plane) + { + // node - recurse down the BSP tree + int sides = BoxOnPlaneSide(mins, maxs, node->plane); + if (sides < 3) + { + if (sides == 0) + return -1; // ERROR: NAN bounding box! + // box is on one side of plane, take that path + node = node->children[sides-1]; + } + else + { + // box crosses plane, take one path and remember the other + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + } + continue; + } + else + { + // leaf - add clusterindex to list + if (numclusters < maxclusters) + clusterlist[numclusters] = ((mleaf_t *)node)->clusterindex; + numclusters++; + } +#else + if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) + { + if (node->plane) + { + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + continue; + } + else + { + // leaf - add clusterindex to list + if (numclusters < maxclusters) + clusterlist[numclusters] = ((mleaf_t *)node)->clusterindex; + numclusters++; + } + } +#endif + // try another path we didn't take earlier + if (nodestackindex == 0) + break; + node = nodestack[--nodestackindex]; + } + // return number of clusters found (even if more than the maxclusters) + return numclusters; +} + +static int Mod_Q1BSP_BoxTouchingPVS(dp_model_t *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs) +{ + int nodestackindex = 0; + mnode_t *node, *nodestack[1024]; + if (!model->brush.num_pvsclusters) + return true; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + for (;;) + { +#if 1 + if (node->plane) + { + // node - recurse down the BSP tree + int sides = BoxOnPlaneSide(mins, maxs, node->plane); + if (sides < 3) + { + if (sides == 0) + return -1; // ERROR: NAN bounding box! + // box is on one side of plane, take that path + node = node->children[sides-1]; + } + else + { + // box crosses plane, take one path and remember the other + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + } + continue; + } + else + { + // leaf - check cluster bit + int clusterindex = ((mleaf_t *)node)->clusterindex; + if (CHECKPVSBIT(pvs, clusterindex)) + { + // it is visible, return immediately with the news + return true; + } + } +#else + if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) + { + if (node->plane) + { + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + continue; + } + else + { + // leaf - check cluster bit + int clusterindex = ((mleaf_t *)node)->clusterindex; + if (CHECKPVSBIT(pvs, clusterindex)) + { + // it is visible, return immediately with the news + return true; + } + } + } +#endif + // nothing to see here, try another path we didn't take earlier + if (nodestackindex == 0) + break; + node = nodestack[--nodestackindex]; + } + // it is not visible + return false; +} + +static int Mod_Q1BSP_BoxTouchingLeafPVS(dp_model_t *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs) +{ + int nodestackindex = 0; + mnode_t *node, *nodestack[1024]; + if (!model->brush.num_leafs) + return true; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + for (;;) + { +#if 1 + if (node->plane) + { + // node - recurse down the BSP tree + int sides = BoxOnPlaneSide(mins, maxs, node->plane); + if (sides < 3) + { + if (sides == 0) + return -1; // ERROR: NAN bounding box! + // box is on one side of plane, take that path + node = node->children[sides-1]; + } + else + { + // box crosses plane, take one path and remember the other + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + } + continue; + } + else + { + // leaf - check cluster bit + int clusterindex = ((mleaf_t *)node) - model->brush.data_leafs; + if (CHECKPVSBIT(pvs, clusterindex)) + { + // it is visible, return immediately with the news + return true; + } + } +#else + if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) + { + if (node->plane) + { + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + continue; + } + else + { + // leaf - check cluster bit + int clusterindex = ((mleaf_t *)node) - model->brush.data_leafs; + if (CHECKPVSBIT(pvs, clusterindex)) + { + // it is visible, return immediately with the news + return true; + } + } + } +#endif + // nothing to see here, try another path we didn't take earlier + if (nodestackindex == 0) + break; + node = nodestack[--nodestackindex]; + } + // it is not visible + return false; +} + +static int Mod_Q1BSP_BoxTouchingVisibleLeafs(dp_model_t *model, const unsigned char *visibleleafs, const vec3_t mins, const vec3_t maxs) +{ + int nodestackindex = 0; + mnode_t *node, *nodestack[1024]; + if (!model->brush.num_leafs) + return true; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + for (;;) + { +#if 1 + if (node->plane) + { + // node - recurse down the BSP tree + int sides = BoxOnPlaneSide(mins, maxs, node->plane); + if (sides < 3) + { + if (sides == 0) + return -1; // ERROR: NAN bounding box! + // box is on one side of plane, take that path + node = node->children[sides-1]; + } + else + { + // box crosses plane, take one path and remember the other + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + } + continue; + } + else + { + // leaf - check if it is visible + if (visibleleafs[(mleaf_t *)node - model->brush.data_leafs]) + { + // it is visible, return immediately with the news + return true; + } + } +#else + if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) + { + if (node->plane) + { + if (nodestackindex < 1024) + nodestack[nodestackindex++] = node->children[0]; + node = node->children[1]; + continue; + } + else + { + // leaf - check if it is visible + if (visibleleafs[(mleaf_t *)node - model->brush.data_leafs]) + { + // it is visible, return immediately with the news + return true; + } + } + } +#endif + // nothing to see here, try another path we didn't take earlier + if (nodestackindex == 0) + break; + node = nodestack[--nodestackindex]; + } + // it is not visible + return false; +} + +typedef struct findnonsolidlocationinfo_s +{ + vec3_t center; + vec3_t absmin, absmax; + vec_t radius; + vec3_t nudge; + vec_t bestdist; + dp_model_t *model; +} +findnonsolidlocationinfo_t; + +static void Mod_Q1BSP_FindNonSolidLocation_r_Triangle(findnonsolidlocationinfo_t *info, msurface_t *surface, int k) +{ + int i, *tri; + float dist, f, vert[3][3], edge[3][3], facenormal[3], edgenormal[3][3], point[3]; + + tri = (info->model->surfmesh.data_element3i + 3 * surface->num_firsttriangle) + k * 3; + VectorCopy((info->model->surfmesh.data_vertex3f + tri[0] * 3), vert[0]); + VectorCopy((info->model->surfmesh.data_vertex3f + tri[1] * 3), vert[1]); + VectorCopy((info->model->surfmesh.data_vertex3f + tri[2] * 3), vert[2]); + VectorSubtract(vert[1], vert[0], edge[0]); + VectorSubtract(vert[2], vert[1], edge[1]); + CrossProduct(edge[1], edge[0], facenormal); + if (facenormal[0] || facenormal[1] || facenormal[2]) + { + VectorNormalize(facenormal); + f = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal); + if (f <= info->bestdist && f >= -info->bestdist) + { + VectorSubtract(vert[0], vert[2], edge[2]); + VectorNormalize(edge[0]); + VectorNormalize(edge[1]); + VectorNormalize(edge[2]); + CrossProduct(facenormal, edge[0], edgenormal[0]); + CrossProduct(facenormal, edge[1], edgenormal[1]); + CrossProduct(facenormal, edge[2], edgenormal[2]); + // face distance + if (DotProduct(info->center, edgenormal[0]) < DotProduct(vert[0], edgenormal[0]) + && DotProduct(info->center, edgenormal[1]) < DotProduct(vert[1], edgenormal[1]) + && DotProduct(info->center, edgenormal[2]) < DotProduct(vert[2], edgenormal[2])) + { + // we got lucky, the center is within the face + dist = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal); + if (dist < 0) + { + dist = -dist; + if (info->bestdist > dist) + { + info->bestdist = dist; + VectorScale(facenormal, (info->radius - -dist), info->nudge); + } + } + else + { + if (info->bestdist > dist) + { + info->bestdist = dist; + VectorScale(facenormal, (info->radius - dist), info->nudge); + } + } + } + else + { + // check which edge or vertex the center is nearest + for (i = 0;i < 3;i++) + { + f = DotProduct(info->center, edge[i]); + if (f >= DotProduct(vert[0], edge[i]) + && f <= DotProduct(vert[1], edge[i])) + { + // on edge + VectorMA(info->center, -f, edge[i], point); + dist = sqrt(DotProduct(point, point)); + if (info->bestdist > dist) + { + info->bestdist = dist; + VectorScale(point, (info->radius / dist), info->nudge); + } + // skip both vertex checks + // (both are further away than this edge) + i++; + } + else + { + // not on edge, check first vertex of edge + VectorSubtract(info->center, vert[i], point); + dist = sqrt(DotProduct(point, point)); + if (info->bestdist > dist) + { + info->bestdist = dist; + VectorScale(point, (info->radius / dist), info->nudge); + } + } + } + } + } + } +} + +static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *info, mleaf_t *leaf) +{ + int surfacenum, k, *mark; + msurface_t *surface; + for (surfacenum = 0, mark = leaf->firstleafsurface;surfacenum < leaf->numleafsurfaces;surfacenum++, mark++) + { + surface = info->model->data_surfaces + *mark; + if (surface->texture->supercontents & SUPERCONTENTS_SOLID) + { + if(surface->deprecatedq3num_bboxstride > 0) + { + int i, cnt, tri; + cnt = (surface->num_triangles + surface->deprecatedq3num_bboxstride - 1) / surface->deprecatedq3num_bboxstride; + for(i = 0; i < cnt; ++i) + { + if(BoxesOverlap(surface->deprecatedq3data_bbox6f + i * 6, surface->deprecatedq3data_bbox6f + i * 6 + 3, info->absmin, info->absmax)) + { + for(k = 0; k < surface->deprecatedq3num_bboxstride; ++k) + { + tri = i * surface->deprecatedq3num_bboxstride + k; + if(tri >= surface->num_triangles) + break; + Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, tri); + } + } + } + } + else + { + for (k = 0;k < surface->num_triangles;k++) + { + Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, k); + } + } + } + } +} + +static void Mod_Q1BSP_FindNonSolidLocation_r(findnonsolidlocationinfo_t *info, mnode_t *node) +{ + if (node->plane) + { + float f = PlaneDiff(info->center, node->plane); + if (f >= -info->bestdist) + Mod_Q1BSP_FindNonSolidLocation_r(info, node->children[0]); + if (f <= info->bestdist) + Mod_Q1BSP_FindNonSolidLocation_r(info, node->children[1]); + } + else + { + if (((mleaf_t *)node)->numleafsurfaces) + Mod_Q1BSP_FindNonSolidLocation_r_Leaf(info, (mleaf_t *)node); + } +} + +static void Mod_Q1BSP_FindNonSolidLocation(dp_model_t *model, const vec3_t in, vec3_t out, float radius) +{ + int i; + findnonsolidlocationinfo_t info; + if (model == NULL) + { + VectorCopy(in, out); + return; + } + VectorCopy(in, info.center); + info.radius = radius; + info.model = model; + i = 0; + do + { + VectorClear(info.nudge); + info.bestdist = radius; + VectorCopy(info.center, info.absmin); + VectorCopy(info.center, info.absmax); + info.absmin[0] -= info.radius + 1; + info.absmin[1] -= info.radius + 1; + info.absmin[2] -= info.radius + 1; + info.absmax[0] += info.radius + 1; + info.absmax[1] += info.radius + 1; + info.absmax[2] += info.radius + 1; + Mod_Q1BSP_FindNonSolidLocation_r(&info, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode); + VectorAdd(info.center, info.nudge, info.center); + } + while (info.bestdist < radius && ++i < 10); + VectorCopy(info.center, out); +} + +int Mod_Q1BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents) +{ + switch(nativecontents) + { + case CONTENTS_EMPTY: + return 0; + case CONTENTS_SOLID: + return SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; + case CONTENTS_WATER: + return SUPERCONTENTS_WATER; + case CONTENTS_SLIME: + return SUPERCONTENTS_SLIME; + case CONTENTS_LAVA: + return SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP; + case CONTENTS_SKY: + return SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP | SUPERCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque + } + return 0; +} + +int Mod_Q1BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents) +{ + if (supercontents & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY)) + return CONTENTS_SOLID; + if (supercontents & SUPERCONTENTS_SKY) + return CONTENTS_SKY; + if (supercontents & SUPERCONTENTS_LAVA) + return CONTENTS_LAVA; + if (supercontents & SUPERCONTENTS_SLIME) + return CONTENTS_SLIME; + if (supercontents & SUPERCONTENTS_WATER) + return CONTENTS_WATER; + return CONTENTS_EMPTY; +} + +typedef struct RecursiveHullCheckTraceInfo_s +{ + // the hull we're tracing through + const hull_t *hull; + + // the trace structure to fill in + trace_t *trace; + + // start, end, and end - start (in model space) + double start[3]; + double end[3]; + double dist[3]; +} +RecursiveHullCheckTraceInfo_t; + +// 1/32 epsilon to keep floating point happy +#define DIST_EPSILON (0.03125) + +#define HULLCHECKSTATE_EMPTY 0 +#define HULLCHECKSTATE_SOLID 1 +#define HULLCHECKSTATE_DONE 2 + +extern cvar_t collision_prefernudgedfraction; +static int Mod_Q1BSP_RecursiveHullCheck(RecursiveHullCheckTraceInfo_t *t, int num, double p1f, double p2f, double p1[3], double p2[3]) +{ + // status variables, these don't need to be saved on the stack when + // recursing... but are because this should be thread-safe + // (note: tracing against a bbox is not thread-safe, yet) + int ret; + mplane_t *plane; + double t1, t2; + + // variables that need to be stored on the stack when recursing + mclipnode_t *node; + int side; + double midf, mid[3]; + + // LordHavoc: a goto! everyone flee in terror... :) +loc0: + // check for empty + if (num < 0) + { + num = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); + if (!t->trace->startfound) + { + t->trace->startfound = true; + t->trace->startsupercontents |= num; + } + if (num & SUPERCONTENTS_LIQUIDSMASK) + t->trace->inwater = true; + if (num == 0) + t->trace->inopen = true; + if (num & SUPERCONTENTS_SOLID) + t->trace->hittexture = &mod_q1bsp_texture_solid; + else if (num & SUPERCONTENTS_SKY) + t->trace->hittexture = &mod_q1bsp_texture_sky; + else if (num & SUPERCONTENTS_LAVA) + t->trace->hittexture = &mod_q1bsp_texture_lava; + else if (num & SUPERCONTENTS_SLIME) + t->trace->hittexture = &mod_q1bsp_texture_slime; + else + t->trace->hittexture = &mod_q1bsp_texture_water; + t->trace->hitq3surfaceflags = t->trace->hittexture->surfaceflags; + t->trace->hitsupercontents = num; + if (num & t->trace->hitsupercontentsmask) + { + // if the first leaf is solid, set startsolid + if (t->trace->allsolid) + t->trace->startsolid = true; +#if COLLISIONPARANOID >= 3 + Con_Print("S"); +#endif + return HULLCHECKSTATE_SOLID; + } + else + { + t->trace->allsolid = false; +#if COLLISIONPARANOID >= 3 + Con_Print("E"); +#endif + return HULLCHECKSTATE_EMPTY; + } + } + + // find the point distances + node = t->hull->clipnodes + num; + + plane = t->hull->planes + node->planenum; + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + } + + if (t1 < 0) + { + if (t2 < 0) + { +#if COLLISIONPARANOID >= 3 + Con_Print("<"); +#endif + num = node->children[1]; + goto loc0; + } + side = 1; + } + else + { + if (t2 >= 0) + { +#if COLLISIONPARANOID >= 3 + Con_Print(">"); +#endif + num = node->children[0]; + goto loc0; + } + side = 0; + } + + // the line intersects, find intersection point + // LordHavoc: this uses the original trace for maximum accuracy +#if COLLISIONPARANOID >= 3 + Con_Print("M"); +#endif + if (plane->type < 3) + { + t1 = t->start[plane->type] - plane->dist; + t2 = t->end[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, t->start) - plane->dist; + t2 = DotProduct (plane->normal, t->end) - plane->dist; + } + + midf = t1 / (t1 - t2); + midf = bound(p1f, midf, p2f); + VectorMA(t->start, midf, t->dist, mid); + + // recurse both sides, front side first + ret = Mod_Q1BSP_RecursiveHullCheck(t, node->children[side], p1f, midf, p1, mid); + // if this side is not empty, return what it is (solid or done) + if (ret != HULLCHECKSTATE_EMPTY) + return ret; + + ret = Mod_Q1BSP_RecursiveHullCheck(t, node->children[side ^ 1], midf, p2f, mid, p2); + // if other side is not solid, return what it is (empty or done) + if (ret != HULLCHECKSTATE_SOLID) + return ret; + + // front is air and back is solid, this is the impact point... + if (side) + { + t->trace->plane.dist = -plane->dist; + VectorNegate (plane->normal, t->trace->plane.normal); + } + else + { + t->trace->plane.dist = plane->dist; + VectorCopy (plane->normal, t->trace->plane.normal); + } + + // calculate the true fraction + t1 = DotProduct(t->trace->plane.normal, t->start) - t->trace->plane.dist; + t2 = DotProduct(t->trace->plane.normal, t->end) - t->trace->plane.dist; + midf = t1 / (t1 - t2); + t->trace->realfraction = bound(0, midf, 1); + + // calculate the return fraction which is nudged off the surface a bit + midf = (t1 - DIST_EPSILON) / (t1 - t2); + t->trace->fraction = bound(0, midf, 1); + + if (collision_prefernudgedfraction.integer) + t->trace->realfraction = t->trace->fraction; + +#if COLLISIONPARANOID >= 3 + Con_Print("D"); +#endif + return HULLCHECKSTATE_DONE; +} + +//#if COLLISIONPARANOID < 2 +static int Mod_Q1BSP_RecursiveHullCheckPoint(RecursiveHullCheckTraceInfo_t *t, int num) +{ + mplane_t *plane; + mclipnode_t *nodes = t->hull->clipnodes; + mplane_t *planes = t->hull->planes; + vec3_t point; + VectorCopy(t->start, point); + while (num >= 0) + { + plane = planes + nodes[num].planenum; + num = nodes[num].children[(plane->type < 3 ? point[plane->type] : DotProduct(plane->normal, point)) < plane->dist]; + } + num = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); + t->trace->startsupercontents |= num; + if (num & SUPERCONTENTS_LIQUIDSMASK) + t->trace->inwater = true; + if (num == 0) + t->trace->inopen = true; + if (num & t->trace->hitsupercontentsmask) + { + t->trace->allsolid = t->trace->startsolid = true; + return HULLCHECKSTATE_SOLID; + } + else + { + t->trace->allsolid = t->trace->startsolid = false; + return HULLCHECKSTATE_EMPTY; + } +} +//#endif + +static void Mod_Q1BSP_TracePoint(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask) +{ + RecursiveHullCheckTraceInfo_t rhc; + + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + rhc.trace = trace; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 + VectorCopy(start, rhc.start); + VectorCopy(start, rhc.end); + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); +} + +static void Mod_Q1BSP_TraceLineAgainstSurfaces(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); + +static void Mod_Q1BSP_TraceLine(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + RecursiveHullCheckTraceInfo_t rhc; + + if (VectorCompare(start, end)) + { + Mod_Q1BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + return; + } + + // sometimes we want to traceline against polygons so we can report the texture that was hit rather than merely a contents, but using this method breaks one of negke's maps so it must be a cvar check... + if (sv_gameplayfix_q1bsptracelinereportstexture.integer) + { + Mod_Q1BSP_TraceLineAgainstSurfaces(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + return; + } + + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + rhc.trace = trace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 + VectorCopy(start, rhc.start); + VectorCopy(end, rhc.end); + VectorSubtract(rhc.end, rhc.start, rhc.dist); +#if COLLISIONPARANOID >= 2 + Con_Printf("t(%f %f %f,%f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[2]); + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + { + + double test[3]; + trace_t testtrace; + VectorLerp(rhc.start, rhc.trace->fraction, rhc.end, test); + memset(&testtrace, 0, sizeof(trace_t)); + rhc.trace = &testtrace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + VectorCopy(test, rhc.start); + VectorCopy(test, rhc.end); + VectorClear(rhc.dist); + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); + //Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, test, test); + if (!trace->startsolid && testtrace.startsolid) + Con_Printf(" - ended in solid!\n"); + } + Con_Print("\n"); +#else + if (VectorLength2(rhc.dist)) + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + else + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); +#endif +} + +static void Mod_Q1BSP_TraceBox(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask) +{ + // this function currently only supports same size start and end + double boxsize[3]; + RecursiveHullCheckTraceInfo_t rhc; + + if (VectorCompare(boxmins, boxmaxs)) + { + if (VectorCompare(start, end)) + Mod_Q1BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + else + Mod_Q1BSP_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + return; + } + + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + rhc.trace = trace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + VectorSubtract(boxmaxs, boxmins, boxsize); + if (boxsize[0] < 3) + rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 + else if (model->brush.ishlbsp) + { + // LordHavoc: this has to have a minor tolerance (the .1) because of + // minor float precision errors from the box being transformed around + if (boxsize[0] < 32.1) + { + if (boxsize[2] < 54) // pick the nearest of 36 or 72 + rhc.hull = &model->brushq1.hulls[3]; // 32x32x36 + else + rhc.hull = &model->brushq1.hulls[1]; // 32x32x72 + } + else + rhc.hull = &model->brushq1.hulls[2]; // 64x64x64 + } + else + { + // LordHavoc: this has to have a minor tolerance (the .1) because of + // minor float precision errors from the box being transformed around + if (boxsize[0] < 32.1) + rhc.hull = &model->brushq1.hulls[1]; // 32x32x56 + else + rhc.hull = &model->brushq1.hulls[2]; // 64x64x88 + } + VectorMAMAM(1, start, 1, boxmins, -1, rhc.hull->clip_mins, rhc.start); + VectorMAMAM(1, end, 1, boxmins, -1, rhc.hull->clip_mins, rhc.end); + VectorSubtract(rhc.end, rhc.start, rhc.dist); +#if COLLISIONPARANOID >= 2 + Con_Printf("t(%f %f %f,%f %f %f,%i %f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[2], rhc.hull - model->brushq1.hulls, rhc.hull->clip_mins[0], rhc.hull->clip_mins[1], rhc.hull->clip_mins[2]); + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + { + + double test[3]; + trace_t testtrace; + VectorLerp(rhc.start, rhc.trace->fraction, rhc.end, test); + memset(&testtrace, 0, sizeof(trace_t)); + rhc.trace = &testtrace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + VectorCopy(test, rhc.start); + VectorCopy(test, rhc.end); + VectorClear(rhc.dist); + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); + //Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, test, test); + if (!trace->startsolid && testtrace.startsolid) + Con_Printf(" - ended in solid!\n"); + } + Con_Print("\n"); +#else + if (VectorLength2(rhc.dist)) + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + else + Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); +#endif +} + +static int Mod_Q1BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point) +{ + int num = model->brushq1.hulls[0].firstclipnode; + mplane_t *plane; + mclipnode_t *nodes = model->brushq1.hulls[0].clipnodes; + mplane_t *planes = model->brushq1.hulls[0].planes; + while (num >= 0) + { + plane = planes + nodes[num].planenum; + num = nodes[num].children[(plane->type < 3 ? point[plane->type] : DotProduct(plane->normal, point)) < plane->dist]; + } + return Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); +} + +void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture) +{ +#if 1 + colbrushf_t cbox; + colplanef_t cbox_planes[6]; + cbox.isaabb = true; + cbox.hasaabbplanes = true; + cbox.supercontents = boxsupercontents; + cbox.numplanes = 6; + cbox.numpoints = 0; + cbox.numtriangles = 0; + cbox.planes = cbox_planes; + cbox.points = NULL; + cbox.elements = NULL; + cbox.markframe = 0; + cbox.mins[0] = 0; + cbox.mins[1] = 0; + cbox.mins[2] = 0; + cbox.maxs[0] = 0; + cbox.maxs[1] = 0; + cbox.maxs[2] = 0; + cbox_planes[0].normal[0] = 1;cbox_planes[0].normal[1] = 0;cbox_planes[0].normal[2] = 0;cbox_planes[0].dist = cmaxs[0] - mins[0]; + cbox_planes[1].normal[0] = -1;cbox_planes[1].normal[1] = 0;cbox_planes[1].normal[2] = 0;cbox_planes[1].dist = maxs[0] - cmins[0]; + cbox_planes[2].normal[0] = 0;cbox_planes[2].normal[1] = 1;cbox_planes[2].normal[2] = 0;cbox_planes[2].dist = cmaxs[1] - mins[1]; + cbox_planes[3].normal[0] = 0;cbox_planes[3].normal[1] = -1;cbox_planes[3].normal[2] = 0;cbox_planes[3].dist = maxs[1] - cmins[1]; + cbox_planes[4].normal[0] = 0;cbox_planes[4].normal[1] = 0;cbox_planes[4].normal[2] = 1;cbox_planes[4].dist = cmaxs[2] - mins[2]; + cbox_planes[5].normal[0] = 0;cbox_planes[5].normal[1] = 0;cbox_planes[5].normal[2] = -1;cbox_planes[5].dist = maxs[2] - cmins[2]; + cbox_planes[0].q3surfaceflags = boxq3surfaceflags;cbox_planes[0].texture = boxtexture; + cbox_planes[1].q3surfaceflags = boxq3surfaceflags;cbox_planes[1].texture = boxtexture; + cbox_planes[2].q3surfaceflags = boxq3surfaceflags;cbox_planes[2].texture = boxtexture; + cbox_planes[3].q3surfaceflags = boxq3surfaceflags;cbox_planes[3].texture = boxtexture; + cbox_planes[4].q3surfaceflags = boxq3surfaceflags;cbox_planes[4].texture = boxtexture; + cbox_planes[5].q3surfaceflags = boxq3surfaceflags;cbox_planes[5].texture = boxtexture; + memset(trace, 0, sizeof(trace_t)); + trace->hitsupercontentsmask = hitsupercontentsmask; + trace->fraction = 1; + trace->realfraction = 1; + Collision_TraceLineBrushFloat(trace, start, end, &cbox, &cbox); +#else + RecursiveHullCheckTraceInfo_t rhc; + static hull_t box_hull; + static mclipnode_t box_clipnodes[6]; + static mplane_t box_planes[6]; + // fill in a default trace + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + //To keep everything totally uniform, bounding boxes are turned into small + //BSP trees instead of being compared directly. + // create a temp hull from bounding box sizes + box_planes[0].dist = cmaxs[0] - mins[0]; + box_planes[1].dist = cmins[0] - maxs[0]; + box_planes[2].dist = cmaxs[1] - mins[1]; + box_planes[3].dist = cmins[1] - maxs[1]; + box_planes[4].dist = cmaxs[2] - mins[2]; + box_planes[5].dist = cmins[2] - maxs[2]; +#if COLLISIONPARANOID >= 3 + Con_Printf("box_planes %f:%f %f:%f %f:%f\ncbox %f %f %f:%f %f %f\nbox %f %f %f:%f %f %f\n", box_planes[0].dist, box_planes[1].dist, box_planes[2].dist, box_planes[3].dist, box_planes[4].dist, box_planes[5].dist, cmins[0], cmins[1], cmins[2], cmaxs[0], cmaxs[1], cmaxs[2], mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); +#endif + + if (box_hull.clipnodes == NULL) + { + int i, side; + + //Set up the planes and clipnodes so that the six floats of a bounding box + //can just be stored out and get a proper hull_t structure. + + box_hull.clipnodes = box_clipnodes; + box_hull.planes = box_planes; + box_hull.firstclipnode = 0; + box_hull.lastclipnode = 5; + + for (i = 0;i < 6;i++) + { + box_clipnodes[i].planenum = i; + + side = i&1; + + box_clipnodes[i].children[side] = CONTENTS_EMPTY; + if (i != 5) + box_clipnodes[i].children[side^1] = i + 1; + else + box_clipnodes[i].children[side^1] = CONTENTS_SOLID; + + box_planes[i].type = i>>1; + box_planes[i].normal[i>>1] = 1; + } + } + + // trace a line through the generated clipping hull + //rhc.boxsupercontents = boxsupercontents; + rhc.hull = &box_hull; + rhc.trace = trace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + VectorCopy(start, rhc.start); + VectorCopy(end, rhc.end); + VectorSubtract(rhc.end, rhc.start, rhc.dist); + Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); + //VectorMA(rhc.start, rhc.trace->fraction, rhc.dist, rhc.trace->endpos); + if (rhc.trace->startsupercontents) + rhc.trace->startsupercontents = boxsupercontents; +#endif +} + +void Collision_ClipTrace_Point(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, int hitsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture) +{ + memset(trace, 0, sizeof(trace_t)); + trace->fraction = 1; + trace->realfraction = 1; + if (BoxesOverlap(start, start, cmins, cmaxs)) + { + trace->startsupercontents |= boxsupercontents; + if (hitsupercontentsmask & boxsupercontents) + { + trace->startsolid = true; + trace->allsolid = true; + } + } +} + +static qboolean Mod_Q1BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end) +{ + trace_t trace; + Mod_Q1BSP_TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK); + return trace.fraction == 1; +} + +static int Mod_Q1BSP_LightPoint_RecursiveBSPNode(dp_model_t *model, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal, const mnode_t *node, float x, float y, float startz, float endz) +{ + int side; + float front, back; + float mid, distz = endz - startz; + +loc0: + if (!node->plane) + return false; // didn't hit anything + + switch (node->plane->type) + { + case PLANE_X: + node = node->children[x < node->plane->dist]; + goto loc0; + case PLANE_Y: + node = node->children[y < node->plane->dist]; + goto loc0; + case PLANE_Z: + side = startz < node->plane->dist; + if ((endz < node->plane->dist) == side) + { + node = node->children[side]; + goto loc0; + } + // found an intersection + mid = node->plane->dist; + break; + default: + back = front = x * node->plane->normal[0] + y * node->plane->normal[1]; + front += startz * node->plane->normal[2]; + back += endz * node->plane->normal[2]; + side = front < node->plane->dist; + if ((back < node->plane->dist) == side) + { + node = node->children[side]; + goto loc0; + } + // found an intersection + mid = startz + distz * (front - node->plane->dist) / (front - back); + break; + } + + // go down front side + if (node->children[side]->plane && Mod_Q1BSP_LightPoint_RecursiveBSPNode(model, ambientcolor, diffusecolor, diffusenormal, node->children[side], x, y, startz, mid)) + return true; // hit something + else + { + // check for impact on this node + if (node->numsurfaces) + { + unsigned int i; + int dsi, dti, lmwidth, lmheight; + float ds, dt; + msurface_t *surface; + unsigned char *lightmap; + int maps, line3, size3; + float dsfrac; + float dtfrac; + float scale, w, w00, w01, w10, w11; + + surface = model->data_surfaces + node->firstsurface; + for (i = 0;i < node->numsurfaces;i++, surface++) + { + if (!(surface->texture->basematerialflags & MATERIALFLAG_WALL) || !surface->lightmapinfo || !surface->lightmapinfo->samples) + continue; // no lightmaps + + // location we want to sample in the lightmap + ds = ((x * surface->lightmapinfo->texinfo->vecs[0][0] + y * surface->lightmapinfo->texinfo->vecs[0][1] + mid * surface->lightmapinfo->texinfo->vecs[0][2] + surface->lightmapinfo->texinfo->vecs[0][3]) - surface->lightmapinfo->texturemins[0]) * 0.0625f; + dt = ((x * surface->lightmapinfo->texinfo->vecs[1][0] + y * surface->lightmapinfo->texinfo->vecs[1][1] + mid * surface->lightmapinfo->texinfo->vecs[1][2] + surface->lightmapinfo->texinfo->vecs[1][3]) - surface->lightmapinfo->texturemins[1]) * 0.0625f; + + // check the bounds + dsi = (int)ds; + dti = (int)dt; + lmwidth = ((surface->lightmapinfo->extents[0]>>4)+1); + lmheight = ((surface->lightmapinfo->extents[1]>>4)+1); + + // is it in bounds? + if (dsi >= 0 && dsi < lmwidth-1 && dti >= 0 && dti < lmheight-1) + { + // calculate bilinear interpolation factors + // and also multiply by fixedpoint conversion factors + dsfrac = ds - dsi; + dtfrac = dt - dti; + w00 = (1 - dsfrac) * (1 - dtfrac) * (1.0f / 32768.0f); + w01 = ( dsfrac) * (1 - dtfrac) * (1.0f / 32768.0f); + w10 = (1 - dsfrac) * ( dtfrac) * (1.0f / 32768.0f); + w11 = ( dsfrac) * ( dtfrac) * (1.0f / 32768.0f); + + // values for pointer math + line3 = lmwidth * 3; // LordHavoc: *3 for colored lighting + size3 = lmwidth * lmheight * 3; // LordHavoc: *3 for colored lighting + + // look up the pixel + lightmap = surface->lightmapinfo->samples + dti * line3 + dsi*3; // LordHavoc: *3 for colored lighting + + // bilinear filter each lightmap style, and sum them + for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++) + { + scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]]; + w = w00 * scale;VectorMA(ambientcolor, w, lightmap , ambientcolor); + w = w01 * scale;VectorMA(ambientcolor, w, lightmap + 3 , ambientcolor); + w = w10 * scale;VectorMA(ambientcolor, w, lightmap + line3 , ambientcolor); + w = w11 * scale;VectorMA(ambientcolor, w, lightmap + line3 + 3, ambientcolor); + lightmap += size3; + } + + return true; // success + } + } + } + + // go down back side + node = node->children[side ^ 1]; + startz = mid; + distz = endz - startz; + goto loc0; + } +} + +static void Mod_Q1BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal) +{ + // pretend lighting is coming down from above (due to lack of a lightgrid to know primary lighting direction) + VectorSet(diffusenormal, 0, 0, 1); + + if (!model->brushq1.lightdata) + { + VectorSet(ambientcolor, 1, 1, 1); + VectorSet(diffusecolor, 0, 0, 0); + return; + } + + Mod_Q1BSP_LightPoint_RecursiveBSPNode(model, ambientcolor, diffusecolor, diffusenormal, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode, p[0], p[1], p[2] + 0.125, p[2] - 65536); +} + +static const texture_t *Mod_Q1BSP_TraceLineAgainstSurfacesFindTextureOnNode(RecursiveHullCheckTraceInfo_t *t, const dp_model_t *model, const mnode_t *node, double mid[3]) +{ + unsigned int i; + int j; + int k; + const msurface_t *surface; + float normal[3]; + float v0[3]; + float v1[3]; + float edgedir[3]; + float edgenormal[3]; + float p[4]; + float midf; + float t1; + float t2; + VectorCopy(mid, p); + p[3] = 1; + surface = model->data_surfaces + node->firstsurface; + for (i = 0;i < node->numsurfaces;i++, surface++) + { + // skip surfaces whose bounding box does not include the point +// if (!BoxesOverlap(mid, mid, surface->mins, surface->maxs)) +// continue; + // skip faces with contents we don't care about + if (!(t->trace->hitsupercontentsmask & surface->texture->supercontents)) + continue; + // get the surface normal - since it is flat we know any vertex normal will suffice + VectorCopy(model->surfmesh.data_normal3f + 3 * surface->num_firstvertex, normal); + // skip backfaces + if (DotProduct(t->dist, normal) > 0) + continue; + // iterate edges and see if the point is outside one of them + for (j = 0, k = surface->num_vertices - 1;j < surface->num_vertices;k = j, j++) + { + VectorCopy(model->surfmesh.data_vertex3f + 3 * (surface->num_firstvertex + k), v0); + VectorCopy(model->surfmesh.data_vertex3f + 3 * (surface->num_firstvertex + j), v1); + VectorSubtract(v0, v1, edgedir); + CrossProduct(edgedir, normal, edgenormal); + if (DotProduct(edgenormal, p) > DotProduct(edgenormal, v0)) + break; + } + // if the point is outside one of the edges, it is not within the surface + if (j < surface->num_vertices) + continue; + + // we hit a surface, this is the impact point... + VectorCopy(normal, t->trace->plane.normal); + t->trace->plane.dist = DotProduct(normal, p); + + // calculate the true fraction + t1 = DotProduct(t->start, t->trace->plane.normal) - t->trace->plane.dist; + t2 = DotProduct(t->end, t->trace->plane.normal) - t->trace->plane.dist; + midf = t1 / (t1 - t2); + t->trace->realfraction = midf; + + // calculate the return fraction which is nudged off the surface a bit + midf = (t1 - DIST_EPSILON) / (t1 - t2); + t->trace->fraction = bound(0, midf, 1); + + if (collision_prefernudgedfraction.integer) + t->trace->realfraction = t->trace->fraction; + + t->trace->hittexture = surface->texture->currentframe; + t->trace->hitq3surfaceflags = t->trace->hittexture->surfaceflags; + t->trace->hitsupercontents = t->trace->hittexture->supercontents; + return surface->texture->currentframe; + } + return NULL; +} + +static int Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(RecursiveHullCheckTraceInfo_t *t, const dp_model_t *model, const mnode_t *node, const double p1[3], const double p2[3]) +{ + const mplane_t *plane; + double t1, t2; + int side; + double midf, mid[3]; + const mleaf_t *leaf; + + while (node->plane) + { + plane = node->plane; + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + } + if (t1 < 0) + { + if (t2 < 0) + { + node = node->children[1]; + continue; + } + side = 1; + } + else + { + if (t2 >= 0) + { + node = node->children[0]; + continue; + } + side = 0; + } + + // the line intersects, find intersection point + // LordHavoc: this uses the original trace for maximum accuracy + if (plane->type < 3) + { + t1 = t->start[plane->type] - plane->dist; + t2 = t->end[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, t->start) - plane->dist; + t2 = DotProduct (plane->normal, t->end) - plane->dist; + } + + midf = t1 / (t1 - t2); + VectorMA(t->start, midf, t->dist, mid); + + // recurse both sides, front side first, return if we hit a surface + if (Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(t, model, node->children[side], p1, mid) == HULLCHECKSTATE_DONE) + return HULLCHECKSTATE_DONE; + + // test each surface on the node + Mod_Q1BSP_TraceLineAgainstSurfacesFindTextureOnNode(t, model, node, mid); + if (t->trace->hittexture) + return HULLCHECKSTATE_DONE; + + // recurse back side + return Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(t, model, node->children[side ^ 1], mid, p2); + } + leaf = (const mleaf_t *)node; + side = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, leaf->contents); + if (!t->trace->startfound) + { + t->trace->startfound = true; + t->trace->startsupercontents |= side; + } + if (side & SUPERCONTENTS_LIQUIDSMASK) + t->trace->inwater = true; + if (side == 0) + t->trace->inopen = true; + if (side & t->trace->hitsupercontentsmask) + { + // if the first leaf is solid, set startsolid + if (t->trace->allsolid) + t->trace->startsolid = true; + return HULLCHECKSTATE_SOLID; + } + else + { + t->trace->allsolid = false; + return HULLCHECKSTATE_EMPTY; + } +} + +static void Mod_Q1BSP_TraceLineAgainstSurfaces(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + RecursiveHullCheckTraceInfo_t rhc; + + memset(&rhc, 0, sizeof(rhc)); + memset(trace, 0, sizeof(trace_t)); + rhc.trace = trace; + rhc.trace->hitsupercontentsmask = hitsupercontentsmask; + rhc.trace->fraction = 1; + rhc.trace->realfraction = 1; + rhc.trace->allsolid = true; + rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 + VectorCopy(start, rhc.start); + VectorCopy(end, rhc.end); + VectorSubtract(rhc.end, rhc.start, rhc.dist); + Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(&rhc, model, model->brush.data_nodes + rhc.hull->firstclipnode, rhc.start, rhc.end); + VectorMA(rhc.start, rhc.trace->fraction, rhc.dist, rhc.trace->endpos); +} + +static void Mod_Q1BSP_DecompressVis(const unsigned char *in, const unsigned char *inend, unsigned char *out, unsigned char *outend) +{ + int c; + unsigned char *outstart = out; + while (out < outend) + { + if (in == inend) + { + Con_Printf("Mod_Q1BSP_DecompressVis: input underrun on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); + return; + } + c = *in++; + if (c) + *out++ = c; + else + { + if (in == inend) + { + Con_Printf("Mod_Q1BSP_DecompressVis: input underrun (during zero-run) on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); + return; + } + for (c = *in++;c > 0;c--) + { + if (out == outend) + { + Con_Printf("Mod_Q1BSP_DecompressVis: output overrun on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); + return; + } + *out++ = 0; + } + } + } +} + +/* +============= +R_Q1BSP_LoadSplitSky + +A sky texture is 256*128, with the right side being a masked overlay +============== +*/ +static void R_Q1BSP_LoadSplitSky (unsigned char *src, int width, int height, int bytesperpixel) +{ + int x, y; + int w = width/2; + int h = height; + unsigned int *solidpixels = (unsigned int *)Mem_Alloc(tempmempool, w*h*sizeof(unsigned char[4])); + unsigned int *alphapixels = (unsigned int *)Mem_Alloc(tempmempool, w*h*sizeof(unsigned char[4])); + + // allocate a texture pool if we need it + if (loadmodel->texturepool == NULL && cls.state != ca_dedicated) + loadmodel->texturepool = R_AllocTexturePool(); + + if (bytesperpixel == 4) + { + for (y = 0;y < h;y++) + { + for (x = 0;x < w;x++) + { + solidpixels[y*w+x] = ((unsigned *)src)[y*width+x+w]; + alphapixels[y*w+x] = ((unsigned *)src)[y*width+x]; + } + } + } + else + { + // make an average value for the back to avoid + // a fringe on the top level + int p, r, g, b; + union + { + unsigned int i; + unsigned char b[4]; + } + bgra; + r = g = b = 0; + for (y = 0;y < h;y++) + { + for (x = 0;x < w;x++) + { + p = src[x*width+y+w]; + r += palette_rgb[p][0]; + g += palette_rgb[p][1]; + b += palette_rgb[p][2]; + } + } + bgra.b[2] = r/(w*h); + bgra.b[1] = g/(w*h); + bgra.b[0] = b/(w*h); + bgra.b[3] = 0; + for (y = 0;y < h;y++) + { + for (x = 0;x < w;x++) + { + solidpixels[y*w+x] = palette_bgra_complete[src[y*width+x+w]]; + p = src[y*width+x]; + alphapixels[y*w+x] = p ? palette_bgra_complete[p] : bgra.i; + } + } + } + + loadmodel->brush.solidskyskinframe = R_SkinFrame_LoadInternalBGRA("sky_solidtexture", 0 , (unsigned char *) solidpixels, w, h, vid.sRGB3D); + loadmodel->brush.alphaskyskinframe = R_SkinFrame_LoadInternalBGRA("sky_alphatexture", TEXF_ALPHA, (unsigned char *) alphapixels, w, h, vid.sRGB3D); + Mem_Free(solidpixels); + Mem_Free(alphapixels); +} + +static void Mod_Q1BSP_LoadTextures(sizebuf_t *sb) +{ + int i, j, k, num, max, altmax, mtwidth, mtheight, doffset, incomplete, nummiptex = 0; + skinframe_t *skinframe; + texture_t *tx, *tx2, *anims[10], *altanims[10]; + texture_t backuptex; + unsigned char *data, *mtdata; + const char *s; + char mapname[MAX_QPATH], name[MAX_QPATH]; + unsigned char zeroopaque[4], zerotrans[4]; + sizebuf_t miptexsb; + char vabuf[1024]; + Vector4Set(zeroopaque, 0, 0, 0, 255); + Vector4Set(zerotrans, 0, 0, 0, 128); + + loadmodel->data_textures = NULL; + + // add two slots for notexture walls and notexture liquids + if (sb->cursize) + { + nummiptex = MSG_ReadLittleLong(sb); + loadmodel->num_textures = nummiptex + 2; + loadmodel->num_texturesperskin = loadmodel->num_textures; + } + else + { + loadmodel->num_textures = 2; + loadmodel->num_texturesperskin = loadmodel->num_textures; + } + + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_textures * sizeof(texture_t)); + + // fill out all slots with notexture + if (cls.state != ca_dedicated) + skinframe = R_SkinFrame_LoadMissing(); + else + skinframe = NULL; + for (i = 0, tx = loadmodel->data_textures;i < loadmodel->num_textures;i++, tx++) + { + strlcpy(tx->name, "NO TEXTURE FOUND", sizeof(tx->name)); + tx->width = 16; + tx->height = 16; + if (cls.state != ca_dedicated) + { + tx->numskinframes = 1; + tx->skinframerate = 1; + tx->skinframes[0] = skinframe; + tx->currentskinframe = tx->skinframes[0]; + } + tx->basematerialflags = MATERIALFLAG_WALL; + if (i == loadmodel->num_textures - 1) + { + tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW; + tx->supercontents = mod_q1bsp_texture_water.supercontents; + tx->surfaceflags = mod_q1bsp_texture_water.surfaceflags; + } + else + { + tx->supercontents = mod_q1bsp_texture_solid.supercontents; + tx->surfaceflags = mod_q1bsp_texture_solid.surfaceflags; + } + tx->currentframe = tx; + + // clear water settings + tx->reflectmin = 0; + tx->reflectmax = 1; + tx->refractfactor = 1; + Vector4Set(tx->refractcolor4f, 1, 1, 1, 1); + tx->reflectfactor = 1; + Vector4Set(tx->reflectcolor4f, 1, 1, 1, 1); + tx->r_water_wateralpha = 1; + tx->offsetmapping = OFFSETMAPPING_DEFAULT; + tx->offsetscale = 1; + tx->offsetbias = 0; + tx->specularscalemod = 1; + tx->specularpowermod = 1; + tx->transparentsort = TRANSPARENTSORT_DISTANCE; + // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS + // JUST GREP FOR "specularscalemod = 1". + } + + if (!sb->cursize) + { + Con_Printf("%s: no miptex lump to load textures from\n", loadmodel->name); + return; + } + + s = loadmodel->name; + if (!strncasecmp(s, "maps/", 5)) + s += 5; + FS_StripExtension(s, mapname, sizeof(mapname)); + + // just to work around bounds checking when debugging with it (array index out of bounds error thing) + // LordHavoc: mostly rewritten map texture loader + for (i = 0;i < nummiptex;i++) + { + doffset = MSG_ReadLittleLong(sb); + if (r_nosurftextures.integer) + continue; + if (doffset == -1) + { + Con_DPrintf("%s: miptex #%i missing\n", loadmodel->name, i); + continue; + } + + MSG_InitReadBuffer(&miptexsb, sb->data + doffset, sb->cursize - doffset); + + // copy name, but only up to 16 characters + // (the output buffer can hold more than this, but the input buffer is + // only 16) + for (j = 0;j < 16;j++) + name[j] = MSG_ReadByte(&miptexsb); + name[j] = 0; + // pretty up the buffer (replacing any trailing garbage with 0) + for (j = strlen(name);j < 16;j++) + name[j] = 0; + + if (!name[0]) + { + dpsnprintf(name, sizeof(name), "unnamed%i", i); + Con_DPrintf("%s: warning: renaming unnamed texture to %s\n", loadmodel->name, name); + } + + mtwidth = MSG_ReadLittleLong(&miptexsb); + mtheight = MSG_ReadLittleLong(&miptexsb); + mtdata = NULL; + j = MSG_ReadLittleLong(&miptexsb); + if (j) + { + // texture included + if (j < 40 || j + mtwidth * mtheight > miptexsb.cursize) + { + Con_Printf("%s: Texture \"%s\" is corrupt or incomplete\n", loadmodel->name, name); + continue; + } + mtdata = miptexsb.data + j; + } + + if ((mtwidth & 15) || (mtheight & 15)) + Con_DPrintf("%s: warning: texture \"%s\" is not 16 aligned\n", loadmodel->name, name); + + // LordHavoc: force all names to lowercase + for (j = 0;name[j];j++) + if (name[j] >= 'A' && name[j] <= 'Z') + name[j] += 'a' - 'A'; + + // LordHavoc: backup the texture_t because q3 shader loading overwrites it + backuptex = loadmodel->data_textures[i]; + if (name[0] && Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i, name, false, false, 0)) + continue; + loadmodel->data_textures[i] = backuptex; + + tx = loadmodel->data_textures + i; + strlcpy(tx->name, name, sizeof(tx->name)); + tx->width = mtwidth; + tx->height = mtheight; + + if (tx->name[0] == '*') + { + if (!strncmp(tx->name, "*lava", 5)) + { + tx->supercontents = mod_q1bsp_texture_lava.supercontents; + tx->surfaceflags = mod_q1bsp_texture_lava.surfaceflags; + } + else if (!strncmp(tx->name, "*slime", 6)) + { + tx->supercontents = mod_q1bsp_texture_slime.supercontents; + tx->surfaceflags = mod_q1bsp_texture_slime.surfaceflags; + } + else + { + tx->supercontents = mod_q1bsp_texture_water.supercontents; + tx->surfaceflags = mod_q1bsp_texture_water.surfaceflags; + } + } + else if (!strncmp(tx->name, "sky", 3)) + { + tx->supercontents = mod_q1bsp_texture_sky.supercontents; + tx->surfaceflags = mod_q1bsp_texture_sky.surfaceflags; + // for the surface traceline we need to hit this surface as a solid... + tx->supercontents |= SUPERCONTENTS_SOLID; + } + else + { + tx->supercontents = mod_q1bsp_texture_solid.supercontents; + tx->surfaceflags = mod_q1bsp_texture_solid.surfaceflags; + } + + if (cls.state != ca_dedicated) + { + // LordHavoc: HL sky textures are entirely different than quake + if (!loadmodel->brush.ishlbsp && !strncmp(tx->name, "sky", 3) && mtwidth == mtheight * 2) + { + data = loadimagepixelsbgra(gamemode == GAME_TENEBRAE ? tx->name : va(vabuf, sizeof(vabuf), "textures/%s/%s", mapname, tx->name), false, false, false, NULL); + if (!data) + data = loadimagepixelsbgra(gamemode == GAME_TENEBRAE ? tx->name : va(vabuf, sizeof(vabuf), "textures/%s", tx->name), false, false, false, NULL); + if (data && image_width == image_height * 2) + { + R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4); + Mem_Free(data); + } + else if (mtdata != NULL) + R_Q1BSP_LoadSplitSky(mtdata, mtwidth, mtheight, 1); + } + else + { + skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va(vabuf, sizeof(vabuf), "textures/%s/%s", mapname, tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS, false); + if (!skinframe) + skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va(vabuf, sizeof(vabuf), "textures/%s", tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS, false); + if (skinframe) + tx->offsetmapping = OFFSETMAPPING_DEFAULT; // allow offsetmapping on external textures without a q3 shader + if (!skinframe) + { + // did not find external texture, load it from the bsp or wad3 + if (loadmodel->brush.ishlbsp) + { + // internal texture overrides wad + unsigned char *pixels, *freepixels; + pixels = freepixels = NULL; + if (mtdata) + pixels = W_ConvertWAD3TextureBGRA(&miptexsb); + if (pixels == NULL) + pixels = freepixels = W_GetTextureBGRA(tx->name); + if (pixels != NULL) + { + tx->width = image_width; + tx->height = image_height; + skinframe = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP, pixels, image_width, image_height, true); + } + if (freepixels) + Mem_Free(freepixels); + } + else if (mtdata) // texture included + skinframe = R_SkinFrame_LoadInternalQuake(tx->name, TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP, false, r_fullbrights.integer, mtdata, tx->width, tx->height); + } + // if skinframe is still NULL the "missing" texture will be used + if (skinframe) + tx->skinframes[0] = skinframe; + } + // LordHavoc: some Tenebrae textures get replaced by black + if (!strncmp(tx->name, "*glassmirror", 12)) // Tenebrae + tx->skinframes[0] = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_MIPMAP | TEXF_ALPHA, zerotrans, 1, 1, false); + else if (!strncmp(tx->name, "mirror", 6)) // Tenebrae + tx->skinframes[0] = R_SkinFrame_LoadInternalBGRA(tx->name, 0, zeroopaque, 1, 1, false); + } + + tx->basematerialflags = MATERIALFLAG_WALL; + if (tx->name[0] == '*') + { + // LordHavoc: some turbulent textures should not be affected by wateralpha + if (!strncmp(tx->name, "*glassmirror", 12)) // Tenebrae + tx->basematerialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_REFLECTION; + else if (!strncmp(tx->name,"*lava",5) + || !strncmp(tx->name,"*teleport",9) + || !strncmp(tx->name,"*rift",5)) // Scourge of Armagon texture + tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW; + else + tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW | MATERIALFLAG_WATERALPHA | MATERIALFLAG_WATERSHADER; + if (tx->skinframes[0] && tx->skinframes[0]->hasalpha) + tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + } + else if (!strncmp(tx->name, "mirror", 6)) // Tenebrae + { + // replace the texture with black + tx->basematerialflags |= MATERIALFLAG_REFLECTION; + } + else if (!strncmp(tx->name, "sky", 3)) + tx->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; + else if (!strcmp(tx->name, "caulk")) + tx->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; + else if (tx->skinframes[0] && tx->skinframes[0]->hasalpha) + tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + + // start out with no animation + tx->currentframe = tx; + tx->currentskinframe = tx->skinframes[0]; + tx->currentmaterialflags = tx->basematerialflags; + } + + // sequence the animations + for (i = 0;i < nummiptex;i++) + { + tx = loadmodel->data_textures + i; + if (!tx || tx->name[0] != '+' || tx->name[1] == 0 || tx->name[2] == 0) + continue; + if (tx->anim_total[0] || tx->anim_total[1]) + continue; // already sequenced + + // find the number of frames in the animation + memset(anims, 0, sizeof(anims)); + memset(altanims, 0, sizeof(altanims)); + + for (j = i;j < nummiptex;j++) + { + tx2 = loadmodel->data_textures + j; + if (!tx2 || tx2->name[0] != '+' || strcmp(tx2->name+2, tx->name+2)) + continue; + + num = tx2->name[1]; + if (num >= '0' && num <= '9') + anims[num - '0'] = tx2; + else if (num >= 'a' && num <= 'j') + altanims[num - 'a'] = tx2; + else + Con_Printf("Bad animating texture %s\n", tx->name); + } + + max = altmax = 0; + for (j = 0;j < 10;j++) + { + if (anims[j]) + max = j + 1; + if (altanims[j]) + altmax = j + 1; + } + //Con_Printf("linking animation %s (%i:%i frames)\n\n", tx->name, max, altmax); + + incomplete = false; + for (j = 0;j < max;j++) + { + if (!anims[j]) + { + Con_Printf("Missing frame %i of %s\n", j, tx->name); + incomplete = true; + } + } + for (j = 0;j < altmax;j++) + { + if (!altanims[j]) + { + Con_Printf("Missing altframe %i of %s\n", j, tx->name); + incomplete = true; + } + } + if (incomplete) + continue; + + if (altmax < 1) + { + // if there is no alternate animation, duplicate the primary + // animation into the alternate + altmax = max; + for (k = 0;k < 10;k++) + altanims[k] = anims[k]; + } + + // link together the primary animation + for (j = 0;j < max;j++) + { + tx2 = anims[j]; + tx2->animated = true; + tx2->anim_total[0] = max; + tx2->anim_total[1] = altmax; + for (k = 0;k < 10;k++) + { + tx2->anim_frames[0][k] = anims[k]; + tx2->anim_frames[1][k] = altanims[k]; + } + } + + // if there really is an alternate anim... + if (anims[0] != altanims[0]) + { + // link together the alternate animation + for (j = 0;j < altmax;j++) + { + tx2 = altanims[j]; + tx2->animated = true; + // the primary/alternate are reversed here + tx2->anim_total[0] = altmax; + tx2->anim_total[1] = max; + for (k = 0;k < 10;k++) + { + tx2->anim_frames[0][k] = altanims[k]; + tx2->anim_frames[1][k] = anims[k]; + } + } + } + } +} + +static void Mod_Q1BSP_LoadLighting(sizebuf_t *sb) +{ + int i; + unsigned char *in, *out, *data, d; + char litfilename[MAX_QPATH]; + char dlitfilename[MAX_QPATH]; + fs_offset_t filesize; + if (loadmodel->brush.ishlbsp) // LordHavoc: load the colored lighting data straight + { + loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, sb->cursize); + for (i = 0;i < sb->cursize;i++) + loadmodel->brushq1.lightdata[i] = sb->data[i] >>= 1; + } + else // LordHavoc: bsp version 29 (normal white lighting) + { + // LordHavoc: hope is not lost yet, check for a .lit file to load + strlcpy (litfilename, loadmodel->name, sizeof (litfilename)); + FS_StripExtension (litfilename, litfilename, sizeof (litfilename)); + strlcpy (dlitfilename, litfilename, sizeof (dlitfilename)); + strlcat (litfilename, ".lit", sizeof (litfilename)); + strlcat (dlitfilename, ".dlit", sizeof (dlitfilename)); + data = (unsigned char*) FS_LoadFile(litfilename, tempmempool, false, &filesize); + if (data) + { + if (filesize == (fs_offset_t)(8 + sb->cursize * 3) && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') + { + i = LittleLong(((int *)data)[1]); + if (i == 1) + { + if (developer_loading.integer) + Con_Printf("loaded %s\n", litfilename); + loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, filesize - 8); + memcpy(loadmodel->brushq1.lightdata, data + 8, filesize - 8); + Mem_Free(data); + data = (unsigned char*) FS_LoadFile(dlitfilename, tempmempool, false, &filesize); + if (data) + { + if (filesize == (fs_offset_t)(8 + sb->cursize * 3) && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') + { + i = LittleLong(((int *)data)[1]); + if (i == 1) + { + if (developer_loading.integer) + Con_Printf("loaded %s\n", dlitfilename); + loadmodel->brushq1.nmaplightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, filesize - 8); + memcpy(loadmodel->brushq1.nmaplightdata, data + 8, filesize - 8); + loadmodel->brushq3.deluxemapping_modelspace = false; + loadmodel->brushq3.deluxemapping = true; + } + } + Mem_Free(data); + data = NULL; + } + return; + } + else + Con_Printf("Unknown .lit file version (%d)\n", i); + } + else if (filesize == 8) + Con_Print("Empty .lit file, ignoring\n"); + else + Con_Printf("Corrupt .lit file (file size %i bytes, should be %i bytes), ignoring\n", (int) filesize, (int) (8 + sb->cursize * 3)); + if (data) + { + Mem_Free(data); + data = NULL; + } + } + // LordHavoc: oh well, expand the white lighting data + if (!sb->cursize) + return; + loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, sb->cursize*3); + in = sb->data; + out = loadmodel->brushq1.lightdata; + for (i = 0;i < sb->cursize;i++) + { + d = *in++; + *out++ = d; + *out++ = d; + *out++ = d; + } + } +} + +static void Mod_Q1BSP_LoadVisibility(sizebuf_t *sb) +{ + loadmodel->brushq1.num_compressedpvs = 0; + loadmodel->brushq1.data_compressedpvs = NULL; + if (!sb->cursize) + return; + loadmodel->brushq1.num_compressedpvs = sb->cursize; + loadmodel->brushq1.data_compressedpvs = (unsigned char *)Mem_Alloc(loadmodel->mempool, sb->cursize); + MSG_ReadBytes(sb, sb->cursize, loadmodel->brushq1.data_compressedpvs); +} + +// used only for HalfLife maps +static void Mod_Q1BSP_ParseWadsFromEntityLump(const char *data) +{ + char key[128], value[4096]; + int i, j, k; + if (!data) + return; + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + if (com_token[0] != '{') + return; // error + while (1) + { + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strlcpy(key, com_token + 1, sizeof(key)); + else + strlcpy(key, com_token, sizeof(key)); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + dpsnprintf(value, sizeof(value), "%s", com_token); + if (!strcmp("wad", key)) // for HalfLife maps + { + if (loadmodel->brush.ishlbsp) + { + j = 0; + for (i = 0;i < (int)sizeof(value);i++) + if (value[i] != ';' && value[i] != '\\' && value[i] != '/' && value[i] != ':') + break; + if (value[i]) + { + for (;i < (int)sizeof(value);i++) + { + // ignore path - the \\ check is for HalfLife... stupid windoze 'programmers'... + if (value[i] == '\\' || value[i] == '/' || value[i] == ':') + j = i+1; + else if (value[i] == ';' || value[i] == 0) + { + k = value[i]; + value[i] = 0; + W_LoadTextureWadFile(&value[j], false); + j = i+1; + if (!k) + break; + } + } + } + } + } + } +} + +static void Mod_Q1BSP_LoadEntities(sizebuf_t *sb) +{ + loadmodel->brush.entities = NULL; + if (!sb->cursize) + return; + loadmodel->brush.entities = (char *)Mem_Alloc(loadmodel->mempool, sb->cursize + 1); + MSG_ReadBytes(sb, sb->cursize, (unsigned char *)loadmodel->brush.entities); + loadmodel->brush.entities[sb->cursize] = 0; + if (loadmodel->brush.ishlbsp) + Mod_Q1BSP_ParseWadsFromEntityLump(loadmodel->brush.entities); +} + + +static void Mod_Q1BSP_LoadVertexes(sizebuf_t *sb) +{ + mvertex_t *out; + int i, count; + size_t structsize = 12; + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadVertexes: funny lump size in %s",loadmodel->name); + count = sb->cursize / structsize; + out = (mvertex_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brushq1.vertexes = out; + loadmodel->brushq1.numvertexes = count; + + for ( i=0 ; iposition[0] = MSG_ReadLittleFloat(sb); + out->position[1] = MSG_ReadLittleFloat(sb); + out->position[2] = MSG_ReadLittleFloat(sb); + } +} + +static void Mod_Q1BSP_LoadSubmodels(sizebuf_t *sb, hullinfo_t *hullinfo) +{ + mmodel_t *out; + int i, j, count; + size_t structsize = (48+4*hullinfo->filehulls); + + if (sb->cursize % structsize) + Host_Error ("Mod_Q1BSP_LoadSubmodels: funny lump size in %s", loadmodel->name); + + count = sb->cursize / structsize; + out = (mmodel_t *)Mem_Alloc (loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brushq1.submodels = out; + loadmodel->brush.numsubmodels = count; + + for (i = 0; i < count; i++, out++) + { + // spread out the mins / maxs by a pixel + out->mins[0] = MSG_ReadLittleFloat(sb) - 1; + out->mins[1] = MSG_ReadLittleFloat(sb) - 1; + out->mins[2] = MSG_ReadLittleFloat(sb) - 1; + out->maxs[0] = MSG_ReadLittleFloat(sb) + 1; + out->maxs[1] = MSG_ReadLittleFloat(sb) + 1; + out->maxs[2] = MSG_ReadLittleFloat(sb) + 1; + out->origin[0] = MSG_ReadLittleFloat(sb); + out->origin[1] = MSG_ReadLittleFloat(sb); + out->origin[2] = MSG_ReadLittleFloat(sb); + for (j = 0; j < hullinfo->filehulls; j++) + out->headnode[j] = MSG_ReadLittleLong(sb); + out->visleafs = MSG_ReadLittleLong(sb); + out->firstface = MSG_ReadLittleLong(sb); + out->numfaces = MSG_ReadLittleLong(sb); + } +} + +static void Mod_Q1BSP_LoadEdges(sizebuf_t *sb) +{ + medge_t *out; + int i, count; + size_t structsize = loadmodel->brush.isbsp2 ? 8 : 4; + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadEdges: funny lump size in %s",loadmodel->name); + count = sb->cursize / structsize; + out = (medge_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brushq1.edges = out; + loadmodel->brushq1.numedges = count; + + for ( i=0 ; ibrush.isbsp2) + { + out->v[0] = (unsigned int)MSG_ReadLittleLong(sb); + out->v[1] = (unsigned int)MSG_ReadLittleLong(sb); + } + else + { + out->v[0] = (unsigned short)MSG_ReadLittleShort(sb); + out->v[1] = (unsigned short)MSG_ReadLittleShort(sb); + } + if ((int)out->v[0] >= loadmodel->brushq1.numvertexes || (int)out->v[1] >= loadmodel->brushq1.numvertexes) + { + Con_Printf("Mod_Q1BSP_LoadEdges: %s has invalid vertex indices in edge %i (vertices %i %i >= numvertices %i)\n", loadmodel->name, i, out->v[0], out->v[1], loadmodel->brushq1.numvertexes); + if(!loadmodel->brushq1.numvertexes) + Host_Error("Mod_Q1BSP_LoadEdges: %s has edges but no vertexes, cannot fix\n", loadmodel->name); + + out->v[0] = 0; + out->v[1] = 0; + } + } +} + +static void Mod_Q1BSP_LoadTexinfo(sizebuf_t *sb) +{ + mtexinfo_t *out; + int i, j, k, count, miptex; + size_t structsize = 40; + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadTexinfo: funny lump size in %s",loadmodel->name); + count = sb->cursize / structsize; + out = (mtexinfo_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brushq1.texinfo = out; + loadmodel->brushq1.numtexinfo = count; + + for (i = 0;i < count;i++, out++) + { + for (k = 0;k < 2;k++) + for (j = 0;j < 4;j++) + out->vecs[k][j] = MSG_ReadLittleFloat(sb); + + miptex = MSG_ReadLittleLong(sb); + out->flags = MSG_ReadLittleLong(sb); + + out->texture = NULL; + if (loadmodel->data_textures) + { + if ((unsigned int) miptex >= (unsigned int) loadmodel->num_textures) + Con_Printf("error in model \"%s\": invalid miptex index %i(of %i)\n", loadmodel->name, miptex, loadmodel->num_textures); + else + out->texture = loadmodel->data_textures + miptex; + } + if (out->flags & TEX_SPECIAL) + { + // if texture chosen is NULL or the shader needs a lightmap, + // force to notexture water shader + if (out->texture == NULL) + out->texture = loadmodel->data_textures + (loadmodel->num_textures - 1); + } + else + { + // if texture chosen is NULL, force to notexture + if (out->texture == NULL) + out->texture = loadmodel->data_textures + (loadmodel->num_textures - 2); + } + } +} + +#if 0 +void BoundPoly(int numverts, float *verts, vec3_t mins, vec3_t maxs) +{ + int i, j; + float *v; + + mins[0] = mins[1] = mins[2] = 9999; + maxs[0] = maxs[1] = maxs[2] = -9999; + v = verts; + for (i = 0;i < numverts;i++) + { + for (j = 0;j < 3;j++, v++) + { + if (*v < mins[j]) + mins[j] = *v; + if (*v > maxs[j]) + maxs[j] = *v; + } + } +} + +#define MAX_SUBDIVPOLYTRIANGLES 4096 +#define MAX_SUBDIVPOLYVERTS(MAX_SUBDIVPOLYTRIANGLES * 3) + +static int subdivpolyverts, subdivpolytriangles; +static int subdivpolyindex[MAX_SUBDIVPOLYTRIANGLES][3]; +static float subdivpolyvert[MAX_SUBDIVPOLYVERTS][3]; + +static int subdivpolylookupvert(vec3_t v) +{ + int i; + for (i = 0;i < subdivpolyverts;i++) + if (subdivpolyvert[i][0] == v[0] + && subdivpolyvert[i][1] == v[1] + && subdivpolyvert[i][2] == v[2]) + return i; + if (subdivpolyverts >= MAX_SUBDIVPOLYVERTS) + Host_Error("SubDividePolygon: ran out of vertices in buffer, please increase your r_subdivide_size"); + VectorCopy(v, subdivpolyvert[subdivpolyverts]); + return subdivpolyverts++; +} + +static void SubdividePolygon(int numverts, float *verts) +{ + int i, i1, i2, i3, f, b, c, p; + vec3_t mins, maxs, front[256], back[256]; + float m, *pv, *cv, dist[256], frac; + + if (numverts > 250) + Host_Error("SubdividePolygon: ran out of verts in buffer"); + + BoundPoly(numverts, verts, mins, maxs); + + for (i = 0;i < 3;i++) + { + m = (mins[i] + maxs[i]) * 0.5; + m = r_subdivide_size.value * floor(m/r_subdivide_size.value + 0.5); + if (maxs[i] - m < 8) + continue; + if (m - mins[i] < 8) + continue; + + // cut it + for (cv = verts, c = 0;c < numverts;c++, cv += 3) + dist[c] = cv[i] - m; + + f = b = 0; + for (p = numverts - 1, c = 0, pv = verts + p * 3, cv = verts;c < numverts;p = c, c++, pv = cv, cv += 3) + { + if (dist[p] >= 0) + { + VectorCopy(pv, front[f]); + f++; + } + if (dist[p] <= 0) + { + VectorCopy(pv, back[b]); + b++; + } + if (dist[p] == 0 || dist[c] == 0) + continue; + if ((dist[p] > 0) != (dist[c] > 0) ) + { + // clip point + frac = dist[p] / (dist[p] - dist[c]); + front[f][0] = back[b][0] = pv[0] + frac * (cv[0] - pv[0]); + front[f][1] = back[b][1] = pv[1] + frac * (cv[1] - pv[1]); + front[f][2] = back[b][2] = pv[2] + frac * (cv[2] - pv[2]); + f++; + b++; + } + } + + SubdividePolygon(f, front[0]); + SubdividePolygon(b, back[0]); + return; + } + + i1 = subdivpolylookupvert(verts); + i2 = subdivpolylookupvert(verts + 3); + for (i = 2;i < numverts;i++) + { + if (subdivpolytriangles >= MAX_SUBDIVPOLYTRIANGLES) + { + Con_Print("SubdividePolygon: ran out of triangles in buffer, please increase your r_subdivide_size\n"); + return; + } + + i3 = subdivpolylookupvert(verts + i * 3); + subdivpolyindex[subdivpolytriangles][0] = i1; + subdivpolyindex[subdivpolytriangles][1] = i2; + subdivpolyindex[subdivpolytriangles][2] = i3; + i2 = i3; + subdivpolytriangles++; + } +} + +//Breaks a polygon up along axial 64 unit +//boundaries so that turbulent and sky warps +//can be done reasonably. +static void Mod_Q1BSP_GenerateWarpMesh(msurface_t *surface) +{ + int i, j; + surfvertex_t *v; + surfmesh_t *mesh; + + subdivpolytriangles = 0; + subdivpolyverts = 0; + SubdividePolygon(surface->num_vertices, (surface->mesh->data_vertex3f + 3 * surface->num_firstvertex)); + if (subdivpolytriangles < 1) + Host_Error("Mod_Q1BSP_GenerateWarpMesh: no triangles?"); + + surface->mesh = mesh = Mem_Alloc(loadmodel->mempool, sizeof(surfmesh_t) + subdivpolytriangles * sizeof(int[3]) + subdivpolyverts * sizeof(surfvertex_t)); + mesh->num_vertices = subdivpolyverts; + mesh->num_triangles = subdivpolytriangles; + mesh->vertex = (surfvertex_t *)(mesh + 1); + mesh->index = (int *)(mesh->vertex + mesh->num_vertices); + memset(mesh->vertex, 0, mesh->num_vertices * sizeof(surfvertex_t)); + + for (i = 0;i < mesh->num_triangles;i++) + for (j = 0;j < 3;j++) + mesh->index[i*3+j] = subdivpolyindex[i][j]; + + for (i = 0, v = mesh->vertex;i < subdivpolyverts;i++, v++) + { + VectorCopy(subdivpolyvert[i], v->v); + v->st[0] = DotProduct(v->v, surface->lightmapinfo->texinfo->vecs[0]); + v->st[1] = DotProduct(v->v, surface->lightmapinfo->texinfo->vecs[1]); + } +} +#endif + +extern cvar_t gl_max_lightmapsize; +static void Mod_Q1BSP_LoadFaces(sizebuf_t *sb) +{ + msurface_t *surface; + int i, j, count, surfacenum, planenum, smax, tmax, ssize, tsize, firstedge, numedges, totalverts, totaltris, lightmapnumber, lightmapsize, totallightmapsamples, lightmapoffset, texinfoindex; + float texmins[2], texmaxs[2], val; + rtexture_t *lightmaptexture, *deluxemaptexture; + char vabuf[1024]; + size_t structsize = loadmodel->brush.isbsp2 ? 28 : 20; + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadFaces: funny lump size in %s",loadmodel->name); + count = sb->cursize / structsize; + loadmodel->data_surfaces = (msurface_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_t)); + loadmodel->data_surfaces_lightmapinfo = (msurface_lightmapinfo_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_lightmapinfo_t)); + + loadmodel->num_surfaces = count; + + loadmodel->brushq1.firstrender = true; + loadmodel->brushq1.lightmapupdateflags = (unsigned char *)Mem_Alloc(loadmodel->mempool, count*sizeof(unsigned char)); + + totalverts = 0; + totaltris = 0; + for (surfacenum = 0;surfacenum < count;surfacenum++) + { + if (loadmodel->brush.isbsp2) + numedges = BuffLittleLong(sb->data + structsize * surfacenum + 12); + else + numedges = BuffLittleShort(sb->data + structsize * surfacenum + 8); + totalverts += numedges; + totaltris += numedges - 2; + } + + Mod_AllocSurfMesh(loadmodel->mempool, totalverts, totaltris, true, false, false); + + lightmaptexture = NULL; + deluxemaptexture = r_texture_blanknormalmap; + lightmapnumber = 0; + lightmapsize = bound(256, gl_max_lightmapsize.integer, (int)vid.maxtexturesize_2d); + totallightmapsamples = 0; + + totalverts = 0; + totaltris = 0; + for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++) + { + surface->lightmapinfo = loadmodel->data_surfaces_lightmapinfo + surfacenum; + planenum = loadmodel->brush.isbsp2 ? MSG_ReadLittleLong(sb) : (unsigned short)MSG_ReadLittleShort(sb); + /*side = */loadmodel->brush.isbsp2 ? MSG_ReadLittleLong(sb) : (unsigned short)MSG_ReadLittleShort(sb); + firstedge = MSG_ReadLittleLong(sb); + numedges = loadmodel->brush.isbsp2 ? MSG_ReadLittleLong(sb) : (unsigned short)MSG_ReadLittleShort(sb); + texinfoindex = loadmodel->brush.isbsp2 ? MSG_ReadLittleLong(sb) : (unsigned short)MSG_ReadLittleShort(sb); + for (i = 0;i < MAXLIGHTMAPS;i++) + surface->lightmapinfo->styles[i] = MSG_ReadByte(sb); + lightmapoffset = MSG_ReadLittleLong(sb); + + // FIXME: validate edges, texinfo, etc? + if ((unsigned int) firstedge > (unsigned int) loadmodel->brushq1.numsurfedges || (unsigned int) numedges > (unsigned int) loadmodel->brushq1.numsurfedges || (unsigned int) firstedge + (unsigned int) numedges > (unsigned int) loadmodel->brushq1.numsurfedges) + Host_Error("Mod_Q1BSP_LoadFaces: invalid edge range (firstedge %i, numedges %i, model edges %i)", firstedge, numedges, loadmodel->brushq1.numsurfedges); + if ((unsigned int) texinfoindex >= (unsigned int) loadmodel->brushq1.numtexinfo) + Host_Error("Mod_Q1BSP_LoadFaces: invalid texinfo index %i(model has %i texinfos)", texinfoindex, loadmodel->brushq1.numtexinfo); + if ((unsigned int) planenum >= (unsigned int) loadmodel->brush.num_planes) + Host_Error("Mod_Q1BSP_LoadFaces: invalid plane index %i (model has %i planes)", planenum, loadmodel->brush.num_planes); + + surface->lightmapinfo->texinfo = loadmodel->brushq1.texinfo + texinfoindex; + surface->texture = surface->lightmapinfo->texinfo->texture; + + //surface->flags = surface->texture->flags; + //if (LittleShort(in->side)) + // surface->flags |= SURF_PLANEBACK; + //surface->plane = loadmodel->brush.data_planes + planenum; + + surface->num_firstvertex = totalverts; + surface->num_vertices = numedges; + surface->num_firsttriangle = totaltris; + surface->num_triangles = numedges - 2; + totalverts += numedges; + totaltris += numedges - 2; + + // convert edges back to a normal polygon + for (i = 0;i < surface->num_vertices;i++) + { + int lindex = loadmodel->brushq1.surfedges[firstedge + i]; + float s, t; + // note: the q1bsp format does not allow a 0 surfedge (it would have no negative counterpart) + if (lindex >= 0) + VectorCopy(loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[lindex].v[0]].position, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3); + else + VectorCopy(loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[-lindex].v[1]].position, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3); + s = DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]; + t = DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]; + (loadmodel->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[i * 2 + 0] = s / surface->texture->width; + (loadmodel->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[i * 2 + 1] = t / surface->texture->height; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 0] = 0; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 1] = 0; + (loadmodel->surfmesh.data_lightmapoffsets + surface->num_firstvertex)[i] = 0; + } + + for (i = 0;i < surface->num_triangles;i++) + { + (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 0] = 0 + surface->num_firstvertex; + (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 1] = i + 1 + surface->num_firstvertex; + (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 2] = i + 2 + surface->num_firstvertex; + } + + // compile additional data about the surface geometry + Mod_BuildNormals(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, loadmodel->surfmesh.data_vertex3f, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle), loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle), loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + BoxFromPoints(surface->mins, surface->maxs, surface->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex)); + + // generate surface extents information + texmins[0] = texmaxs[0] = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]; + texmins[1] = texmaxs[1] = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]; + for (i = 1;i < surface->num_vertices;i++) + { + for (j = 0;j < 2;j++) + { + val = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3, surface->lightmapinfo->texinfo->vecs[j]) + surface->lightmapinfo->texinfo->vecs[j][3]; + texmins[j] = min(texmins[j], val); + texmaxs[j] = max(texmaxs[j], val); + } + } + for (i = 0;i < 2;i++) + { + surface->lightmapinfo->texturemins[i] = (int) floor(texmins[i] / 16.0) * 16; + surface->lightmapinfo->extents[i] = (int) ceil(texmaxs[i] / 16.0) * 16 - surface->lightmapinfo->texturemins[i]; + } + + smax = surface->lightmapinfo->extents[0] >> 4; + tmax = surface->lightmapinfo->extents[1] >> 4; + ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; + tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; + + // lighting info + surface->lightmaptexture = NULL; + surface->deluxemaptexture = r_texture_blanknormalmap; + if (lightmapoffset == -1) + { + surface->lightmapinfo->samples = NULL; +#if 1 + // give non-lightmapped water a 1x white lightmap + if (surface->texture->name[0] == '*' && (surface->lightmapinfo->texinfo->flags & TEX_SPECIAL) && ssize <= 256 && tsize <= 256) + { + surface->lightmapinfo->samples = (unsigned char *)Mem_Alloc(loadmodel->mempool, ssize * tsize * 3); + surface->lightmapinfo->styles[0] = 0; + memset(surface->lightmapinfo->samples, 128, ssize * tsize * 3); + } +#endif + } + else if (loadmodel->brush.ishlbsp) // LordHavoc: HalfLife map (bsp version 30) + surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + lightmapoffset; + else // LordHavoc: white lighting (bsp version 29) + { + surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + (lightmapoffset * 3); + if (loadmodel->brushq1.nmaplightdata) + surface->lightmapinfo->nmapsamples = loadmodel->brushq1.nmaplightdata + (lightmapoffset * 3); + } + + // check if we should apply a lightmap to this + if (!(surface->lightmapinfo->texinfo->flags & TEX_SPECIAL) || surface->lightmapinfo->samples) + { + if (ssize > 256 || tsize > 256) + Host_Error("Bad surface extents"); + + if (lightmapsize < ssize) + lightmapsize = ssize; + if (lightmapsize < tsize) + lightmapsize = tsize; + + totallightmapsamples += ssize*tsize; + + // force lightmap upload on first time seeing the surface + // + // additionally this is used by the later code to see if a + // lightmap is needed on this surface (rather than duplicating the + // logic above) + loadmodel->brushq1.lightmapupdateflags[surfacenum] = true; + loadmodel->lit = true; + } + } + + // small maps (such as ammo boxes especially) don't need big lightmap + // textures, so this code tries to guess a good size based on + // totallightmapsamples (size of the lightmaps lump basically), as well as + // trying to max out the size if there is a lot of lightmap data to store + // additionally, never choose a lightmapsize that is smaller than the + // largest surface encountered (as it would fail) + i = lightmapsize; + for (lightmapsize = 64; (lightmapsize < i) && (lightmapsize < bound(128, gl_max_lightmapsize.integer, (int)vid.maxtexturesize_2d)) && (totallightmapsamples > lightmapsize*lightmapsize); lightmapsize*=2) + ; + + // now that we've decided the lightmap texture size, we can do the rest + if (cls.state != ca_dedicated) + { + int stainmapsize = 0; + mod_alloclightmap_state_t allocState; + + Mod_AllocLightmap_Init(&allocState, lightmapsize, lightmapsize); + for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++) + { + int i, iu, iv, lightmapx = 0, lightmapy = 0; + float u, v, ubase, vbase, uscale, vscale; + + if (!loadmodel->brushq1.lightmapupdateflags[surfacenum]) + continue; + + smax = surface->lightmapinfo->extents[0] >> 4; + tmax = surface->lightmapinfo->extents[1] >> 4; + ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; + tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; + stainmapsize += ssize * tsize * 3; + + if (!lightmaptexture || !Mod_AllocLightmap_Block(&allocState, ssize, tsize, &lightmapx, &lightmapy)) + { + // allocate a texture pool if we need it + if (loadmodel->texturepool == NULL) + loadmodel->texturepool = R_AllocTexturePool(); + // could not find room, make a new lightmap + loadmodel->brushq3.num_mergedlightmaps = lightmapnumber + 1; + loadmodel->brushq3.data_lightmaps = (rtexture_t **)Mem_Realloc(loadmodel->mempool, loadmodel->brushq3.data_lightmaps, loadmodel->brushq3.num_mergedlightmaps * sizeof(loadmodel->brushq3.data_lightmaps[0])); + loadmodel->brushq3.data_deluxemaps = (rtexture_t **)Mem_Realloc(loadmodel->mempool, loadmodel->brushq3.data_deluxemaps, loadmodel->brushq3.num_mergedlightmaps * sizeof(loadmodel->brushq3.data_deluxemaps[0])); + loadmodel->brushq3.data_lightmaps[lightmapnumber] = lightmaptexture = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "lightmap%i", lightmapnumber), lightmapsize, lightmapsize, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_ALLOWUPDATES, -1, NULL); + if (loadmodel->brushq1.nmaplightdata) + loadmodel->brushq3.data_deluxemaps[lightmapnumber] = deluxemaptexture = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "deluxemap%i", lightmapnumber), lightmapsize, lightmapsize, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_ALLOWUPDATES, -1, NULL); + lightmapnumber++; + Mod_AllocLightmap_Reset(&allocState); + Mod_AllocLightmap_Block(&allocState, ssize, tsize, &lightmapx, &lightmapy); + } + surface->lightmaptexture = lightmaptexture; + surface->deluxemaptexture = deluxemaptexture; + surface->lightmapinfo->lightmaporigin[0] = lightmapx; + surface->lightmapinfo->lightmaporigin[1] = lightmapy; + + uscale = 1.0f / (float)lightmapsize; + vscale = 1.0f / (float)lightmapsize; + ubase = lightmapx * uscale; + vbase = lightmapy * vscale; + + for (i = 0;i < surface->num_vertices;i++) + { + u = ((DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]) + 8 - surface->lightmapinfo->texturemins[0]) * (1.0 / 16.0); + v = ((DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]) + 8 - surface->lightmapinfo->texturemins[1]) * (1.0 / 16.0); + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 0] = u * uscale + ubase; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 1] = v * vscale + vbase; + // LordHavoc: calc lightmap data offset for vertex lighting to use + iu = (int) u; + iv = (int) v; + (loadmodel->surfmesh.data_lightmapoffsets + surface->num_firstvertex)[i] = (bound(0, iv, tmax) * ssize + bound(0, iu, smax)) * 3; + } + } + + if (cl_stainmaps.integer) + { + // allocate stainmaps for permanent marks on walls and clear white + unsigned char *stainsamples = NULL; + stainsamples = (unsigned char *)Mem_Alloc(loadmodel->mempool, stainmapsize); + memset(stainsamples, 255, stainmapsize); + // assign pointers + for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++) + { + if (!loadmodel->brushq1.lightmapupdateflags[surfacenum]) + continue; + ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; + tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; + surface->lightmapinfo->stainsamples = stainsamples; + stainsamples += ssize * tsize * 3; + } + } + } + + // generate ushort elements array if possible + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; +} + +static void Mod_Q1BSP_LoadNodes_RecursiveSetParent(mnode_t *node, mnode_t *parent) +{ + //if (node->parent) + // Host_Error("Mod_Q1BSP_LoadNodes_RecursiveSetParent: runaway recursion"); + node->parent = parent; + if (node->plane) + { + // this is a node, recurse to children + Mod_Q1BSP_LoadNodes_RecursiveSetParent(node->children[0], node); + Mod_Q1BSP_LoadNodes_RecursiveSetParent(node->children[1], node); + // combine supercontents of children + node->combinedsupercontents = node->children[0]->combinedsupercontents | node->children[1]->combinedsupercontents; + } + else + { + int j; + mleaf_t *leaf = (mleaf_t *)node; + // if this is a leaf, calculate supercontents mask from all collidable + // primitives in the leaf (brushes and collision surfaces) + // also flag if the leaf contains any collision surfaces + leaf->combinedsupercontents = 0; + // combine the supercontents values of all brushes in this leaf + for (j = 0;j < leaf->numleafbrushes;j++) + leaf->combinedsupercontents |= loadmodel->brush.data_brushes[leaf->firstleafbrush[j]].texture->supercontents; + // check if this leaf contains any collision surfaces (q3 patches) + for (j = 0;j < leaf->numleafsurfaces;j++) + { + msurface_t *surface = loadmodel->data_surfaces + leaf->firstleafsurface[j]; + if (surface->num_collisiontriangles) + { + leaf->containscollisionsurfaces = true; + leaf->combinedsupercontents |= surface->texture->supercontents; + } + } + } +} + +static void Mod_Q1BSP_LoadNodes(sizebuf_t *sb) +{ + int i, j, count, p, child[2]; + mnode_t *out; + size_t structsize = loadmodel->brush.isbsp2rmqe ? 32 : (loadmodel->brush.isbsp2 ? 44 : 24); + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadNodes: funny lump size in %s",loadmodel->name); + count = sb->cursize / structsize; + if (count == 0) + Host_Error("Mod_Q1BSP_LoadNodes: missing BSP tree in %s",loadmodel->name); + out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brush.data_nodes = out; + loadmodel->brush.num_nodes = count; + + for ( i=0 ; iplane = loadmodel->brush.data_planes + p; + + if (loadmodel->brush.isbsp2rmqe) + { + child[0] = MSG_ReadLittleLong(sb); + child[1] = MSG_ReadLittleLong(sb); + out->mins[0] = MSG_ReadLittleShort(sb); + out->mins[1] = MSG_ReadLittleShort(sb); + out->mins[2] = MSG_ReadLittleShort(sb); + out->maxs[0] = MSG_ReadLittleShort(sb); + out->maxs[1] = MSG_ReadLittleShort(sb); + out->maxs[2] = MSG_ReadLittleShort(sb); + out->firstsurface = MSG_ReadLittleLong(sb); + out->numsurfaces = MSG_ReadLittleLong(sb); + } + else if (loadmodel->brush.isbsp2) + { + child[0] = MSG_ReadLittleLong(sb); + child[1] = MSG_ReadLittleLong(sb); + out->mins[0] = MSG_ReadLittleFloat(sb); + out->mins[1] = MSG_ReadLittleFloat(sb); + out->mins[2] = MSG_ReadLittleFloat(sb); + out->maxs[0] = MSG_ReadLittleFloat(sb); + out->maxs[1] = MSG_ReadLittleFloat(sb); + out->maxs[2] = MSG_ReadLittleFloat(sb); + out->firstsurface = MSG_ReadLittleLong(sb); + out->numsurfaces = MSG_ReadLittleLong(sb); + } + else + { + child[0] = (unsigned short)MSG_ReadLittleShort(sb); + child[1] = (unsigned short)MSG_ReadLittleShort(sb); + if (child[0] >= count) + child[0] -= 65536; + if (child[1] >= count) + child[1] -= 65536; + + out->mins[0] = MSG_ReadLittleShort(sb); + out->mins[1] = MSG_ReadLittleShort(sb); + out->mins[2] = MSG_ReadLittleShort(sb); + out->maxs[0] = MSG_ReadLittleShort(sb); + out->maxs[1] = MSG_ReadLittleShort(sb); + out->maxs[2] = MSG_ReadLittleShort(sb); + + out->firstsurface = (unsigned short)MSG_ReadLittleShort(sb); + out->numsurfaces = (unsigned short)MSG_ReadLittleShort(sb); + } + + for (j=0 ; j<2 ; j++) + { + // LordHavoc: this code supports broken bsp files produced by + // arguire qbsp which can produce more than 32768 nodes, any value + // below count is assumed to be a node number, any other value is + // assumed to be a leaf number + p = child[j]; + if (p >= 0) + { + if (p < loadmodel->brush.num_nodes) + out->children[j] = loadmodel->brush.data_nodes + p; + else + { + Con_Printf("Mod_Q1BSP_LoadNodes: invalid node index %i (file has only %i nodes)\n", p, loadmodel->brush.num_nodes); + // map it to the solid leaf + out->children[j] = (mnode_t *)loadmodel->brush.data_leafs; + } + } + else + { + // get leaf index as a positive value starting at 0 (-1 becomes 0, -2 becomes 1, etc) + p = -(p+1); + if (p < loadmodel->brush.num_leafs) + out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + p); + else + { + Con_Printf("Mod_Q1BSP_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->brush.num_leafs); + // map it to the solid leaf + out->children[j] = (mnode_t *)loadmodel->brush.data_leafs; + } + } + } + } + + Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL); // sets nodes and leafs +} + +static void Mod_Q1BSP_LoadLeafs(sizebuf_t *sb) +{ + mleaf_t *out; + int i, j, count, p, firstmarksurface, nummarksurfaces; + size_t structsize = loadmodel->brush.isbsp2rmqe ? 32 : (loadmodel->brush.isbsp2 ? 44 : 28); + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadLeafs: funny lump size in %s",loadmodel->name); + count = sb->cursize / structsize; + out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brush.data_leafs = out; + loadmodel->brush.num_leafs = count; + // get visleafs from the submodel data + loadmodel->brush.num_pvsclusters = loadmodel->brushq1.submodels[0].visleafs; + loadmodel->brush.num_pvsclusterbytes = (loadmodel->brush.num_pvsclusters+7)>>3; + loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_pvsclusters * loadmodel->brush.num_pvsclusterbytes); + memset(loadmodel->brush.data_pvsclusters, 0xFF, loadmodel->brush.num_pvsclusters * loadmodel->brush.num_pvsclusterbytes); + + // FIXME: this function could really benefit from some error checking + for ( i=0 ; icontents = MSG_ReadLittleLong(sb); + + out->clusterindex = i - 1; + if (out->clusterindex >= loadmodel->brush.num_pvsclusters) + out->clusterindex = -1; + + p = MSG_ReadLittleLong(sb); + // ignore visofs errors on leaf 0 (solid) + if (p >= 0 && out->clusterindex >= 0) + { + if (p >= loadmodel->brushq1.num_compressedpvs) + Con_Print("Mod_Q1BSP_LoadLeafs: invalid visofs\n"); + else + Mod_Q1BSP_DecompressVis(loadmodel->brushq1.data_compressedpvs + p, loadmodel->brushq1.data_compressedpvs + loadmodel->brushq1.num_compressedpvs, loadmodel->brush.data_pvsclusters + out->clusterindex * loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.data_pvsclusters + (out->clusterindex + 1) * loadmodel->brush.num_pvsclusterbytes); + } + + if (loadmodel->brush.isbsp2rmqe) + { + out->mins[0] = MSG_ReadLittleShort(sb); + out->mins[1] = MSG_ReadLittleShort(sb); + out->mins[2] = MSG_ReadLittleShort(sb); + out->maxs[0] = MSG_ReadLittleShort(sb); + out->maxs[1] = MSG_ReadLittleShort(sb); + out->maxs[2] = MSG_ReadLittleShort(sb); + + firstmarksurface = MSG_ReadLittleLong(sb); + nummarksurfaces = MSG_ReadLittleLong(sb); + } + else if (loadmodel->brush.isbsp2) + { + out->mins[0] = MSG_ReadLittleFloat(sb); + out->mins[1] = MSG_ReadLittleFloat(sb); + out->mins[2] = MSG_ReadLittleFloat(sb); + out->maxs[0] = MSG_ReadLittleFloat(sb); + out->maxs[1] = MSG_ReadLittleFloat(sb); + out->maxs[2] = MSG_ReadLittleFloat(sb); + + firstmarksurface = MSG_ReadLittleLong(sb); + nummarksurfaces = MSG_ReadLittleLong(sb); + } + else + { + out->mins[0] = MSG_ReadLittleShort(sb); + out->mins[1] = MSG_ReadLittleShort(sb); + out->mins[2] = MSG_ReadLittleShort(sb); + out->maxs[0] = MSG_ReadLittleShort(sb); + out->maxs[1] = MSG_ReadLittleShort(sb); + out->maxs[2] = MSG_ReadLittleShort(sb); + + firstmarksurface = (unsigned short)MSG_ReadLittleShort(sb); + nummarksurfaces = (unsigned short)MSG_ReadLittleShort(sb); + } + + if (firstmarksurface >= 0 && firstmarksurface + nummarksurfaces <= loadmodel->brush.num_leafsurfaces) + { + out->firstleafsurface = loadmodel->brush.data_leafsurfaces + firstmarksurface; + out->numleafsurfaces = nummarksurfaces; + } + else + { + Con_Printf("Mod_Q1BSP_LoadLeafs: invalid leafsurface range %i:%i outside range %i:%i\n", firstmarksurface, firstmarksurface+nummarksurfaces, 0, loadmodel->brush.num_leafsurfaces); + out->firstleafsurface = NULL; + out->numleafsurfaces = 0; + } + + for (j = 0;j < 4;j++) + out->ambient_sound_level[j] = MSG_ReadByte(sb); + } +} + +static qboolean Mod_Q1BSP_CheckWaterAlphaSupport(void) +{ + int i, j; + mleaf_t *leaf; + const unsigned char *pvs; + // if there's no vis data, assume supported (because everything is visible all the time) + if (!loadmodel->brush.data_pvsclusters) + return true; + // check all liquid leafs to see if they can see into empty leafs, if any + // can we can assume this map supports r_wateralpha + for (i = 0, leaf = loadmodel->brush.data_leafs;i < loadmodel->brush.num_leafs;i++, leaf++) + { + if ((leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME) && leaf->clusterindex >= 0) + { + pvs = loadmodel->brush.data_pvsclusters + leaf->clusterindex * loadmodel->brush.num_pvsclusterbytes; + for (j = 0;j < loadmodel->brush.num_leafs;j++) + if (CHECKPVSBIT(pvs, loadmodel->brush.data_leafs[j].clusterindex) && loadmodel->brush.data_leafs[j].contents == CONTENTS_EMPTY) + return true; + } + } + return false; +} + +static void Mod_Q1BSP_LoadClipnodes(sizebuf_t *sb, hullinfo_t *hullinfo) +{ + mclipnode_t *out; + int i, count; + hull_t *hull; + size_t structsize = loadmodel->brush.isbsp2 ? 12 : 8; + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadClipnodes: funny lump size in %s",loadmodel->name); + count = sb->cursize / structsize; + out = (mclipnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); + + loadmodel->brushq1.clipnodes = out; + loadmodel->brushq1.numclipnodes = count; + + for (i = 1; i < MAX_MAP_HULLS; i++) + { + hull = &loadmodel->brushq1.hulls[i]; + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = count-1; + hull->planes = loadmodel->brush.data_planes; + hull->clip_mins[0] = hullinfo->hullsizes[i][0][0]; + hull->clip_mins[1] = hullinfo->hullsizes[i][0][1]; + hull->clip_mins[2] = hullinfo->hullsizes[i][0][2]; + hull->clip_maxs[0] = hullinfo->hullsizes[i][1][0]; + hull->clip_maxs[1] = hullinfo->hullsizes[i][1][1]; + hull->clip_maxs[2] = hullinfo->hullsizes[i][1][2]; + VectorSubtract(hull->clip_maxs, hull->clip_mins, hull->clip_size); + } + + for (i=0 ; iplanenum = MSG_ReadLittleLong(sb); + if (out->planenum < 0 || out->planenum >= loadmodel->brush.num_planes) + Host_Error("%s: Corrupt clipping hull(out of range planenum)", loadmodel->name); + if (loadmodel->brush.isbsp2) + { + out->children[0] = MSG_ReadLittleLong(sb); + out->children[1] = MSG_ReadLittleLong(sb); + if (out->children[0] >= count) + Host_Error("%s: Corrupt clipping hull (invalid child index)", loadmodel->name); + if (out->children[1] >= count) + Host_Error("%s: Corrupt clipping hull (invalid child index)", loadmodel->name); + } + else + { + // LordHavoc: this code supports arguire qbsp's broken clipnodes indices (more than 32768 clipnodes), values above count are assumed to be contents values + out->children[0] = (unsigned short)MSG_ReadLittleShort(sb); + out->children[1] = (unsigned short)MSG_ReadLittleShort(sb); + if (out->children[0] >= count) + out->children[0] -= 65536; + if (out->children[1] >= count) + out->children[1] -= 65536; + } + } +} + +//Duplicate the drawing hull structure as a clipping hull +static void Mod_Q1BSP_MakeHull0(void) +{ + mnode_t *in; + mclipnode_t *out; + int i; + hull_t *hull; + + hull = &loadmodel->brushq1.hulls[0]; + + in = loadmodel->brush.data_nodes; + out = (mclipnode_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_nodes * sizeof(*out)); + + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = loadmodel->brush.num_nodes - 1; + hull->planes = loadmodel->brush.data_planes; + + for (i = 0;i < loadmodel->brush.num_nodes;i++, out++, in++) + { + out->planenum = in->plane - loadmodel->brush.data_planes; + out->children[0] = in->children[0]->plane ? in->children[0] - loadmodel->brush.data_nodes : ((mleaf_t *)in->children[0])->contents; + out->children[1] = in->children[1]->plane ? in->children[1] - loadmodel->brush.data_nodes : ((mleaf_t *)in->children[1])->contents; + } +} + +static void Mod_Q1BSP_LoadLeaffaces(sizebuf_t *sb) +{ + int i, j; + size_t structsize = loadmodel->brush.isbsp2 ? 4 : 2; + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadLeaffaces: funny lump size in %s",loadmodel->name); + loadmodel->brush.num_leafsurfaces = sb->cursize / structsize; + loadmodel->brush.data_leafsurfaces = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_leafsurfaces * sizeof(int)); + + if (loadmodel->brush.isbsp2) + { + for (i = 0;i < loadmodel->brush.num_leafsurfaces;i++) + { + j = MSG_ReadLittleLong(sb); + if (j < 0 || j >= loadmodel->num_surfaces) + Host_Error("Mod_Q1BSP_LoadLeaffaces: bad surface number"); + loadmodel->brush.data_leafsurfaces[i] = j; + } + } + else + { + for (i = 0;i < loadmodel->brush.num_leafsurfaces;i++) + { + j = (unsigned short) MSG_ReadLittleShort(sb); + if (j >= loadmodel->num_surfaces) + Host_Error("Mod_Q1BSP_LoadLeaffaces: bad surface number"); + loadmodel->brush.data_leafsurfaces[i] = j; + } + } +} + +static void Mod_Q1BSP_LoadSurfedges(sizebuf_t *sb) +{ + int i; + size_t structsize = 4; + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadSurfedges: funny lump size in %s",loadmodel->name); + loadmodel->brushq1.numsurfedges = sb->cursize / structsize; + loadmodel->brushq1.surfedges = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numsurfedges * sizeof(int)); + + for (i = 0;i < loadmodel->brushq1.numsurfedges;i++) + loadmodel->brushq1.surfedges[i] = MSG_ReadLittleLong(sb); +} + + +static void Mod_Q1BSP_LoadPlanes(sizebuf_t *sb) +{ + int i; + mplane_t *out; + size_t structsize = 20; + + if (sb->cursize % structsize) + Host_Error("Mod_Q1BSP_LoadPlanes: funny lump size in %s", loadmodel->name); + loadmodel->brush.num_planes = sb->cursize / structsize; + loadmodel->brush.data_planes = out = (mplane_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_planes * sizeof(*out)); + + for (i = 0;i < loadmodel->brush.num_planes;i++, out++) + { + out->normal[0] = MSG_ReadLittleFloat(sb); + out->normal[1] = MSG_ReadLittleFloat(sb); + out->normal[2] = MSG_ReadLittleFloat(sb); + out->dist = MSG_ReadLittleFloat(sb); + MSG_ReadLittleLong(sb); // type is not used, we use PlaneClassify + PlaneClassify(out); + } +} + +static void Mod_Q1BSP_LoadMapBrushes(void) +{ +#if 0 +// unfinished + int submodel, numbrushes; + qboolean firstbrush; + char *text, *maptext; + char mapfilename[MAX_QPATH]; + FS_StripExtension (loadmodel->name, mapfilename, sizeof (mapfilename)); + strlcat (mapfilename, ".map", sizeof (mapfilename)); + maptext = (unsigned char*) FS_LoadFile(mapfilename, tempmempool, false, NULL); + if (!maptext) + return; + text = maptext; + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + submodel = 0; + for (;;) + { + if (!COM_ParseToken_Simple(&data, false, false, true)) + break; + if (com_token[0] != '{') + return; // error + // entity + firstbrush = true; + numbrushes = 0; + maxbrushes = 256; + brushes = Mem_Alloc(loadmodel->mempool, maxbrushes * sizeof(mbrush_t)); + for (;;) + { + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + if (com_token[0] == '}') + break; // end of entity + if (com_token[0] == '{') + { + // brush + if (firstbrush) + { + if (submodel) + { + if (submodel > loadmodel->brush.numsubmodels) + { + Con_Printf("Mod_Q1BSP_LoadMapBrushes: .map has more submodels than .bsp!\n"); + model = NULL; + } + else + model = loadmodel->brush.submodels[submodel]; + } + else + model = loadmodel; + } + for (;;) + { + if (!COM_ParseToken_Simple(&data, false, false, true)) + return; // error + if (com_token[0] == '}') + break; // end of brush + // each brush face should be this format: + // ( x y z ) ( x y z ) ( x y z ) texture scroll_s scroll_t rotateangle scale_s scale_t + // FIXME: support hl .map format + for (pointnum = 0;pointnum < 3;pointnum++) + { + COM_ParseToken_Simple(&data, false, false, true); + for (componentnum = 0;componentnum < 3;componentnum++) + { + COM_ParseToken_Simple(&data, false, false, true); + point[pointnum][componentnum] = atof(com_token); + } + COM_ParseToken_Simple(&data, false, false, true); + } + COM_ParseToken_Simple(&data, false, false, true); + strlcpy(facetexture, com_token, sizeof(facetexture)); + COM_ParseToken_Simple(&data, false, false, true); + //scroll_s = atof(com_token); + COM_ParseToken_Simple(&data, false, false, true); + //scroll_t = atof(com_token); + COM_ParseToken_Simple(&data, false, false, true); + //rotate = atof(com_token); + COM_ParseToken_Simple(&data, false, false, true); + //scale_s = atof(com_token); + COM_ParseToken_Simple(&data, false, false, true); + //scale_t = atof(com_token); + TriangleNormal(point[0], point[1], point[2], planenormal); + VectorNormalizeDouble(planenormal); + planedist = DotProduct(point[0], planenormal); + //ChooseTexturePlane(planenormal, texturevector[0], texturevector[1]); + } + continue; + } + } + } +#endif +} + + +#define MAX_PORTALPOINTS 64 + +typedef struct portal_s +{ + mplane_t plane; + mnode_t *nodes[2]; // [0] = front side of plane + struct portal_s *next[2]; + int numpoints; + double points[3*MAX_PORTALPOINTS]; + struct portal_s *chain; // all portals are linked into a list +} +portal_t; + +static memexpandablearray_t portalarray; + +static void Mod_Q1BSP_RecursiveRecalcNodeBBox(mnode_t *node) +{ + // process only nodes (leafs already had their box calculated) + if (!node->plane) + return; + + // calculate children first + Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[0]); + Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[1]); + + // make combined bounding box from children + node->mins[0] = min(node->children[0]->mins[0], node->children[1]->mins[0]); + node->mins[1] = min(node->children[0]->mins[1], node->children[1]->mins[1]); + node->mins[2] = min(node->children[0]->mins[2], node->children[1]->mins[2]); + node->maxs[0] = max(node->children[0]->maxs[0], node->children[1]->maxs[0]); + node->maxs[1] = max(node->children[0]->maxs[1], node->children[1]->maxs[1]); + node->maxs[2] = max(node->children[0]->maxs[2], node->children[1]->maxs[2]); +} + +static void Mod_Q1BSP_FinalizePortals(void) +{ + int i, j, numportals, numpoints, portalindex, portalrange = Mem_ExpandableArray_IndexRange(&portalarray); + portal_t *p; + mportal_t *portal; + mvertex_t *point; + mleaf_t *leaf, *endleaf; + + // tally up portal and point counts and recalculate bounding boxes for all + // leafs (because qbsp is very sloppy) + leaf = loadmodel->brush.data_leafs; + endleaf = leaf + loadmodel->brush.num_leafs; + if (mod_recalculatenodeboxes.integer) + { + for (;leaf < endleaf;leaf++) + { + VectorSet(leaf->mins, 2000000000, 2000000000, 2000000000); + VectorSet(leaf->maxs, -2000000000, -2000000000, -2000000000); + } + } + numportals = 0; + numpoints = 0; + for (portalindex = 0;portalindex < portalrange;portalindex++) + { + p = (portal_t*)Mem_ExpandableArray_RecordAtIndex(&portalarray, portalindex); + if (!p) + continue; + // note: this check must match the one below or it will usually corrupt memory + // the nodes[0] != nodes[1] check is because leaf 0 is the shared solid leaf, it can have many portals inside with leaf 0 on both sides + if (p->numpoints >= 3 && p->nodes[0] != p->nodes[1] && ((mleaf_t *)p->nodes[0])->clusterindex >= 0 && ((mleaf_t *)p->nodes[1])->clusterindex >= 0) + { + numportals += 2; + numpoints += p->numpoints * 2; + } + } + loadmodel->brush.data_portals = (mportal_t *)Mem_Alloc(loadmodel->mempool, numportals * sizeof(mportal_t) + numpoints * sizeof(mvertex_t)); + loadmodel->brush.num_portals = numportals; + loadmodel->brush.data_portalpoints = (mvertex_t *)((unsigned char *) loadmodel->brush.data_portals + numportals * sizeof(mportal_t)); + loadmodel->brush.num_portalpoints = numpoints; + // clear all leaf portal chains + for (i = 0;i < loadmodel->brush.num_leafs;i++) + loadmodel->brush.data_leafs[i].portals = NULL; + // process all portals in the global portal chain, while freeing them + portal = loadmodel->brush.data_portals; + point = loadmodel->brush.data_portalpoints; + for (portalindex = 0;portalindex < portalrange;portalindex++) + { + p = (portal_t*)Mem_ExpandableArray_RecordAtIndex(&portalarray, portalindex); + if (!p) + continue; + if (p->numpoints >= 3 && p->nodes[0] != p->nodes[1]) + { + // note: this check must match the one above or it will usually corrupt memory + // the nodes[0] != nodes[1] check is because leaf 0 is the shared solid leaf, it can have many portals inside with leaf 0 on both sides + if (((mleaf_t *)p->nodes[0])->clusterindex >= 0 && ((mleaf_t *)p->nodes[1])->clusterindex >= 0) + { + // first make the back to front portal(forward portal) + portal->points = point; + portal->numpoints = p->numpoints; + portal->plane.dist = p->plane.dist; + VectorCopy(p->plane.normal, portal->plane.normal); + portal->here = (mleaf_t *)p->nodes[1]; + portal->past = (mleaf_t *)p->nodes[0]; + // copy points + for (j = 0;j < portal->numpoints;j++) + { + VectorCopy(p->points + j*3, point->position); + point++; + } + BoxFromPoints(portal->mins, portal->maxs, portal->numpoints, portal->points->position); + PlaneClassify(&portal->plane); + + // link into leaf's portal chain + portal->next = portal->here->portals; + portal->here->portals = portal; + + // advance to next portal + portal++; + + // then make the front to back portal(backward portal) + portal->points = point; + portal->numpoints = p->numpoints; + portal->plane.dist = -p->plane.dist; + VectorNegate(p->plane.normal, portal->plane.normal); + portal->here = (mleaf_t *)p->nodes[0]; + portal->past = (mleaf_t *)p->nodes[1]; + // copy points + for (j = portal->numpoints - 1;j >= 0;j--) + { + VectorCopy(p->points + j*3, point->position); + point++; + } + BoxFromPoints(portal->mins, portal->maxs, portal->numpoints, portal->points->position); + PlaneClassify(&portal->plane); + + // link into leaf's portal chain + portal->next = portal->here->portals; + portal->here->portals = portal; + + // advance to next portal + portal++; + } + // add the portal's polygon points to the leaf bounding boxes + if (mod_recalculatenodeboxes.integer) + { + for (i = 0;i < 2;i++) + { + leaf = (mleaf_t *)p->nodes[i]; + for (j = 0;j < p->numpoints;j++) + { + if (leaf->mins[0] > p->points[j*3+0]) leaf->mins[0] = p->points[j*3+0]; + if (leaf->mins[1] > p->points[j*3+1]) leaf->mins[1] = p->points[j*3+1]; + if (leaf->mins[2] > p->points[j*3+2]) leaf->mins[2] = p->points[j*3+2]; + if (leaf->maxs[0] < p->points[j*3+0]) leaf->maxs[0] = p->points[j*3+0]; + if (leaf->maxs[1] < p->points[j*3+1]) leaf->maxs[1] = p->points[j*3+1]; + if (leaf->maxs[2] < p->points[j*3+2]) leaf->maxs[2] = p->points[j*3+2]; + } + } + } + } + } + // now recalculate the node bounding boxes from the leafs + if (mod_recalculatenodeboxes.integer) + Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode); +} + +/* +============= +AddPortalToNodes +============= +*/ +static void AddPortalToNodes(portal_t *p, mnode_t *front, mnode_t *back) +{ + if (!front) + Host_Error("AddPortalToNodes: NULL front node"); + if (!back) + Host_Error("AddPortalToNodes: NULL back node"); + if (p->nodes[0] || p->nodes[1]) + Host_Error("AddPortalToNodes: already included"); + // note: front == back is handled gracefully, because leaf 0 is the shared solid leaf, it can often have portals with the same leaf on both sides + + p->nodes[0] = front; + p->next[0] = (portal_t *)front->portals; + front->portals = (mportal_t *)p; + + p->nodes[1] = back; + p->next[1] = (portal_t *)back->portals; + back->portals = (mportal_t *)p; +} + +/* +============= +RemovePortalFromNode +============= +*/ +static void RemovePortalFromNodes(portal_t *portal) +{ + int i; + mnode_t *node; + void **portalpointer; + portal_t *t; + for (i = 0;i < 2;i++) + { + node = portal->nodes[i]; + + portalpointer = (void **) &node->portals; + while (1) + { + t = (portal_t *)*portalpointer; + if (!t) + Host_Error("RemovePortalFromNodes: portal not in leaf"); + + if (t == portal) + { + if (portal->nodes[0] == node) + { + *portalpointer = portal->next[0]; + portal->nodes[0] = NULL; + } + else if (portal->nodes[1] == node) + { + *portalpointer = portal->next[1]; + portal->nodes[1] = NULL; + } + else + Host_Error("RemovePortalFromNodes: portal not bounding leaf"); + break; + } + + if (t->nodes[0] == node) + portalpointer = (void **) &t->next[0]; + else if (t->nodes[1] == node) + portalpointer = (void **) &t->next[1]; + else + Host_Error("RemovePortalFromNodes: portal not bounding leaf"); + } + } +} + +#define PORTAL_DIST_EPSILON (1.0 / 32.0) +static double *portalpointsbuffer; +static int portalpointsbufferoffset; +static int portalpointsbuffersize; +static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node) +{ + int i, side; + mnode_t *front, *back, *other_node; + mplane_t clipplane, *plane; + portal_t *portal, *nextportal, *nodeportal, *splitportal, *temp; + int numfrontpoints, numbackpoints; + double *frontpoints, *backpoints; + + // if a leaf, we're done + if (!node->plane) + return; + + // get some space for our clipping operations to use + if (portalpointsbuffersize < portalpointsbufferoffset + 6*MAX_PORTALPOINTS) + { + portalpointsbuffersize = portalpointsbufferoffset * 2; + portalpointsbuffer = (double *)Mem_Realloc(loadmodel->mempool, portalpointsbuffer, portalpointsbuffersize * sizeof(*portalpointsbuffer)); + } + frontpoints = portalpointsbuffer + portalpointsbufferoffset; + portalpointsbufferoffset += 3*MAX_PORTALPOINTS; + backpoints = portalpointsbuffer + portalpointsbufferoffset; + portalpointsbufferoffset += 3*MAX_PORTALPOINTS; + + plane = node->plane; + + front = node->children[0]; + back = node->children[1]; + if (front == back) + Host_Error("Mod_Q1BSP_RecursiveNodePortals: corrupt node hierarchy"); + + // create the new portal by generating a polygon for the node plane, + // and clipping it by all of the other portals(which came from nodes above this one) + nodeportal = (portal_t *)Mem_ExpandableArray_AllocRecord(&portalarray); + nodeportal->plane = *plane; + + // TODO: calculate node bounding boxes during recursion and calculate a maximum plane size accordingly to improve precision (as most maps do not need 1 billion unit plane polygons) + PolygonD_QuadForPlane(nodeportal->points, nodeportal->plane.normal[0], nodeportal->plane.normal[1], nodeportal->plane.normal[2], nodeportal->plane.dist, 1024.0*1024.0*1024.0); + nodeportal->numpoints = 4; + // side = 0; // shut up compiler warning -> should be no longer needed, Host_Error is declared noreturn now + for (portal = (portal_t *)node->portals;portal;portal = portal->next[side]) + { + clipplane = portal->plane; + if (portal->nodes[0] == portal->nodes[1]) + Host_Error("Mod_Q1BSP_RecursiveNodePortals: portal has same node on both sides(1)"); + if (portal->nodes[0] == node) + side = 0; + else if (portal->nodes[1] == node) + { + clipplane.dist = -clipplane.dist; + VectorNegate(clipplane.normal, clipplane.normal); + side = 1; + } + else + { + Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal"); + side = 0; // hush warning + } + + for (i = 0;i < nodeportal->numpoints*3;i++) + frontpoints[i] = nodeportal->points[i]; + PolygonD_Divide(nodeportal->numpoints, frontpoints, clipplane.normal[0], clipplane.normal[1], clipplane.normal[2], clipplane.dist, PORTAL_DIST_EPSILON, MAX_PORTALPOINTS, nodeportal->points, &nodeportal->numpoints, 0, NULL, NULL, NULL); + if (nodeportal->numpoints <= 0 || nodeportal->numpoints >= MAX_PORTALPOINTS) + break; + } + + if (nodeportal->numpoints < 3) + { + Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal was clipped away\n"); + nodeportal->numpoints = 0; + } + else if (nodeportal->numpoints >= MAX_PORTALPOINTS) + { + Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal has too many points\n"); + nodeportal->numpoints = 0; + } + + AddPortalToNodes(nodeportal, front, back); + + // split the portals of this node along this node's plane and assign them to the children of this node + // (migrating the portals downward through the tree) + for (portal = (portal_t *)node->portals;portal;portal = nextportal) + { + if (portal->nodes[0] == portal->nodes[1]) + Host_Error("Mod_Q1BSP_RecursiveNodePortals: portal has same node on both sides(2)"); + if (portal->nodes[0] == node) + side = 0; + else if (portal->nodes[1] == node) + side = 1; + else + { + Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal"); + side = 0; // hush warning + } + nextportal = portal->next[side]; + if (!portal->numpoints) + continue; + + other_node = portal->nodes[!side]; + RemovePortalFromNodes(portal); + + // cut the portal into two portals, one on each side of the node plane + PolygonD_Divide(portal->numpoints, portal->points, plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, PORTAL_DIST_EPSILON, MAX_PORTALPOINTS, frontpoints, &numfrontpoints, MAX_PORTALPOINTS, backpoints, &numbackpoints, NULL); + + if (!numfrontpoints) + { + if (side == 0) + AddPortalToNodes(portal, back, other_node); + else + AddPortalToNodes(portal, other_node, back); + continue; + } + if (!numbackpoints) + { + if (side == 0) + AddPortalToNodes(portal, front, other_node); + else + AddPortalToNodes(portal, other_node, front); + continue; + } + + // the portal is split + splitportal = (portal_t *)Mem_ExpandableArray_AllocRecord(&portalarray); + temp = splitportal->chain; + *splitportal = *portal; + splitportal->chain = temp; + for (i = 0;i < numbackpoints*3;i++) + splitportal->points[i] = backpoints[i]; + splitportal->numpoints = numbackpoints; + for (i = 0;i < numfrontpoints*3;i++) + portal->points[i] = frontpoints[i]; + portal->numpoints = numfrontpoints; + + if (side == 0) + { + AddPortalToNodes(portal, front, other_node); + AddPortalToNodes(splitportal, back, other_node); + } + else + { + AddPortalToNodes(portal, other_node, front); + AddPortalToNodes(splitportal, other_node, back); + } + } + + Mod_Q1BSP_RecursiveNodePortals(front); + Mod_Q1BSP_RecursiveNodePortals(back); + + portalpointsbufferoffset -= 6*MAX_PORTALPOINTS; +} + +static void Mod_Q1BSP_MakePortals(void) +{ + Mem_ExpandableArray_NewArray(&portalarray, loadmodel->mempool, sizeof(portal_t), 1020*1024/sizeof(portal_t)); + portalpointsbufferoffset = 0; + portalpointsbuffersize = 6*MAX_PORTALPOINTS*128; + portalpointsbuffer = (double *)Mem_Alloc(loadmodel->mempool, portalpointsbuffersize * sizeof(*portalpointsbuffer)); + Mod_Q1BSP_RecursiveNodePortals(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode); + Mem_Free(portalpointsbuffer); + portalpointsbuffer = NULL; + portalpointsbufferoffset = 0; + portalpointsbuffersize = 0; + Mod_Q1BSP_FinalizePortals(); + Mem_ExpandableArray_FreeArray(&portalarray); +} + +//Returns PVS data for a given point +//(note: can return NULL) +static unsigned char *Mod_Q1BSP_GetPVS(dp_model_t *model, const vec3_t p) +{ + mnode_t *node; + node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; + while (node->plane) + node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist]; + if (((mleaf_t *)node)->clusterindex >= 0) + return model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes; + else + return NULL; +} + +static void Mod_Q1BSP_FatPVS_RecursiveBSPNode(dp_model_t *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbytes, mnode_t *node) +{ + while (node->plane) + { + float d = PlaneDiff(org, node->plane); + if (d > radius) + node = node->children[0]; + else if (d < -radius) + node = node->children[1]; + else + { + // go down both sides + Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, pvsbytes, node->children[0]); + node = node->children[1]; + } + } + // if this leaf is in a cluster, accumulate the pvs bits + if (((mleaf_t *)node)->clusterindex >= 0) + { + int i; + unsigned char *pvs = model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes; + for (i = 0;i < pvsbytes;i++) + pvsbuffer[i] |= pvs[i]; + } +} + +//Calculates a PVS that is the inclusive or of all leafs within radius pixels +//of the given point. +static int Mod_Q1BSP_FatPVS(dp_model_t *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbufferlength, qboolean merge) +{ + int bytes = model->brush.num_pvsclusterbytes; + bytes = min(bytes, pvsbufferlength); + if (r_novis.integer || r_trippy.integer || !model->brush.num_pvsclusters || !Mod_Q1BSP_GetPVS(model, org)) + { + memset(pvsbuffer, 0xFF, bytes); + return bytes; + } + if (!merge) + memset(pvsbuffer, 0, bytes); + Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode); + return bytes; +} + +static void Mod_Q1BSP_RoundUpToHullSize(dp_model_t *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs) +{ + vec3_t size; + const hull_t *hull; + + VectorSubtract(inmaxs, inmins, size); + if (cmodel->brush.ishlbsp) + { + if (size[0] < 3) + hull = &cmodel->brushq1.hulls[0]; // 0x0x0 + else if (size[0] <= 32) + { + if (size[2] < 54) // pick the nearest of 36 or 72 + hull = &cmodel->brushq1.hulls[3]; // 32x32x36 + else + hull = &cmodel->brushq1.hulls[1]; // 32x32x72 + } + else + hull = &cmodel->brushq1.hulls[2]; // 64x64x64 + } + else + { + if (size[0] < 3) + hull = &cmodel->brushq1.hulls[0]; // 0x0x0 + else if (size[0] <= 32) + hull = &cmodel->brushq1.hulls[1]; // 32x32x56 + else + hull = &cmodel->brushq1.hulls[2]; // 64x64x88 + } + VectorCopy(inmins, outmins); + VectorAdd(inmins, hull->clip_size, outmaxs); +} + +static int Mod_Q1BSP_CreateShadowMesh(dp_model_t *mod) +{ + int j; + int numshadowmeshtriangles = 0; + msurface_t *surface; + if (cls.state == ca_dedicated) + return 0; + // make a single combined shadow mesh to allow optimized shadow volume creation + + for (j = 0, surface = mod->data_surfaces;j < mod->num_surfaces;j++, surface++) + { + surface->num_firstshadowmeshtriangle = numshadowmeshtriangles; + numshadowmeshtriangles += surface->num_triangles; + } + mod->brush.shadowmesh = Mod_ShadowMesh_Begin(mod->mempool, numshadowmeshtriangles * 3, numshadowmeshtriangles, NULL, NULL, NULL, false, false, true); + for (j = 0, surface = mod->data_surfaces;j < mod->num_surfaces;j++, surface++) + if (surface->num_triangles > 0) + Mod_ShadowMesh_AddMesh(mod->mempool, mod->brush.shadowmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); + mod->brush.shadowmesh = Mod_ShadowMesh_Finish(mod->mempool, mod->brush.shadowmesh, false, r_enableshadowvolumes.integer != 0, false); + if (mod->brush.shadowmesh && mod->brush.shadowmesh->neighbor3i) + Mod_BuildTriangleNeighbors(mod->brush.shadowmesh->neighbor3i, mod->brush.shadowmesh->element3i, mod->brush.shadowmesh->numtriangles); + + return numshadowmeshtriangles; +} + +void Mod_CollisionBIH_TraceLineAgainstSurfaces(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); + +void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, k; + sizebuf_t lumpsb[HEADER_LUMPS]; + mmodel_t *bm; + float dist, modelyawradius, modelradius; + msurface_t *surface; + hullinfo_t hullinfo; + int totalstylesurfaces, totalstyles, stylecounts[256], remapstyles[256]; + model_brush_lightstyleinfo_t styleinfo[256]; + unsigned char *datapointer; + sizebuf_t sb; + + MSG_InitReadBuffer(&sb, (unsigned char *)buffer, (unsigned char *)bufferend - (unsigned char *)buffer); + + mod->type = mod_brushq1; + + mod->brush.isbsp2 = false; + mod->brush.ishlbsp = false; + i = MSG_ReadLittleLong(&sb); + switch(i) + { + case BSPVERSION: + mod->modeldatatypestring = "Q1BSP"; + break; + case 30: + mod->brush.ishlbsp = true; + mod->modeldatatypestring = "HLBSP"; + break; + case ('2' + 'P' * 256 + 'S' * 65536 + 'B' * 16777216): + mod->brush.isbsp2 = true; + mod->brush.isbsp2rmqe = true; // like bsp2 except leaf/node bounds are 16bit (unexpanded) + mod->modeldatatypestring = "Q1BSP2rmqe"; + break; + case ('B' + 'S' * 256 + 'P' * 65536 + '2' * 16777216): + mod->brush.isbsp2 = true; + mod->modeldatatypestring = "Q1BSP2"; + break; + default: + mod->modeldatatypestring = "Unknown BSP"; + Host_Error("Mod_Q1BSP_Load: %s has wrong version number %i: supported versions are 29 (Quake), 30 (Half-Life), \"BSP2\" or \"2PSB\" (rmqe)", mod->name, i); + return; + } + +// fill in hull info + VectorClear (hullinfo.hullsizes[0][0]); + VectorClear (hullinfo.hullsizes[0][1]); + if (mod->brush.ishlbsp) + { + hullinfo.filehulls = 4; + VectorSet (hullinfo.hullsizes[1][0], -16, -16, -36); + VectorSet (hullinfo.hullsizes[1][1], 16, 16, 36); + VectorSet (hullinfo.hullsizes[2][0], -32, -32, -32); + VectorSet (hullinfo.hullsizes[2][1], 32, 32, 32); + VectorSet (hullinfo.hullsizes[3][0], -16, -16, -18); + VectorSet (hullinfo.hullsizes[3][1], 16, 16, 18); + } + else + { + hullinfo.filehulls = 4; + VectorSet (hullinfo.hullsizes[1][0], -16, -16, -24); + VectorSet (hullinfo.hullsizes[1][1], 16, 16, 32); + VectorSet (hullinfo.hullsizes[2][0], -32, -32, -24); + VectorSet (hullinfo.hullsizes[2][1], 32, 32, 64); + } + +// read lumps + for (i = 0; i < HEADER_LUMPS; i++) + { + int offset = MSG_ReadLittleLong(&sb); + int size = MSG_ReadLittleLong(&sb); + if (offset < 0 || offset + size > sb.cursize) + Host_Error("Mod_Q1BSP_Load: %s has invalid lump %i (offset %i, size %i, file size %i)\n", mod->name, i, offset, size, (int)sb.cursize); + MSG_InitReadBuffer(&lumpsb[i], sb.data + offset, size); + } + + mod->soundfromcenter = true; + mod->TraceBox = Mod_Q1BSP_TraceBox; + mod->TraceLine = Mod_Q1BSP_TraceLine; + mod->TracePoint = Mod_Q1BSP_TracePoint; + mod->PointSuperContents = Mod_Q1BSP_PointSuperContents; + mod->TraceLineAgainstSurfaces = Mod_Q1BSP_TraceLineAgainstSurfaces; + mod->brush.TraceLineOfSight = Mod_Q1BSP_TraceLineOfSight; + mod->brush.SuperContentsFromNativeContents = Mod_Q1BSP_SuperContentsFromNativeContents; + mod->brush.NativeContentsFromSuperContents = Mod_Q1BSP_NativeContentsFromSuperContents; + mod->brush.GetPVS = Mod_Q1BSP_GetPVS; + mod->brush.FatPVS = Mod_Q1BSP_FatPVS; + mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS; + mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS; + mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs; + mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters; + mod->brush.LightPoint = Mod_Q1BSP_LightPoint; + mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation; + mod->brush.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint; + mod->brush.RoundUpToHullSize = Mod_Q1BSP_RoundUpToHullSize; + mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf; + mod->Draw = R_Q1BSP_Draw; + mod->DrawDepth = R_Q1BSP_DrawDepth; + mod->DrawDebug = R_Q1BSP_DrawDebug; + mod->DrawPrepass = R_Q1BSP_DrawPrepass; + mod->GetLightInfo = R_Q1BSP_GetLightInfo; + mod->CompileShadowMap = R_Q1BSP_CompileShadowMap; + mod->DrawShadowMap = R_Q1BSP_DrawShadowMap; + mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + mod->DrawLight = R_Q1BSP_DrawLight; + +// load into heap + + mod->brush.qw_md4sum = 0; + mod->brush.qw_md4sum2 = 0; + for (i = 0;i < HEADER_LUMPS;i++) + { + int temp; + if (i == LUMP_ENTITIES) + continue; + temp = Com_BlockChecksum(lumpsb[i].data, lumpsb[i].cursize); + mod->brush.qw_md4sum ^= LittleLong(temp); + if (i == LUMP_VISIBILITY || i == LUMP_LEAFS || i == LUMP_NODES) + continue; + mod->brush.qw_md4sum2 ^= LittleLong(temp); + } + + Mod_Q1BSP_LoadEntities(&lumpsb[LUMP_ENTITIES]); + Mod_Q1BSP_LoadVertexes(&lumpsb[LUMP_VERTEXES]); + Mod_Q1BSP_LoadEdges(&lumpsb[LUMP_EDGES]); + Mod_Q1BSP_LoadSurfedges(&lumpsb[LUMP_SURFEDGES]); + Mod_Q1BSP_LoadTextures(&lumpsb[LUMP_TEXTURES]); + Mod_Q1BSP_LoadLighting(&lumpsb[LUMP_LIGHTING]); + Mod_Q1BSP_LoadPlanes(&lumpsb[LUMP_PLANES]); + Mod_Q1BSP_LoadTexinfo(&lumpsb[LUMP_TEXINFO]); + Mod_Q1BSP_LoadFaces(&lumpsb[LUMP_FACES]); + Mod_Q1BSP_LoadLeaffaces(&lumpsb[LUMP_MARKSURFACES]); + Mod_Q1BSP_LoadVisibility(&lumpsb[LUMP_VISIBILITY]); + // load submodels before leafs because they contain the number of vis leafs + Mod_Q1BSP_LoadSubmodels(&lumpsb[LUMP_MODELS], &hullinfo); + Mod_Q1BSP_LoadLeafs(&lumpsb[LUMP_LEAFS]); + Mod_Q1BSP_LoadNodes(&lumpsb[LUMP_NODES]); + Mod_Q1BSP_LoadClipnodes(&lumpsb[LUMP_CLIPNODES], &hullinfo); + + for (i = 0; i < HEADER_LUMPS; i++) + if (lumpsb[i].readcount != lumpsb[i].cursize && i != LUMP_TEXTURES && i != LUMP_LIGHTING) + Host_Error("Lump %i incorrectly loaded (readcount %i, size %i)\n", i, lumpsb[i].readcount, lumpsb[i].cursize); + + // check if the map supports transparent water rendering + loadmodel->brush.supportwateralpha = Mod_Q1BSP_CheckWaterAlphaSupport(); + + if (mod->brushq1.data_compressedpvs) + Mem_Free(mod->brushq1.data_compressedpvs); + mod->brushq1.data_compressedpvs = NULL; + mod->brushq1.num_compressedpvs = 0; + + Mod_Q1BSP_MakeHull0(); + if (mod_bsp_portalize.integer) + Mod_Q1BSP_MakePortals(); + + mod->numframes = 2; // regular and alternate animation + mod->numskins = 1; + + // make a single combined shadow mesh to allow optimized shadow volume creation + Mod_Q1BSP_CreateShadowMesh(loadmodel); + + if (loadmodel->brush.numsubmodels) + loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); + + // LordHavoc: to clear the fog around the original quake submodel code, I + // will explain: + // first of all, some background info on the submodels: + // model 0 is the map model (the world, named maps/e1m1.bsp for example) + // model 1 and higher are submodels (doors and the like, named *1, *2, etc) + // now the weird for loop itself: + // the loop functions in an odd way, on each iteration it sets up the + // current 'mod' model (which despite the confusing code IS the model of + // the number i), at the end of the loop it duplicates the model to become + // the next submodel, and loops back to set up the new submodel. + + // LordHavoc: now the explanation of my sane way (which works identically): + // set up the world model, then on each submodel copy from the world model + // and set up the submodel with the respective model info. + totalstylesurfaces = 0; + totalstyles = 0; + for (i = 0;i < mod->brush.numsubmodels;i++) + { + memset(stylecounts, 0, sizeof(stylecounts)); + for (k = 0;k < mod->brushq1.submodels[i].numfaces;k++) + { + surface = mod->data_surfaces + mod->brushq1.submodels[i].firstface + k; + for (j = 0;j < MAXLIGHTMAPS;j++) + stylecounts[surface->lightmapinfo->styles[j]]++; + } + for (k = 0;k < 255;k++) + { + totalstyles++; + if (stylecounts[k]) + totalstylesurfaces += stylecounts[k]; + } + } + datapointer = (unsigned char *)Mem_Alloc(mod->mempool, mod->num_surfaces * sizeof(int) + totalstyles * sizeof(model_brush_lightstyleinfo_t) + totalstylesurfaces * sizeof(int *)); + for (i = 0;i < mod->brush.numsubmodels;i++) + { + // LordHavoc: this code was originally at the end of this loop, but + // has been transformed to something more readable at the start here. + + if (i > 0) + { + char name[10]; + // duplicate the basic information + dpsnprintf(name, sizeof(name), "*%i", i); + mod = Mod_FindName(name, loadmodel->name); + // copy the base model to this one + *mod = *loadmodel; + // rename the clone back to its proper name + strlcpy(mod->name, name, sizeof(mod->name)); + mod->brush.parentmodel = loadmodel; + // textures and memory belong to the main model + mod->texturepool = NULL; + mod->mempool = NULL; + mod->brush.GetPVS = NULL; + mod->brush.FatPVS = NULL; + mod->brush.BoxTouchingPVS = NULL; + mod->brush.BoxTouchingLeafPVS = NULL; + mod->brush.BoxTouchingVisibleLeafs = NULL; + mod->brush.FindBoxClusters = NULL; + mod->brush.LightPoint = NULL; + mod->brush.AmbientSoundLevelsForPoint = NULL; + } + + mod->brush.submodel = i; + + if (loadmodel->brush.submodels) + loadmodel->brush.submodels[i] = mod; + + bm = &mod->brushq1.submodels[i]; + + mod->brushq1.hulls[0].firstclipnode = bm->headnode[0]; + for (j=1 ; jbrushq1.hulls[j].firstclipnode = bm->headnode[j]; + mod->brushq1.hulls[j].lastclipnode = mod->brushq1.numclipnodes - 1; + } + + mod->firstmodelsurface = bm->firstface; + mod->nummodelsurfaces = bm->numfaces; + + // set node/leaf parents for this submodel + Mod_Q1BSP_LoadNodes_RecursiveSetParent(mod->brush.data_nodes + mod->brushq1.hulls[0].firstclipnode, NULL); + + // make the model surface list (used by shadowing/lighting) + mod->sortedmodelsurfaces = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int); + Mod_MakeSortedSurfaces(mod); + + // copy the submodel bounds, then enlarge the yaw and rotated bounds according to radius + // (previously this code measured the radius of the vertices of surfaces in the submodel, but that broke submodels that contain only CLIP brushes, which do not produce surfaces) + VectorCopy(bm->mins, mod->normalmins); + VectorCopy(bm->maxs, mod->normalmaxs); + dist = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); + modelyawradius = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); + modelyawradius = dist*dist+modelyawradius*modelyawradius; + modelradius = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); + modelradius = modelyawradius + modelradius * modelradius; + modelyawradius = sqrt(modelyawradius); + modelradius = sqrt(modelradius); + mod->yawmins[0] = mod->yawmins[1] = -modelyawradius; + mod->yawmins[2] = mod->normalmins[2]; + mod->yawmaxs[0] = mod->yawmaxs[1] = modelyawradius; + mod->yawmaxs[2] = mod->normalmaxs[2]; + mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; + mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; + mod->radius = modelradius; + mod->radius2 = modelradius * modelradius; + + // this gets altered below if sky or water is used + mod->DrawSky = NULL; + mod->DrawAddWaterPlanes = NULL; + + // scan surfaces for sky and water and flag the submodel as possessing these features or not + // build lightstyle lists for quick marking of dirty lightmaps when lightstyles flicker + if (mod->nummodelsurfaces) + { + for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++) + if (surface->texture->basematerialflags & MATERIALFLAG_SKY) + break; + if (j < mod->nummodelsurfaces) + mod->DrawSky = R_Q1BSP_DrawSky; + + for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++) + if (surface->texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + break; + if (j < mod->nummodelsurfaces) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + + // build lightstyle update chains + // (used to rapidly mark lightmapupdateflags on many surfaces + // when d_lightstylevalue changes) + memset(stylecounts, 0, sizeof(stylecounts)); + for (k = 0;k < mod->nummodelsurfaces;k++) + { + surface = mod->data_surfaces + mod->firstmodelsurface + k; + for (j = 0;j < MAXLIGHTMAPS;j++) + stylecounts[surface->lightmapinfo->styles[j]]++; + } + mod->brushq1.num_lightstyles = 0; + for (k = 0;k < 255;k++) + { + if (stylecounts[k]) + { + styleinfo[mod->brushq1.num_lightstyles].style = k; + styleinfo[mod->brushq1.num_lightstyles].value = 0; + styleinfo[mod->brushq1.num_lightstyles].numsurfaces = 0; + styleinfo[mod->brushq1.num_lightstyles].surfacelist = (int *)datapointer;datapointer += stylecounts[k] * sizeof(int); + remapstyles[k] = mod->brushq1.num_lightstyles; + mod->brushq1.num_lightstyles++; + } + } + for (k = 0;k < mod->nummodelsurfaces;k++) + { + surface = mod->data_surfaces + mod->firstmodelsurface + k; + for (j = 0;j < MAXLIGHTMAPS;j++) + { + if (surface->lightmapinfo->styles[j] != 255) + { + int r = remapstyles[surface->lightmapinfo->styles[j]]; + styleinfo[r].surfacelist[styleinfo[r].numsurfaces++] = mod->firstmodelsurface + k; + } + } + } + mod->brushq1.data_lightstyleinfo = (model_brush_lightstyleinfo_t *)datapointer;datapointer += mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t); + memcpy(mod->brushq1.data_lightstyleinfo, styleinfo, mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t)); + } + else + { + // LordHavoc: empty submodel(lacrima.bsp has such a glitch) + Con_Printf("warning: empty submodel *%i in %s\n", i+1, loadmodel->name); + } + //mod->brushq1.num_visleafs = bm->visleafs; + + // build a Bounding Interval Hierarchy for culling triangles in light rendering + Mod_MakeCollisionBIH(mod, true, &mod->render_bih); + + if (mod_q1bsp_polygoncollisions.integer) + { + mod->collision_bih = mod->render_bih; + // point traces and contents checks still use the bsp tree + mod->TraceLine = Mod_CollisionBIH_TraceLine; + mod->TraceBox = Mod_CollisionBIH_TraceBox; + mod->TraceBrush = Mod_CollisionBIH_TraceBrush; + mod->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLineAgainstSurfaces; + } + + // generate VBOs and other shared data before cloning submodels + if (i == 0) + { + Mod_BuildVBOs(); + Mod_Q1BSP_LoadMapBrushes(); + //Mod_Q1BSP_ProcessLightList(); + } + } + + Con_DPrintf("Stats for q1bsp model \"%s\": %i faces, %i nodes, %i leafs, %i visleafs, %i visleafportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); +} + +static void Mod_Q2BSP_LoadEntities(lump_t *l) +{ +} + +static void Mod_Q2BSP_LoadPlanes(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadPlanes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadVertices(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadVertices: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadVisibility(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadVisibility: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadNodes(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadNodes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadTexInfo(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadTexInfo: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadFaces(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadLighting(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadLighting: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadLeafs(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadLeafs: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadLeafFaces(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadLeafFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadLeafBrushes(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadLeafBrushes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadEdges(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadEdges: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadSurfEdges(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadSurfEdges: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadBrushes(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadBrushes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadBrushSides(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadAreas(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadAreas: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadAreaPortals(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadAreaPortals: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_LoadModels(lump_t *l) +{ +/* + d_t *in; + m_t *out; + int i, count; + + in = (void *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q2BSP_LoadModels: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel-> = out; + loadmodel->num = count; + + for (i = 0;i < count;i++, in++, out++) + { + } +*/ +} + +static void Mod_Q2BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i; + q2dheader_t *header; + + Host_Error("Mod_Q2BSP_Load: not yet implemented"); + + mod->modeldatatypestring = "Q2BSP"; + + mod->type = mod_brushq2; + + header = (q2dheader_t *)buffer; + + i = LittleLong(header->version); + if (i != Q2BSPVERSION) + Host_Error("Mod_Q2BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q2BSPVERSION); + + mod_base = (unsigned char *)header; + + // swap all the lumps + for (i = 0;i < (int) sizeof(*header) / 4;i++) + ((int *)header)[i] = LittleLong(((int *)header)[i]); + + mod->brush.qw_md4sum = 0; + mod->brush.qw_md4sum2 = 0; + for (i = 0;i < Q2HEADER_LUMPS;i++) + { + if (i == Q2LUMP_ENTITIES) + continue; + mod->brush.qw_md4sum ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + if (i == Q2LUMP_VISIBILITY || i == Q2LUMP_LEAFS || i == Q2LUMP_NODES) + continue; + mod->brush.qw_md4sum2 ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + } + + Mod_Q2BSP_LoadEntities(&header->lumps[Q2LUMP_ENTITIES]); + Mod_Q2BSP_LoadPlanes(&header->lumps[Q2LUMP_PLANES]); + Mod_Q2BSP_LoadVertices(&header->lumps[Q2LUMP_VERTEXES]); + Mod_Q2BSP_LoadVisibility(&header->lumps[Q2LUMP_VISIBILITY]); + Mod_Q2BSP_LoadNodes(&header->lumps[Q2LUMP_NODES]); + Mod_Q2BSP_LoadTexInfo(&header->lumps[Q2LUMP_TEXINFO]); + Mod_Q2BSP_LoadFaces(&header->lumps[Q2LUMP_FACES]); + Mod_Q2BSP_LoadLighting(&header->lumps[Q2LUMP_LIGHTING]); + Mod_Q2BSP_LoadLeafs(&header->lumps[Q2LUMP_LEAFS]); + Mod_Q2BSP_LoadLeafFaces(&header->lumps[Q2LUMP_LEAFFACES]); + Mod_Q2BSP_LoadLeafBrushes(&header->lumps[Q2LUMP_LEAFBRUSHES]); + Mod_Q2BSP_LoadEdges(&header->lumps[Q2LUMP_EDGES]); + Mod_Q2BSP_LoadSurfEdges(&header->lumps[Q2LUMP_SURFEDGES]); + Mod_Q2BSP_LoadBrushes(&header->lumps[Q2LUMP_BRUSHES]); + Mod_Q2BSP_LoadBrushSides(&header->lumps[Q2LUMP_BRUSHSIDES]); + Mod_Q2BSP_LoadAreas(&header->lumps[Q2LUMP_AREAS]); + Mod_Q2BSP_LoadAreaPortals(&header->lumps[Q2LUMP_AREAPORTALS]); + // LordHavoc: must go last because this makes the submodels + Mod_Q2BSP_LoadModels(&header->lumps[Q2LUMP_MODELS]); +} + +static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents); +static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents); + +static void Mod_Q3BSP_LoadEntities(lump_t *l) +{ + const char *data; + char key[128], value[MAX_INPUTLINE]; + float v[3]; + loadmodel->brushq3.num_lightgrid_cellsize[0] = 64; + loadmodel->brushq3.num_lightgrid_cellsize[1] = 64; + loadmodel->brushq3.num_lightgrid_cellsize[2] = 128; + if (!l->filelen) + return; + loadmodel->brush.entities = (char *)Mem_Alloc(loadmodel->mempool, l->filelen + 1); + memcpy(loadmodel->brush.entities, mod_base + l->fileofs, l->filelen); + loadmodel->brush.entities[l->filelen] = 0; + data = loadmodel->brush.entities; + // some Q3 maps override the lightgrid_cellsize with a worldspawn key + // VorteX: q3map2 FS-R generates tangentspace deluxemaps for q3bsp and sets 'deluxeMaps' key + loadmodel->brushq3.deluxemapping = false; + if (data && COM_ParseToken_Simple(&data, false, false, true) && com_token[0] == '{') + { + while (1) + { + if (!COM_ParseToken_Simple(&data, false, false, true)) + break; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + strlcpy(key, com_token + 1, sizeof(key)); + else + strlcpy(key, com_token, sizeof(key)); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + if (!COM_ParseToken_Simple(&data, false, false, true)) + break; // error + strlcpy(value, com_token, sizeof(value)); + if (!strcasecmp("gridsize", key)) // this one is case insensitive to 100% match q3map2 + { +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif +#if 0 + if (sscanf(value, "%f %f %f", &v[0], &v[1], &v[2]) == 3 && v[0] != 0 && v[1] != 0 && v[2] != 0) + VectorCopy(v, loadmodel->brushq3.num_lightgrid_cellsize); +#else + VectorSet(v, 64, 64, 128); + if(sscanf(value, "%f %f %f", &v[0], &v[1], &v[2]) != 3) + Con_Printf("Mod_Q3BSP_LoadEntities: funny gridsize \"%s\" in %s, interpreting as \"%f %f %f\" to match q3map2's parsing\n", value, loadmodel->name, v[0], v[1], v[2]); + if (v[0] != 0 && v[1] != 0 && v[2] != 0) + VectorCopy(v, loadmodel->brushq3.num_lightgrid_cellsize); +#endif + } + else if (!strcmp("deluxeMaps", key)) + { + if (!strcmp(com_token, "1")) + { + loadmodel->brushq3.deluxemapping = true; + loadmodel->brushq3.deluxemapping_modelspace = true; + } + else if (!strcmp(com_token, "2")) + { + loadmodel->brushq3.deluxemapping = true; + loadmodel->brushq3.deluxemapping_modelspace = false; + } + } + } + } +} + +static void Mod_Q3BSP_LoadTextures(lump_t *l) +{ + q3dtexture_t *in; + texture_t *out; + int i, count; + + in = (q3dtexture_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadTextures: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (texture_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->data_textures = out; + loadmodel->num_textures = count; + loadmodel->num_texturesperskin = loadmodel->num_textures; + + for (i = 0;i < count;i++) + { + strlcpy (out[i].name, in[i].name, sizeof (out[i].name)); + out[i].surfaceflags = LittleLong(in[i].surfaceflags); + out[i].supercontents = Mod_Q3BSP_SuperContentsFromNativeContents(loadmodel, LittleLong(in[i].contents)); + Mod_LoadTextureFromQ3Shader(out + i, out[i].name, true, true, TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS); + // restore the surfaceflags and supercontents + out[i].surfaceflags = LittleLong(in[i].surfaceflags); + out[i].supercontents = Mod_Q3BSP_SuperContentsFromNativeContents(loadmodel, LittleLong(in[i].contents)); + } +} + +static void Mod_Q3BSP_LoadPlanes(lump_t *l) +{ + q3dplane_t *in; + mplane_t *out; + int i, count; + + in = (q3dplane_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadPlanes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mplane_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_planes = out; + loadmodel->brush.num_planes = count; + + for (i = 0;i < count;i++, in++, out++) + { + out->normal[0] = LittleFloat(in->normal[0]); + out->normal[1] = LittleFloat(in->normal[1]); + out->normal[2] = LittleFloat(in->normal[2]); + out->dist = LittleFloat(in->dist); + PlaneClassify(out); + } +} + +static void Mod_Q3BSP_LoadBrushSides(lump_t *l) +{ + q3dbrushside_t *in; + q3mbrushside_t *out; + int i, n, count; + + in = (q3dbrushside_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_brushsides = out; + loadmodel->brush.num_brushsides = count; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(in->planeindex); + if (n < 0 || n >= loadmodel->brush.num_planes) + Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); + out->plane = loadmodel->brush.data_planes + n; + n = LittleLong(in->textureindex); + if (n < 0 || n >= loadmodel->num_textures) + Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); + out->texture = loadmodel->data_textures + n; + } +} + +static void Mod_Q3BSP_LoadBrushSides_IG(lump_t *l) +{ + q3dbrushside_ig_t *in; + q3mbrushside_t *out; + int i, n, count; + + in = (q3dbrushside_ig_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_brushsides = out; + loadmodel->brush.num_brushsides = count; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(in->planeindex); + if (n < 0 || n >= loadmodel->brush.num_planes) + Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); + out->plane = loadmodel->brush.data_planes + n; + n = LittleLong(in->textureindex); + if (n < 0 || n >= loadmodel->num_textures) + Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); + out->texture = loadmodel->data_textures + n; + } +} + +static void Mod_Q3BSP_LoadBrushes(lump_t *l) +{ + q3dbrush_t *in; + q3mbrush_t *out; + int i, j, n, c, count, maxplanes, q3surfaceflags; + colplanef_t *planes; + + in = (q3dbrush_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadBrushes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3mbrush_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_brushes = out; + loadmodel->brush.num_brushes = count; + + maxplanes = 0; + planes = NULL; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(in->firstbrushside); + c = LittleLong(in->numbrushsides); + if (n < 0 || n + c > loadmodel->brush.num_brushsides) + Host_Error("Mod_Q3BSP_LoadBrushes: invalid brushside range %i : %i (%i brushsides)", n, n + c, loadmodel->brush.num_brushsides); + out->firstbrushside = loadmodel->brush.data_brushsides + n; + out->numbrushsides = c; + n = LittleLong(in->textureindex); + if (n < 0 || n >= loadmodel->num_textures) + Host_Error("Mod_Q3BSP_LoadBrushes: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); + out->texture = loadmodel->data_textures + n; + + // make a list of mplane_t structs to construct a colbrush from + if (maxplanes < out->numbrushsides) + { + maxplanes = out->numbrushsides; + if (planes) + Mem_Free(planes); + planes = (colplanef_t *)Mem_Alloc(tempmempool, sizeof(colplanef_t) * maxplanes); + } + q3surfaceflags = 0; + for (j = 0;j < out->numbrushsides;j++) + { + VectorCopy(out->firstbrushside[j].plane->normal, planes[j].normal); + planes[j].dist = out->firstbrushside[j].plane->dist; + planes[j].q3surfaceflags = out->firstbrushside[j].texture->surfaceflags; + planes[j].texture = out->firstbrushside[j].texture; + q3surfaceflags |= planes[j].q3surfaceflags; + } + // make the colbrush from the planes + out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents, q3surfaceflags, out->texture, true); + + // this whole loop can take a while (e.g. on redstarrepublic4) + CL_KeepaliveMessage(false); + } + if (planes) + Mem_Free(planes); +} + +static void Mod_Q3BSP_LoadEffects(lump_t *l) +{ + q3deffect_t *in; + q3deffect_t *out; + int i, n, count; + + in = (q3deffect_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadEffects: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3deffect_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brushq3.data_effects = out; + loadmodel->brushq3.num_effects = count; + + for (i = 0;i < count;i++, in++, out++) + { + strlcpy (out->shadername, in->shadername, sizeof (out->shadername)); + n = LittleLong(in->brushindex); + if (n >= loadmodel->brush.num_brushes) + { + Con_Printf("Mod_Q3BSP_LoadEffects: invalid brushindex %i (%i brushes), setting to -1\n", n, loadmodel->brush.num_brushes); + n = -1; + } + out->brushindex = n; + out->unknown = LittleLong(in->unknown); + } +} + +static void Mod_Q3BSP_LoadVertices(lump_t *l) +{ + q3dvertex_t *in; + int i, count; + + in = (q3dvertex_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadVertices: funny lump size in %s",loadmodel->name); + loadmodel->brushq3.num_vertices = count = l->filelen / sizeof(*in); + loadmodel->brushq3.data_vertex3f = (float *)Mem_Alloc(loadmodel->mempool, count * (sizeof(float) * (3 + 3 + 2 + 2 + 4))); + loadmodel->brushq3.data_normal3f = loadmodel->brushq3.data_vertex3f + count * 3; + loadmodel->brushq3.data_texcoordtexture2f = loadmodel->brushq3.data_normal3f + count * 3; + loadmodel->brushq3.data_texcoordlightmap2f = loadmodel->brushq3.data_texcoordtexture2f + count * 2; + loadmodel->brushq3.data_color4f = loadmodel->brushq3.data_texcoordlightmap2f + count * 2; + + for (i = 0;i < count;i++, in++) + { + loadmodel->brushq3.data_vertex3f[i * 3 + 0] = LittleFloat(in->origin3f[0]); + loadmodel->brushq3.data_vertex3f[i * 3 + 1] = LittleFloat(in->origin3f[1]); + loadmodel->brushq3.data_vertex3f[i * 3 + 2] = LittleFloat(in->origin3f[2]); + loadmodel->brushq3.data_normal3f[i * 3 + 0] = LittleFloat(in->normal3f[0]); + loadmodel->brushq3.data_normal3f[i * 3 + 1] = LittleFloat(in->normal3f[1]); + loadmodel->brushq3.data_normal3f[i * 3 + 2] = LittleFloat(in->normal3f[2]); + loadmodel->brushq3.data_texcoordtexture2f[i * 2 + 0] = LittleFloat(in->texcoord2f[0]); + loadmodel->brushq3.data_texcoordtexture2f[i * 2 + 1] = LittleFloat(in->texcoord2f[1]); + loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 0] = LittleFloat(in->lightmap2f[0]); + loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 1] = LittleFloat(in->lightmap2f[1]); + // svector/tvector are calculated later in face loading + if(mod_q3bsp_sRGBlightmaps.integer) + { + // if lightmaps are sRGB, vertex colors are sRGB too, so we need to linearize them + // note: when this is in use, lightmap color 128 is no longer neutral, but "sRGB half power" is + // working like this may be odd, but matches q3map2 -gamma 2.2 + if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + { + loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f); + loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f); + loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f); + // we fix the brightness consistently via lightmapscale + } + else + { + loadmodel->brushq3.data_color4f[i * 4 + 0] = Image_LinearFloatFromsRGB(in->color4ub[0]); + loadmodel->brushq3.data_color4f[i * 4 + 1] = Image_LinearFloatFromsRGB(in->color4ub[1]); + loadmodel->brushq3.data_color4f[i * 4 + 2] = Image_LinearFloatFromsRGB(in->color4ub[2]); + } + } + else + { + if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + { + loadmodel->brushq3.data_color4f[i * 4 + 0] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[0]); + loadmodel->brushq3.data_color4f[i * 4 + 1] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[1]); + loadmodel->brushq3.data_color4f[i * 4 + 2] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[2]); + } + else + { + loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f); + loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f); + loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f); + } + } + loadmodel->brushq3.data_color4f[i * 4 + 3] = in->color4ub[3] * (1.0f / 255.0f); + if(in->color4ub[0] != 255 || in->color4ub[1] != 255 || in->color4ub[2] != 255) + loadmodel->lit = true; + } +} + +static void Mod_Q3BSP_LoadTriangles(lump_t *l) +{ + int *in; + int *out; + int i, count; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(int[3])) + Host_Error("Mod_Q3BSP_LoadTriangles: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + + if(!loadmodel->brushq3.num_vertices) + { + if (count) + Con_Printf("Mod_Q3BSP_LoadTriangles: %s has triangles but no vertexes, broken compiler, ignoring problem\n", loadmodel->name); + loadmodel->brushq3.num_triangles = 0; + return; + } + + out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + loadmodel->brushq3.num_triangles = count / 3; + loadmodel->brushq3.data_element3i = out; + + for (i = 0;i < count;i++, in++, out++) + { + *out = LittleLong(*in); + if (*out < 0 || *out >= loadmodel->brushq3.num_vertices) + { + Con_Printf("Mod_Q3BSP_LoadTriangles: invalid vertexindex %i (%i vertices), setting to 0\n", *out, loadmodel->brushq3.num_vertices); + *out = 0; + } + } +} + +static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump) +{ + q3dlightmap_t *input_pointer; + int i; + int j; + int k; + int count; + int powerx; + int powery; + int powerxy; + int powerdxy; + int endlightmap; + int mergegoal; + int lightmapindex; + int realcount; + int realindex; + int mergedwidth; + int mergedheight; + int mergedcolumns; + int mergedrows; + int mergedrowsxcolumns; + int size; + int bytesperpixel; + int rgbmap[3]; + unsigned char *c; + unsigned char *mergedpixels; + unsigned char *mergeddeluxepixels; + unsigned char *mergebuf; + char mapname[MAX_QPATH]; + qboolean external; + unsigned char *inpixels[10000]; // max count q3map2 can output (it uses 4 digits) + char vabuf[1024]; + + // defaults for q3bsp + size = 128; + bytesperpixel = 3; + rgbmap[0] = 2; + rgbmap[1] = 1; + rgbmap[2] = 0; + external = false; + loadmodel->brushq3.lightmapsize = 128; + + if (cls.state == ca_dedicated) + return; + + if(mod_q3bsp_nolightmaps.integer) + { + return; + } + else if(l->filelen) + { + // prefer internal LMs for compatibility (a BSP contains no info on whether external LMs exist) + if (developer_loading.integer) + Con_Printf("Using internal lightmaps\n"); + input_pointer = (q3dlightmap_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*input_pointer)) + Host_Error("Mod_Q3BSP_LoadLightmaps: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*input_pointer); + for(i = 0; i < count; ++i) + inpixels[i] = input_pointer[i].rgb; + } + else + { + // no internal lightmaps + // try external lightmaps + if (developer_loading.integer) + Con_Printf("Using external lightmaps\n"); + FS_StripExtension(loadmodel->name, mapname, sizeof(mapname)); + inpixels[0] = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s/lm_%04d", mapname, 0), false, false, false, NULL); + if(!inpixels[0]) + return; + + // using EXTERNAL lightmaps instead + if(image_width != (int) CeilPowerOf2(image_width) || image_width != image_height) + { + Mem_Free(inpixels[0]); + Host_Error("Mod_Q3BSP_LoadLightmaps: invalid external lightmap size in %s",loadmodel->name); + } + + size = image_width; + bytesperpixel = 4; + rgbmap[0] = 0; + rgbmap[1] = 1; + rgbmap[2] = 2; + external = true; + + for(count = 1; ; ++count) + { + inpixels[count] = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s/lm_%04d", mapname, count), false, false, false, NULL); + if(!inpixels[count]) + break; // we got all of them + if(image_width != size || image_height != size) + { + Mem_Free(inpixels[count]); + inpixels[count] = NULL; + Con_Printf("Mod_Q3BSP_LoadLightmaps: mismatched lightmap size in %s - external lightmap %s/lm_%04d does not match earlier ones\n", loadmodel->name, mapname, count); + break; + } + } + } + + loadmodel->brushq3.lightmapsize = size; + loadmodel->brushq3.num_originallightmaps = count; + + // now check the surfaces to see if any of them index an odd numbered + // lightmap, if so this is not a deluxemapped bsp file + // + // also check what lightmaps are actually used, because q3map2 sometimes + // (always?) makes an unused one at the end, which + // q3map2 sometimes (or always?) makes a second blank lightmap for no + // reason when only one lightmap is used, which can throw off the + // deluxemapping detection method, so check 2-lightmap bsp's specifically + // to see if the second lightmap is blank, if so it is not deluxemapped. + // VorteX: autodetect only if previous attempt to find "deluxeMaps" key + // in Mod_Q3BSP_LoadEntities was failed + if (!loadmodel->brushq3.deluxemapping) + { + loadmodel->brushq3.deluxemapping = !(count & 1); + loadmodel->brushq3.deluxemapping_modelspace = true; + endlightmap = 0; + if (loadmodel->brushq3.deluxemapping) + { + int facecount = faceslump->filelen / sizeof(q3dface_t); + q3dface_t *faces = (q3dface_t *)(mod_base + faceslump->fileofs); + for (i = 0;i < facecount;i++) + { + j = LittleLong(faces[i].lightmapindex); + if (j >= 0) + { + endlightmap = max(endlightmap, j + 1); + if ((j & 1) || j + 1 >= count) + { + loadmodel->brushq3.deluxemapping = false; + break; + } + } + } + } + + // q3map2 sometimes (or always?) makes a second blank lightmap for no + // reason when only one lightmap is used, which can throw off the + // deluxemapping detection method, so check 2-lightmap bsp's specifically + // to see if the second lightmap is blank, if so it is not deluxemapped. + // + // further research has shown q3map2 sometimes creates a deluxemap and two + // blank lightmaps, which must be handled properly as well + if (endlightmap == 1 && count > 1) + { + c = inpixels[1]; + for (i = 0;i < size*size;i++) + { + if (c[bytesperpixel*i + rgbmap[0]]) + break; + if (c[bytesperpixel*i + rgbmap[1]]) + break; + if (c[bytesperpixel*i + rgbmap[2]]) + break; + } + if (i == size*size) + { + // all pixels in the unused lightmap were black... + loadmodel->brushq3.deluxemapping = false; + } + } + } + + Con_DPrintf("%s is %sdeluxemapped\n", loadmodel->name, loadmodel->brushq3.deluxemapping ? "" : "not "); + + // figure out what the most reasonable merge power is within limits + + // find the appropriate NxN dimensions to merge to, to avoid wasted space + realcount = count >> (int)loadmodel->brushq3.deluxemapping; + + // figure out how big the merged texture has to be + mergegoal = 128< size && mergegoal * mergegoal / 4 >= size * size * realcount) + mergegoal /= 2; + mergedwidth = mergegoal; + mergedheight = mergegoal; + // choose non-square size (2x1 aspect) if only half the space is used; + // this really only happens when the entire set fits in one texture, if + // there are multiple textures, we don't worry about shrinking the last + // one to fit, because the driver prefers the same texture size on + // consecutive draw calls... + if (mergedwidth * mergedheight / 2 >= size*size*realcount) + mergedheight /= 2; + + loadmodel->brushq3.num_lightmapmergedwidthpower = 0; + loadmodel->brushq3.num_lightmapmergedheightpower = 0; + while (mergedwidth > size<brushq3.num_lightmapmergedwidthpower) + loadmodel->brushq3.num_lightmapmergedwidthpower++; + while (mergedheight > size<brushq3.num_lightmapmergedheightpower) + loadmodel->brushq3.num_lightmapmergedheightpower++; + loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower = loadmodel->brushq3.num_lightmapmergedwidthpower + loadmodel->brushq3.num_lightmapmergedheightpower + (loadmodel->brushq3.deluxemapping ? 1 : 0); + + powerx = loadmodel->brushq3.num_lightmapmergedwidthpower; + powery = loadmodel->brushq3.num_lightmapmergedheightpower; + powerxy = powerx+powery; + powerdxy = loadmodel->brushq3.deluxemapping + powerxy; + + mergedcolumns = 1 << powerx; + mergedrows = 1 << powery; + mergedrowsxcolumns = 1 << powerxy; + + loadmodel->brushq3.num_mergedlightmaps = (realcount + (1 << powerxy) - 1) >> powerxy; + loadmodel->brushq3.data_lightmaps = (rtexture_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); + if (loadmodel->brushq3.deluxemapping) + loadmodel->brushq3.data_deluxemaps = (rtexture_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); + + // allocate a texture pool if we need it + if (loadmodel->texturepool == NULL && cls.state != ca_dedicated) + loadmodel->texturepool = R_AllocTexturePool(); + + mergedpixels = (unsigned char *) Mem_Alloc(tempmempool, mergedwidth * mergedheight * 4); + mergeddeluxepixels = loadmodel->brushq3.deluxemapping ? (unsigned char *) Mem_Alloc(tempmempool, mergedwidth * mergedheight * 4) : NULL; + for (i = 0;i < count;i++) + { + // figure out which merged lightmap texture this fits into + realindex = i >> (int)loadmodel->brushq3.deluxemapping; + lightmapindex = i >> powerdxy; + + // choose the destination address + mergebuf = (loadmodel->brushq3.deluxemapping && (i & 1)) ? mergeddeluxepixels : mergedpixels; + mergebuf += 4 * (realindex & (mergedcolumns-1))*size + 4 * ((realindex >> powerx) & (mergedrows-1))*mergedwidth*size; + if ((i & 1) == 0 || !loadmodel->brushq3.deluxemapping) + Con_DPrintf("copying original lightmap %i (%ix%i) to %i (at %i,%i)\n", i, size, size, lightmapindex, (realindex & (mergedcolumns-1))*size, ((realindex >> powerx) & (mergedrows-1))*size); + + // convert pixels from RGB or BGRA while copying them into the destination rectangle + for (j = 0;j < size;j++) + for (k = 0;k < size;k++) + { + mergebuf[(j*mergedwidth+k)*4+0] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[0]]; + mergebuf[(j*mergedwidth+k)*4+1] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[1]]; + mergebuf[(j*mergedwidth+k)*4+2] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[2]]; + mergebuf[(j*mergedwidth+k)*4+3] = 255; + } + + // upload texture if this was the last tile being written to the texture + if (((realindex + 1) & (mergedrowsxcolumns - 1)) == 0 || (realindex + 1) == realcount) + { + if (loadmodel->brushq3.deluxemapping && (i & 1)) + loadmodel->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "deluxemap%04i", lightmapindex), mergedwidth, mergedheight, mergeddeluxepixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bspdeluxemaps.integer ? TEXF_COMPRESS : 0), -1, NULL); + else + { + if(mod_q3bsp_sRGBlightmaps.integer) + { + textype_t t; + if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + { + t = TEXTYPE_BGRA; // in stupid fallback mode, we upload lightmaps in sRGB form and just fix their brightness + // we fix the brightness consistently via lightmapscale + } + else + t = TEXTYPE_SRGB_BGRA; // normally, we upload lightmaps in sRGB form (possibly downconverted to linear) + loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "lightmap%04i", lightmapindex), mergedwidth, mergedheight, mergedpixels, t, TEXF_FORCELINEAR | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), -1, NULL); + } + else + { + if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + Image_MakesRGBColorsFromLinear_Lightmap(mergedpixels, mergedpixels, mergedwidth * mergedheight); + loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "lightmap%04i", lightmapindex), mergedwidth, mergedheight, mergedpixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), -1, NULL); + } + } + } + } + + if (mergeddeluxepixels) + Mem_Free(mergeddeluxepixels); + Mem_Free(mergedpixels); + if(external) + { + for(i = 0; i < count; ++i) + Mem_Free(inpixels[i]); + } +} + +static void Mod_Q3BSP_BuildBBoxes(const int *element3i, int num_triangles, const float *vertex3f, float **collisionbbox6f, int *collisionstride, int stride) +{ + int j, k, cnt, tri; + float *mins, *maxs; + const float *vert; + *collisionstride = stride; + if(stride > 0) + { + cnt = (num_triangles + stride - 1) / stride; + *collisionbbox6f = (float *) Mem_Alloc(loadmodel->mempool, sizeof(float[6]) * cnt); + for(j = 0; j < cnt; ++j) + { + mins = &((*collisionbbox6f)[6 * j + 0]); + maxs = &((*collisionbbox6f)[6 * j + 3]); + for(k = 0; k < stride; ++k) + { + tri = j * stride + k; + if(tri >= num_triangles) + break; + vert = &(vertex3f[element3i[3 * tri + 0] * 3]); + if(!k || vert[0] < mins[0]) mins[0] = vert[0]; + if(!k || vert[1] < mins[1]) mins[1] = vert[1]; + if(!k || vert[2] < mins[2]) mins[2] = vert[2]; + if(!k || vert[0] > maxs[0]) maxs[0] = vert[0]; + if(!k || vert[1] > maxs[1]) maxs[1] = vert[1]; + if(!k || vert[2] > maxs[2]) maxs[2] = vert[2]; + vert = &(vertex3f[element3i[3 * tri + 1] * 3]); + if(vert[0] < mins[0]) mins[0] = vert[0]; + if(vert[1] < mins[1]) mins[1] = vert[1]; + if(vert[2] < mins[2]) mins[2] = vert[2]; + if(vert[0] > maxs[0]) maxs[0] = vert[0]; + if(vert[1] > maxs[1]) maxs[1] = vert[1]; + if(vert[2] > maxs[2]) maxs[2] = vert[2]; + vert = &(vertex3f[element3i[3 * tri + 2] * 3]); + if(vert[0] < mins[0]) mins[0] = vert[0]; + if(vert[1] < mins[1]) mins[1] = vert[1]; + if(vert[2] < mins[2]) mins[2] = vert[2]; + if(vert[0] > maxs[0]) maxs[0] = vert[0]; + if(vert[1] > maxs[1]) maxs[1] = vert[1]; + if(vert[2] > maxs[2]) maxs[2] = vert[2]; + } + } + } + else + *collisionbbox6f = NULL; +} + +typedef struct patchtess_s +{ + patchinfo_t info; + + // Auxiliary data used only by patch loading code in Mod_Q3BSP_LoadFaces + int surface_id; + float lodgroup[6]; + float *originalvertex3f; +} patchtess_t; + +#define PATCHTESS_SAME_LODGROUP(a,b) \ + ( \ + (a).lodgroup[0] == (b).lodgroup[0] && \ + (a).lodgroup[1] == (b).lodgroup[1] && \ + (a).lodgroup[2] == (b).lodgroup[2] && \ + (a).lodgroup[3] == (b).lodgroup[3] && \ + (a).lodgroup[4] == (b).lodgroup[4] && \ + (a).lodgroup[5] == (b).lodgroup[5] \ + ) + +static void Mod_Q3BSP_LoadFaces(lump_t *l) +{ + q3dface_t *in, *oldin; + msurface_t *out, *oldout; + int i, oldi, j, n, count, invalidelements, patchsize[2], finalwidth, finalheight, xtess, ytess, finalvertices, finaltriangles, firstvertex, firstelement, type, oldnumtriangles, oldnumtriangles2, meshvertices, meshtriangles, collisionvertices, collisiontriangles, numvertices, numtriangles, cxtess, cytess; + float lightmaptcbase[2], lightmaptcscale[2]; + //int *originalelement3i; + //int *originalneighbor3i; + float *originalvertex3f; + //float *originalsvector3f; + //float *originaltvector3f; + float *originalnormal3f; + float *originalcolor4f; + float *originaltexcoordtexture2f; + float *originaltexcoordlightmap2f; + float *surfacecollisionvertex3f; + int *surfacecollisionelement3i; + float *v; + patchtess_t *patchtess = NULL; + int patchtesscount = 0; + qboolean again; + + in = (q3dface_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (msurface_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->data_surfaces = out; + loadmodel->num_surfaces = count; + + if(count > 0) + patchtess = (patchtess_t*) Mem_Alloc(tempmempool, count * sizeof(*patchtess)); + + i = 0; + oldi = i; + oldin = in; + oldout = out; + meshvertices = 0; + meshtriangles = 0; + for (;i < count;i++, in++, out++) + { + // check face type first + type = LittleLong(in->type); + if (type != Q3FACETYPE_FLAT + && type != Q3FACETYPE_PATCH + && type != Q3FACETYPE_MESH + && type != Q3FACETYPE_FLARE) + { + Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i: unknown face type %i\n", i, type); + continue; + } + + n = LittleLong(in->textureindex); + if (n < 0 || n >= loadmodel->num_textures) + { + Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i: invalid textureindex %i (%i textures)\n", i, n, loadmodel->num_textures); + continue; + } + out->texture = loadmodel->data_textures + n; + n = LittleLong(in->effectindex); + if (n < -1 || n >= loadmodel->brushq3.num_effects) + { + if (developer_extra.integer) + Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid effectindex %i (%i effects)\n", i, out->texture->name, n, loadmodel->brushq3.num_effects); + n = -1; + } + if (n == -1) + out->effect = NULL; + else + out->effect = loadmodel->brushq3.data_effects + n; + + if (cls.state != ca_dedicated) + { + out->lightmaptexture = NULL; + out->deluxemaptexture = r_texture_blanknormalmap; + n = LittleLong(in->lightmapindex); + if (n < 0) + n = -1; + else if (n >= loadmodel->brushq3.num_originallightmaps) + { + if(loadmodel->brushq3.num_originallightmaps != 0) + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid lightmapindex %i (%i lightmaps)\n", i, out->texture->name, n, loadmodel->brushq3.num_originallightmaps); + n = -1; + } + else + { + out->lightmaptexture = loadmodel->brushq3.data_lightmaps[n >> loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower]; + if (loadmodel->brushq3.deluxemapping) + out->deluxemaptexture = loadmodel->brushq3.data_deluxemaps[n >> loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower]; + loadmodel->lit = true; + } + } + + firstvertex = LittleLong(in->firstvertex); + numvertices = LittleLong(in->numvertices); + firstelement = LittleLong(in->firstelement); + numtriangles = LittleLong(in->numelements) / 3; + if (numtriangles * 3 != LittleLong(in->numelements)) + { + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): numelements %i is not a multiple of 3\n", i, out->texture->name, LittleLong(in->numelements)); + continue; + } + if (firstvertex < 0 || firstvertex + numvertices > loadmodel->brushq3.num_vertices) + { + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid vertex range %i : %i (%i vertices)\n", i, out->texture->name, firstvertex, firstvertex + numvertices, loadmodel->brushq3.num_vertices); + continue; + } + if (firstelement < 0 || firstelement + numtriangles * 3 > loadmodel->brushq3.num_triangles * 3) + { + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid element range %i : %i (%i elements)\n", i, out->texture->name, firstelement, firstelement + numtriangles * 3, loadmodel->brushq3.num_triangles * 3); + continue; + } + switch(type) + { + case Q3FACETYPE_FLAT: + case Q3FACETYPE_MESH: + // no processing necessary + break; + case Q3FACETYPE_PATCH: + patchsize[0] = LittleLong(in->specific.patch.patchsize[0]); + patchsize[1] = LittleLong(in->specific.patch.patchsize[1]); + if (numvertices != (patchsize[0] * patchsize[1]) || patchsize[0] < 3 || patchsize[1] < 3 || !(patchsize[0] & 1) || !(patchsize[1] & 1) || patchsize[0] * patchsize[1] >= min(r_subdivisions_maxvertices.integer, r_subdivisions_collision_maxvertices.integer)) + { + Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid patchsize %ix%i\n", i, out->texture->name, patchsize[0], patchsize[1]); + continue; + } + originalvertex3f = loadmodel->brushq3.data_vertex3f + firstvertex * 3; + + // convert patch to Q3FACETYPE_MESH + xtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value); + ytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value); + // bound to user settings + xtess = bound(r_subdivisions_mintess.integer, xtess, r_subdivisions_maxtess.integer); + ytess = bound(r_subdivisions_mintess.integer, ytess, r_subdivisions_maxtess.integer); + // bound to sanity settings + xtess = bound(0, xtess, 1024); + ytess = bound(0, ytess, 1024); + + // lower quality collision patches! Same procedure as before, but different cvars + // convert patch to Q3FACETYPE_MESH + cxtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value); + cytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value); + // bound to user settings + cxtess = bound(r_subdivisions_collision_mintess.integer, cxtess, r_subdivisions_collision_maxtess.integer); + cytess = bound(r_subdivisions_collision_mintess.integer, cytess, r_subdivisions_collision_maxtess.integer); + // bound to sanity settings + cxtess = bound(0, cxtess, 1024); + cytess = bound(0, cytess, 1024); + + // store it for the LOD grouping step + patchtess[patchtesscount].info.xsize = patchsize[0]; + patchtess[patchtesscount].info.ysize = patchsize[1]; + patchtess[patchtesscount].info.lods[PATCH_LOD_VISUAL].xtess = xtess; + patchtess[patchtesscount].info.lods[PATCH_LOD_VISUAL].ytess = ytess; + patchtess[patchtesscount].info.lods[PATCH_LOD_COLLISION].xtess = cxtess; + patchtess[patchtesscount].info.lods[PATCH_LOD_COLLISION].ytess = cytess; + + patchtess[patchtesscount].surface_id = i; + patchtess[patchtesscount].lodgroup[0] = LittleFloat(in->specific.patch.mins[0]); + patchtess[patchtesscount].lodgroup[1] = LittleFloat(in->specific.patch.mins[1]); + patchtess[patchtesscount].lodgroup[2] = LittleFloat(in->specific.patch.mins[2]); + patchtess[patchtesscount].lodgroup[3] = LittleFloat(in->specific.patch.maxs[0]); + patchtess[patchtesscount].lodgroup[4] = LittleFloat(in->specific.patch.maxs[1]); + patchtess[patchtesscount].lodgroup[5] = LittleFloat(in->specific.patch.maxs[2]); + patchtess[patchtesscount].originalvertex3f = originalvertex3f; + ++patchtesscount; + break; + case Q3FACETYPE_FLARE: + if (developer_extra.integer) + Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): Q3FACETYPE_FLARE not supported (yet)\n", i, out->texture->name); + // don't render it + continue; + } + out->num_vertices = numvertices; + out->num_triangles = numtriangles; + meshvertices += out->num_vertices; + meshtriangles += out->num_triangles; + } + + // Fix patches tesselations so that they make no seams + do + { + again = false; + for(i = 0; i < patchtesscount; ++i) + { + for(j = i+1; j < patchtesscount; ++j) + { + if (!PATCHTESS_SAME_LODGROUP(patchtess[i], patchtess[j])) + continue; + + if (Q3PatchAdjustTesselation(3, &patchtess[i].info, patchtess[i].originalvertex3f, &patchtess[j].info, patchtess[j].originalvertex3f) ) + again = true; + } + } + } + while (again); + + // Calculate resulting number of triangles + collisionvertices = 0; + collisiontriangles = 0; + for(i = 0; i < patchtesscount; ++i) + { + finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_VISUAL].xtess); + finalheight = Q3PatchDimForTess(patchtess[i].info.ysize,patchtess[i].info.lods[PATCH_LOD_VISUAL].ytess); + numvertices = finalwidth * finalheight; + numtriangles = (finalwidth - 1) * (finalheight - 1) * 2; + + oldout[patchtess[i].surface_id].num_vertices = numvertices; + oldout[patchtess[i].surface_id].num_triangles = numtriangles; + meshvertices += oldout[patchtess[i].surface_id].num_vertices; + meshtriangles += oldout[patchtess[i].surface_id].num_triangles; + + finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_COLLISION].xtess); + finalheight = Q3PatchDimForTess(patchtess[i].info.ysize,patchtess[i].info.lods[PATCH_LOD_COLLISION].ytess); + numvertices = finalwidth * finalheight; + numtriangles = (finalwidth - 1) * (finalheight - 1) * 2; + + oldout[patchtess[i].surface_id].num_collisionvertices = numvertices; + oldout[patchtess[i].surface_id].num_collisiontriangles = numtriangles; + collisionvertices += oldout[patchtess[i].surface_id].num_collisionvertices; + collisiontriangles += oldout[patchtess[i].surface_id].num_collisiontriangles; + } + + i = oldi; + in = oldin; + out = oldout; + Mod_AllocSurfMesh(loadmodel->mempool, meshvertices, meshtriangles, false, true, false); + if (collisiontriangles) + { + loadmodel->brush.data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, collisionvertices * sizeof(float[3])); + loadmodel->brush.data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, collisiontriangles * sizeof(int[3])); + } + meshvertices = 0; + meshtriangles = 0; + collisionvertices = 0; + collisiontriangles = 0; + for (;i < count && meshvertices + out->num_vertices <= loadmodel->surfmesh.num_vertices;i++, in++, out++) + { + if (out->num_vertices < 3 || out->num_triangles < 1) + continue; + + type = LittleLong(in->type); + firstvertex = LittleLong(in->firstvertex); + firstelement = LittleLong(in->firstelement); + out->num_firstvertex = meshvertices; + out->num_firsttriangle = meshtriangles; + out->num_firstcollisiontriangle = collisiontriangles; + switch(type) + { + case Q3FACETYPE_FLAT: + case Q3FACETYPE_MESH: + // no processing necessary, except for lightmap merging + for (j = 0;j < out->num_vertices;j++) + { + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 0] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 0]; + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 1] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 1]; + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 2] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 2]; + (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 0] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 0]; + (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 1] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 1]; + (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 2] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 2]; + (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex)[j * 2 + 0] = loadmodel->brushq3.data_texcoordtexture2f[(firstvertex + j) * 2 + 0]; + (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex)[j * 2 + 1] = loadmodel->brushq3.data_texcoordtexture2f[(firstvertex + j) * 2 + 1]; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex)[j * 2 + 0] = loadmodel->brushq3.data_texcoordlightmap2f[(firstvertex + j) * 2 + 0]; + (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex)[j * 2 + 1] = loadmodel->brushq3.data_texcoordlightmap2f[(firstvertex + j) * 2 + 1]; + (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 0] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 0]; + (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 1] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 1]; + (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 2] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 2]; + (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 3] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 3]; + } + for (j = 0;j < out->num_triangles*3;j++) + (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] = loadmodel->brushq3.data_element3i[firstelement + j] + out->num_firstvertex; + break; + case Q3FACETYPE_PATCH: + patchsize[0] = LittleLong(in->specific.patch.patchsize[0]); + patchsize[1] = LittleLong(in->specific.patch.patchsize[1]); + originalvertex3f = loadmodel->brushq3.data_vertex3f + firstvertex * 3; + originalnormal3f = loadmodel->brushq3.data_normal3f + firstvertex * 3; + originaltexcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f + firstvertex * 2; + originaltexcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f + firstvertex * 2; + originalcolor4f = loadmodel->brushq3.data_color4f + firstvertex * 4; + + xtess = ytess = cxtess = cytess = -1; + for(j = 0; j < patchtesscount; ++j) + if(patchtess[j].surface_id == i) + { + xtess = patchtess[j].info.lods[PATCH_LOD_VISUAL].xtess; + ytess = patchtess[j].info.lods[PATCH_LOD_VISUAL].ytess; + cxtess = patchtess[j].info.lods[PATCH_LOD_COLLISION].xtess; + cytess = patchtess[j].info.lods[PATCH_LOD_COLLISION].ytess; + break; + } + if(xtess == -1) + { + Con_Printf("ERROR: patch %d isn't preprocessed?!?\n", i); + xtess = ytess = cxtess = cytess = 0; + } + + finalwidth = Q3PatchDimForTess(patchsize[0],xtess); //((patchsize[0] - 1) * xtess) + 1; + finalheight = Q3PatchDimForTess(patchsize[1],ytess); //((patchsize[1] - 1) * ytess) + 1; + finalvertices = finalwidth * finalheight; + oldnumtriangles = finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2; + type = Q3FACETYPE_MESH; + // generate geometry + // (note: normals are skipped because they get recalculated) + Q3PatchTesselateFloat(3, sizeof(float[3]), (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, xtess, ytess); + Q3PatchTesselateFloat(3, sizeof(float[3]), (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[3]), originalnormal3f, xtess, ytess); + Q3PatchTesselateFloat(2, sizeof(float[2]), (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[2]), originaltexcoordtexture2f, xtess, ytess); + Q3PatchTesselateFloat(2, sizeof(float[2]), (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[2]), originaltexcoordlightmap2f, xtess, ytess); + Q3PatchTesselateFloat(4, sizeof(float[4]), (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[4]), originalcolor4f, xtess, ytess); + Q3PatchTriangleElements((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), finalwidth, finalheight, out->num_firstvertex); + + out->num_triangles = Mod_RemoveDegenerateTriangles(out->num_triangles, (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), loadmodel->surfmesh.data_vertex3f); + + if (developer_extra.integer) + { + if (out->num_triangles < finaltriangles) + Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles, %i degenerate triangles removed (leaving %i)\n", patchsize[0], patchsize[1], out->num_vertices, finaltriangles, finaltriangles - out->num_triangles, out->num_triangles); + else + Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles\n", patchsize[0], patchsize[1], out->num_vertices, out->num_triangles); + } + // q3map does not put in collision brushes for curves... ugh + // build the lower quality collision geometry + finalwidth = Q3PatchDimForTess(patchsize[0],cxtess); //((patchsize[0] - 1) * cxtess) + 1; + finalheight = Q3PatchDimForTess(patchsize[1],cytess); //((patchsize[1] - 1) * cytess) + 1; + finalvertices = finalwidth * finalheight; + oldnumtriangles2 = finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2; + + // legacy collision geometry implementation + out->deprecatedq3data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[3]) * finalvertices); + out->deprecatedq3data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * finaltriangles); + out->num_collisionvertices = finalvertices; + out->num_collisiontriangles = finaltriangles; + Q3PatchTesselateFloat(3, sizeof(float[3]), out->deprecatedq3data_collisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess); + Q3PatchTriangleElements(out->deprecatedq3data_collisionelement3i, finalwidth, finalheight, 0); + + //Mod_SnapVertices(3, out->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), 0.25); + Mod_SnapVertices(3, finalvertices, out->deprecatedq3data_collisionvertex3f, 1); + + out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(finaltriangles, out->deprecatedq3data_collisionelement3i, out->deprecatedq3data_collisionelement3i, out->deprecatedq3data_collisionvertex3f); + + // now optimize the collision mesh by finding triangle bboxes... + Mod_Q3BSP_BuildBBoxes(out->deprecatedq3data_collisionelement3i, out->num_collisiontriangles, out->deprecatedq3data_collisionvertex3f, &out->deprecatedq3data_collisionbbox6f, &out->deprecatedq3num_collisionbboxstride, mod_q3bsp_curves_collisions_stride.integer); + Mod_Q3BSP_BuildBBoxes(loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle, out->num_triangles, loadmodel->surfmesh.data_vertex3f, &out->deprecatedq3data_bbox6f, &out->deprecatedq3num_bboxstride, mod_q3bsp_curves_stride.integer); + + // store collision geometry for BIH collision tree + surfacecollisionvertex3f = loadmodel->brush.data_collisionvertex3f + collisionvertices * 3; + surfacecollisionelement3i = loadmodel->brush.data_collisionelement3i + collisiontriangles * 3; + Q3PatchTesselateFloat(3, sizeof(float[3]), surfacecollisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess); + Q3PatchTriangleElements(surfacecollisionelement3i, finalwidth, finalheight, collisionvertices); + Mod_SnapVertices(3, finalvertices, surfacecollisionvertex3f, 1); +#if 1 + // remove this once the legacy code is removed + { + int nc = out->num_collisiontriangles; +#endif + out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(finaltriangles, surfacecollisionelement3i, surfacecollisionelement3i, loadmodel->brush.data_collisionvertex3f); +#if 1 + if(nc != out->num_collisiontriangles) + { + Con_Printf("number of collision triangles differs between BIH and BSP. FAIL.\n"); + } + } +#endif + + if (developer_extra.integer) + Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve became %i:%i vertices / %i:%i triangles (%i:%i degenerate)\n", patchsize[0], patchsize[1], out->num_vertices, out->num_collisionvertices, oldnumtriangles, oldnumtriangles2, oldnumtriangles - out->num_triangles, oldnumtriangles2 - out->num_collisiontriangles); + + collisionvertices += finalvertices; + collisiontriangles += out->num_collisiontriangles; + break; + default: + break; + } + meshvertices += out->num_vertices; + meshtriangles += out->num_triangles; + for (j = 0, invalidelements = 0;j < out->num_triangles * 3;j++) + if ((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] < out->num_firstvertex || (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] >= out->num_firstvertex + out->num_vertices) + invalidelements++; + if (invalidelements) + { + Con_Printf("Mod_Q3BSP_LoadFaces: Warning: face #%i has %i invalid elements, type = %i, texture->name = \"%s\", texture->surfaceflags = %i, firstvertex = %i, numvertices = %i, firstelement = %i, numelements = %i, elements list:\n", i, invalidelements, type, out->texture->name, out->texture->surfaceflags, firstvertex, out->num_vertices, firstelement, out->num_triangles * 3); + for (j = 0;j < out->num_triangles * 3;j++) + { + Con_Printf(" %i", (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] - out->num_firstvertex); + if ((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] < out->num_firstvertex || (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] >= out->num_firstvertex + out->num_vertices) + (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] = out->num_firstvertex; + } + Con_Print("\n"); + } + // calculate a bounding box + VectorClear(out->mins); + VectorClear(out->maxs); + if (out->num_vertices) + { + if (cls.state != ca_dedicated && out->lightmaptexture) + { + // figure out which part of the merged lightmap this fits into + int lightmapindex = LittleLong(in->lightmapindex) >> (loadmodel->brushq3.deluxemapping ? 1 : 0); + int mergewidth = R_TextureWidth(out->lightmaptexture) / loadmodel->brushq3.lightmapsize; + int mergeheight = R_TextureHeight(out->lightmaptexture) / loadmodel->brushq3.lightmapsize; + lightmapindex &= mergewidth * mergeheight - 1; + lightmaptcscale[0] = 1.0f / mergewidth; + lightmaptcscale[1] = 1.0f / mergeheight; + lightmaptcbase[0] = (lightmapindex % mergewidth) * lightmaptcscale[0]; + lightmaptcbase[1] = (lightmapindex / mergewidth) * lightmaptcscale[1]; + // modify the lightmap texcoords to match this region of the merged lightmap + for (j = 0, v = loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex;j < out->num_vertices;j++, v += 2) + { + v[0] = v[0] * lightmaptcscale[0] + lightmaptcbase[0]; + v[1] = v[1] * lightmaptcscale[1] + lightmaptcbase[1]; + } + } + VectorCopy((loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), out->mins); + VectorCopy((loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), out->maxs); + for (j = 1, v = (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex) + 3;j < out->num_vertices;j++, v += 3) + { + out->mins[0] = min(out->mins[0], v[0]); + out->maxs[0] = max(out->maxs[0], v[0]); + out->mins[1] = min(out->mins[1], v[1]); + out->maxs[1] = max(out->maxs[1], v[1]); + out->mins[2] = min(out->mins[2], v[2]); + out->maxs[2] = max(out->maxs[2], v[2]); + } + out->mins[0] -= 1.0f; + out->mins[1] -= 1.0f; + out->mins[2] -= 1.0f; + out->maxs[0] += 1.0f; + out->maxs[1] += 1.0f; + out->maxs[2] += 1.0f; + } + // set lightmap styles for consistency with q1bsp + //out->lightmapinfo->styles[0] = 0; + //out->lightmapinfo->styles[1] = 255; + //out->lightmapinfo->styles[2] = 255; + //out->lightmapinfo->styles[3] = 255; + } + + i = oldi; + out = oldout; + for (;i < count;i++, out++) + { + if(out->num_vertices && out->num_triangles) + continue; + if(out->num_vertices == 0) + { + Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s) has no vertices, ignoring\n", i, out->texture ? out->texture->name : "(none)"); + if(out->num_triangles == 0) + Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s) has no triangles, ignoring\n", i, out->texture ? out->texture->name : "(none)"); + } + else if(out->num_triangles == 0) + Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s, near %f %f %f) has no triangles, ignoring\n", i, out->texture ? out->texture->name : "(none)", + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[0 * 3 + 0], + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[1 * 3 + 0], + (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[2 * 3 + 0]); + } + + // for per pixel lighting + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + + // generate ushort elements array if possible + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + + // free the no longer needed vertex data + loadmodel->brushq3.num_vertices = 0; + if (loadmodel->brushq3.data_vertex3f) + Mem_Free(loadmodel->brushq3.data_vertex3f); + loadmodel->brushq3.data_vertex3f = NULL; + loadmodel->brushq3.data_normal3f = NULL; + loadmodel->brushq3.data_texcoordtexture2f = NULL; + loadmodel->brushq3.data_texcoordlightmap2f = NULL; + loadmodel->brushq3.data_color4f = NULL; + // free the no longer needed triangle data + loadmodel->brushq3.num_triangles = 0; + if (loadmodel->brushq3.data_element3i) + Mem_Free(loadmodel->brushq3.data_element3i); + loadmodel->brushq3.data_element3i = NULL; + + if(patchtess) + Mem_Free(patchtess); +} + +static void Mod_Q3BSP_LoadModels(lump_t *l) +{ + q3dmodel_t *in; + q3dmodel_t *out; + int i, j, n, c, count; + + in = (q3dmodel_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadModels: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (q3dmodel_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brushq3.data_models = out; + loadmodel->brushq3.num_models = count; + + for (i = 0;i < count;i++, in++, out++) + { + for (j = 0;j < 3;j++) + { + out->mins[j] = LittleFloat(in->mins[j]); + out->maxs[j] = LittleFloat(in->maxs[j]); + } + n = LittleLong(in->firstface); + c = LittleLong(in->numfaces); + if (n < 0 || n + c > loadmodel->num_surfaces) + Host_Error("Mod_Q3BSP_LoadModels: invalid face range %i : %i (%i faces)", n, n + c, loadmodel->num_surfaces); + out->firstface = n; + out->numfaces = c; + n = LittleLong(in->firstbrush); + c = LittleLong(in->numbrushes); + if (n < 0 || n + c > loadmodel->brush.num_brushes) + Host_Error("Mod_Q3BSP_LoadModels: invalid brush range %i : %i (%i brushes)", n, n + c, loadmodel->brush.num_brushes); + out->firstbrush = n; + out->numbrushes = c; + } +} + +static void Mod_Q3BSP_LoadLeafBrushes(lump_t *l) +{ + int *in; + int *out; + int i, n, count; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadLeafBrushes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_leafbrushes = out; + loadmodel->brush.num_leafbrushes = count; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(*in); + if (n < 0 || n >= loadmodel->brush.num_brushes) + Host_Error("Mod_Q3BSP_LoadLeafBrushes: invalid brush index %i (%i brushes)", n, loadmodel->brush.num_brushes); + *out = n; + } +} + +static void Mod_Q3BSP_LoadLeafFaces(lump_t *l) +{ + int *in; + int *out; + int i, n, count; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadLeafFaces: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_leafsurfaces = out; + loadmodel->brush.num_leafsurfaces = count; + + for (i = 0;i < count;i++, in++, out++) + { + n = LittleLong(*in); + if (n < 0 || n >= loadmodel->num_surfaces) + Host_Error("Mod_Q3BSP_LoadLeafFaces: invalid face index %i (%i faces)", n, loadmodel->num_surfaces); + *out = n; + } +} + +static void Mod_Q3BSP_LoadLeafs(lump_t *l) +{ + q3dleaf_t *in; + mleaf_t *out; + int i, j, n, c, count; + + in = (q3dleaf_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadLeafs: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_leafs = out; + loadmodel->brush.num_leafs = count; + + for (i = 0;i < count;i++, in++, out++) + { + out->parent = NULL; + out->plane = NULL; + out->clusterindex = LittleLong(in->clusterindex); + out->areaindex = LittleLong(in->areaindex); + for (j = 0;j < 3;j++) + { + // yes the mins/maxs are ints + out->mins[j] = LittleLong(in->mins[j]) - 1; + out->maxs[j] = LittleLong(in->maxs[j]) + 1; + } + n = LittleLong(in->firstleafface); + c = LittleLong(in->numleaffaces); + if (n < 0 || n + c > loadmodel->brush.num_leafsurfaces) + Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafsurface range %i : %i (%i leafsurfaces)", n, n + c, loadmodel->brush.num_leafsurfaces); + out->firstleafsurface = loadmodel->brush.data_leafsurfaces + n; + out->numleafsurfaces = c; + n = LittleLong(in->firstleafbrush); + c = LittleLong(in->numleafbrushes); + if (n < 0 || n + c > loadmodel->brush.num_leafbrushes) + Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafbrush range %i : %i (%i leafbrushes)", n, n + c, loadmodel->brush.num_leafbrushes); + out->firstleafbrush = loadmodel->brush.data_leafbrushes + n; + out->numleafbrushes = c; + } +} + +static void Mod_Q3BSP_LoadNodes(lump_t *l) +{ + q3dnode_t *in; + mnode_t *out; + int i, j, n, count; + + in = (q3dnode_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadNodes: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + if (count == 0) + Host_Error("Mod_Q3BSP_LoadNodes: missing BSP tree in %s",loadmodel->name); + out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + + loadmodel->brush.data_nodes = out; + loadmodel->brush.num_nodes = count; + + for (i = 0;i < count;i++, in++, out++) + { + out->parent = NULL; + n = LittleLong(in->planeindex); + if (n < 0 || n >= loadmodel->brush.num_planes) + Host_Error("Mod_Q3BSP_LoadNodes: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); + out->plane = loadmodel->brush.data_planes + n; + for (j = 0;j < 2;j++) + { + n = LittleLong(in->childrenindex[j]); + if (n >= 0) + { + if (n >= loadmodel->brush.num_nodes) + Host_Error("Mod_Q3BSP_LoadNodes: invalid child node index %i (%i nodes)", n, loadmodel->brush.num_nodes); + out->children[j] = loadmodel->brush.data_nodes + n; + } + else + { + n = -1 - n; + if (n >= loadmodel->brush.num_leafs) + Host_Error("Mod_Q3BSP_LoadNodes: invalid child leaf index %i (%i leafs)", n, loadmodel->brush.num_leafs); + out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + n); + } + } + for (j = 0;j < 3;j++) + { + // yes the mins/maxs are ints + out->mins[j] = LittleLong(in->mins[j]) - 1; + out->maxs[j] = LittleLong(in->maxs[j]) + 1; + } + } + + // set the parent pointers + Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL); +} + +static void Mod_Q3BSP_LoadLightGrid(lump_t *l) +{ + q3dlightgrid_t *in; + q3dlightgrid_t *out; + int count; + int i; + + in = (q3dlightgrid_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadLightGrid: funny lump size in %s",loadmodel->name); + loadmodel->brushq3.num_lightgrid_scale[0] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[0]; + loadmodel->brushq3.num_lightgrid_scale[1] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[1]; + loadmodel->brushq3.num_lightgrid_scale[2] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[2]; + loadmodel->brushq3.num_lightgrid_imins[0] = (int)ceil(loadmodel->brushq3.data_models->mins[0] * loadmodel->brushq3.num_lightgrid_scale[0]); + loadmodel->brushq3.num_lightgrid_imins[1] = (int)ceil(loadmodel->brushq3.data_models->mins[1] * loadmodel->brushq3.num_lightgrid_scale[1]); + loadmodel->brushq3.num_lightgrid_imins[2] = (int)ceil(loadmodel->brushq3.data_models->mins[2] * loadmodel->brushq3.num_lightgrid_scale[2]); + loadmodel->brushq3.num_lightgrid_imaxs[0] = (int)floor(loadmodel->brushq3.data_models->maxs[0] * loadmodel->brushq3.num_lightgrid_scale[0]); + loadmodel->brushq3.num_lightgrid_imaxs[1] = (int)floor(loadmodel->brushq3.data_models->maxs[1] * loadmodel->brushq3.num_lightgrid_scale[1]); + loadmodel->brushq3.num_lightgrid_imaxs[2] = (int)floor(loadmodel->brushq3.data_models->maxs[2] * loadmodel->brushq3.num_lightgrid_scale[2]); + loadmodel->brushq3.num_lightgrid_isize[0] = loadmodel->brushq3.num_lightgrid_imaxs[0] - loadmodel->brushq3.num_lightgrid_imins[0] + 1; + loadmodel->brushq3.num_lightgrid_isize[1] = loadmodel->brushq3.num_lightgrid_imaxs[1] - loadmodel->brushq3.num_lightgrid_imins[1] + 1; + loadmodel->brushq3.num_lightgrid_isize[2] = loadmodel->brushq3.num_lightgrid_imaxs[2] - loadmodel->brushq3.num_lightgrid_imins[2] + 1; + count = loadmodel->brushq3.num_lightgrid_isize[0] * loadmodel->brushq3.num_lightgrid_isize[1] * loadmodel->brushq3.num_lightgrid_isize[2]; + Matrix4x4_CreateScale3(&loadmodel->brushq3.num_lightgrid_indexfromworld, loadmodel->brushq3.num_lightgrid_scale[0], loadmodel->brushq3.num_lightgrid_scale[1], loadmodel->brushq3.num_lightgrid_scale[2]); + Matrix4x4_ConcatTranslate(&loadmodel->brushq3.num_lightgrid_indexfromworld, -loadmodel->brushq3.num_lightgrid_imins[0] * loadmodel->brushq3.num_lightgrid_cellsize[0], -loadmodel->brushq3.num_lightgrid_imins[1] * loadmodel->brushq3.num_lightgrid_cellsize[1], -loadmodel->brushq3.num_lightgrid_imins[2] * loadmodel->brushq3.num_lightgrid_cellsize[2]); + + // if lump is empty there is nothing to load, we can deal with that in the LightPoint code + if (l->filelen) + { + if (l->filelen < count * (int)sizeof(*in)) + { + Con_Printf("Mod_Q3BSP_LoadLightGrid: invalid lightgrid lump size %i bytes, should be %i bytes (%ix%ix%i)", l->filelen, (int)(count * sizeof(*in)), loadmodel->brushq3.num_lightgrid_isize[0], loadmodel->brushq3.num_lightgrid_isize[1], loadmodel->brushq3.num_lightgrid_isize[2]); + return; // ignore the grid if we cannot understand it + } + if (l->filelen != count * (int)sizeof(*in)) + Con_Printf("Mod_Q3BSP_LoadLightGrid: Warning: calculated lightgrid size %i bytes does not match lump size %i\n", (int)(count * sizeof(*in)), l->filelen); + out = (q3dlightgrid_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); + loadmodel->brushq3.data_lightgrid = out; + loadmodel->brushq3.num_lightgrid = count; + // no swapping or validation necessary + memcpy(out, in, count * (int)sizeof(*out)); + + if(mod_q3bsp_sRGBlightmaps.integer) + { + if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + { + // we fix the brightness consistently via lightmapscale + } + else + { + for(i = 0; i < count; ++i) + { + out[i].ambientrgb[0] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[0]) * 255.0f + 0.5f); + out[i].ambientrgb[1] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[1]) * 255.0f + 0.5f); + out[i].ambientrgb[2] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[2]) * 255.0f + 0.5f); + out[i].diffusergb[0] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[0]) * 255.0f + 0.5f); + out[i].diffusergb[1] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[1]) * 255.0f + 0.5f); + out[i].diffusergb[2] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[2]) * 255.0f + 0.5f); + } + } + } + else + { + if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + { + for(i = 0; i < count; ++i) + { + out[i].ambientrgb[0] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[0]) * 255.0f + 0.5f); + out[i].ambientrgb[1] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[1]) * 255.0f + 0.5f); + out[i].ambientrgb[2] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[2]) * 255.0f + 0.5f); + out[i].diffusergb[0] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[0]) * 255.0f + 0.5f); + out[i].diffusergb[1] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[1]) * 255.0f + 0.5f); + out[i].diffusergb[2] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[2]) * 255.0f + 0.5f); + } + } + else + { + // all is good + } + } + } +} + +static void Mod_Q3BSP_LoadPVS(lump_t *l) +{ + q3dpvs_t *in; + int totalchains; + + if (l->filelen == 0) + { + int i; + // unvised maps often have cluster indices even without pvs, so check + // leafs to find real number of clusters + loadmodel->brush.num_pvsclusters = 1; + for (i = 0;i < loadmodel->brush.num_leafs;i++) + loadmodel->brush.num_pvsclusters = max(loadmodel->brush.num_pvsclusters, loadmodel->brush.data_leafs[i].clusterindex + 1); + + // create clusters + loadmodel->brush.num_pvsclusterbytes = (loadmodel->brush.num_pvsclusters + 7) / 8; + totalchains = loadmodel->brush.num_pvsclusterbytes * loadmodel->brush.num_pvsclusters; + loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, totalchains); + memset(loadmodel->brush.data_pvsclusters, 0xFF, totalchains); + return; + } + + in = (q3dpvs_t *)(mod_base + l->fileofs); + if (l->filelen < 9) + Host_Error("Mod_Q3BSP_LoadPVS: funny lump size in %s",loadmodel->name); + + loadmodel->brush.num_pvsclusters = LittleLong(in->numclusters); + loadmodel->brush.num_pvsclusterbytes = LittleLong(in->chainlength); + if (loadmodel->brush.num_pvsclusterbytes < ((loadmodel->brush.num_pvsclusters + 7) / 8)) + Host_Error("Mod_Q3BSP_LoadPVS: (chainlength = %i) < ((numclusters = %i) + 7) / 8", loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.num_pvsclusters); + totalchains = loadmodel->brush.num_pvsclusterbytes * loadmodel->brush.num_pvsclusters; + if (l->filelen < totalchains + (int)sizeof(*in)) + Host_Error("Mod_Q3BSP_LoadPVS: lump too small ((numclusters = %i) * (chainlength = %i) + sizeof(q3dpvs_t) == %i bytes, lump is %i bytes)", loadmodel->brush.num_pvsclusters, loadmodel->brush.num_pvsclusterbytes, (int)(totalchains + sizeof(*in)), l->filelen); + + loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, totalchains); + memcpy(loadmodel->brush.data_pvsclusters, (unsigned char *)(in + 1), totalchains); +} + +static void Mod_Q3BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal) +{ + int i, j, k, index[3]; + float transformed[3], blend1, blend2, blend, stylescale = 1; + q3dlightgrid_t *a, *s; + + // scale lighting by lightstyle[0] so that darkmode in dpmod works properly + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + // LordHavoc: FIXME: is this true? + stylescale = 1; // added while render + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + stylescale = r_refdef.scene.rtlightstylevalue[0]; + break; + } + + if (!model->brushq3.num_lightgrid) + { + ambientcolor[0] = stylescale; + ambientcolor[1] = stylescale; + ambientcolor[2] = stylescale; + return; + } + + Matrix4x4_Transform(&model->brushq3.num_lightgrid_indexfromworld, p, transformed); + //Matrix4x4_Print(&model->brushq3.num_lightgrid_indexfromworld); + //Con_Printf("%f %f %f transformed %f %f %f clamped ", p[0], p[1], p[2], transformed[0], transformed[1], transformed[2]); + transformed[0] = bound(0, transformed[0], model->brushq3.num_lightgrid_isize[0] - 1); + transformed[1] = bound(0, transformed[1], model->brushq3.num_lightgrid_isize[1] - 1); + transformed[2] = bound(0, transformed[2], model->brushq3.num_lightgrid_isize[2] - 1); + index[0] = (int)floor(transformed[0]); + index[1] = (int)floor(transformed[1]); + index[2] = (int)floor(transformed[2]); + //Con_Printf("%f %f %f index %i %i %i:\n", transformed[0], transformed[1], transformed[2], index[0], index[1], index[2]); + + // now lerp the values + VectorClear(diffusenormal); + a = &model->brushq3.data_lightgrid[(index[2] * model->brushq3.num_lightgrid_isize[1] + index[1]) * model->brushq3.num_lightgrid_isize[0] + index[0]]; + for (k = 0;k < 2;k++) + { + blend1 = (k ? (transformed[2] - index[2]) : (1 - (transformed[2] - index[2]))); + if (blend1 < 0.001f || index[2] + k >= model->brushq3.num_lightgrid_isize[2]) + continue; + for (j = 0;j < 2;j++) + { + blend2 = blend1 * (j ? (transformed[1] - index[1]) : (1 - (transformed[1] - index[1]))); + if (blend2 < 0.001f || index[1] + j >= model->brushq3.num_lightgrid_isize[1]) + continue; + for (i = 0;i < 2;i++) + { + blend = blend2 * (i ? (transformed[0] - index[0]) : (1 - (transformed[0] - index[0]))) * stylescale; + if (blend < 0.001f || index[0] + i >= model->brushq3.num_lightgrid_isize[0]) + continue; + s = a + (k * model->brushq3.num_lightgrid_isize[1] + j) * model->brushq3.num_lightgrid_isize[0] + i; + VectorMA(ambientcolor, blend * (1.0f / 128.0f), s->ambientrgb, ambientcolor); + VectorMA(diffusecolor, blend * (1.0f / 128.0f), s->diffusergb, diffusecolor); + // this uses the mod_md3_sin table because the values are + // already in the 0-255 range, the 64+ bias fetches a cosine + // instead of a sine value + diffusenormal[0] += blend * (mod_md3_sin[64 + s->diffuseyaw] * mod_md3_sin[s->diffusepitch]); + diffusenormal[1] += blend * (mod_md3_sin[ s->diffuseyaw] * mod_md3_sin[s->diffusepitch]); + diffusenormal[2] += blend * (mod_md3_sin[64 + s->diffusepitch]); + //Con_Printf("blend %f: ambient %i %i %i, diffuse %i %i %i, diffusepitch %i diffuseyaw %i (%f %f, normal %f %f %f)\n", blend, s->ambientrgb[0], s->ambientrgb[1], s->ambientrgb[2], s->diffusergb[0], s->diffusergb[1], s->diffusergb[2], s->diffusepitch, s->diffuseyaw, pitch, yaw, (cos(yaw) * cospitch), (sin(yaw) * cospitch), (-sin(pitch))); + } + } + } + + // normalize the light direction before turning + VectorNormalize(diffusenormal); + //Con_Printf("result: ambient %f %f %f diffuse %f %f %f diffusenormal %f %f %f\n", ambientcolor[0], ambientcolor[1], ambientcolor[2], diffusecolor[0], diffusecolor[1], diffusecolor[2], diffusenormal[0], diffusenormal[1], diffusenormal[2]); +} + +static int Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p1[3], double p2[3]) +{ + double t1, t2; + double midf, mid[3]; + int ret, side; + + // check for empty + while (node->plane) + { + // find the point distances + mplane_t *plane = node->plane; + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + } + + if (t1 < 0) + { + if (t2 < 0) + { + node = node->children[1]; + continue; + } + side = 1; + } + else + { + if (t2 >= 0) + { + node = node->children[0]; + continue; + } + side = 0; + } + + midf = t1 / (t1 - t2); + VectorLerp(p1, midf, p2, mid); + + // recurse both sides, front side first + // return 2 if empty is followed by solid (hit something) + // do not return 2 if both are solid or both empty, + // or if start is solid and end is empty + // as these degenerate cases usually indicate the eye is in solid and + // should see the target point anyway + ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ], p1, mid); + if (ret != 0) + return ret; + ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2); + if (ret != 1) + return ret; + return 2; + } + return ((mleaf_t *)node)->clusterindex < 0; +} + +static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end) +{ + if (model->brush.submodel || mod_q3bsp_tracelineofsight_brushes.integer) + { + trace_t trace; + model->TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK); + return trace.fraction == 1; + } + else + { + double tracestart[3], traceend[3]; + VectorCopy(start, tracestart); + VectorCopy(end, traceend); + return !Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend); + } +} + +void Mod_CollisionBIH_TracePoint(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask) +{ + const bih_t *bih; + const bih_leaf_t *leaf; + const bih_node_t *node; + const colbrushf_t *brush; + int axis; + int nodenum; + int nodestackpos = 0; + int nodestack[1024]; + + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + + bih = &model->collision_bih; + if(!bih->nodes) + return; + + nodenum = bih->rootnode; + nodestack[nodestackpos++] = nodenum; + while (nodestackpos) + { + nodenum = nodestack[--nodestackpos]; + node = bih->nodes + nodenum; +#if 1 + if (!BoxesOverlap(start, start, node->mins, node->maxs)) + continue; +#endif + if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) + { + axis = node->type - BIH_SPLITX; + if (start[axis] >= node->frontmin) + nodestack[nodestackpos++] = node->front; + if (start[axis] <= node->backmax) + nodestack[nodestackpos++] = node->back; + } + else if (node->type == BIH_UNORDERED) + { + for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) + { + leaf = bih->leafs + node->children[axis]; +#if 1 + if (!BoxesOverlap(start, start, leaf->mins, leaf->maxs)) + continue; +#endif + switch(leaf->type) + { + case BIH_BRUSH: + brush = model->brush.data_brushes[leaf->itemindex].colbrushf; + Collision_TracePointBrushFloat(trace, start, brush); + break; + case BIH_COLLISIONTRIANGLE: + // collision triangle - skipped because they have no volume + break; + case BIH_RENDERTRIANGLE: + // render triangle - skipped because they have no volume + break; + } + } + } + } +} + +static void Mod_CollisionBIH_TraceLineShared(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, const bih_t *bih) +{ + const bih_leaf_t *leaf; + const bih_node_t *node; + const colbrushf_t *brush; + const int *e; + const texture_t *texture; + vec3_t nodebigmins, nodebigmaxs, nodestart, nodeend, sweepnodemins, sweepnodemaxs; + vec_t d1, d2, d3, d4, f, nodestackline[1024][6]; + int axis, nodenum, nodestackpos = 0, nodestack[1024]; + + if(!bih->nodes) + return; + + if (VectorCompare(start, end)) + { + Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + return; + } + + nodenum = bih->rootnode; + + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + + // push first node + nodestackline[nodestackpos][0] = start[0]; + nodestackline[nodestackpos][1] = start[1]; + nodestackline[nodestackpos][2] = start[2]; + nodestackline[nodestackpos][3] = end[0]; + nodestackline[nodestackpos][4] = end[1]; + nodestackline[nodestackpos][5] = end[2]; + nodestack[nodestackpos++] = nodenum; + while (nodestackpos) + { + nodenum = nodestack[--nodestackpos]; + node = bih->nodes + nodenum; + VectorCopy(nodestackline[nodestackpos], nodestart); + VectorCopy(nodestackline[nodestackpos] + 3, nodeend); + sweepnodemins[0] = min(nodestart[0], nodeend[0]); sweepnodemins[1] = min(nodestart[1], nodeend[1]); sweepnodemins[2] = min(nodestart[2], nodeend[2]); sweepnodemaxs[0] = max(nodestart[0], nodeend[0]); sweepnodemaxs[1] = max(nodestart[1], nodeend[1]); sweepnodemaxs[2] = max(nodestart[2], nodeend[2]); + if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, node->mins, node->maxs)) + continue; + if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) + { + // recurse children of the split + axis = node->type - BIH_SPLITX; + d1 = node->backmax - nodestart[axis]; + d2 = node->backmax - nodeend[axis]; + d3 = nodestart[axis] - node->frontmin; + d4 = nodeend[axis] - node->frontmin; + switch((d1 < 0) | ((d2 < 0) << 1) | ((d3 < 0) << 2) | ((d4 < 0) << 3)) + { + case 0: /* >>>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 1: /* <>>> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 2: /* ><>> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 3: /* <<>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 4: /* >><> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 5: /* <><> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 6: /* ><<> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 7: /* <<<> */ f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 8: /* >>>< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 9: /* <>>< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 10: /* ><>< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 11: /* <<>< */ f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 12: /* >><< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 13: /* <><< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 14: /* ><<< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 15: /* <<<< */ break; + } + } + else if (node->type == BIH_UNORDERED) + { + // calculate sweep bounds for this node + // copy node bounds into local variables + VectorCopy(node->mins, nodebigmins); + VectorCopy(node->maxs, nodebigmaxs); + // clip line to this node bounds + axis = 0; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + axis = 1; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + axis = 2; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + // some of the line intersected the enlarged node box + // calculate sweep bounds for this node + sweepnodemins[0] = min(nodestart[0], nodeend[0]); sweepnodemins[1] = min(nodestart[1], nodeend[1]); sweepnodemins[2] = min(nodestart[2], nodeend[2]); sweepnodemaxs[0] = max(nodestart[0], nodeend[0]); sweepnodemaxs[1] = max(nodestart[1], nodeend[1]); sweepnodemaxs[2] = max(nodestart[2], nodeend[2]); + for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) + { + leaf = bih->leafs + node->children[axis]; + if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, leaf->mins, leaf->maxs)) + continue; + switch(leaf->type) + { + case BIH_BRUSH: + brush = model->brush.data_brushes[leaf->itemindex].colbrushf; + Collision_TraceLineBrushFloat(trace, start, end, brush, brush); + break; + case BIH_COLLISIONTRIANGLE: + if (!mod_q3bsp_curves_collisions.integer) + continue; + e = model->brush.data_collisionelement3i + 3*leaf->itemindex; + texture = model->data_textures + leaf->textureindex; + Collision_TraceLineTriangleFloat(trace, start, end, model->brush.data_collisionvertex3f + e[0] * 3, model->brush.data_collisionvertex3f + e[1] * 3, model->brush.data_collisionvertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); + break; + case BIH_RENDERTRIANGLE: + e = model->surfmesh.data_element3i + 3*leaf->itemindex; + texture = model->data_textures + leaf->textureindex; + Collision_TraceLineTriangleFloat(trace, start, end, model->surfmesh.data_vertex3f + e[0] * 3, model->surfmesh.data_vertex3f + e[1] * 3, model->surfmesh.data_vertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); + break; + } + } + } + } +} + +void Mod_CollisionBIH_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + if (VectorCompare(start, end)) + { + Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + return; + } + Mod_CollisionBIH_TraceLineShared(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, &model->collision_bih); +} + +void Mod_CollisionBIH_TraceBrush(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, colbrushf_t *thisbrush_start, colbrushf_t *thisbrush_end, int hitsupercontentsmask) +{ + const bih_t *bih; + const bih_leaf_t *leaf; + const bih_node_t *node; + const colbrushf_t *brush; + const int *e; + const texture_t *texture; + vec3_t start, end, startmins, startmaxs, endmins, endmaxs, mins, maxs; + vec3_t nodebigmins, nodebigmaxs, nodestart, nodeend, sweepnodemins, sweepnodemaxs; + vec_t d1, d2, d3, d4, f, nodestackline[1024][6]; + int axis, nodenum, nodestackpos = 0, nodestack[1024]; + + if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(thisbrush_start->mins, thisbrush_start->maxs) && VectorCompare(thisbrush_end->mins, thisbrush_end->maxs)) + { + if (VectorCompare(thisbrush_start->mins, thisbrush_end->mins)) + Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, thisbrush_start->mins, hitsupercontentsmask); + else + Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, thisbrush_start->mins, thisbrush_end->mins, hitsupercontentsmask); + return; + } + + bih = &model->collision_bih; + if(!bih->nodes) + return; + nodenum = bih->rootnode; + + // box trace, performed as brush trace + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + + // calculate tracebox-like parameters for efficient culling + VectorMAM(0.5f, thisbrush_start->mins, 0.5f, thisbrush_start->maxs, start); + VectorMAM(0.5f, thisbrush_end->mins, 0.5f, thisbrush_end->maxs, end); + VectorSubtract(thisbrush_start->mins, start, startmins); + VectorSubtract(thisbrush_start->maxs, start, startmaxs); + VectorSubtract(thisbrush_end->mins, end, endmins); + VectorSubtract(thisbrush_end->maxs, end, endmaxs); + mins[0] = min(startmins[0], endmins[0]); + mins[1] = min(startmins[1], endmins[1]); + mins[2] = min(startmins[2], endmins[2]); + maxs[0] = max(startmaxs[0], endmaxs[0]); + maxs[1] = max(startmaxs[1], endmaxs[1]); + maxs[2] = max(startmaxs[2], endmaxs[2]); + + // push first node + nodestackline[nodestackpos][0] = start[0]; + nodestackline[nodestackpos][1] = start[1]; + nodestackline[nodestackpos][2] = start[2]; + nodestackline[nodestackpos][3] = end[0]; + nodestackline[nodestackpos][4] = end[1]; + nodestackline[nodestackpos][5] = end[2]; + nodestack[nodestackpos++] = nodenum; + while (nodestackpos) + { + nodenum = nodestack[--nodestackpos]; + node = bih->nodes + nodenum; + VectorCopy(nodestackline[nodestackpos], nodestart); + VectorCopy(nodestackline[nodestackpos] + 3, nodeend); + sweepnodemins[0] = min(nodestart[0], nodeend[0]) + mins[0]; sweepnodemins[1] = min(nodestart[1], nodeend[1]) + mins[1]; sweepnodemins[2] = min(nodestart[2], nodeend[2]) + mins[2]; sweepnodemaxs[0] = max(nodestart[0], nodeend[0]) + maxs[0]; sweepnodemaxs[1] = max(nodestart[1], nodeend[1]) + maxs[1]; sweepnodemaxs[2] = max(nodestart[2], nodeend[2]) + maxs[2]; + if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, node->mins, node->maxs)) + continue; + if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) + { + // recurse children of the split + axis = node->type - BIH_SPLITX; + d1 = node->backmax - nodestart[axis] - mins[axis]; + d2 = node->backmax - nodeend[axis] - mins[axis]; + d3 = nodestart[axis] - node->frontmin + maxs[axis]; + d4 = nodeend[axis] - node->frontmin + maxs[axis]; + switch((d1 < 0) | ((d2 < 0) << 1) | ((d3 < 0) << 2) | ((d4 < 0) << 3)) + { + case 0: /* >>>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 1: /* <>>> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 2: /* ><>> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 3: /* <<>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 4: /* >><> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 5: /* <><> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 6: /* ><<> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 7: /* <<<> */ f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 8: /* >>>< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 9: /* <>>< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 10: /* ><>< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 11: /* <<>< */ f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; + case 12: /* >><< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 13: /* <><< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 14: /* ><<< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; + case 15: /* <<<< */ break; + } + } + else if (node->type == BIH_UNORDERED) + { + // calculate sweep bounds for this node + // copy node bounds into local variables and expand to get Minkowski Sum of the two shapes + VectorSubtract(node->mins, maxs, nodebigmins); + VectorSubtract(node->maxs, mins, nodebigmaxs); + // clip line to this node bounds + axis = 0; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + axis = 1; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + axis = 2; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } + // some of the line intersected the enlarged node box + // calculate sweep bounds for this node + sweepnodemins[0] = min(nodestart[0], nodeend[0]) + mins[0]; sweepnodemins[1] = min(nodestart[1], nodeend[1]) + mins[1]; sweepnodemins[2] = min(nodestart[2], nodeend[2]) + mins[2]; sweepnodemaxs[0] = max(nodestart[0], nodeend[0]) + maxs[0]; sweepnodemaxs[1] = max(nodestart[1], nodeend[1]) + maxs[1]; sweepnodemaxs[2] = max(nodestart[2], nodeend[2]) + maxs[2]; + for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) + { + leaf = bih->leafs + node->children[axis]; + if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, leaf->mins, leaf->maxs)) + continue; + switch(leaf->type) + { + case BIH_BRUSH: + brush = model->brush.data_brushes[leaf->itemindex].colbrushf; + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush, brush); + break; + case BIH_COLLISIONTRIANGLE: + if (!mod_q3bsp_curves_collisions.integer) + continue; + e = model->brush.data_collisionelement3i + 3*leaf->itemindex; + texture = model->data_textures + leaf->textureindex; + Collision_TraceBrushTriangleFloat(trace, thisbrush_start, thisbrush_end, model->brush.data_collisionvertex3f + e[0] * 3, model->brush.data_collisionvertex3f + e[1] * 3, model->brush.data_collisionvertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); + break; + case BIH_RENDERTRIANGLE: + e = model->surfmesh.data_element3i + 3*leaf->itemindex; + texture = model->data_textures + leaf->textureindex; + Collision_TraceBrushTriangleFloat(trace, thisbrush_start, thisbrush_end, model->surfmesh.data_vertex3f + e[0] * 3, model->surfmesh.data_vertex3f + e[1] * 3, model->surfmesh.data_vertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); + break; + } + } + } + } +} + +void Mod_CollisionBIH_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask) +{ + colboxbrushf_t thisbrush_start, thisbrush_end; + vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; + + // box trace, performed as brush trace + VectorAdd(start, boxmins, boxstartmins); + VectorAdd(start, boxmaxs, boxstartmaxs); + VectorAdd(end, boxmins, boxendmins); + VectorAdd(end, boxmaxs, boxendmaxs); + Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); + Mod_CollisionBIH_TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask); +} + + +static int Mod_CollisionBIH_PointSuperContents(struct model_s *model, int frame, const vec3_t point) +{ + trace_t trace; + Mod_CollisionBIH_TracePoint(model, NULL, NULL, &trace, point, 0); + return trace.startsupercontents; +} + +void Mod_CollisionBIH_TracePoint_Mesh(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask) +{ +#if 0 + // broken - needs to be modified to count front faces and backfaces to figure out if it is in solid + vec3_t end; + int hitsupercontents; + VectorSet(end, start[0], start[1], model->normalmins[2]); +#endif + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; +#if 0 + Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + hitsupercontents = trace->hitsupercontents; + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + trace->startsupercontents = hitsupercontents; +#endif +} + +int Mod_CollisionBIH_PointSuperContents_Mesh(struct model_s *model, int frame, const vec3_t start) +{ +#if 0 + // broken - needs to be modified to count front faces and backfaces to figure out if it is in solid + trace_t trace; + vec3_t end; + VectorSet(end, start[0], start[1], model->normalmins[2]); + memset(&trace, 0, sizeof(trace)); + trace.fraction = 1; + trace.realfraction = 1; + trace.hitsupercontentsmask = 0; + Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + return trace.hitsupercontents; +#else + return 0; +#endif +} + +static void Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t point, int markframe) +{ + int i; + mleaf_t *leaf; + colbrushf_t *brush; + // find which leaf the point is in + while (node->plane) + node = node->children[(node->plane->type < 3 ? point[node->plane->type] : DotProduct(point, node->plane->normal)) < node->plane->dist]; + // point trace the brushes + leaf = (mleaf_t *)node; + for (i = 0;i < leaf->numleafbrushes;i++) + { + brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; + if (brush && brush->markframe != markframe && BoxesOverlap(point, point, brush->mins, brush->maxs)) + { + brush->markframe = markframe; + Collision_TracePointBrushFloat(trace, point, brush); + } + } + // can't do point traces on curves (they have no thickness) +} + +static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t start, const vec3_t end, vec_t startfrac, vec_t endfrac, const vec3_t linestart, const vec3_t lineend, int markframe, const vec3_t segmentmins, const vec3_t segmentmaxs) +{ + int i, startside, endside; + float dist1, dist2, midfrac, mid[3], nodesegmentmins[3], nodesegmentmaxs[3]; + mleaf_t *leaf; + msurface_t *surface; + mplane_t *plane; + colbrushf_t *brush; + // walk the tree until we hit a leaf, recursing for any split cases + while (node->plane) + { +#if 0 + if (!BoxesOverlap(segmentmins, segmentmaxs, node->mins, node->maxs)) + return; + Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[0], start, end, startfrac, endfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); + node = node->children[1]; +#else + // abort if this part of the bsp tree can not be hit by this trace +// if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) +// return; + plane = node->plane; + // axial planes are much more common than non-axial, so an optimized + // axial case pays off here + if (plane->type < 3) + { + dist1 = start[plane->type] - plane->dist; + dist2 = end[plane->type] - plane->dist; + } + else + { + dist1 = DotProduct(start, plane->normal) - plane->dist; + dist2 = DotProduct(end, plane->normal) - plane->dist; + } + startside = dist1 < 0; + endside = dist2 < 0; + if (startside == endside) + { + // most of the time the line fragment is on one side of the plane + node = node->children[startside]; + } + else + { + // line crosses node plane, split the line + dist1 = PlaneDiff(linestart, plane); + dist2 = PlaneDiff(lineend, plane); + midfrac = dist1 / (dist1 - dist2); + VectorLerp(linestart, midfrac, lineend, mid); + // take the near side first + Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[startside], start, mid, startfrac, midfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); + // if we found an impact on the front side, don't waste time + // exploring the far side + if (midfrac <= trace->realfraction) + Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[endside], mid, end, midfrac, endfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); + return; + } +#endif + } + // abort if this part of the bsp tree can not be hit by this trace +// if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) +// return; + // hit a leaf + nodesegmentmins[0] = min(start[0], end[0]) - 1; + nodesegmentmins[1] = min(start[1], end[1]) - 1; + nodesegmentmins[2] = min(start[2], end[2]) - 1; + nodesegmentmaxs[0] = max(start[0], end[0]) + 1; + nodesegmentmaxs[1] = max(start[1], end[1]) + 1; + nodesegmentmaxs[2] = max(start[2], end[2]) + 1; + // line trace the brushes + leaf = (mleaf_t *)node; +#if 0 + if (!BoxesOverlap(segmentmins, segmentmaxs, leaf->mins, leaf->maxs)) + return; +#endif + for (i = 0;i < leaf->numleafbrushes;i++) + { + brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; + if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs)) + { + brush->markframe = markframe; + Collision_TraceLineBrushFloat(trace, linestart, lineend, brush, brush); + } + } + // can't do point traces on curves (they have no thickness) + if (leaf->containscollisionsurfaces && mod_q3bsp_curves_collisions.integer && !VectorCompare(start, end)) + { + // line trace the curves + for (i = 0;i < leaf->numleafsurfaces;i++) + { + surface = model->data_surfaces + leaf->firstleafsurface[i]; + if (surface->num_collisiontriangles && surface->deprecatedq3collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs)) + { + surface->deprecatedq3collisionmarkframe = markframe; + Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); + } + } + } +} + +static void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int markframe, const vec3_t segmentmins, const vec3_t segmentmaxs) +{ + int i; + int sides; + mleaf_t *leaf; + colbrushf_t *brush; + msurface_t *surface; + mplane_t *plane; + float nodesegmentmins[3], nodesegmentmaxs[3]; + // walk the tree until we hit a leaf, recursing for any split cases + while (node->plane) + { +#if 0 + if (!BoxesOverlap(segmentmins, segmentmaxs, node->mins, node->maxs)) + return; + Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, node->children[0], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs); + node = node->children[1]; +#else + // abort if this part of the bsp tree can not be hit by this trace +// if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) +// return; + plane = node->plane; + // axial planes are much more common than non-axial, so an optimized + // axial case pays off here + if (plane->type < 3) + { + // this is an axial plane, compare bounding box directly to it and + // recurse sides accordingly + // recurse down node sides + // use an inlined axial BoxOnPlaneSide to slightly reduce overhead + //sides = BoxOnPlaneSide(nodesegmentmins, nodesegmentmaxs, plane); + //sides = ((segmentmaxs[plane->type] >= plane->dist) | ((segmentmins[plane->type] < plane->dist) << 1)); + sides = ((segmentmaxs[plane->type] >= plane->dist) + ((segmentmins[plane->type] < plane->dist) * 2)); + } + else + { + // this is a non-axial plane, so check if the start and end boxes + // are both on one side of the plane to handle 'diagonal' cases + sides = BoxOnPlaneSide(thisbrush_start->mins, thisbrush_start->maxs, plane) | BoxOnPlaneSide(thisbrush_end->mins, thisbrush_end->maxs, plane); + } + if (sides == 3) + { + // segment crosses plane + Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, node->children[0], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs); + sides = 2; + } + // if sides == 0 then the trace itself is bogus (Not A Number values), + // in this case we simply pretend the trace hit nothing + if (sides == 0) + return; // ERROR: NAN bounding box! + // take whichever side the segment box is on + node = node->children[sides - 1]; +#endif + } + // abort if this part of the bsp tree can not be hit by this trace +// if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) +// return; + nodesegmentmins[0] = max(segmentmins[0], node->mins[0] - 1); + nodesegmentmins[1] = max(segmentmins[1], node->mins[1] - 1); + nodesegmentmins[2] = max(segmentmins[2], node->mins[2] - 1); + nodesegmentmaxs[0] = min(segmentmaxs[0], node->maxs[0] + 1); + nodesegmentmaxs[1] = min(segmentmaxs[1], node->maxs[1] + 1); + nodesegmentmaxs[2] = min(segmentmaxs[2], node->maxs[2] + 1); + // hit a leaf + leaf = (mleaf_t *)node; +#if 0 + if (!BoxesOverlap(segmentmins, segmentmaxs, leaf->mins, leaf->maxs)) + return; +#endif + for (i = 0;i < leaf->numleafbrushes;i++) + { + brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; + if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs)) + { + brush->markframe = markframe; + Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush, brush); + } + } + if (leaf->containscollisionsurfaces && mod_q3bsp_curves_collisions.integer) + { + for (i = 0;i < leaf->numleafsurfaces;i++) + { + surface = model->data_surfaces + leaf->firstleafsurface[i]; + if (surface->num_collisiontriangles && surface->deprecatedq3collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs)) + { + surface->deprecatedq3collisionmarkframe = markframe; + Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); + } + } + } +} + + +static int markframe = 0; + +static void Mod_Q3BSP_TracePoint(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask) +{ + int i; + q3mbrush_t *brush; + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + if (mod_collision_bih.integer) + Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + else if (model->brush.submodel) + { + for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) + if (brush->colbrushf) + Collision_TracePointBrushFloat(trace, start, brush->colbrushf); + } + else + Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, ++markframe); +} + +static void Mod_Q3BSP_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + int i; + float segmentmins[3], segmentmaxs[3]; + msurface_t *surface; + q3mbrush_t *brush; + + if (VectorCompare(start, end)) + { + Mod_Q3BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask); + return; + } + + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + segmentmins[0] = min(start[0], end[0]) - 1; + segmentmins[1] = min(start[1], end[1]) - 1; + segmentmins[2] = min(start[2], end[2]) - 1; + segmentmaxs[0] = max(start[0], end[0]) + 1; + segmentmaxs[1] = max(start[1], end[1]) + 1; + segmentmaxs[2] = max(start[2], end[2]) + 1; + if (mod_collision_bih.integer) + Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + else if (model->brush.submodel) + { + for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) + if (brush->colbrushf && BoxesOverlap(segmentmins, segmentmaxs, brush->colbrushf->mins, brush->colbrushf->maxs)) + Collision_TraceLineBrushFloat(trace, start, end, brush->colbrushf, brush->colbrushf); + if (mod_q3bsp_curves_collisions.integer) + for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++) + if (surface->num_collisiontriangles && BoxesOverlap(segmentmins, segmentmaxs, surface->mins, surface->maxs)) + Collision_TraceLineTriangleMeshFloat(trace, start, end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); + } + else + Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, end, 0, 1, start, end, ++markframe, segmentmins, segmentmaxs); +} + +static void Mod_Q3BSP_TraceBrush(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, colbrushf_t *start, colbrushf_t *end, int hitsupercontentsmask) +{ + float segmentmins[3], segmentmaxs[3]; + int i; + msurface_t *surface; + q3mbrush_t *brush; + + if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(start->mins, start->maxs) && VectorCompare(end->mins, end->maxs)) + { + if (VectorCompare(start->mins, end->mins)) + Mod_Q3BSP_TracePoint(model, frameblend, skeleton, trace, start->mins, hitsupercontentsmask); + else + Mod_Q3BSP_TraceLine(model, frameblend, skeleton, trace, start->mins, end->mins, hitsupercontentsmask); + return; + } + + // box trace, performed as brush trace + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; + trace->realfraction = 1; + trace->hitsupercontentsmask = hitsupercontentsmask; + segmentmins[0] = min(start->mins[0], end->mins[0]); + segmentmins[1] = min(start->mins[1], end->mins[1]); + segmentmins[2] = min(start->mins[2], end->mins[2]); + segmentmaxs[0] = max(start->maxs[0], end->maxs[0]); + segmentmaxs[1] = max(start->maxs[1], end->maxs[1]); + segmentmaxs[2] = max(start->maxs[2], end->maxs[2]); + if (mod_collision_bih.integer) + Mod_CollisionBIH_TraceBrush(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask); + else if (model->brush.submodel) + { + for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) + if (brush->colbrushf && BoxesOverlap(segmentmins, segmentmaxs, brush->colbrushf->mins, brush->colbrushf->maxs)) + Collision_TraceBrushBrushFloat(trace, start, end, brush->colbrushf, brush->colbrushf); + if (mod_q3bsp_curves_collisions.integer) + for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++) + if (surface->num_collisiontriangles && BoxesOverlap(segmentmins, segmentmaxs, surface->mins, surface->maxs)) + Collision_TraceBrushTriangleMeshFloat(trace, start, end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); + } + else + Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, end, ++markframe, segmentmins, segmentmaxs); +} + +static void Mod_Q3BSP_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask) +{ + colboxbrushf_t thisbrush_start, thisbrush_end; + vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; + + // box trace, performed as brush trace + VectorAdd(start, boxmins, boxstartmins); + VectorAdd(start, boxmaxs, boxstartmaxs); + VectorAdd(end, boxmins, boxendmins); + VectorAdd(end, boxmaxs, boxendmaxs); + Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); + Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); + Mod_Q3BSP_TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask); +} + +static int Mod_Q3BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point) +{ + int i; + int supercontents = 0; + q3mbrush_t *brush; + if (mod_collision_bih.integer) + { + supercontents = Mod_CollisionBIH_PointSuperContents(model, frame, point); + } + // test if the point is inside each brush + else if (model->brush.submodel) + { + // submodels are effectively one leaf + for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) + if (brush->colbrushf && Collision_PointInsideBrushFloat(point, brush->colbrushf)) + supercontents |= brush->colbrushf->supercontents; + } + else + { + mnode_t *node = model->brush.data_nodes; + mleaf_t *leaf; + // find which leaf the point is in + while (node->plane) + node = node->children[(node->plane->type < 3 ? point[node->plane->type] : DotProduct(point, node->plane->normal)) < node->plane->dist]; + leaf = (mleaf_t *)node; + // now check the brushes in the leaf + for (i = 0;i < leaf->numleafbrushes;i++) + { + brush = model->brush.data_brushes + leaf->firstleafbrush[i]; + if (brush->colbrushf && Collision_PointInsideBrushFloat(point, brush->colbrushf)) + supercontents |= brush->colbrushf->supercontents; + } + } + return supercontents; +} + +void Mod_CollisionBIH_TraceLineAgainstSurfaces(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask) +{ + Mod_CollisionBIH_TraceLineShared(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, &model->render_bih); +} + + +bih_t *Mod_MakeCollisionBIH(dp_model_t *model, qboolean userendersurfaces, bih_t *out) +{ + int j; + int bihnumleafs; + int bihmaxnodes; + int brushindex; + int triangleindex; + int bihleafindex; + int nummodelbrushes = model->nummodelbrushes; + int nummodelsurfaces = model->nummodelsurfaces; + const int *e; + const int *collisionelement3i; + const float *collisionvertex3f; + const int *renderelement3i; + const float *rendervertex3f; + bih_leaf_t *bihleafs; + bih_node_t *bihnodes; + int *temp_leafsort; + int *temp_leafsortscratch; + const msurface_t *surface; + const q3mbrush_t *brush; + + // find out how many BIH leaf nodes we need + bihnumleafs = 0; + if (userendersurfaces) + { + for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) + bihnumleafs += surface->num_triangles; + } + else + { + for (brushindex = 0, brush = model->brush.data_brushes + brushindex+model->firstmodelbrush;brushindex < nummodelbrushes;brushindex++, brush++) + if (brush->colbrushf) + bihnumleafs++; + for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) + { + if (surface->texture->basematerialflags & MATERIALFLAG_MESHCOLLISIONS) + bihnumleafs += surface->num_triangles + surface->num_collisiontriangles; + else + bihnumleafs += surface->num_collisiontriangles; + } + } + + if (!bihnumleafs) + return NULL; + + // allocate the memory for the BIH leaf nodes + bihleafs = (bih_leaf_t *)Mem_Alloc(loadmodel->mempool, sizeof(bih_leaf_t) * bihnumleafs); + + // now populate the BIH leaf nodes + bihleafindex = 0; + + // add render surfaces + renderelement3i = model->surfmesh.data_element3i; + rendervertex3f = model->surfmesh.data_vertex3f; + for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) + { + for (triangleindex = 0, e = renderelement3i + 3*surface->num_firsttriangle;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + if (!userendersurfaces && !(surface->texture->basematerialflags & MATERIALFLAG_MESHCOLLISIONS)) + continue; + bihleafs[bihleafindex].type = BIH_RENDERTRIANGLE; + bihleafs[bihleafindex].textureindex = surface->texture - model->data_textures; + bihleafs[bihleafindex].surfaceindex = surface - model->data_surfaces; + bihleafs[bihleafindex].itemindex = triangleindex+surface->num_firsttriangle; + bihleafs[bihleafindex].mins[0] = min(rendervertex3f[3*e[0]+0], min(rendervertex3f[3*e[1]+0], rendervertex3f[3*e[2]+0])) - 1; + bihleafs[bihleafindex].mins[1] = min(rendervertex3f[3*e[0]+1], min(rendervertex3f[3*e[1]+1], rendervertex3f[3*e[2]+1])) - 1; + bihleafs[bihleafindex].mins[2] = min(rendervertex3f[3*e[0]+2], min(rendervertex3f[3*e[1]+2], rendervertex3f[3*e[2]+2])) - 1; + bihleafs[bihleafindex].maxs[0] = max(rendervertex3f[3*e[0]+0], max(rendervertex3f[3*e[1]+0], rendervertex3f[3*e[2]+0])) + 1; + bihleafs[bihleafindex].maxs[1] = max(rendervertex3f[3*e[0]+1], max(rendervertex3f[3*e[1]+1], rendervertex3f[3*e[2]+1])) + 1; + bihleafs[bihleafindex].maxs[2] = max(rendervertex3f[3*e[0]+2], max(rendervertex3f[3*e[1]+2], rendervertex3f[3*e[2]+2])) + 1; + bihleafindex++; + } + } + + if (!userendersurfaces) + { + // add collision brushes + for (brushindex = 0, brush = model->brush.data_brushes + brushindex+model->firstmodelbrush;brushindex < nummodelbrushes;brushindex++, brush++) + { + if (!brush->colbrushf) + continue; + bihleafs[bihleafindex].type = BIH_BRUSH; + bihleafs[bihleafindex].textureindex = brush->texture - model->data_textures; + bihleafs[bihleafindex].surfaceindex = -1; + bihleafs[bihleafindex].itemindex = brushindex+model->firstmodelbrush; + VectorCopy(brush->colbrushf->mins, bihleafs[bihleafindex].mins); + VectorCopy(brush->colbrushf->maxs, bihleafs[bihleafindex].maxs); + bihleafindex++; + } + + // add collision surfaces + collisionelement3i = model->brush.data_collisionelement3i; + collisionvertex3f = model->brush.data_collisionvertex3f; + for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) + { + for (triangleindex = 0, e = collisionelement3i + 3*surface->num_firstcollisiontriangle;triangleindex < surface->num_collisiontriangles;triangleindex++, e += 3) + { + bihleafs[bihleafindex].type = BIH_COLLISIONTRIANGLE; + bihleafs[bihleafindex].textureindex = surface->texture - model->data_textures; + bihleafs[bihleafindex].surfaceindex = surface - model->data_surfaces; + bihleafs[bihleafindex].itemindex = triangleindex+surface->num_firstcollisiontriangle; + bihleafs[bihleafindex].mins[0] = min(collisionvertex3f[3*e[0]+0], min(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0])) - 1; + bihleafs[bihleafindex].mins[1] = min(collisionvertex3f[3*e[0]+1], min(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1])) - 1; + bihleafs[bihleafindex].mins[2] = min(collisionvertex3f[3*e[0]+2], min(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2])) - 1; + bihleafs[bihleafindex].maxs[0] = max(collisionvertex3f[3*e[0]+0], max(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0])) + 1; + bihleafs[bihleafindex].maxs[1] = max(collisionvertex3f[3*e[0]+1], max(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1])) + 1; + bihleafs[bihleafindex].maxs[2] = max(collisionvertex3f[3*e[0]+2], max(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2])) + 1; + bihleafindex++; + } + } + } + + // allocate buffers for the produced and temporary data + bihmaxnodes = bihnumleafs + 1; + bihnodes = (bih_node_t *)Mem_Alloc(loadmodel->mempool, sizeof(bih_node_t) * bihmaxnodes); + temp_leafsort = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int) * bihnumleafs * 2); + temp_leafsortscratch = temp_leafsort + bihnumleafs; + + // now build it + BIH_Build(out, bihnumleafs, bihleafs, bihmaxnodes, bihnodes, temp_leafsort, temp_leafsortscratch); + + // we're done with the temporary data + Mem_Free(temp_leafsort); + + // resize the BIH nodes array if it over-allocated + if (out->maxnodes > out->numnodes) + { + out->maxnodes = out->numnodes; + out->nodes = (bih_node_t *)Mem_Realloc(loadmodel->mempool, out->nodes, out->numnodes * sizeof(bih_node_t)); + } + + return out; +} + +static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents) +{ + int supercontents = 0; + if (nativecontents & CONTENTSQ3_SOLID) + supercontents |= SUPERCONTENTS_SOLID; + if (nativecontents & CONTENTSQ3_WATER) + supercontents |= SUPERCONTENTS_WATER; + if (nativecontents & CONTENTSQ3_SLIME) + supercontents |= SUPERCONTENTS_SLIME; + if (nativecontents & CONTENTSQ3_LAVA) + supercontents |= SUPERCONTENTS_LAVA; + if (nativecontents & CONTENTSQ3_BODY) + supercontents |= SUPERCONTENTS_BODY; + if (nativecontents & CONTENTSQ3_CORPSE) + supercontents |= SUPERCONTENTS_CORPSE; + if (nativecontents & CONTENTSQ3_NODROP) + supercontents |= SUPERCONTENTS_NODROP; + if (nativecontents & CONTENTSQ3_PLAYERCLIP) + supercontents |= SUPERCONTENTS_PLAYERCLIP; + if (nativecontents & CONTENTSQ3_MONSTERCLIP) + supercontents |= SUPERCONTENTS_MONSTERCLIP; + if (nativecontents & CONTENTSQ3_DONOTENTER) + supercontents |= SUPERCONTENTS_DONOTENTER; + if (nativecontents & CONTENTSQ3_BOTCLIP) + supercontents |= SUPERCONTENTS_BOTCLIP; + if (!(nativecontents & CONTENTSQ3_TRANSLUCENT)) + supercontents |= SUPERCONTENTS_OPAQUE; + return supercontents; +} + +static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents) +{ + int nativecontents = 0; + if (supercontents & SUPERCONTENTS_SOLID) + nativecontents |= CONTENTSQ3_SOLID; + if (supercontents & SUPERCONTENTS_WATER) + nativecontents |= CONTENTSQ3_WATER; + if (supercontents & SUPERCONTENTS_SLIME) + nativecontents |= CONTENTSQ3_SLIME; + if (supercontents & SUPERCONTENTS_LAVA) + nativecontents |= CONTENTSQ3_LAVA; + if (supercontents & SUPERCONTENTS_BODY) + nativecontents |= CONTENTSQ3_BODY; + if (supercontents & SUPERCONTENTS_CORPSE) + nativecontents |= CONTENTSQ3_CORPSE; + if (supercontents & SUPERCONTENTS_NODROP) + nativecontents |= CONTENTSQ3_NODROP; + if (supercontents & SUPERCONTENTS_PLAYERCLIP) + nativecontents |= CONTENTSQ3_PLAYERCLIP; + if (supercontents & SUPERCONTENTS_MONSTERCLIP) + nativecontents |= CONTENTSQ3_MONSTERCLIP; + if (supercontents & SUPERCONTENTS_DONOTENTER) + nativecontents |= CONTENTSQ3_DONOTENTER; + if (supercontents & SUPERCONTENTS_BOTCLIP) + nativecontents |= CONTENTSQ3_BOTCLIP; + if (!(supercontents & SUPERCONTENTS_OPAQUE)) + nativecontents |= CONTENTSQ3_TRANSLUCENT; + return nativecontents; +} + +static void Mod_Q3BSP_RecursiveFindNumLeafs(mnode_t *node) +{ + int numleafs; + while (node->plane) + { + Mod_Q3BSP_RecursiveFindNumLeafs(node->children[0]); + node = node->children[1]; + } + numleafs = ((mleaf_t *)node - loadmodel->brush.data_leafs) + 1; + if (loadmodel->brush.num_leafs < numleafs) + loadmodel->brush.num_leafs = numleafs; +} + +static void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, j, lumps; + q3dheader_t *header; + float corner[3], yawradius, modelradius; + + mod->modeldatatypestring = "Q3BSP"; + + mod->type = mod_brushq3; + mod->numframes = 2; // although alternate textures are not supported it is annoying to complain about no such frame 1 + mod->numskins = 1; + + header = (q3dheader_t *)buffer; + if((char *) bufferend < (char *) buffer + sizeof(q3dheader_t)) + Host_Error("Mod_Q3BSP_Load: %s is smaller than its header", mod->name); + + i = LittleLong(header->version); + if (i != Q3BSPVERSION && i != Q3BSPVERSION_IG && i != Q3BSPVERSION_LIVE) + Host_Error("Mod_Q3BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q3BSPVERSION); + + mod->soundfromcenter = true; + mod->TraceBox = Mod_Q3BSP_TraceBox; + mod->TraceBrush = Mod_Q3BSP_TraceBrush; + mod->TraceLine = Mod_Q3BSP_TraceLine; + mod->TracePoint = Mod_Q3BSP_TracePoint; + mod->PointSuperContents = Mod_Q3BSP_PointSuperContents; + mod->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLine; + mod->brush.TraceLineOfSight = Mod_Q3BSP_TraceLineOfSight; + mod->brush.SuperContentsFromNativeContents = Mod_Q3BSP_SuperContentsFromNativeContents; + mod->brush.NativeContentsFromSuperContents = Mod_Q3BSP_NativeContentsFromSuperContents; + mod->brush.GetPVS = Mod_Q1BSP_GetPVS; + mod->brush.FatPVS = Mod_Q1BSP_FatPVS; + mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS; + mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS; + mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs; + mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters; + mod->brush.LightPoint = Mod_Q3BSP_LightPoint; + mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation; + mod->brush.AmbientSoundLevelsForPoint = NULL; + mod->brush.RoundUpToHullSize = NULL; + mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf; + mod->Draw = R_Q1BSP_Draw; + mod->DrawDepth = R_Q1BSP_DrawDepth; + mod->DrawDebug = R_Q1BSP_DrawDebug; + mod->DrawPrepass = R_Q1BSP_DrawPrepass; + mod->GetLightInfo = R_Q1BSP_GetLightInfo; + mod->CompileShadowMap = R_Q1BSP_CompileShadowMap; + mod->DrawShadowMap = R_Q1BSP_DrawShadowMap; + mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + mod->DrawLight = R_Q1BSP_DrawLight; + + mod_base = (unsigned char *)header; + + // swap all the lumps + header->ident = LittleLong(header->ident); + header->version = LittleLong(header->version); + lumps = (header->version == Q3BSPVERSION_LIVE) ? Q3HEADER_LUMPS_LIVE : Q3HEADER_LUMPS; + for (i = 0;i < lumps;i++) + { + j = (header->lumps[i].fileofs = LittleLong(header->lumps[i].fileofs)); + if((char *) bufferend < (char *) buffer + j) + Host_Error("Mod_Q3BSP_Load: %s has a lump that starts outside the file!", mod->name); + j += (header->lumps[i].filelen = LittleLong(header->lumps[i].filelen)); + if((char *) bufferend < (char *) buffer + j) + Host_Error("Mod_Q3BSP_Load: %s has a lump that ends outside the file!", mod->name); + } + /* + * NO, do NOT clear them! + * they contain actual data referenced by other stuff. + * Instead, before using the advertisements lump, check header->versio + * again! + * Sorry, but otherwise it breaks memory of the first lump. + for (i = lumps;i < Q3HEADER_LUMPS_MAX;i++) + { + header->lumps[i].fileofs = 0; + header->lumps[i].filelen = 0; + } + */ + + mod->brush.qw_md4sum = 0; + mod->brush.qw_md4sum2 = 0; + for (i = 0;i < lumps;i++) + { + if (i == Q3LUMP_ENTITIES) + continue; + mod->brush.qw_md4sum ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + if (i == Q3LUMP_PVS || i == Q3LUMP_LEAFS || i == Q3LUMP_NODES) + continue; + mod->brush.qw_md4sum2 ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); + + // all this checksumming can take a while, so let's send keepalives here too + CL_KeepaliveMessage(false); + } + + Mod_Q3BSP_LoadEntities(&header->lumps[Q3LUMP_ENTITIES]); + Mod_Q3BSP_LoadTextures(&header->lumps[Q3LUMP_TEXTURES]); + Mod_Q3BSP_LoadPlanes(&header->lumps[Q3LUMP_PLANES]); + if (header->version == Q3BSPVERSION_IG) + Mod_Q3BSP_LoadBrushSides_IG(&header->lumps[Q3LUMP_BRUSHSIDES]); + else + Mod_Q3BSP_LoadBrushSides(&header->lumps[Q3LUMP_BRUSHSIDES]); + Mod_Q3BSP_LoadBrushes(&header->lumps[Q3LUMP_BRUSHES]); + Mod_Q3BSP_LoadEffects(&header->lumps[Q3LUMP_EFFECTS]); + Mod_Q3BSP_LoadVertices(&header->lumps[Q3LUMP_VERTICES]); + Mod_Q3BSP_LoadTriangles(&header->lumps[Q3LUMP_TRIANGLES]); + Mod_Q3BSP_LoadLightmaps(&header->lumps[Q3LUMP_LIGHTMAPS], &header->lumps[Q3LUMP_FACES]); + Mod_Q3BSP_LoadFaces(&header->lumps[Q3LUMP_FACES]); + Mod_Q3BSP_LoadModels(&header->lumps[Q3LUMP_MODELS]); + Mod_Q3BSP_LoadLeafBrushes(&header->lumps[Q3LUMP_LEAFBRUSHES]); + Mod_Q3BSP_LoadLeafFaces(&header->lumps[Q3LUMP_LEAFFACES]); + Mod_Q3BSP_LoadLeafs(&header->lumps[Q3LUMP_LEAFS]); + Mod_Q3BSP_LoadNodes(&header->lumps[Q3LUMP_NODES]); + Mod_Q3BSP_LoadLightGrid(&header->lumps[Q3LUMP_LIGHTGRID]); + Mod_Q3BSP_LoadPVS(&header->lumps[Q3LUMP_PVS]); + loadmodel->brush.numsubmodels = loadmodel->brushq3.num_models; + + // the MakePortals code works fine on the q3bsp data as well + if (mod_bsp_portalize.integer) + Mod_Q1BSP_MakePortals(); + + // FIXME: shader alpha should replace r_wateralpha support in q3bsp + loadmodel->brush.supportwateralpha = true; + + // make a single combined shadow mesh to allow optimized shadow volume creation + Mod_Q1BSP_CreateShadowMesh(loadmodel); + + loadmodel->brush.num_leafs = 0; + Mod_Q3BSP_RecursiveFindNumLeafs(loadmodel->brush.data_nodes); + + if (loadmodel->brush.numsubmodels) + loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); + + mod = loadmodel; + for (i = 0;i < loadmodel->brush.numsubmodels;i++) + { + if (i > 0) + { + char name[10]; + // duplicate the basic information + dpsnprintf(name, sizeof(name), "*%i", i); + mod = Mod_FindName(name, loadmodel->name); + // copy the base model to this one + *mod = *loadmodel; + // rename the clone back to its proper name + strlcpy(mod->name, name, sizeof(mod->name)); + mod->brush.parentmodel = loadmodel; + // textures and memory belong to the main model + mod->texturepool = NULL; + mod->mempool = NULL; + mod->brush.GetPVS = NULL; + mod->brush.FatPVS = NULL; + mod->brush.BoxTouchingPVS = NULL; + mod->brush.BoxTouchingLeafPVS = NULL; + mod->brush.BoxTouchingVisibleLeafs = NULL; + mod->brush.FindBoxClusters = NULL; + mod->brush.LightPoint = NULL; + mod->brush.AmbientSoundLevelsForPoint = NULL; + } + mod->brush.submodel = i; + if (loadmodel->brush.submodels) + loadmodel->brush.submodels[i] = mod; + + // make the model surface list (used by shadowing/lighting) + mod->firstmodelsurface = mod->brushq3.data_models[i].firstface; + mod->nummodelsurfaces = mod->brushq3.data_models[i].numfaces; + mod->firstmodelbrush = mod->brushq3.data_models[i].firstbrush; + mod->nummodelbrushes = mod->brushq3.data_models[i].numbrushes; + mod->sortedmodelsurfaces = (int *)Mem_Alloc(loadmodel->mempool, mod->nummodelsurfaces * sizeof(*mod->sortedmodelsurfaces)); + Mod_MakeSortedSurfaces(mod); + + VectorCopy(mod->brushq3.data_models[i].mins, mod->normalmins); + VectorCopy(mod->brushq3.data_models[i].maxs, mod->normalmaxs); + // enlarge the bounding box to enclose all geometry of this model, + // because q3map2 sometimes lies (mostly to affect the lightgrid), + // which can in turn mess up the farclip (as well as culling when + // outside the level - an unimportant concern) + + //printf("Editing model %d... BEFORE re-bounding: %f %f %f - %f %f %f\n", i, mod->normalmins[0], mod->normalmins[1], mod->normalmins[2], mod->normalmaxs[0], mod->normalmaxs[1], mod->normalmaxs[2]); + for (j = 0;j < mod->nummodelsurfaces;j++) + { + const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; + const float *v = mod->surfmesh.data_vertex3f + 3 * surface->num_firstvertex; + int k; + if (!surface->num_vertices) + continue; + for (k = 0;k < surface->num_vertices;k++, v += 3) + { + mod->normalmins[0] = min(mod->normalmins[0], v[0]); + mod->normalmins[1] = min(mod->normalmins[1], v[1]); + mod->normalmins[2] = min(mod->normalmins[2], v[2]); + mod->normalmaxs[0] = max(mod->normalmaxs[0], v[0]); + mod->normalmaxs[1] = max(mod->normalmaxs[1], v[1]); + mod->normalmaxs[2] = max(mod->normalmaxs[2], v[2]); + } + } + //printf("Editing model %d... AFTER re-bounding: %f %f %f - %f %f %f\n", i, mod->normalmins[0], mod->normalmins[1], mod->normalmins[2], mod->normalmaxs[0], mod->normalmaxs[1], mod->normalmaxs[2]); + corner[0] = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); + corner[1] = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); + corner[2] = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); + modelradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]+corner[2]*corner[2]); + yawradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); + mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; + mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; + mod->yawmaxs[0] = mod->yawmaxs[1] = yawradius; + mod->yawmins[0] = mod->yawmins[1] = -yawradius; + mod->yawmins[2] = mod->normalmins[2]; + mod->yawmaxs[2] = mod->normalmaxs[2]; + mod->radius = modelradius; + mod->radius2 = modelradius * modelradius; + + // this gets altered below if sky or water is used + mod->DrawSky = NULL; + mod->DrawAddWaterPlanes = NULL; + + for (j = 0;j < mod->nummodelsurfaces;j++) + if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & MATERIALFLAG_SKY) + break; + if (j < mod->nummodelsurfaces) + mod->DrawSky = R_Q1BSP_DrawSky; + + for (j = 0;j < mod->nummodelsurfaces;j++) + if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + break; + if (j < mod->nummodelsurfaces) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + + Mod_MakeCollisionBIH(mod, false, &mod->collision_bih); + Mod_MakeCollisionBIH(mod, true, &mod->render_bih); + + // generate VBOs and other shared data before cloning submodels + if (i == 0) + Mod_BuildVBOs(); + } + + if (mod_q3bsp_sRGBlightmaps.integer) + { + if (vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) + { + // actually we do in sRGB fallback with sRGB lightmaps: Image_sRGBFloatFromLinear_Lightmap(Image_LinearFloatFromsRGBFloat(x)) + // neutral point is at Image_sRGBFloatFromLinearFloat(0.5) + // so we need to map Image_sRGBFloatFromLinearFloat(0.5) to 0.5 + // factor is 0.5 / Image_sRGBFloatFromLinearFloat(0.5) + //loadmodel->lightmapscale *= 0.679942f; // fixes neutral level + } + else // if this is NOT set, regular rendering looks right by this requirement anyway + { + /* + // we want color 1 to do the same as without sRGB + // so, we want to map 1 to Image_LinearFloatFromsRGBFloat(2) instead of to 2 + loadmodel->lightmapscale *= 2.476923f; // fixes max level + */ + + // neutral level 0.5 gets uploaded as sRGB and becomes Image_LinearFloatFromsRGBFloat(0.5) + // we need to undo that + loadmodel->lightmapscale *= 2.336f; // fixes neutral level + } + } + + Con_DPrintf("Stats for q3bsp model \"%s\": %i faces, %i nodes, %i leafs, %i clusters, %i clusterportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); +} + +void Mod_IBSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i = LittleLong(((int *)buffer)[1]); + if (i == Q3BSPVERSION || i == Q3BSPVERSION_IG || i == Q3BSPVERSION_LIVE) + Mod_Q3BSP_Load(mod,buffer, bufferend); + else if (i == Q2BSPVERSION) + Mod_Q2BSP_Load(mod,buffer, bufferend); + else + Host_Error("Mod_IBSP_Load: unknown/unsupported version %i", i); +} + +void Mod_MAP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + Host_Error("Mod_MAP_Load: not yet implemented"); +} + +typedef struct objvertex_s +{ + int nextindex; + int submodelindex; + int textureindex; + float v[3]; + float vt[2]; + float vn[3]; +} +objvertex_t; + +static unsigned char nobsp_pvs[1] = {1}; + +void Mod_OBJ_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + const char *textbase = (char *)buffer, *text = textbase; + char *s; + char *argv[512]; + char line[1024]; + char materialname[MAX_QPATH]; + int i, j, l, numvertices, firstvertex, firsttriangle, elementindex, vertexindex, surfacevertices, surfacetriangles, surfaceelements, submodelindex = 0; + int index1, index2, index3; + objvertex_t vfirst, vprev, vcurrent; + int argc; + int linelen; + int numtriangles = 0; + int maxtriangles = 0; + objvertex_t *vertices = NULL; + int linenumber = 0; + int maxtextures = 0, numtextures = 0, textureindex = 0; + int maxv = 0, numv = 1; + int maxvt = 0, numvt = 1; + int maxvn = 0, numvn = 1; + char *texturenames = NULL; + float dist, modelradius, modelyawradius, yawradius; + float *v = NULL; + float *vt = NULL; + float *vn = NULL; + float mins[3]; + float maxs[3]; + float corner[3]; + objvertex_t *thisvertex = NULL; + int vertexhashindex; + int *vertexhashtable = NULL; + objvertex_t *vertexhashdata = NULL; + objvertex_t *vdata = NULL; + int vertexhashsize = 0; + int vertexhashcount = 0; + skinfile_t *skinfiles = NULL; + unsigned char *data = NULL; + int *submodelfirstsurface; + msurface_t *surface; + msurface_t *tempsurfaces; + + memset(&vfirst, 0, sizeof(vfirst)); + memset(&vprev, 0, sizeof(vprev)); + memset(&vcurrent, 0, sizeof(vcurrent)); + + dpsnprintf(materialname, sizeof(materialname), "%s", loadmodel->name); + + loadmodel->modeldatatypestring = "OBJ"; + + loadmodel->type = mod_obj; + loadmodel->soundfromcenter = true; + loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; + loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; + loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; + loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; + loadmodel->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLine; + loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; + loadmodel->brush.TraceLineOfSight = NULL; + loadmodel->brush.SuperContentsFromNativeContents = NULL; + loadmodel->brush.NativeContentsFromSuperContents = NULL; + loadmodel->brush.GetPVS = NULL; + loadmodel->brush.FatPVS = NULL; + loadmodel->brush.BoxTouchingPVS = NULL; + loadmodel->brush.BoxTouchingLeafPVS = NULL; + loadmodel->brush.BoxTouchingVisibleLeafs = NULL; + loadmodel->brush.FindBoxClusters = NULL; + loadmodel->brush.LightPoint = NULL; + loadmodel->brush.FindNonSolidLocation = NULL; + loadmodel->brush.AmbientSoundLevelsForPoint = NULL; + loadmodel->brush.RoundUpToHullSize = NULL; + loadmodel->brush.PointInLeaf = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawDepth = R_Q1BSP_DrawDepth; + loadmodel->DrawDebug = R_Q1BSP_DrawDebug; + loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; + loadmodel->GetLightInfo = R_Q1BSP_GetLightInfo; + loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; + loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; + loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + // make skinscenes for the skins (no groups) + loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + + VectorClear(mins); + VectorClear(maxs); + + // we always have model 0, i.e. the first "submodel" + loadmodel->brush.numsubmodels = 1; + + // parse the OBJ text now + for(;;) + { + static char emptyarg[1] = ""; + if (!*text) + break; + linenumber++; + linelen = 0; + for (linelen = 0;text[linelen] && text[linelen] != '\r' && text[linelen] != '\n';linelen++) + line[linelen] = text[linelen]; + line[linelen] = 0; + for (argc = 0;argc < 4;argc++) + argv[argc] = emptyarg; + argc = 0; + s = line; + while (*s == ' ' || *s == '\t') + s++; + while (*s) + { + argv[argc++] = s; + while (*s > ' ') + s++; + if (!*s) + break; + *s++ = 0; + while (*s == ' ' || *s == '\t') + s++; + } + text += linelen; + if (*text == '\r') + text++; + if (*text == '\n') + text++; + if (!argc) + continue; + if (argv[0][0] == '#') + continue; + if (!strcmp(argv[0], "v")) + { + if (maxv <= numv) + { + maxv = max(maxv * 2, 1024); + v = (float *)Mem_Realloc(tempmempool, v, maxv * sizeof(float[3])); + } + if(mod_obj_orientation.integer) + { + v[numv*3+0] = atof(argv[1]); + v[numv*3+2] = atof(argv[2]); + v[numv*3+1] = atof(argv[3]); + } + else + { + v[numv*3+0] = atof(argv[1]); + v[numv*3+1] = atof(argv[2]); + v[numv*3+2] = atof(argv[3]); + } + numv++; + } + else if (!strcmp(argv[0], "vt")) + { + if (maxvt <= numvt) + { + maxvt = max(maxvt * 2, 1024); + vt = (float *)Mem_Realloc(tempmempool, vt, maxvt * sizeof(float[2])); + } + vt[numvt*2+0] = atof(argv[1]); + vt[numvt*2+1] = 1-atof(argv[2]); + numvt++; + } + else if (!strcmp(argv[0], "vn")) + { + if (maxvn <= numvn) + { + maxvn = max(maxvn * 2, 1024); + vn = (float *)Mem_Realloc(tempmempool, vn, maxvn * sizeof(float[3])); + } + if(mod_obj_orientation.integer) + { + vn[numvn*3+0] = atof(argv[1]); + vn[numvn*3+2] = atof(argv[2]); + vn[numvn*3+1] = atof(argv[3]); + } + else + { + vn[numvn*3+0] = atof(argv[1]); + vn[numvn*3+1] = atof(argv[2]); + vn[numvn*3+2] = atof(argv[3]); + } + numvn++; + } + else if (!strcmp(argv[0], "f")) + { + if (!numtextures) + { + if (maxtextures <= numtextures) + { + maxtextures = max(maxtextures * 2, 256); + texturenames = (char *)Mem_Realloc(loadmodel->mempool, texturenames, maxtextures * MAX_QPATH); + } + textureindex = numtextures++; + strlcpy(texturenames + textureindex*MAX_QPATH, loadmodel->name, MAX_QPATH); + } + for (j = 1;j < argc;j++) + { + index1 = atoi(argv[j]); + while(argv[j][0] && argv[j][0] != '/') + argv[j]++; + if (argv[j][0]) + argv[j]++; + index2 = atoi(argv[j]); + while(argv[j][0] && argv[j][0] != '/') + argv[j]++; + if (argv[j][0]) + argv[j]++; + index3 = atoi(argv[j]); + // negative refers to a recent vertex + // zero means not specified + // positive means an absolute vertex index + if (index1 < 0) + index1 = numv - index1; + if (index2 < 0) + index2 = numvt - index2; + if (index3 < 0) + index3 = numvn - index3; + vcurrent.nextindex = -1; + vcurrent.textureindex = textureindex; + vcurrent.submodelindex = submodelindex; + if (v && index1 >= 0 && index1 < numv) + VectorCopy(v + 3*index1, vcurrent.v); + if (vt && index2 >= 0 && index2 < numvt) + Vector2Copy(vt + 2*index2, vcurrent.vt); + if (vn && index3 >= 0 && index3 < numvn) + VectorCopy(vn + 3*index3, vcurrent.vn); + if (numtriangles == 0) + { + VectorCopy(vcurrent.v, mins); + VectorCopy(vcurrent.v, maxs); + } + else + { + mins[0] = min(mins[0], vcurrent.v[0]); + mins[1] = min(mins[1], vcurrent.v[1]); + mins[2] = min(mins[2], vcurrent.v[2]); + maxs[0] = max(maxs[0], vcurrent.v[0]); + maxs[1] = max(maxs[1], vcurrent.v[1]); + maxs[2] = max(maxs[2], vcurrent.v[2]); + } + if (j == 1) + vfirst = vcurrent; + else if (j >= 3) + { + if (maxtriangles <= numtriangles) + { + maxtriangles = max(maxtriangles * 2, 32768); + vertices = (objvertex_t*)Mem_Realloc(loadmodel->mempool, vertices, maxtriangles * sizeof(objvertex_t[3])); + } + if(mod_obj_orientation.integer) + { + vertices[numtriangles*3+0] = vfirst; + vertices[numtriangles*3+1] = vprev; + vertices[numtriangles*3+2] = vcurrent; + } + else + { + vertices[numtriangles*3+0] = vfirst; + vertices[numtriangles*3+2] = vprev; + vertices[numtriangles*3+1] = vcurrent; + } + numtriangles++; + } + vprev = vcurrent; + } + } + else if (!strcmp(argv[0], "o") || !strcmp(argv[0], "g")) + { + submodelindex = atof(argv[1]); + loadmodel->brush.numsubmodels = max(submodelindex + 1, loadmodel->brush.numsubmodels); + } + else if (!strcmp(argv[0], "usemtl")) + { + for (i = 0;i < numtextures;i++) + if (!strcmp(texturenames+i*MAX_QPATH, argv[1])) + break; + if (i < numtextures) + textureindex = i; + else + { + if (maxtextures <= numtextures) + { + maxtextures = max(maxtextures * 2, 256); + texturenames = (char *)Mem_Realloc(loadmodel->mempool, texturenames, maxtextures * MAX_QPATH); + } + textureindex = numtextures++; + strlcpy(texturenames + textureindex*MAX_QPATH, argv[1], MAX_QPATH); + } + } + } + + // now that we have the OBJ data loaded as-is, we can convert it + + // copy the model bounds, then enlarge the yaw and rotated bounds according to radius + VectorCopy(mins, loadmodel->normalmins); + VectorCopy(maxs, loadmodel->normalmaxs); + dist = max(fabs(loadmodel->normalmins[0]), fabs(loadmodel->normalmaxs[0])); + modelyawradius = max(fabs(loadmodel->normalmins[1]), fabs(loadmodel->normalmaxs[1])); + modelyawradius = dist*dist+modelyawradius*modelyawradius; + modelradius = max(fabs(loadmodel->normalmins[2]), fabs(loadmodel->normalmaxs[2])); + modelradius = modelyawradius + modelradius * modelradius; + modelyawradius = sqrt(modelyawradius); + modelradius = sqrt(modelradius); + loadmodel->yawmins[0] = loadmodel->yawmins[1] = -modelyawradius; + loadmodel->yawmins[2] = loadmodel->normalmins[2]; + loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = modelyawradius; + loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; + loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -modelradius; + loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = modelradius; + loadmodel->radius = modelradius; + loadmodel->radius2 = modelradius * modelradius; + + // allocate storage for triangles + loadmodel->surfmesh.data_element3i = (int *)Mem_Alloc(loadmodel->mempool, numtriangles * sizeof(int[3])); + // allocate vertex hash structures to build an optimal vertex subset + vertexhashsize = numtriangles*2; + vertexhashtable = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int) * vertexhashsize); + memset(vertexhashtable, 0xFF, sizeof(int) * vertexhashsize); + vertexhashdata = (objvertex_t *)Mem_Alloc(loadmodel->mempool, sizeof(*vertexhashdata) * numtriangles*3); + vertexhashcount = 0; + + // gather surface stats for assigning vertex/triangle ranges + firstvertex = 0; + firsttriangle = 0; + elementindex = 0; + loadmodel->num_surfaces = 0; + // allocate storage for the worst case number of surfaces, later we resize + tempsurfaces = (msurface_t *)Mem_Alloc(loadmodel->mempool, numtextures * loadmodel->brush.numsubmodels * sizeof(msurface_t)); + submodelfirstsurface = (int *)Mem_Alloc(loadmodel->mempool, (loadmodel->brush.numsubmodels+1) * sizeof(int)); + surface = tempsurfaces; + for (submodelindex = 0;submodelindex < loadmodel->brush.numsubmodels;submodelindex++) + { + submodelfirstsurface[submodelindex] = loadmodel->num_surfaces; + for (textureindex = 0;textureindex < numtextures;textureindex++) + { + for (vertexindex = 0;vertexindex < numtriangles*3;vertexindex++) + { + thisvertex = vertices + vertexindex; + if (thisvertex->submodelindex == submodelindex && thisvertex->textureindex == textureindex) + break; + } + // skip the surface creation if there are no triangles for it + if (vertexindex == numtriangles*3) + continue; + // create a surface for these vertices + surfacevertices = 0; + surfaceelements = 0; + // we hack in a texture index in the surface to be fixed up later... + surface->texture = (texture_t *)((size_t)textureindex); + // calculate bounds as we go + VectorCopy(thisvertex->v, surface->mins); + VectorCopy(thisvertex->v, surface->maxs); + for (;vertexindex < numtriangles*3;vertexindex++) + { + thisvertex = vertices + vertexindex; + if (thisvertex->submodelindex != submodelindex) + continue; + if (thisvertex->textureindex != textureindex) + continue; + // add vertex to surface bounds + surface->mins[0] = min(surface->mins[0], thisvertex->v[0]); + surface->mins[1] = min(surface->mins[1], thisvertex->v[1]); + surface->mins[2] = min(surface->mins[2], thisvertex->v[2]); + surface->maxs[0] = max(surface->maxs[0], thisvertex->v[0]); + surface->maxs[1] = max(surface->maxs[1], thisvertex->v[1]); + surface->maxs[2] = max(surface->maxs[2], thisvertex->v[2]); + // add the vertex if it is not found in the merged set, and + // get its index (triangle element) for the surface + vertexhashindex = (unsigned int)(thisvertex->v[0] * 3571 + thisvertex->v[0] * 1777 + thisvertex->v[0] * 457) % (unsigned int)vertexhashsize; + for (i = vertexhashtable[vertexhashindex];i >= 0;i = vertexhashdata[i].nextindex) + { + vdata = vertexhashdata + i; + if (vdata->submodelindex == thisvertex->submodelindex && vdata->textureindex == thisvertex->textureindex && VectorCompare(thisvertex->v, vdata->v) && VectorCompare(thisvertex->vn, vdata->vn) && Vector2Compare(thisvertex->vt, vdata->vt)) + break; + } + if (i < 0) + { + i = vertexhashcount++; + vdata = vertexhashdata + i; + *vdata = *thisvertex; + vdata->nextindex = vertexhashtable[vertexhashindex]; + vertexhashtable[vertexhashindex] = i; + surfacevertices++; + } + loadmodel->surfmesh.data_element3i[elementindex++] = i; + surfaceelements++; + } + surfacetriangles = surfaceelements / 3; + surface->num_vertices = surfacevertices; + surface->num_triangles = surfacetriangles; + surface->num_firstvertex = firstvertex; + surface->num_firsttriangle = firsttriangle; + firstvertex += surface->num_vertices; + firsttriangle += surface->num_triangles; + surface++; + loadmodel->num_surfaces++; + } + } + submodelfirstsurface[submodelindex] = loadmodel->num_surfaces; + numvertices = firstvertex; + loadmodel->data_surfaces = (msurface_t *)Mem_Realloc(loadmodel->mempool, tempsurfaces, loadmodel->num_surfaces * sizeof(msurface_t)); + tempsurfaces = NULL; + + // allocate storage for final mesh data + loadmodel->num_textures = numtextures * loadmodel->numskins; + loadmodel->num_texturesperskin = numtextures; + data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + numtriangles * sizeof(int[3]) + (numvertices <= 65536 ? numtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? numtriangles * sizeof(int[3]) : 0) + numvertices * sizeof(float[14]) + loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); + loadmodel->brush.submodels = (dp_model_t **)data;data += loadmodel->brush.numsubmodels * sizeof(dp_model_t *); + loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->surfmesh.num_vertices = numvertices; + loadmodel->surfmesh.num_triangles = numtriangles; + if (r_enableshadowvolumes.integer) + loadmodel->surfmesh.data_neighbor3i = (int *)data;data += numtriangles * sizeof(int[3]); + loadmodel->surfmesh.data_vertex3f = (float *)data;data += numvertices * sizeof(float[3]); + loadmodel->surfmesh.data_svector3f = (float *)data;data += numvertices * sizeof(float[3]); + loadmodel->surfmesh.data_tvector3f = (float *)data;data += numvertices * sizeof(float[3]); + loadmodel->surfmesh.data_normal3f = (float *)data;data += numvertices * sizeof(float[3]); + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += numvertices * sizeof(float[2]); + if (loadmodel->surfmesh.num_vertices <= 65536) + loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); + + for (j = 0;j < loadmodel->surfmesh.num_vertices;j++) + { + VectorCopy(vertexhashdata[j].v, loadmodel->surfmesh.data_vertex3f + 3*j); + VectorCopy(vertexhashdata[j].vn, loadmodel->surfmesh.data_normal3f + 3*j); + Vector2Copy(vertexhashdata[j].vt, loadmodel->surfmesh.data_texcoordtexture2f + 2*j); + } + + // load the textures + for (textureindex = 0;textureindex < numtextures;textureindex++) + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + textureindex, skinfiles, texturenames + textureindex*MAX_QPATH, texturenames + textureindex*MAX_QPATH); + Mod_FreeSkinFiles(skinfiles); + + // set the surface textures to their real values now that we loaded them... + for (i = 0;i < loadmodel->num_surfaces;i++) + loadmodel->data_surfaces[i].texture = loadmodel->data_textures + (size_t)loadmodel->data_surfaces[i].texture; + + // free data + Mem_Free(vertices); + Mem_Free(texturenames); + Mem_Free(v); + Mem_Free(vt); + Mem_Free(vn); + Mem_Free(vertexhashtable); + Mem_Free(vertexhashdata); + + // make a single combined shadow mesh to allow optimized shadow volume creation + Mod_Q1BSP_CreateShadowMesh(loadmodel); + + // compute all the mesh information that was not loaded from the file + if (loadmodel->surfmesh.data_element3s) + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); + // generate normals if the file did not have them + if (!VectorLength2(loadmodel->surfmesh.data_normal3f)) + Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); + Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); + if (loadmodel->surfmesh.data_neighbor3i) + Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); + + // if this is a worldmodel and has no BSP tree, create a fake one for the purpose + loadmodel->brush.num_visleafs = 1; + loadmodel->brush.num_leafs = 1; + loadmodel->brush.num_nodes = 0; + loadmodel->brush.num_leafsurfaces = loadmodel->num_surfaces; + loadmodel->brush.data_leafs = (mleaf_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_leafs * sizeof(mleaf_t)); + loadmodel->brush.data_nodes = (mnode_t *)loadmodel->brush.data_leafs; + loadmodel->brush.num_pvsclusters = 1; + loadmodel->brush.num_pvsclusterbytes = 1; + loadmodel->brush.data_pvsclusters = nobsp_pvs; + //if (loadmodel->num_nodes) loadmodel->data_nodes = (mnode_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_nodes * sizeof(mnode_t)); + //loadmodel->data_leafsurfaces = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->num_leafsurfaces * sizeof(int)); + loadmodel->brush.data_leafsurfaces = loadmodel->sortedmodelsurfaces; + VectorCopy(loadmodel->normalmins, loadmodel->brush.data_leafs->mins); + VectorCopy(loadmodel->normalmaxs, loadmodel->brush.data_leafs->maxs); + loadmodel->brush.data_leafs->combinedsupercontents = 0; // FIXME? + loadmodel->brush.data_leafs->clusterindex = 0; + loadmodel->brush.data_leafs->areaindex = 0; + loadmodel->brush.data_leafs->numleafsurfaces = loadmodel->brush.num_leafsurfaces; + loadmodel->brush.data_leafs->firstleafsurface = loadmodel->brush.data_leafsurfaces; + loadmodel->brush.data_leafs->numleafbrushes = 0; + loadmodel->brush.data_leafs->firstleafbrush = NULL; + loadmodel->brush.supportwateralpha = true; + + if (loadmodel->brush.numsubmodels) + loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); + + mod = loadmodel; + for (i = 0;i < loadmodel->brush.numsubmodels;i++) + { + if (i > 0) + { + char name[10]; + // duplicate the basic information + dpsnprintf(name, sizeof(name), "*%i", i); + mod = Mod_FindName(name, loadmodel->name); + // copy the base model to this one + *mod = *loadmodel; + // rename the clone back to its proper name + strlcpy(mod->name, name, sizeof(mod->name)); + mod->brush.parentmodel = loadmodel; + // textures and memory belong to the main model + mod->texturepool = NULL; + mod->mempool = NULL; + mod->brush.GetPVS = NULL; + mod->brush.FatPVS = NULL; + mod->brush.BoxTouchingPVS = NULL; + mod->brush.BoxTouchingLeafPVS = NULL; + mod->brush.BoxTouchingVisibleLeafs = NULL; + mod->brush.FindBoxClusters = NULL; + mod->brush.LightPoint = NULL; + mod->brush.AmbientSoundLevelsForPoint = NULL; + } + mod->brush.submodel = i; + if (loadmodel->brush.submodels) + loadmodel->brush.submodels[i] = mod; + + // make the model surface list (used by shadowing/lighting) + mod->firstmodelsurface = submodelfirstsurface[i]; + mod->nummodelsurfaces = submodelfirstsurface[i+1] - submodelfirstsurface[i]; + mod->firstmodelbrush = 0; + mod->nummodelbrushes = 0; + mod->sortedmodelsurfaces = loadmodel->sortedmodelsurfaces + mod->firstmodelsurface; + Mod_MakeSortedSurfaces(mod); + + VectorClear(mod->normalmins); + VectorClear(mod->normalmaxs); + l = false; + for (j = 0;j < mod->nummodelsurfaces;j++) + { + const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; + const float *v = mod->surfmesh.data_vertex3f + 3 * surface->num_firstvertex; + int k; + if (!surface->num_vertices) + continue; + if (!l) + { + l = true; + VectorCopy(v, mod->normalmins); + VectorCopy(v, mod->normalmaxs); + } + for (k = 0;k < surface->num_vertices;k++, v += 3) + { + mod->normalmins[0] = min(mod->normalmins[0], v[0]); + mod->normalmins[1] = min(mod->normalmins[1], v[1]); + mod->normalmins[2] = min(mod->normalmins[2], v[2]); + mod->normalmaxs[0] = max(mod->normalmaxs[0], v[0]); + mod->normalmaxs[1] = max(mod->normalmaxs[1], v[1]); + mod->normalmaxs[2] = max(mod->normalmaxs[2], v[2]); + } + } + corner[0] = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); + corner[1] = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); + corner[2] = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); + modelradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]+corner[2]*corner[2]); + yawradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); + mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; + mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; + mod->yawmaxs[0] = mod->yawmaxs[1] = yawradius; + mod->yawmins[0] = mod->yawmins[1] = -yawradius; + mod->yawmins[2] = mod->normalmins[2]; + mod->yawmaxs[2] = mod->normalmaxs[2]; + mod->radius = modelradius; + mod->radius2 = modelradius * modelradius; + + // this gets altered below if sky or water is used + mod->DrawSky = NULL; + mod->DrawAddWaterPlanes = NULL; + + for (j = 0;j < mod->nummodelsurfaces;j++) + if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & MATERIALFLAG_SKY) + break; + if (j < mod->nummodelsurfaces) + mod->DrawSky = R_Q1BSP_DrawSky; + + for (j = 0;j < mod->nummodelsurfaces;j++) + if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) + break; + if (j < mod->nummodelsurfaces) + mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; + + Mod_MakeCollisionBIH(mod, true, &mod->collision_bih); + mod->render_bih = mod->collision_bih; + + // generate VBOs and other shared data before cloning submodels + if (i == 0) + Mod_BuildVBOs(); + } + mod = loadmodel; + Mem_Free(submodelfirstsurface); + + Con_DPrintf("Stats for obj model \"%s\": %i faces, %i nodes, %i leafs, %i clusters, %i clusterportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); +} diff --git a/app/jni/model_brush.h b/app/jni/model_brush.h new file mode 100644 index 0000000..1621b49 --- /dev/null +++ b/app/jni/model_brush.h @@ -0,0 +1,716 @@ +/* +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. + +*/ + +#ifndef MODEL_BRUSH_H +#define MODEL_BRUSH_H + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + + +// +// in memory representation +// +typedef struct mvertex_s +{ + vec3_t position; +} +mvertex_t; + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + + +// plane_t structure +typedef struct mplane_s +{ + vec3_t normal; + float dist; + // for texture axis selection and fast side tests + int type; // set by PlaneClassify() + int signbits; // set by PlaneClassify() +} +mplane_t; + +#define SHADERSTAGE_SKY 0 +#define SHADERSTAGE_NORMAL 1 +#define SHADERSTAGE_COUNT 2 + +//#define SURF_PLANEBACK 2 + +// indicates that all triangles of the surface should be added to the BIH collision system +#define MATERIALFLAG_MESHCOLLISIONS 1 +// use alpha blend on this material +#define MATERIALFLAG_ALPHA 2 +// use additive blend on this material +#define MATERIALFLAG_ADD 4 +// turn off depth test on this material +#define MATERIALFLAG_NODEPTHTEST 8 +// multiply alpha by r_wateralpha cvar +#define MATERIALFLAG_WATERALPHA 16 +// draw with no lighting +#define MATERIALFLAG_FULLBRIGHT 32 +// drawn as a normal surface (alternative to SKY) +#define MATERIALFLAG_WALL 64 +// this surface shows the sky in its place, alternative to WALL +// skipped if transparent +#define MATERIALFLAG_SKY 128 +// swirling water effect (used with MATERIALFLAG_WALL) +#define MATERIALFLAG_WATERSCROLL 256 +// skips drawing the surface +#define MATERIALFLAG_NODRAW 512 +// probably used only on q1bsp water +#define MATERIALFLAG_LIGHTBOTHSIDES 1024 +// use alpha test on this material +#define MATERIALFLAG_ALPHATEST 2048 +// treat this material as a blended transparency (as opposed to an alpha test +// transparency), this causes special fog behavior, and disables glDepthMask +#define MATERIALFLAG_BLENDED 4096 +// render using a custom blendfunc +#define MATERIALFLAG_CUSTOMBLEND 8192 +// do not cast shadows from this material +#define MATERIALFLAG_NOSHADOW 16384 +// render using vertex alpha (q3bsp) as texture blend parameter between foreground (normal) skinframe and background skinframe +#define MATERIALFLAG_VERTEXTEXTUREBLEND 32768 +// disables GL_CULL_FACE on this texture (making it double sided) +#define MATERIALFLAG_NOCULLFACE 65536 +// render with a very short depth range (like 10% of normal), this causes entities to appear infront of most of the scene +#define MATERIALFLAG_SHORTDEPTHRANGE 131072 +// render water, comprising refraction and reflection (note: this is always opaque, the shader does the alpha effect) +#define MATERIALFLAG_WATERSHADER 262144 +// render refraction (note: this is just a way to distort the background, otherwise useless) +#define MATERIALFLAG_REFRACTION 524288 +// render reflection +#define MATERIALFLAG_REFLECTION 1048576 +// use model lighting on this material (q1bsp lightmap sampling or q3bsp lightgrid, implies FULLBRIGHT is false) +#define MATERIALFLAG_MODELLIGHT 4194304 +// add directional model lighting to this material (q3bsp lightgrid only) +#define MATERIALFLAG_MODELLIGHT_DIRECTIONAL 8388608 +// causes RSurf_GetCurrentTexture to leave alone certain fields +#define MATERIALFLAG_CUSTOMSURFACE 16777216 +// causes MATERIALFLAG_BLENDED to render a depth pass before rendering, hiding backfaces and other hidden geometry +#define MATERIALFLAG_TRANSDEPTH 33554432 +// like refraction, but doesn't distort etc. +#define MATERIALFLAG_CAMERA 67108864 +// disable rtlight on surface, use R_LightPoint instead +#define MATERIALFLAG_NORTLIGHT 134217728 +// alphagen vertex +#define MATERIALFLAG_ALPHAGEN_VERTEX 268435456 +// combined mask of all attributes that require depth sorted rendering +#define MATERIALFLAGMASK_DEPTHSORTED (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST) +// combined mask of all attributes that cause some sort of transparency +#define MATERIALFLAGMASK_TRANSLUCENT (MATERIALFLAG_WATERALPHA | MATERIALFLAG_SKY | MATERIALFLAG_NODRAW | MATERIALFLAG_ALPHATEST | MATERIALFLAG_BLENDED | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION) + +typedef struct medge_s +{ + unsigned int v[2]; +} +medge_t; + +struct entity_render_s; +struct texture_s; +struct msurface_s; + +typedef struct mnode_s +{ + //this part shared between node and leaf + mplane_t *plane; // != NULL + struct mnode_s *parent; + struct mportal_s *portals; + // for bounding box culling + vec3_t mins; + vec3_t maxs; + // supercontents from all brushes inside this node or leaf + int combinedsupercontents; + + // this part unique to node + struct mnode_s *children[2]; + + // q1bsp specific + unsigned int firstsurface; + unsigned int numsurfaces; +} +mnode_t; + +typedef struct mleaf_s +{ + //this part shared between node and leaf + mplane_t *plane; // == NULL + struct mnode_s *parent; + struct mportal_s *portals; + // for bounding box culling + vec3_t mins; + vec3_t maxs; + // supercontents from all brushes inside this node or leaf + int combinedsupercontents; + + // this part unique to leaf + // common + int clusterindex; // -1 is not in pvs, >= 0 is pvs bit number + int areaindex; // q3bsp + int containscollisionsurfaces; // indicates whether the leafsurfaces contains q3 patches + int numleafsurfaces; + int *firstleafsurface; + int numleafbrushes; // q3bsp + int *firstleafbrush; // q3bsp + unsigned char ambient_sound_level[NUM_AMBIENTS]; // q1bsp + int contents; // q1bsp: // TODO: remove (only used temporarily during loading when making collision hull 0) + int portalmarkid; // q1bsp // used by see-polygon-through-portals visibility checker +} +mleaf_t; + +typedef struct mclipnode_s +{ + int planenum; + int children[2]; // negative numbers are contents +} mclipnode_t; + +typedef struct hull_s +{ + mclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; + vec3_t clip_size; +} +hull_t; + +typedef struct mportal_s +{ + struct mportal_s *next; // the next portal on this leaf + mleaf_t *here; // the leaf this portal is on + mleaf_t *past; // the leaf through this portal (infront) + int numpoints; + mvertex_t *points; + vec3_t mins, maxs; // culling + mplane_t plane; +} +mportal_t; + +typedef struct svbspmesh_s +{ + struct svbspmesh_s *next; + int numverts, maxverts; + int numtriangles, maxtriangles; + float *verts; + int *elements; +} +svbspmesh_t; + +// Q2 bsp stuff + +#define Q2BSPVERSION 38 + +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits + +//============================================================================= + +#define Q2LUMP_ENTITIES 0 +#define Q2LUMP_PLANES 1 +#define Q2LUMP_VERTEXES 2 +#define Q2LUMP_VISIBILITY 3 +#define Q2LUMP_NODES 4 +#define Q2LUMP_TEXINFO 5 +#define Q2LUMP_FACES 6 +#define Q2LUMP_LIGHTING 7 +#define Q2LUMP_LEAFS 8 +#define Q2LUMP_LEAFFACES 9 +#define Q2LUMP_LEAFBRUSHES 10 +#define Q2LUMP_EDGES 11 +#define Q2LUMP_SURFEDGES 12 +#define Q2LUMP_MODELS 13 +#define Q2LUMP_BRUSHES 14 +#define Q2LUMP_BRUSHSIDES 15 +#define Q2LUMP_POP 16 +#define Q2LUMP_AREAS 17 +#define Q2LUMP_AREAPORTALS 18 +#define Q2HEADER_LUMPS 19 + +typedef struct q2dheader_s +{ + int ident; + int version; + lump_t lumps[Q2HEADER_LUMPS]; +} q2dheader_t; + +typedef struct q2dmodel_s +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} q2dmodel_t; + +// planes (x&~1) and (x&~1)+1 are always opposites + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#define Q2CONTENTS_SOLID 1 // an eye is never valid in a solid +#define Q2CONTENTS_WINDOW 2 // translucent, but not watery +#define Q2CONTENTS_AUX 4 +#define Q2CONTENTS_LAVA 8 +#define Q2CONTENTS_SLIME 16 +#define Q2CONTENTS_WATER 32 +#define Q2CONTENTS_MIST 64 +#define Q2LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define Q2CONTENTS_AREAPORTAL 0x8000 + +#define Q2CONTENTS_PLAYERCLIP 0x10000 +#define Q2CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define Q2CONTENTS_CURRENT_0 0x40000 +#define Q2CONTENTS_CURRENT_90 0x80000 +#define Q2CONTENTS_CURRENT_180 0x100000 +#define Q2CONTENTS_CURRENT_270 0x200000 +#define Q2CONTENTS_CURRENT_UP 0x400000 +#define Q2CONTENTS_CURRENT_DOWN 0x800000 + +#define Q2CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define Q2CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define Q2CONTENTS_DEADMONSTER 0x4000000 +#define Q2CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define Q2CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define Q2CONTENTS_LADDER 0x20000000 + + + +#define Q2SURF_LIGHT 0x1 // value will hold the light strength + +#define Q2SURF_SLICK 0x2 // effects game physics + +#define Q2SURF_SKY 0x4 // don't draw, but add to skybox +#define Q2SURF_WARP 0x8 // turbulent water warp +#define Q2SURF_TRANS33 0x10 +#define Q2SURF_TRANS66 0x20 +#define Q2SURF_FLOWING 0x40 // scroll towards angle +#define Q2SURF_NODRAW 0x80 // don't bother referencing the texture + + + + +typedef struct q2dnode_s +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} q2dnode_t; + + +typedef struct q2texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} q2texinfo_t; + +typedef struct q2dleaf_s +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} q2dleaf_t; + +typedef struct q2dbrushside_s +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} q2dbrushside_t; + +typedef struct q2dbrush_s +{ + int firstside; + int numsides; + int contents; +} q2dbrush_t; + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define Q2DVIS_PVS 0 +#define Q2DVIS_PHS 1 +typedef struct q2dvis_s +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} q2dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct q2dareaportal_s +{ + int portalnum; + int otherarea; +} q2dareaportal_t; + +typedef struct q2darea_s +{ + int numareaportals; + int firstareaportal; +} q2darea_t; + + +//Q3 bsp stuff + +#define Q3BSPVERSION 46 +#define Q3BSPVERSION_LIVE 47 +#define Q3BSPVERSION_IG 48 + +#define Q3LUMP_ENTITIES 0 // entities to spawn (used by server and client) +#define Q3LUMP_TEXTURES 1 // textures used (used by faces) +#define Q3LUMP_PLANES 2 // planes used (used by bsp nodes) +#define Q3LUMP_NODES 3 // bsp nodes (used by bsp nodes, bsp leafs, rendering, collisions) +#define Q3LUMP_LEAFS 4 // bsp leafs (used by bsp nodes) +#define Q3LUMP_LEAFFACES 5 // array of ints indexing faces (used by leafs) +#define Q3LUMP_LEAFBRUSHES 6 // array of ints indexing brushes (used by leafs) +#define Q3LUMP_MODELS 7 // models (used by rendering, collisions) +#define Q3LUMP_BRUSHES 8 // brushes (used by effects, collisions) +#define Q3LUMP_BRUSHSIDES 9 // brush faces (used by brushes) +#define Q3LUMP_VERTICES 10 // mesh vertices (used by faces) +#define Q3LUMP_TRIANGLES 11 // mesh triangles (used by faces) +#define Q3LUMP_EFFECTS 12 // fog (used by faces) +#define Q3LUMP_FACES 13 // surfaces (used by leafs) +#define Q3LUMP_LIGHTMAPS 14 // lightmap textures (used by faces) +#define Q3LUMP_LIGHTGRID 15 // lighting as a voxel grid (used by rendering) +#define Q3LUMP_PVS 16 // potentially visible set; bit[clusters][clusters] (used by rendering) +#define Q3HEADER_LUMPS 17 +#define Q3LUMP_ADVERTISEMENTS 17 // quake live stuff written by zeroradiant's q3map2 (ignored by DP) +#define Q3HEADER_LUMPS_LIVE 18 +#define Q3HEADER_LUMPS_MAX 18 + +typedef struct q3dheader_s +{ + int ident; + int version; + lump_t lumps[Q3HEADER_LUMPS_MAX]; +} q3dheader_t; + +typedef struct q3dtexture_s +{ + char name[Q3PATHLENGTH]; + int surfaceflags; + int contents; +} +q3dtexture_t; + +// note: planes are paired, the pair of planes with i and i ^ 1 are opposites. +typedef struct q3dplane_s +{ + float normal[3]; + float dist; +} +q3dplane_t; + +typedef struct q3dnode_s +{ + int planeindex; + int childrenindex[2]; + int mins[3]; + int maxs[3]; +} +q3dnode_t; + +typedef struct q3dleaf_s +{ + int clusterindex; // pvs index + int areaindex; // area index + int mins[3]; + int maxs[3]; + int firstleafface; + int numleaffaces; + int firstleafbrush; + int numleafbrushes; +} +q3dleaf_t; + +typedef struct q3dmodel_s +{ + float mins[3]; + float maxs[3]; + int firstface; + int numfaces; + int firstbrush; + int numbrushes; +} +q3dmodel_t; + +typedef struct q3dbrush_s +{ + int firstbrushside; + int numbrushsides; + int textureindex; +} +q3dbrush_t; + +typedef struct q3dbrushside_s +{ + int planeindex; + int textureindex; +} +q3dbrushside_t; + +typedef struct q3dbrushside_ig_s +{ + int planeindex; + int textureindex; + int surfaceflags; +} +q3dbrushside_ig_t; + +typedef struct q3dvertex_s +{ + float origin3f[3]; + float texcoord2f[2]; + float lightmap2f[2]; + float normal3f[3]; + unsigned char color4ub[4]; +} +q3dvertex_t; + +typedef struct q3dmeshvertex_s +{ + int offset; // first vertex index of mesh +} +q3dmeshvertex_t; + +typedef struct q3deffect_s +{ + char shadername[Q3PATHLENGTH]; + int brushindex; + int unknown; // I read this is always 5 except in q3dm8 which has one effect with -1 +} +q3deffect_t; + +#define Q3FACETYPE_FLAT 1 // common +#define Q3FACETYPE_PATCH 2 // common +#define Q3FACETYPE_MESH 3 // common +#define Q3FACETYPE_FLARE 4 // rare (is this ever used?) + +typedef struct q3dface_s +{ + int textureindex; + int effectindex; // -1 if none + int type; // Q3FACETYPE + int firstvertex; + int numvertices; + int firstelement; + int numelements; + int lightmapindex; // -1 if none + int lightmap_base[2]; + int lightmap_size[2]; + union + { + struct + { + // corrupt or don't care + int blah[14]; + } + unknown; + struct + { + // Q3FACETYPE_FLAT + // mesh is a collection of triangles on a plane, renderable as a mesh (NOT a polygon) + float lightmap_origin[3]; + float lightmap_vectors[2][3]; + float normal[3]; + int unused1[2]; + } + flat; + struct + { + // Q3FACETYPE_PATCH + // patch renders as a bezier mesh, with adjustable tesselation + // level (optionally based on LOD using the bbox and polygon + // count to choose a tesselation level) + // note: multiple patches may have the same bbox to cause them to + // be LOD adjusted together as a group + int unused1[3]; + float mins[3]; // LOD bbox + float maxs[3]; // LOD bbox + int unused2[3]; + int patchsize[2]; // dimensions of vertex grid + } + patch; + struct + { + // Q3FACETYPE_MESH + // mesh renders as simply a triangle mesh + int unused1[3]; + float mins[3]; + float maxs[3]; + int unused2[5]; + } + mesh; + struct + { + // Q3FACETYPE_FLARE + // flare renders as a simple sprite at origin, no geometry + // exists, nor does it have a radius, a cvar controls the radius + // and another cvar controls distance fade + // (they were not used in Q3 I'm told) + float origin[3]; + int unused1[11]; + } + flare; + } + specific; +} +q3dface_t; + +typedef struct q3dlightmap_s +{ + unsigned char rgb[128*128*3]; +} +q3dlightmap_t; + +typedef struct q3dlightgrid_s +{ + unsigned char ambientrgb[3]; + unsigned char diffusergb[3]; + unsigned char diffusepitch; + unsigned char diffuseyaw; +} +q3dlightgrid_t; + +typedef struct q3dpvs_s +{ + int numclusters; + int chainlength; + // unsigned char chains[]; + // containing bits in 0-7 order (not 7-0 order), + // pvschains[mycluster * chainlength + (thatcluster >> 3)] & (1 << (thatcluster & 7)) +} +q3dpvs_t; + +// surfaceflags from bsp +#define Q3SURFACEFLAG_NODAMAGE 1 +#define Q3SURFACEFLAG_SLICK 2 +#define Q3SURFACEFLAG_SKY 4 +#define Q3SURFACEFLAG_LADDER 8 // has no surfaceparm +#define Q3SURFACEFLAG_NOIMPACT 16 +#define Q3SURFACEFLAG_NOMARKS 32 +#define Q3SURFACEFLAG_FLESH 64 // has no surfaceparm +#define Q3SURFACEFLAG_NODRAW 128 +#define Q3SURFACEFLAG_HINT 256 +#define Q3SURFACEFLAG_SKIP 512 // has no surfaceparm +#define Q3SURFACEFLAG_NOLIGHTMAP 1024 +#define Q3SURFACEFLAG_POINTLIGHT 2048 +#define Q3SURFACEFLAG_METALSTEPS 4096 +#define Q3SURFACEFLAG_NOSTEPS 8192 // has no surfaceparm +#define Q3SURFACEFLAG_NONSOLID 16384 +#define Q3SURFACEFLAG_LIGHTFILTER 32768 +#define Q3SURFACEFLAG_ALPHASHADOW 65536 +#define Q3SURFACEFLAG_NODLIGHT 131072 +#define Q3SURFACEFLAG_DUST 262144 + +// surfaceparms from shaders +#define Q3SURFACEPARM_ALPHASHADOW 1 +#define Q3SURFACEPARM_AREAPORTAL 2 +#define Q3SURFACEPARM_CLUSTERPORTAL 4 +#define Q3SURFACEPARM_DETAIL 8 +#define Q3SURFACEPARM_DONOTENTER 16 +#define Q3SURFACEPARM_FOG 32 +#define Q3SURFACEPARM_LAVA 64 +#define Q3SURFACEPARM_LIGHTFILTER 128 +#define Q3SURFACEPARM_METALSTEPS 256 +#define Q3SURFACEPARM_NODAMAGE 512 +#define Q3SURFACEPARM_NODLIGHT 1024 +#define Q3SURFACEPARM_NODRAW 2048 +#define Q3SURFACEPARM_NODROP 4096 +#define Q3SURFACEPARM_NOIMPACT 8192 +#define Q3SURFACEPARM_NOLIGHTMAP 16384 +#define Q3SURFACEPARM_NOMARKS 32768 +#define Q3SURFACEPARM_NOMIPMAPS 65536 +#define Q3SURFACEPARM_NONSOLID 131072 +#define Q3SURFACEPARM_ORIGIN 262144 +#define Q3SURFACEPARM_PLAYERCLIP 524288 +#define Q3SURFACEPARM_SKY 1048576 +#define Q3SURFACEPARM_SLICK 2097152 +#define Q3SURFACEPARM_SLIME 4194304 +#define Q3SURFACEPARM_STRUCTURAL 8388608 +#define Q3SURFACEPARM_TRANS 16777216 +#define Q3SURFACEPARM_WATER 33554432 +#define Q3SURFACEPARM_POINTLIGHT 67108864 +#define Q3SURFACEPARM_HINT 134217728 +#define Q3SURFACEPARM_DUST 268435456 +#define Q3SURFACEPARM_BOTCLIP 536870912 +#define Q3SURFACEPARM_LIGHTGRID 1073741824 +#define Q3SURFACEPARM_ANTIPORTAL 2147483648u + +typedef struct q3mbrush_s +{ + struct colbrushf_s *colbrushf; + int numbrushsides; + struct q3mbrushside_s *firstbrushside; + struct texture_s *texture; +} +q3mbrush_t; + +typedef struct q3mbrushside_s +{ + struct mplane_s *plane; + struct texture_s *texture; +} +q3mbrushside_t; + +// the first cast is to shut up a stupid warning by clang, the second cast is to make both sides have the same type +#define CHECKPVSBIT(pvs,b) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] & (1 << ((b) & 7))) : (unsigned char) false) +#define SETPVSBIT(pvs,b) (void) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] |= (1 << ((b) & 7))) : (unsigned char) false) +#define CLEARPVSBIT(pvs,b) (void) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] &= ~(1 << ((b) & 7))) : (unsigned char) false) + +#endif + diff --git a/app/jni/model_dpmodel.h b/app/jni/model_dpmodel.h new file mode 100644 index 0000000..52ad0ce --- /dev/null +++ b/app/jni/model_dpmodel.h @@ -0,0 +1,123 @@ + +#ifndef MODEL_DPMODEL_H +#define MODEL_DPMODEL_H + +/* +type 2 model (hierarchical skeletal pose) +within this specification, int is assumed to be 32bit, float is assumed to be 32bit, char is assumed to be 8bit, text is assumed to be an array of chars with NULL termination +all values are big endian (also known as network byte ordering), NOT x86 little endian +general notes: +a pose is a 3x4 matrix (rotation matrix, and translate vector) +parent bones must always be lower in number than their children, models will be rejected if this is not obeyed (can be fixed by modelling utilities) +utility notes: +if a hard edge is desired (faceted lighting, or a jump to another set of skin coordinates), vertices must be duplicated +ability to visually edit groupids of triangles is highly recommended +bones should be markable as 'attach' somehow (up to the utility) and thus protected from culling of unused resources +frame 0 is always the base pose (the one the skeleton was built for) +game notes: +the loader should be very thorough about error checking, all vertex and bone indices should be validated, etc +the gamecode can look up bone numbers by name using a builtin function, for use in attachment situations (the client should have the same model as the host of the gamecode in question - that is to say if the server gamecode is setting the bone number, the client and server must have vaguely compatible models so the client understands, and if the client gamecode is setting the bone number, the server could have a completely different model with no harm done) +the triangle groupid values are up to the gamecode, it is recommended that gamecode process this in an object-oriented fashion (I.E. bullet hits entity, call that entity's function for getting properties of that groupid) +frame 0 should be usable, not skipped +speed optimizations for the saver to do: +remove all unused data (unused bones, vertices, etc, be sure to check if bones are used for attachments however) +sort triangles into strips +sort vertices according to first use in a triangle (caching benefits) after sorting triangles +speed optimizations for the loader to do: +if the model only has one frame, process it at load time to create a simple static vertex mesh to render (this is a hassle, but it is rewarding to optimize all such models) +rendering process: +1*. one or two poses are looked up by number +2*. boneposes (matrices) are interpolated, building bone matrix array +3. bones are parsed sequentially, each bone's matrix is transformed by it's parent bone (which can be -1; the model to world matrix) +4. meshs are parsed sequentially, as follows: + 1. vertices are parsed sequentially and may be influenced by more than one bone (the results of the 3x4 matrix transform will be added together - weighting is already built into these) + 2. shader is looked up and called, passing vertex buffer (temporary) and triangle indices (which are stored in the mesh) +5. rendering is complete +* - these stages can be replaced with completely dynamic animation instead of pose animations. +*/ +// header for the entire file +typedef struct dpmheader_s +{ + char id[16]; // "DARKPLACESMODEL\0", length 16 + unsigned int type; // 2 (hierarchical skeletal pose) + unsigned int filesize; // size of entire model file + float mins[3], maxs[3], yawradius, allradius; // for clipping uses + // these offsets are relative to the file + unsigned int num_bones; + unsigned int num_meshs; + unsigned int num_frames; + unsigned int ofs_bones; // dpmbone_t bone[num_bones]; + unsigned int ofs_meshs; // dpmmesh_t mesh[num_meshs]; + unsigned int ofs_frames; // dpmframe_t frame[num_frames]; +} +dpmheader_t; +// there may be more than one of these +typedef struct dpmmesh_s +{ + // these offsets are relative to the file + char shadername[32]; // name of the shader to use + unsigned int num_verts; + unsigned int num_tris; + unsigned int ofs_verts; // dpmvertex_t vert[numvertices]; // see vertex struct + unsigned int ofs_texcoords; // float texcoords[numvertices][2]; + unsigned int ofs_indices; // unsigned int indices[numtris*3]; // designed for glDrawElements (each triangle is 3 unsigned int indices) + unsigned int ofs_groupids; // unsigned int groupids[numtris]; // the meaning of these values is entirely up to the gamecode and modeler +} +dpmmesh_t; +// if set on a bone, it must be protected from removal +#define DPMBONEFLAG_ATTACHMENT 1 +// one per bone +typedef struct dpmbone_s +{ + // name examples: upperleftarm leftfinger1 leftfinger2 hand, etc + char name[32]; + // parent bone number + signed int parent; + // flags for the bone + unsigned int flags; +} +dpmbone_t; +// a bonepose matrix is intended to be used like this: +// (n = output vertex, v = input vertex, m = matrix, f = influence) +// n[0] = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2] + f * m[0][3]; +// n[1] = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2] + f * m[1][3]; +// n[2] = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2] + f * m[2][3]; +typedef struct dpmbonepose_s +{ + float matrix[3][4]; +} +dpmbonepose_t; +// immediately followed by bone positions for the frame +typedef struct dpmframe_s +{ + // name examples: idle_1 idle_2 idle_3 shoot_1 shoot_2 shoot_3, etc + char name[32]; + float mins[3], maxs[3], yawradius, allradius; + int ofs_bonepositions; // dpmbonepose_t bonepositions[bones]; +} +dpmframe_t; +// one or more of these per vertex +typedef struct dpmbonevert_s +{ + // this pairing of origin and influence is intentional + // (in SSE or 3DNow! assembly it can be done as a quad vector op + // (or two dual vector ops) very easily) + float origin[3]; // vertex location (these blend) + float influence; // influence fraction (these must add up to 1) + // this pairing of normal and bonenum is intentional + // (in SSE or 3DNow! assembly it can be done as a quad vector op + // (or two dual vector ops) very easily, the bonenum is ignored) + float normal[3]; // surface normal (these blend) + unsigned int bonenum; // number of the bone +} +dpmbonevert_t; +// variable size, parsed sequentially +typedef struct dpmvertex_s +{ + unsigned int numbones; + // immediately followed by 1 or more dpmbonevert_t structures +} +dpmvertex_t; + +#endif + diff --git a/app/jni/model_iqm.h b/app/jni/model_iqm.h new file mode 100644 index 0000000..1dbb940 --- /dev/null +++ b/app/jni/model_iqm.h @@ -0,0 +1,127 @@ +#ifndef __MODEL_IQM_H__ +#define __MODEL_IQM_H__ + +typedef struct iqmheader_s +{ + char id[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_neighbors; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; +} +iqmheader_t; + +typedef struct iqmmesh_s +{ + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; +} +iqmmesh_t; + +#define IQM_POSITION 0 +#define IQM_TEXCOORD 1 +#define IQM_NORMAL 2 +#define IQM_TANGENT 3 +#define IQM_BLENDINDEXES 4 +#define IQM_BLENDWEIGHTS 5 +#define IQM_COLOR 6 +#define IQM_CUSTOM 0x10 + +#define IQM_BYTE 0 +#define IQM_UBYTE 1 +#define IQM_SHORT 2 +#define IQM_USHORT 3 +#define IQM_INT 4 +#define IQM_UINT 5 +#define IQM_HALF 6 +#define IQM_FLOAT 7 +#define IQM_DOUBLE 8 + +// animflags +#define IQM_LOOP 1 + +typedef struct iqmtriangle_s +{ + unsigned int vertex[3]; +} +iqmtriangle_t; + +typedef struct iqmjoint1_s +{ + unsigned int name; + signed int parent; + float origin[3], rotation[3], scale[3]; +} +iqmjoint1_t; + +typedef struct iqmjoint_s +{ + unsigned int name; + signed int parent; + float origin[3], rotation[4], scale[3]; +} +iqmjoint_t; + +typedef struct iqmpose1_s +{ + signed int parent; + unsigned int channelmask; + float channeloffset[9], channelscale[9]; +} +iqmpose1_t; + +typedef struct iqmpose_s +{ + signed int parent; + unsigned int channelmask; + float channeloffset[10], channelscale[10]; +} +iqmpose_t; + +typedef struct iqmanim_s +{ + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; +} +iqmanim_t; + +typedef struct iqmvertexarray_s +{ + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; +} +iqmvertexarray_t; + +typedef struct iqmextension_s +{ + unsigned int name; + unsigned int num_data, ofs_data; + unsigned int ofs_extensions; // pointer to next extension +} +iqmextension_t; + +typedef struct iqmbounds_s +{ + float mins[3], maxs[3]; + float xyradius, radius; +} +iqmbounds_t; + +#endif + diff --git a/app/jni/model_psk.h b/app/jni/model_psk.h new file mode 100644 index 0000000..480386f --- /dev/null +++ b/app/jni/model_psk.h @@ -0,0 +1,117 @@ + +#ifndef MODEL_PSK_H +#define MODEL_PSK_H + +typedef struct pskchunk_s +{ + // id is one of the following: + // .psk: + // ACTRHEAD (recordsize = 0, numrecords = 0) + // PNTS0000 (recordsize = 12, pskpnts_t) + // VTXW0000 (recordsize = 16, pskvtxw_t) + // FACE0000 (recordsize = 12, pskface_t) + // MATT0000 (recordsize = 88, pskmatt_t) + // REFSKELT (recordsize = 120, pskboneinfo_t) + // RAWWEIGHTS (recordsize = 12, pskrawweights_t) + // .psa: + // ANIMHEAD (recordsize = 0, numrecords = 0) + // BONENAMES (recordsize = 120, pskboneinfo_t) + // ANIMINFO (recordsize = 168, pskaniminfo_t) + // ANIMKEYS (recordsize = 32, pskanimkeys_t) + char id[20]; + // in .psk always 0x1e83b9 + // in .psa always 0x2e + int version; + int recordsize; + int numrecords; +} +pskchunk_t; + +typedef struct pskpnts_s +{ + float origin[3]; +} +pskpnts_t; + +typedef struct pskvtxw_s +{ + unsigned short pntsindex; // index into PNTS0000 chunk + unsigned char unknown1[2]; // seems to be garbage + float texcoord[2]; + unsigned char mattindex; // index into MATT0000 chunk + unsigned char unknown2; // always 0? + unsigned char unknown3[2]; // seems to be garbage +} +pskvtxw_t; + +typedef struct pskface_s +{ + unsigned short vtxwindex[3]; // triangle + unsigned char mattindex; // index into MATT0000 chunk + unsigned char unknown; // seems to be garbage + unsigned int group; // faces seem to be grouped, possibly for smoothing? +} +pskface_t; + +typedef struct pskmatt_s +{ + char name[64]; + int unknown[6]; // observed 0 0 0 0 5 0 +} +pskmatt_t; + +typedef struct pskpose_s +{ + float quat[4]; + float origin[3]; + float unknown; // probably a float, always seems to be 0 + float size[3]; +} +pskpose_t; + +typedef struct pskboneinfo_s +{ + char name[64]; + int unknown1; + int numchildren; + int parent; // root bones have 0 here + pskpose_t basepose; +} +pskboneinfo_t; + +typedef struct pskrawweights_s +{ + float weight; + int pntsindex; + int boneindex; +} +pskrawweights_t; + +typedef struct pskaniminfo_s +{ + char name[64]; + char group[64]; + int numbones; + int unknown1; + int unknown2; + int unknown3; + float unknown4; + float playtime; // not really needed + float fps; // frames per second + int unknown5; + int firstframe; + int numframes; + // firstanimkeys = (firstframe + frameindex) * numbones +} +pskaniminfo_t; + +typedef struct pskanimkeys_s +{ + float origin[3]; + float quat[4]; + float frametime; +} +pskanimkeys_t; + +#endif + diff --git a/app/jni/model_shared.c b/app/jni/model_shared.c new file mode 100644 index 0000000..5c87e3d --- /dev/null +++ b/app/jni/model_shared.c @@ -0,0 +1,4474 @@ +/* +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. + +*/ +// models.c -- model loading and caching + +// models are the only shared resource between a client and server running +// on the same machine. + +#include "quakedef.h" +#include "image.h" +#include "r_shadow.h" +#include "polygon.h" + +cvar_t r_enableshadowvolumes = {CVAR_SAVE, "r_enableshadowvolumes", "1", "Enables use of Stencil Shadow Volume shadowing methods, saves some memory if turned off"}; +cvar_t r_mipskins = {CVAR_SAVE, "r_mipskins", "0", "mipmaps model skins so they render faster in the distance and do not display noise artifacts, can cause discoloration of skins if they contain undesirable border colors"}; +cvar_t r_mipnormalmaps = {CVAR_SAVE, "r_mipnormalmaps", "1", "mipmaps normalmaps (turning it off looks sharper but may have aliasing)"}; +cvar_t mod_generatelightmaps_unitspersample = {CVAR_SAVE, "mod_generatelightmaps_unitspersample", "8", "lightmap resolution"}; +cvar_t mod_generatelightmaps_borderpixels = {CVAR_SAVE, "mod_generatelightmaps_borderpixels", "2", "extra space around polygons to prevent sampling artifacts"}; +cvar_t mod_generatelightmaps_texturesize = {CVAR_SAVE, "mod_generatelightmaps_texturesize", "1024", "size of lightmap textures"}; +cvar_t mod_generatelightmaps_lightmapsamples = {CVAR_SAVE, "mod_generatelightmaps_lightmapsamples", "16", "number of shadow tests done per lightmap pixel"}; +cvar_t mod_generatelightmaps_vertexsamples = {CVAR_SAVE, "mod_generatelightmaps_vertexsamples", "16", "number of shadow tests done per vertex"}; +cvar_t mod_generatelightmaps_gridsamples = {CVAR_SAVE, "mod_generatelightmaps_gridsamples", "64", "number of shadow tests done per lightgrid cell"}; +cvar_t mod_generatelightmaps_lightmapradius = {CVAR_SAVE, "mod_generatelightmaps_lightmapradius", "16", "sampling area around each lightmap pixel"}; +cvar_t mod_generatelightmaps_vertexradius = {CVAR_SAVE, "mod_generatelightmaps_vertexradius", "16", "sampling area around each vertex"}; +cvar_t mod_generatelightmaps_gridradius = {CVAR_SAVE, "mod_generatelightmaps_gridradius", "64", "sampling area around each lightgrid cell center"}; + +dp_model_t *loadmodel; + +static mempool_t *mod_mempool; +static memexpandablearray_t models; + +static mempool_t* q3shaders_mem; +typedef struct q3shader_hash_entry_s +{ + q3shaderinfo_t shader; + struct q3shader_hash_entry_s* chain; +} q3shader_hash_entry_t; +#define Q3SHADER_HASH_SIZE 1021 +typedef struct q3shader_data_s +{ + memexpandablearray_t hash_entries; + q3shader_hash_entry_t hash[Q3SHADER_HASH_SIZE]; + memexpandablearray_t char_ptrs; +} q3shader_data_t; +static q3shader_data_t* q3shader_data; + +static void mod_start(void) +{ + int i, count; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + SCR_PushLoadingScreen(false, "Loading models", 1.0); + count = 0; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') + if (mod->used) + ++count; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') + if (mod->used) + { + SCR_PushLoadingScreen(true, mod->name, 1.0 / count); + Mod_LoadModel(mod, true, false); + SCR_PopLoadingScreen(false); + } + SCR_PopLoadingScreen(false); +} + +static void mod_shutdown(void) +{ + int i; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && (mod->loaded || mod->mempool)) + Mod_UnloadModel(mod); + + Mod_FreeQ3Shaders(); + Mod_Skeletal_FreeBuffers(); +} + +static void mod_newmap(void) +{ + msurface_t *surface; + int i, j, k, surfacenum, ssize, tsize; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->mempool) + { + for (j = 0;j < mod->num_textures && mod->data_textures;j++) + { + for (k = 0;k < mod->data_textures[j].numskinframes;k++) + R_SkinFrame_MarkUsed(mod->data_textures[j].skinframes[k]); + for (k = 0;k < mod->data_textures[j].backgroundnumskinframes;k++) + R_SkinFrame_MarkUsed(mod->data_textures[j].backgroundskinframes[k]); + } + if (mod->brush.solidskyskinframe) + R_SkinFrame_MarkUsed(mod->brush.solidskyskinframe); + if (mod->brush.alphaskyskinframe) + R_SkinFrame_MarkUsed(mod->brush.alphaskyskinframe); + } + } + + if (!cl_stainmaps_clearonload.integer) + return; + + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->mempool && mod->data_surfaces) + { + for (surfacenum = 0, surface = mod->data_surfaces;surfacenum < mod->num_surfaces;surfacenum++, surface++) + { + if (surface->lightmapinfo && surface->lightmapinfo->stainsamples) + { + ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; + tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; + memset(surface->lightmapinfo->stainsamples, 255, ssize * tsize * 3); + mod->brushq1.lightmapupdateflags[surfacenum] = true; + } + } + } + } +} + +/* +=============== +Mod_Init +=============== +*/ +static void Mod_Print(void); +static void Mod_Precache (void); +static void Mod_Decompile_f(void); +static void Mod_GenerateLightmaps_f(void); +void Mod_Init (void) +{ + mod_mempool = Mem_AllocPool("modelinfo", 0, NULL); + Mem_ExpandableArray_NewArray(&models, mod_mempool, sizeof(dp_model_t), 16); + + Mod_BrushInit(); + Mod_AliasInit(); + Mod_SpriteInit(); + + Cvar_RegisterVariable(&r_enableshadowvolumes); + Cvar_RegisterVariable(&r_mipskins); + Cvar_RegisterVariable(&r_mipnormalmaps); + Cvar_RegisterVariable(&mod_generatelightmaps_unitspersample); + Cvar_RegisterVariable(&mod_generatelightmaps_borderpixels); + Cvar_RegisterVariable(&mod_generatelightmaps_texturesize); + + Cvar_RegisterVariable(&mod_generatelightmaps_lightmapsamples); + Cvar_RegisterVariable(&mod_generatelightmaps_vertexsamples); + Cvar_RegisterVariable(&mod_generatelightmaps_gridsamples); + Cvar_RegisterVariable(&mod_generatelightmaps_lightmapradius); + Cvar_RegisterVariable(&mod_generatelightmaps_vertexradius); + Cvar_RegisterVariable(&mod_generatelightmaps_gridradius); + + Cmd_AddCommand ("modellist", Mod_Print, "prints a list of loaded models"); + Cmd_AddCommand ("modelprecache", Mod_Precache, "load a model"); + Cmd_AddCommand ("modeldecompile", Mod_Decompile_f, "exports a model in several formats for editing purposes"); + Cmd_AddCommand ("mod_generatelightmaps", Mod_GenerateLightmaps_f, "rebuilds lighting on current worldmodel"); +} + +void Mod_RenderInit(void) +{ + R_RegisterModule("Models", mod_start, mod_shutdown, mod_newmap, NULL, NULL); +} + +void Mod_UnloadModel (dp_model_t *mod) +{ + char name[MAX_QPATH]; + qboolean used; + dp_model_t *parentmodel; + + if (developer_loading.integer) + Con_Printf("unloading model %s\n", mod->name); + + strlcpy(name, mod->name, sizeof(name)); + parentmodel = mod->brush.parentmodel; + used = mod->used; + if (mod->mempool) + { + if (mod->surfmesh.data_element3i_indexbuffer) + R_Mesh_DestroyMeshBuffer(mod->surfmesh.data_element3i_indexbuffer); + mod->surfmesh.data_element3i_indexbuffer = NULL; + if (mod->surfmesh.data_element3s_indexbuffer) + R_Mesh_DestroyMeshBuffer(mod->surfmesh.data_element3s_indexbuffer); + mod->surfmesh.data_element3s_indexbuffer = NULL; + if (mod->surfmesh.vbo_vertexbuffer) + R_Mesh_DestroyMeshBuffer(mod->surfmesh.vbo_vertexbuffer); + mod->surfmesh.vbo_vertexbuffer = NULL; + } + // free textures/memory attached to the model + R_FreeTexturePool(&mod->texturepool); + Mem_FreePool(&mod->mempool); + // clear the struct to make it available + memset(mod, 0, sizeof(dp_model_t)); + // restore the fields we want to preserve + strlcpy(mod->name, name, sizeof(mod->name)); + mod->brush.parentmodel = parentmodel; + mod->used = used; + mod->loaded = false; +} + +static void R_Model_Null_Draw(entity_render_t *ent) +{ + return; +} + + +typedef void (*mod_framegroupify_parsegroups_t) (unsigned int i, int start, int len, float fps, qboolean loop, const char *name, void *pass); + +static int Mod_FrameGroupify_ParseGroups(const char *buf, mod_framegroupify_parsegroups_t cb, void *pass) +{ + const char *bufptr; + int start, len; + float fps; + unsigned int i; + qboolean loop; + char name[64]; + + bufptr = buf; + i = 0; + while(bufptr) + { + // an anim scene! + + // REQUIRED: fetch start + COM_ParseToken_Simple(&bufptr, true, false, true); + if (!bufptr) + break; // end of file + if (!strcmp(com_token, "\n")) + continue; // empty line + start = atoi(com_token); + + // REQUIRED: fetch length + COM_ParseToken_Simple(&bufptr, true, false, true); + if (!bufptr || !strcmp(com_token, "\n")) + { + Con_Printf("framegroups file: missing number of frames\n"); + continue; + } + len = atoi(com_token); + + // OPTIONAL args start + COM_ParseToken_Simple(&bufptr, true, false, true); + + // OPTIONAL: fetch fps + fps = 20; + if (bufptr && strcmp(com_token, "\n")) + { + fps = atof(com_token); + COM_ParseToken_Simple(&bufptr, true, false, true); + } + + // OPTIONAL: fetch loopflag + loop = true; + if (bufptr && strcmp(com_token, "\n")) + { + loop = (atoi(com_token) != 0); + COM_ParseToken_Simple(&bufptr, true, false, true); + } + + // OPTIONAL: fetch name + name[0] = 0; + if (bufptr && strcmp(com_token, "\n")) + { + strlcpy(name, com_token, sizeof(name)); + COM_ParseToken_Simple(&bufptr, true, false, true); + } + + // OPTIONAL: remaining unsupported tokens (eat them) + while (bufptr && strcmp(com_token, "\n")) + COM_ParseToken_Simple(&bufptr, true, false, true); + + //Con_Printf("data: %d %d %d %f %d (%s)\n", i, start, len, fps, loop, name); + + if(cb) + cb(i, start, len, fps, loop, (name[0] ? name : NULL), pass); + ++i; + } + + return i; +} + +static void Mod_FrameGroupify_ParseGroups_Store (unsigned int i, int start, int len, float fps, qboolean loop, const char *name, void *pass) +{ + dp_model_t *mod = (dp_model_t *) pass; + animscene_t *anim = &mod->animscenes[i]; + if(name) + strlcpy(anim->name, name, sizeof(anim[i].name)); + else + dpsnprintf(anim->name, sizeof(anim[i].name), "groupified_%d_anim", i); + anim->firstframe = bound(0, start, mod->num_poses - 1); + anim->framecount = bound(1, len, mod->num_poses - anim->firstframe); + anim->framerate = max(1, fps); + anim->loop = !!loop; + //Con_Printf("frame group %d is %d %d %f %d\n", i, start, len, fps, loop); +} + +static void Mod_FrameGroupify(dp_model_t *mod, const char *buf) +{ + unsigned int cnt; + + // 0. count + cnt = Mod_FrameGroupify_ParseGroups(buf, NULL, NULL); + if(!cnt) + { + Con_Printf("no scene found in framegroups file, aborting\n"); + return; + } + mod->numframes = cnt; + + // 1. reallocate + // (we do not free the previous animscenes, but model unloading will free the pool owning them, so it's okay) + mod->animscenes = (animscene_t *) Mem_Alloc(mod->mempool, sizeof(animscene_t) * mod->numframes); + + // 2. parse + Mod_FrameGroupify_ParseGroups(buf, Mod_FrameGroupify_ParseGroups_Store, mod); +} + +static void Mod_FindPotentialDeforms(dp_model_t *mod) +{ + int i, j; + texture_t *texture; + mod->wantnormals = false; + mod->wanttangents = false; + for (i = 0;i < mod->num_textures;i++) + { + texture = mod->data_textures + i; + if (texture->tcgen.tcgen == Q3TCGEN_ENVIRONMENT) + mod->wantnormals = true; + for (j = 0;j < Q3MAXDEFORMS;j++) + { + if (texture->deforms[j].deform == Q3DEFORM_AUTOSPRITE) + { + mod->wanttangents = true; + mod->wantnormals = true; + break; + } + if (texture->deforms[j].deform != Q3DEFORM_NONE) + mod->wantnormals = true; + } + } +} + +/* +================== +Mod_LoadModel + +Loads a model +================== +*/ +dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk) +{ + int num; + unsigned int crc; + void *buf; + fs_offset_t filesize = 0; + char vabuf[1024]; + + mod->used = true; + + if (mod->name[0] == '*') // submodel + return mod; + + if (!strcmp(mod->name, "null")) + { + if(mod->loaded) + return mod; + + if (mod->loaded || mod->mempool) + Mod_UnloadModel(mod); + + if (developer_loading.integer) + Con_Printf("loading model %s\n", mod->name); + + mod->used = true; + mod->crc = (unsigned int)-1; + mod->loaded = false; + + VectorClear(mod->normalmins); + VectorClear(mod->normalmaxs); + VectorClear(mod->yawmins); + VectorClear(mod->yawmaxs); + VectorClear(mod->rotatedmins); + VectorClear(mod->rotatedmaxs); + + mod->modeldatatypestring = "null"; + mod->type = mod_null; + mod->Draw = R_Model_Null_Draw; + mod->numframes = 2; + mod->numskins = 1; + + // no fatal errors occurred, so this model is ready to use. + mod->loaded = true; + + return mod; + } + + crc = 0; + buf = NULL; + + // even if the model is loaded it still may need reloading... + + // if it is not loaded or checkdisk is true we need to calculate the crc + if (!mod->loaded || checkdisk) + { + if (checkdisk && mod->loaded) + Con_DPrintf("checking model %s\n", mod->name); + buf = FS_LoadFile (mod->name, tempmempool, false, &filesize); + if (buf) + { + crc = CRC_Block((unsigned char *)buf, filesize); + // we need to reload the model if the crc does not match + if (mod->crc != crc) + mod->loaded = false; + } + } + + // if the model is already loaded and checks passed, just return + if (mod->loaded) + { + if (buf) + Mem_Free(buf); + return mod; + } + + if (developer_loading.integer) + Con_Printf("loading model %s\n", mod->name); + + SCR_PushLoadingScreen(true, mod->name, 1); + + // LordHavoc: unload the existing model in this slot (if there is one) + if (mod->loaded || mod->mempool) + Mod_UnloadModel(mod); + + // load the model + mod->used = true; + mod->crc = crc; + // errors can prevent the corresponding mod->loaded = true; + mod->loaded = false; + + // default lightmap scale + mod->lightmapscale = 1; + + // default model radius and bounding box (mainly for missing models) + mod->radius = 16; + VectorSet(mod->normalmins, -mod->radius, -mod->radius, -mod->radius); + VectorSet(mod->normalmaxs, mod->radius, mod->radius, mod->radius); + VectorSet(mod->yawmins, -mod->radius, -mod->radius, -mod->radius); + VectorSet(mod->yawmaxs, mod->radius, mod->radius, mod->radius); + VectorSet(mod->rotatedmins, -mod->radius, -mod->radius, -mod->radius); + VectorSet(mod->rotatedmaxs, mod->radius, mod->radius, mod->radius); + + if (!q3shaders_mem) + { + // load q3 shaders for the first time, or after a level change + Mod_LoadQ3Shaders(); + } + + if (buf) + { + char *bufend = (char *)buf + filesize; + + // all models use memory, so allocate a memory pool + mod->mempool = Mem_AllocPool(mod->name, 0, NULL); + + num = LittleLong(*((int *)buf)); + // call the apropriate loader + loadmodel = mod; + if (!strcasecmp(FS_FileExtension(mod->name), "obj")) Mod_OBJ_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDPO", 4)) Mod_IDP0_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDP2", 4)) Mod_IDP2_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDP3", 4)) Mod_IDP3_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDSP", 4)) Mod_IDSP_Load(mod, buf, bufend); + else if (!memcmp(buf, "IDS2", 4)) Mod_IDS2_Load(mod, buf, bufend); + else if (!memcmp(buf, "IBSP", 4)) Mod_IBSP_Load(mod, buf, bufend); + else if (!memcmp(buf, "ZYMOTICMODEL", 12)) Mod_ZYMOTICMODEL_Load(mod, buf, bufend); + else if (!memcmp(buf, "DARKPLACESMODEL", 16)) Mod_DARKPLACESMODEL_Load(mod, buf, bufend); + else if (!memcmp(buf, "ACTRHEAD", 8)) Mod_PSKMODEL_Load(mod, buf, bufend); + else if (!memcmp(buf, "INTERQUAKEMODEL", 16)) Mod_INTERQUAKEMODEL_Load(mod, buf, bufend); + else if (strlen(mod->name) >= 4 && !strcmp(mod->name + strlen(mod->name) - 4, ".map")) Mod_MAP_Load(mod, buf, bufend); + else if (num == BSPVERSION || num == 30 || !memcmp(buf, "BSP2", 4) || !memcmp(buf, "2PSB", 4)) Mod_Q1BSP_Load(mod, buf, bufend); + else Con_Printf("Mod_LoadModel: model \"%s\" is of unknown/unsupported type\n", mod->name); + Mem_Free(buf); + + Mod_FindPotentialDeforms(mod); + + buf = FS_LoadFile(va(vabuf, sizeof(vabuf), "%s.framegroups", mod->name), tempmempool, false, &filesize); + if(buf) + { + Mod_FrameGroupify(mod, (const char *)buf); + Mem_Free(buf); + } + + Mod_BuildVBOs(); + } + else if (crash) + { + // LordHavoc: Sys_Error was *ANNOYING* + Con_Printf ("Mod_LoadModel: %s not found\n", mod->name); + } + + // no fatal errors occurred, so this model is ready to use. + mod->loaded = true; + + SCR_PopLoadingScreen(false); + + return mod; +} + +void Mod_ClearUsed(void) +{ + int i; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0]) + mod->used = false; +} + +void Mod_PurgeUnused(void) +{ + int i; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && !mod->used) + { + Mod_UnloadModel(mod); + Mem_ExpandableArray_FreeRecord(&models, mod); + } + } +} + +/* +================== +Mod_FindName + +================== +*/ +dp_model_t *Mod_FindName(const char *name, const char *parentname) +{ + int i; + int nummodels; + dp_model_t *mod; + + if (!parentname) + parentname = ""; + + // if we're not dedicatd, the renderer calls will crash without video + Host_StartVideo(); + + nummodels = Mem_ExpandableArray_IndexRange(&models); + + if (!name[0]) + Host_Error ("Mod_ForName: empty name"); + + // search the currently loaded models + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && !strcmp(mod->name, name) && ((!mod->brush.parentmodel && !parentname[0]) || (mod->brush.parentmodel && parentname[0] && !strcmp(mod->brush.parentmodel->name, parentname)))) + { + mod->used = true; + return mod; + } + } + + // no match found, create a new one + mod = (dp_model_t *) Mem_ExpandableArray_AllocRecord(&models); + strlcpy(mod->name, name, sizeof(mod->name)); + if (parentname[0]) + mod->brush.parentmodel = Mod_FindName(parentname, NULL); + else + mod->brush.parentmodel = NULL; + mod->loaded = false; + mod->used = true; + return mod; +} + +/* +================== +Mod_ForName + +Loads in a model for the given name +================== +*/ +dp_model_t *Mod_ForName(const char *name, qboolean crash, qboolean checkdisk, const char *parentname) +{ + dp_model_t *model; + model = Mod_FindName(name, parentname); + if (!model->loaded || checkdisk) + Mod_LoadModel(model, crash, checkdisk); + return model; +} + +/* +================== +Mod_Reload + +Reloads all models if they have changed +================== +*/ +void Mod_Reload(void) +{ + int i, count; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + SCR_PushLoadingScreen(false, "Reloading models", 1.0); + count = 0; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*' && mod->used) + ++count; + for (i = 0;i < nummodels;i++) + if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*' && mod->used) + { + SCR_PushLoadingScreen(true, mod->name, 1.0 / count); + Mod_LoadModel(mod, true, true); + SCR_PopLoadingScreen(false); + } + SCR_PopLoadingScreen(false); +} + +unsigned char *mod_base; + + +//============================================================================= + +/* +================ +Mod_Print +================ +*/ +static void Mod_Print(void) +{ + int i; + int nummodels = Mem_ExpandableArray_IndexRange(&models); + dp_model_t *mod; + + Con_Print("Loaded models:\n"); + for (i = 0;i < nummodels;i++) + { + if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') + { + if (mod->brush.numsubmodels) + Con_Printf("%4iK %s (%i submodels)\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name, mod->brush.numsubmodels); + else + Con_Printf("%4iK %s\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name); + } + } +} + +/* +================ +Mod_Precache +================ +*/ +static void Mod_Precache(void) +{ + if (Cmd_Argc() == 2) + Mod_ForName(Cmd_Argv(1), false, true, Cmd_Argv(1)[0] == '*' ? cl.model_name[1] : NULL); + else + Con_Print("usage: modelprecache \n"); +} + +int Mod_BuildVertexRemapTableFromElements(int numelements, const int *elements, int numvertices, int *remapvertices) +{ + int i, count; + unsigned char *used; + used = (unsigned char *)Mem_Alloc(tempmempool, numvertices); + memset(used, 0, numvertices); + for (i = 0;i < numelements;i++) + used[elements[i]] = 1; + for (i = 0, count = 0;i < numvertices;i++) + remapvertices[i] = used[i] ? count++ : -1; + Mem_Free(used); + return count; +} + +#if 1 +// fast way, using an edge hash +#define TRIANGLEEDGEHASH 8192 +void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles) +{ + int i, j, p, e1, e2, *n, hashindex, count, match; + const int *e; + typedef struct edgehashentry_s + { + struct edgehashentry_s *next; + int triangle; + int element[2]; + } + edgehashentry_t; + static edgehashentry_t **edgehash; + edgehashentry_t *edgehashentries, *hash; + if (!numtriangles) + return; + edgehash = (edgehashentry_t **)Mem_Alloc(tempmempool, TRIANGLEEDGEHASH * sizeof(*edgehash)); + // if there are too many triangles for the stack array, allocate larger buffer + edgehashentries = (edgehashentry_t *)Mem_Alloc(tempmempool, numtriangles * 3 * sizeof(edgehashentry_t)); + // find neighboring triangles + for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) + { + for (j = 0, p = 2;j < 3;p = j, j++) + { + e1 = e[p]; + e2 = e[j]; + // this hash index works for both forward and backward edges + hashindex = (unsigned int)(e1 + e2) % TRIANGLEEDGEHASH; + hash = edgehashentries + i * 3 + j; + hash->next = edgehash[hashindex]; + edgehash[hashindex] = hash; + hash->triangle = i; + hash->element[0] = e1; + hash->element[1] = e2; + } + } + for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) + { + for (j = 0, p = 2;j < 3;p = j, j++) + { + e1 = e[p]; + e2 = e[j]; + // this hash index works for both forward and backward edges + hashindex = (unsigned int)(e1 + e2) % TRIANGLEEDGEHASH; + count = 0; + match = -1; + for (hash = edgehash[hashindex];hash;hash = hash->next) + { + if (hash->element[0] == e2 && hash->element[1] == e1) + { + if (hash->triangle != i) + match = hash->triangle; + count++; + } + else if ((hash->element[0] == e1 && hash->element[1] == e2)) + count++; + } + // detect edges shared by three triangles and make them seams + if (count > 2) + match = -1; + n[p] = match; + } + + // also send a keepalive here (this can take a while too!) + CL_KeepaliveMessage(false); + } + // free the allocated buffer + Mem_Free(edgehashentries); + Mem_Free(edgehash); +} +#else +// very slow but simple way +static int Mod_FindTriangleWithEdge(const int *elements, int numtriangles, int start, int end, int ignore) +{ + int i, match, count; + count = 0; + match = -1; + for (i = 0;i < numtriangles;i++, elements += 3) + { + if ((elements[0] == start && elements[1] == end) + || (elements[1] == start && elements[2] == end) + || (elements[2] == start && elements[0] == end)) + { + if (i != ignore) + match = i; + count++; + } + else if ((elements[1] == start && elements[0] == end) + || (elements[2] == start && elements[1] == end) + || (elements[0] == start && elements[2] == end)) + count++; + } + // detect edges shared by three triangles and make them seams + if (count > 2) + match = -1; + return match; +} + +void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles) +{ + int i, *n; + const int *e; + for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) + { + n[0] = Mod_FindTriangleWithEdge(elements, numtriangles, e[1], e[0], i); + n[1] = Mod_FindTriangleWithEdge(elements, numtriangles, e[2], e[1], i); + n[2] = Mod_FindTriangleWithEdge(elements, numtriangles, e[0], e[2], i); + } +} +#endif + +void Mod_ValidateElements(int *elements, int numtriangles, int firstvertex, int numverts, const char *filename, int fileline) +{ + int i, warned = false, endvertex = firstvertex + numverts; + for (i = 0;i < numtriangles * 3;i++) + { + if (elements[i] < firstvertex || elements[i] >= endvertex) + { + if (!warned) + { + warned = true; + Con_Printf("Mod_ValidateElements: out of bounds elements detected at %s:%d\n", filename, fileline); + } + elements[i] = firstvertex; + } + } +} + +// warning: this is an expensive function! +void Mod_BuildNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const int *elements, float *normal3f, qboolean areaweighting) +{ + int i, j; + const int *element; + float *vectorNormal; + float areaNormal[3]; + // clear the vectors + memset(normal3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); + // process each vertex of each triangle and accumulate the results + // use area-averaging, to make triangles with a big area have a bigger + // weighting on the vertex normal than triangles with a small area + // to do so, just add the 'normals' together (the bigger the area + // the greater the length of the normal is + element = elements; + for (i = 0; i < numtriangles; i++, element += 3) + { + TriangleNormal( + vertex3f + element[0] * 3, + vertex3f + element[1] * 3, + vertex3f + element[2] * 3, + areaNormal + ); + + if (!areaweighting) + VectorNormalize(areaNormal); + + for (j = 0;j < 3;j++) + { + vectorNormal = normal3f + element[j] * 3; + vectorNormal[0] += areaNormal[0]; + vectorNormal[1] += areaNormal[1]; + vectorNormal[2] += areaNormal[2]; + } + } + // and just normalize the accumulated vertex normal in the end + vectorNormal = normal3f + 3 * firstvertex; + for (i = 0; i < numvertices; i++, vectorNormal += 3) + VectorNormalize(vectorNormal); +} + +#if 0 +static void Mod_BuildBumpVectors(const float *v0, const float *v1, const float *v2, const float *tc0, const float *tc1, const float *tc2, float *svector3f, float *tvector3f, float *normal3f) +{ + float f, tangentcross[3], v10[3], v20[3], tc10[2], tc20[2]; + // 79 add/sub/negate/multiply (1 cycle), 1 compare (3 cycle?), total cycles not counting load/store/exchange roughly 82 cycles + // 6 add, 28 subtract, 39 multiply, 1 compare, 50% chance of 6 negates + + // 6 multiply, 9 subtract + VectorSubtract(v1, v0, v10); + VectorSubtract(v2, v0, v20); + normal3f[0] = v20[1] * v10[2] - v20[2] * v10[1]; + normal3f[1] = v20[2] * v10[0] - v20[0] * v10[2]; + normal3f[2] = v20[0] * v10[1] - v20[1] * v10[0]; + // 12 multiply, 10 subtract + tc10[1] = tc1[1] - tc0[1]; + tc20[1] = tc2[1] - tc0[1]; + svector3f[0] = tc10[1] * v20[0] - tc20[1] * v10[0]; + svector3f[1] = tc10[1] * v20[1] - tc20[1] * v10[1]; + svector3f[2] = tc10[1] * v20[2] - tc20[1] * v10[2]; + tc10[0] = tc1[0] - tc0[0]; + tc20[0] = tc2[0] - tc0[0]; + tvector3f[0] = tc10[0] * v20[0] - tc20[0] * v10[0]; + tvector3f[1] = tc10[0] * v20[1] - tc20[0] * v10[1]; + tvector3f[2] = tc10[0] * v20[2] - tc20[0] * v10[2]; + // 12 multiply, 4 add, 6 subtract + f = DotProduct(svector3f, normal3f); + svector3f[0] -= f * normal3f[0]; + svector3f[1] -= f * normal3f[1]; + svector3f[2] -= f * normal3f[2]; + f = DotProduct(tvector3f, normal3f); + tvector3f[0] -= f * normal3f[0]; + tvector3f[1] -= f * normal3f[1]; + tvector3f[2] -= f * normal3f[2]; + // if texture is mapped the wrong way (counterclockwise), the tangents + // have to be flipped, this is detected by calculating a normal from the + // two tangents, and seeing if it is opposite the surface normal + // 9 multiply, 2 add, 3 subtract, 1 compare, 50% chance of: 6 negates + CrossProduct(tvector3f, svector3f, tangentcross); + if (DotProduct(tangentcross, normal3f) < 0) + { + VectorNegate(svector3f, svector3f); + VectorNegate(tvector3f, tvector3f); + } +} +#endif + +// warning: this is a very expensive function! +void Mod_BuildTextureVectorsFromNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const float *texcoord2f, const float *normal3f, const int *elements, float *svector3f, float *tvector3f, qboolean areaweighting) +{ + int i, tnum; + float sdir[3], tdir[3], normal[3], *sv, *tv; + const float *v0, *v1, *v2, *tc0, *tc1, *tc2, *n; + float f, tangentcross[3], v10[3], v20[3], tc10[2], tc20[2]; + const int *e; + // clear the vectors + memset(svector3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); + memset(tvector3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); + // process each vertex of each triangle and accumulate the results + for (tnum = 0, e = elements;tnum < numtriangles;tnum++, e += 3) + { + v0 = vertex3f + e[0] * 3; + v1 = vertex3f + e[1] * 3; + v2 = vertex3f + e[2] * 3; + tc0 = texcoord2f + e[0] * 2; + tc1 = texcoord2f + e[1] * 2; + tc2 = texcoord2f + e[2] * 2; + + // 79 add/sub/negate/multiply (1 cycle), 1 compare (3 cycle?), total cycles not counting load/store/exchange roughly 82 cycles + // 6 add, 28 subtract, 39 multiply, 1 compare, 50% chance of 6 negates + + // calculate the edge directions and surface normal + // 6 multiply, 9 subtract + VectorSubtract(v1, v0, v10); + VectorSubtract(v2, v0, v20); + normal[0] = v20[1] * v10[2] - v20[2] * v10[1]; + normal[1] = v20[2] * v10[0] - v20[0] * v10[2]; + normal[2] = v20[0] * v10[1] - v20[1] * v10[0]; + + // calculate the tangents + // 12 multiply, 10 subtract + tc10[1] = tc1[1] - tc0[1]; + tc20[1] = tc2[1] - tc0[1]; + sdir[0] = tc10[1] * v20[0] - tc20[1] * v10[0]; + sdir[1] = tc10[1] * v20[1] - tc20[1] * v10[1]; + sdir[2] = tc10[1] * v20[2] - tc20[1] * v10[2]; + tc10[0] = tc1[0] - tc0[0]; + tc20[0] = tc2[0] - tc0[0]; + tdir[0] = tc10[0] * v20[0] - tc20[0] * v10[0]; + tdir[1] = tc10[0] * v20[1] - tc20[0] * v10[1]; + tdir[2] = tc10[0] * v20[2] - tc20[0] * v10[2]; + + // if texture is mapped the wrong way (counterclockwise), the tangents + // have to be flipped, this is detected by calculating a normal from the + // two tangents, and seeing if it is opposite the surface normal + // 9 multiply, 2 add, 3 subtract, 1 compare, 50% chance of: 6 negates + CrossProduct(tdir, sdir, tangentcross); + if (DotProduct(tangentcross, normal) < 0) + { + VectorNegate(sdir, sdir); + VectorNegate(tdir, tdir); + } + + if (!areaweighting) + { + VectorNormalize(sdir); + VectorNormalize(tdir); + } + for (i = 0;i < 3;i++) + { + VectorAdd(svector3f + e[i]*3, sdir, svector3f + e[i]*3); + VectorAdd(tvector3f + e[i]*3, tdir, tvector3f + e[i]*3); + } + } + // make the tangents completely perpendicular to the surface normal, and + // then normalize them + // 16 assignments, 2 divide, 2 sqrt, 2 negates, 14 adds, 24 multiplies + for (i = 0, sv = svector3f + 3 * firstvertex, tv = tvector3f + 3 * firstvertex, n = normal3f + 3 * firstvertex;i < numvertices;i++, sv += 3, tv += 3, n += 3) + { + f = -DotProduct(sv, n); + VectorMA(sv, f, n, sv); + VectorNormalize(sv); + f = -DotProduct(tv, n); + VectorMA(tv, f, n, tv); + VectorNormalize(tv); + } +} + +void Mod_AllocSurfMesh(mempool_t *mempool, int numvertices, int numtriangles, qboolean lightmapoffsets, qboolean vertexcolors, qboolean neighbors) +{ + unsigned char *data; + data = (unsigned char *)Mem_Alloc(mempool, numvertices * (3 + 3 + 3 + 3 + 2 + 2 + (vertexcolors ? 4 : 0)) * sizeof(float) + numvertices * (lightmapoffsets ? 1 : 0) * sizeof(int) + numtriangles * (3 + (neighbors ? 3 : 0)) * sizeof(int) + (numvertices <= 65536 ? numtriangles * sizeof(unsigned short[3]) : 0)); + loadmodel->surfmesh.num_vertices = numvertices; + loadmodel->surfmesh.num_triangles = numtriangles; + if (loadmodel->surfmesh.num_vertices) + { + loadmodel->surfmesh.data_vertex3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_svector3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_tvector3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_normal3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_texcoordtexture2f = (float *)data, data += sizeof(float[2]) * loadmodel->surfmesh.num_vertices; + loadmodel->surfmesh.data_texcoordlightmap2f = (float *)data, data += sizeof(float[2]) * loadmodel->surfmesh.num_vertices; + if (vertexcolors) + loadmodel->surfmesh.data_lightmapcolor4f = (float *)data, data += sizeof(float[4]) * loadmodel->surfmesh.num_vertices; + if (lightmapoffsets) + loadmodel->surfmesh.data_lightmapoffsets = (int *)data, data += sizeof(int) * loadmodel->surfmesh.num_vertices; + } + if (loadmodel->surfmesh.num_triangles) + { + loadmodel->surfmesh.data_element3i = (int *)data, data += sizeof(int[3]) * loadmodel->surfmesh.num_triangles; + if (neighbors) + loadmodel->surfmesh.data_neighbor3i = (int *)data, data += sizeof(int[3]) * loadmodel->surfmesh.num_triangles; + if (loadmodel->surfmesh.num_vertices <= 65536) + loadmodel->surfmesh.data_element3s = (unsigned short *)data, data += sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles; + } +} + +shadowmesh_t *Mod_ShadowMesh_Alloc(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable) +{ + shadowmesh_t *newmesh; + unsigned char *data; + int size; + size = sizeof(shadowmesh_t); + size += maxverts * sizeof(float[3]); + if (light) + size += maxverts * sizeof(float[11]); + size += maxtriangles * sizeof(int[3]); + if (maxverts <= 65536) + size += maxtriangles * sizeof(unsigned short[3]); + if (neighbors) + size += maxtriangles * sizeof(int[3]); + if (expandable) + size += SHADOWMESHVERTEXHASH * sizeof(shadowmeshvertexhash_t *) + maxverts * sizeof(shadowmeshvertexhash_t); + data = (unsigned char *)Mem_Alloc(mempool, size); + newmesh = (shadowmesh_t *)data;data += sizeof(*newmesh); + newmesh->map_diffuse = map_diffuse; + newmesh->map_specular = map_specular; + newmesh->map_normal = map_normal; + newmesh->maxverts = maxverts; + newmesh->maxtriangles = maxtriangles; + newmesh->numverts = 0; + newmesh->numtriangles = 0; + memset(newmesh->sideoffsets, 0, sizeof(newmesh->sideoffsets)); + memset(newmesh->sidetotals, 0, sizeof(newmesh->sidetotals)); + + newmesh->vertex3f = (float *)data;data += maxverts * sizeof(float[3]); + if (light) + { + newmesh->svector3f = (float *)data;data += maxverts * sizeof(float[3]); + newmesh->tvector3f = (float *)data;data += maxverts * sizeof(float[3]); + newmesh->normal3f = (float *)data;data += maxverts * sizeof(float[3]); + newmesh->texcoord2f = (float *)data;data += maxverts * sizeof(float[2]); + } + newmesh->element3i = (int *)data;data += maxtriangles * sizeof(int[3]); + if (neighbors) + { + newmesh->neighbor3i = (int *)data;data += maxtriangles * sizeof(int[3]); + } + if (expandable) + { + newmesh->vertexhashtable = (shadowmeshvertexhash_t **)data;data += SHADOWMESHVERTEXHASH * sizeof(shadowmeshvertexhash_t *); + newmesh->vertexhashentries = (shadowmeshvertexhash_t *)data;data += maxverts * sizeof(shadowmeshvertexhash_t); + } + if (maxverts <= 65536) + newmesh->element3s = (unsigned short *)data;data += maxtriangles * sizeof(unsigned short[3]); + return newmesh; +} + +shadowmesh_t *Mod_ShadowMesh_ReAlloc(mempool_t *mempool, shadowmesh_t *oldmesh, int light, int neighbors) +{ + shadowmesh_t *newmesh; + newmesh = Mod_ShadowMesh_Alloc(mempool, oldmesh->numverts, oldmesh->numtriangles, oldmesh->map_diffuse, oldmesh->map_specular, oldmesh->map_normal, light, neighbors, false); + newmesh->numverts = oldmesh->numverts; + newmesh->numtriangles = oldmesh->numtriangles; + memcpy(newmesh->sideoffsets, oldmesh->sideoffsets, sizeof(oldmesh->sideoffsets)); + memcpy(newmesh->sidetotals, oldmesh->sidetotals, sizeof(oldmesh->sidetotals)); + + memcpy(newmesh->vertex3f, oldmesh->vertex3f, oldmesh->numverts * sizeof(float[3])); + if (newmesh->svector3f && oldmesh->svector3f) + { + memcpy(newmesh->svector3f, oldmesh->svector3f, oldmesh->numverts * sizeof(float[3])); + memcpy(newmesh->tvector3f, oldmesh->tvector3f, oldmesh->numverts * sizeof(float[3])); + memcpy(newmesh->normal3f, oldmesh->normal3f, oldmesh->numverts * sizeof(float[3])); + memcpy(newmesh->texcoord2f, oldmesh->texcoord2f, oldmesh->numverts * sizeof(float[2])); + } + memcpy(newmesh->element3i, oldmesh->element3i, oldmesh->numtriangles * sizeof(int[3])); + if (newmesh->neighbor3i && oldmesh->neighbor3i) + memcpy(newmesh->neighbor3i, oldmesh->neighbor3i, oldmesh->numtriangles * sizeof(int[3])); + return newmesh; +} + +int Mod_ShadowMesh_AddVertex(shadowmesh_t *mesh, float *vertex14f) +{ + int hashindex, vnum; + shadowmeshvertexhash_t *hash; + // this uses prime numbers intentionally + hashindex = (unsigned int) (vertex14f[0] * 2003 + vertex14f[1] * 4001 + vertex14f[2] * 7919) % SHADOWMESHVERTEXHASH; + for (hash = mesh->vertexhashtable[hashindex];hash;hash = hash->next) + { + vnum = (hash - mesh->vertexhashentries); + if ((mesh->vertex3f == NULL || (mesh->vertex3f[vnum * 3 + 0] == vertex14f[0] && mesh->vertex3f[vnum * 3 + 1] == vertex14f[1] && mesh->vertex3f[vnum * 3 + 2] == vertex14f[2])) + && (mesh->svector3f == NULL || (mesh->svector3f[vnum * 3 + 0] == vertex14f[3] && mesh->svector3f[vnum * 3 + 1] == vertex14f[4] && mesh->svector3f[vnum * 3 + 2] == vertex14f[5])) + && (mesh->tvector3f == NULL || (mesh->tvector3f[vnum * 3 + 0] == vertex14f[6] && mesh->tvector3f[vnum * 3 + 1] == vertex14f[7] && mesh->tvector3f[vnum * 3 + 2] == vertex14f[8])) + && (mesh->normal3f == NULL || (mesh->normal3f[vnum * 3 + 0] == vertex14f[9] && mesh->normal3f[vnum * 3 + 1] == vertex14f[10] && mesh->normal3f[vnum * 3 + 2] == vertex14f[11])) + && (mesh->texcoord2f == NULL || (mesh->texcoord2f[vnum * 2 + 0] == vertex14f[12] && mesh->texcoord2f[vnum * 2 + 1] == vertex14f[13]))) + return hash - mesh->vertexhashentries; + } + vnum = mesh->numverts++; + hash = mesh->vertexhashentries + vnum; + hash->next = mesh->vertexhashtable[hashindex]; + mesh->vertexhashtable[hashindex] = hash; + if (mesh->vertex3f) {mesh->vertex3f[vnum * 3 + 0] = vertex14f[0];mesh->vertex3f[vnum * 3 + 1] = vertex14f[1];mesh->vertex3f[vnum * 3 + 2] = vertex14f[2];} + if (mesh->svector3f) {mesh->svector3f[vnum * 3 + 0] = vertex14f[3];mesh->svector3f[vnum * 3 + 1] = vertex14f[4];mesh->svector3f[vnum * 3 + 2] = vertex14f[5];} + if (mesh->tvector3f) {mesh->tvector3f[vnum * 3 + 0] = vertex14f[6];mesh->tvector3f[vnum * 3 + 1] = vertex14f[7];mesh->tvector3f[vnum * 3 + 2] = vertex14f[8];} + if (mesh->normal3f) {mesh->normal3f[vnum * 3 + 0] = vertex14f[9];mesh->normal3f[vnum * 3 + 1] = vertex14f[10];mesh->normal3f[vnum * 3 + 2] = vertex14f[11];} + if (mesh->texcoord2f) {mesh->texcoord2f[vnum * 2 + 0] = vertex14f[12];mesh->texcoord2f[vnum * 2 + 1] = vertex14f[13];} + return vnum; +} + +void Mod_ShadowMesh_AddTriangle(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, float *vertex14f) +{ + if (mesh->numtriangles == 0) + { + // set the properties on this empty mesh to be more favorable... + // (note: this case only occurs for the first triangle added to a new mesh chain) + mesh->map_diffuse = map_diffuse; + mesh->map_specular = map_specular; + mesh->map_normal = map_normal; + } + while (mesh->map_diffuse != map_diffuse || mesh->map_specular != map_specular || mesh->map_normal != map_normal || mesh->numverts + 3 > mesh->maxverts || mesh->numtriangles + 1 > mesh->maxtriangles) + { + if (mesh->next == NULL) + mesh->next = Mod_ShadowMesh_Alloc(mempool, max(mesh->maxverts, 300), max(mesh->maxtriangles, 100), map_diffuse, map_specular, map_normal, mesh->svector3f != NULL, mesh->neighbor3i != NULL, true); + mesh = mesh->next; + } + mesh->element3i[mesh->numtriangles * 3 + 0] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 0); + mesh->element3i[mesh->numtriangles * 3 + 1] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 1); + mesh->element3i[mesh->numtriangles * 3 + 2] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 2); + mesh->numtriangles++; +} + +void Mod_ShadowMesh_AddMesh(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *texcoord2f, int numtris, const int *element3i) +{ + int i, j, e; + float vbuf[3*14], *v; + memset(vbuf, 0, sizeof(vbuf)); + for (i = 0;i < numtris;i++) + { + for (j = 0, v = vbuf;j < 3;j++, v += 14) + { + e = *element3i++; + if (vertex3f) + { + v[0] = vertex3f[e * 3 + 0]; + v[1] = vertex3f[e * 3 + 1]; + v[2] = vertex3f[e * 3 + 2]; + } + if (svector3f) + { + v[3] = svector3f[e * 3 + 0]; + v[4] = svector3f[e * 3 + 1]; + v[5] = svector3f[e * 3 + 2]; + } + if (tvector3f) + { + v[6] = tvector3f[e * 3 + 0]; + v[7] = tvector3f[e * 3 + 1]; + v[8] = tvector3f[e * 3 + 2]; + } + if (normal3f) + { + v[9] = normal3f[e * 3 + 0]; + v[10] = normal3f[e * 3 + 1]; + v[11] = normal3f[e * 3 + 2]; + } + if (texcoord2f) + { + v[12] = texcoord2f[e * 2 + 0]; + v[13] = texcoord2f[e * 2 + 1]; + } + } + Mod_ShadowMesh_AddTriangle(mempool, mesh, map_diffuse, map_specular, map_normal, vbuf); + } + + // the triangle calculation can take a while, so let's do a keepalive here + CL_KeepaliveMessage(false); +} + +shadowmesh_t *Mod_ShadowMesh_Begin(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable) +{ + // the preparation before shadow mesh initialization can take a while, so let's do a keepalive here + CL_KeepaliveMessage(false); + + return Mod_ShadowMesh_Alloc(mempool, maxverts, maxtriangles, map_diffuse, map_specular, map_normal, light, neighbors, expandable); +} + +static void Mod_ShadowMesh_CreateVBOs(shadowmesh_t *mesh, mempool_t *mempool) +{ + if (!mesh->numverts) + return; + + // build r_vertexmesh_t array + // (compressed interleaved array for D3D) + if (!mesh->vertexmesh && mesh->texcoord2f && vid.useinterleavedarrays) + { + int vertexindex; + int numvertices = mesh->numverts; + r_vertexmesh_t *vertexmesh; + mesh->vertexmesh = vertexmesh = (r_vertexmesh_t*)Mem_Alloc(mempool, numvertices * sizeof(*mesh->vertexmesh)); + for (vertexindex = 0;vertexindex < numvertices;vertexindex++, vertexmesh++) + { + VectorCopy(mesh->vertex3f + 3*vertexindex, vertexmesh->vertex3f); + VectorScale(mesh->svector3f + 3*vertexindex, 1.0f, vertexmesh->svector3f); + VectorScale(mesh->tvector3f + 3*vertexindex, 1.0f, vertexmesh->tvector3f); + VectorScale(mesh->normal3f + 3*vertexindex, 1.0f, vertexmesh->normal3f); + Vector2Copy(mesh->texcoord2f + 2*vertexindex, vertexmesh->texcoordtexture2f); + } + } + + // upload short indices as a buffer + if (mesh->element3s && !mesh->element3s_indexbuffer) + mesh->element3s_indexbuffer = R_Mesh_CreateMeshBuffer(mesh->element3s, mesh->numtriangles * sizeof(short[3]), loadmodel->name, true, false, false, true); + + // upload int indices as a buffer + if (mesh->element3i && !mesh->element3i_indexbuffer && !mesh->element3s) + mesh->element3i_indexbuffer = R_Mesh_CreateMeshBuffer(mesh->element3i, mesh->numtriangles * sizeof(int[3]), loadmodel->name, true, false, false, false); + + // vertex buffer is several arrays and we put them in the same buffer + // + // is this wise? the texcoordtexture2f array is used with dynamic + // vertex/svector/tvector/normal when rendering animated models, on the + // other hand animated models don't use a lot of vertices anyway... + if (!mesh->vbo_vertexbuffer && !vid.useinterleavedarrays) + { + size_t size; + unsigned char *mem; + size = 0; + mesh->vbooffset_vertexmesh = size;if (mesh->vertexmesh ) size += mesh->numverts * sizeof(r_vertexmesh_t); + mesh->vbooffset_vertex3f = size;if (mesh->vertex3f ) size += mesh->numverts * sizeof(float[3]); + mesh->vbooffset_svector3f = size;if (mesh->svector3f ) size += mesh->numverts * sizeof(float[3]); + mesh->vbooffset_tvector3f = size;if (mesh->tvector3f ) size += mesh->numverts * sizeof(float[3]); + mesh->vbooffset_normal3f = size;if (mesh->normal3f ) size += mesh->numverts * sizeof(float[3]); + mesh->vbooffset_texcoord2f = size;if (mesh->texcoord2f ) size += mesh->numverts * sizeof(float[2]); + mem = (unsigned char *)Mem_Alloc(tempmempool, size); + if (mesh->vertexmesh ) memcpy(mem + mesh->vbooffset_vertexmesh , mesh->vertexmesh , mesh->numverts * sizeof(r_vertexmesh_t)); + if (mesh->vertex3f ) memcpy(mem + mesh->vbooffset_vertex3f , mesh->vertex3f , mesh->numverts * sizeof(float[3])); + if (mesh->svector3f ) memcpy(mem + mesh->vbooffset_svector3f , mesh->svector3f , mesh->numverts * sizeof(float[3])); + if (mesh->tvector3f ) memcpy(mem + mesh->vbooffset_tvector3f , mesh->tvector3f , mesh->numverts * sizeof(float[3])); + if (mesh->normal3f ) memcpy(mem + mesh->vbooffset_normal3f , mesh->normal3f , mesh->numverts * sizeof(float[3])); + if (mesh->texcoord2f ) memcpy(mem + mesh->vbooffset_texcoord2f , mesh->texcoord2f , mesh->numverts * sizeof(float[2])); + mesh->vbo_vertexbuffer = R_Mesh_CreateMeshBuffer(mem, size, "shadowmesh", false, false, false, false); + Mem_Free(mem); + } +} + +shadowmesh_t *Mod_ShadowMesh_Finish(mempool_t *mempool, shadowmesh_t *firstmesh, qboolean light, qboolean neighbors, qboolean createvbo) +{ + shadowmesh_t *mesh, *newmesh, *nextmesh; + // reallocate meshs to conserve space + for (mesh = firstmesh, firstmesh = NULL;mesh;mesh = nextmesh) + { + nextmesh = mesh->next; + if (mesh->numverts >= 3 && mesh->numtriangles >= 1) + { + newmesh = Mod_ShadowMesh_ReAlloc(mempool, mesh, light, neighbors); + newmesh->next = firstmesh; + firstmesh = newmesh; + if (newmesh->element3s) + { + int i; + for (i = 0;i < newmesh->numtriangles*3;i++) + newmesh->element3s[i] = newmesh->element3i[i]; + } + if (createvbo) + Mod_ShadowMesh_CreateVBOs(newmesh, mempool); + } + Mem_Free(mesh); + } + + // this can take a while, so let's do a keepalive here + CL_KeepaliveMessage(false); + + return firstmesh; +} + +void Mod_ShadowMesh_CalcBBox(shadowmesh_t *firstmesh, vec3_t mins, vec3_t maxs, vec3_t center, float *radius) +{ + int i; + shadowmesh_t *mesh; + vec3_t nmins, nmaxs, ncenter, temp; + float nradius2, dist2, *v; + VectorClear(nmins); + VectorClear(nmaxs); + // calculate bbox + for (mesh = firstmesh;mesh;mesh = mesh->next) + { + if (mesh == firstmesh) + { + VectorCopy(mesh->vertex3f, nmins); + VectorCopy(mesh->vertex3f, nmaxs); + } + for (i = 0, v = mesh->vertex3f;i < mesh->numverts;i++, v += 3) + { + if (nmins[0] > v[0]) nmins[0] = v[0];if (nmaxs[0] < v[0]) nmaxs[0] = v[0]; + if (nmins[1] > v[1]) nmins[1] = v[1];if (nmaxs[1] < v[1]) nmaxs[1] = v[1]; + if (nmins[2] > v[2]) nmins[2] = v[2];if (nmaxs[2] < v[2]) nmaxs[2] = v[2]; + } + } + // calculate center and radius + ncenter[0] = (nmins[0] + nmaxs[0]) * 0.5f; + ncenter[1] = (nmins[1] + nmaxs[1]) * 0.5f; + ncenter[2] = (nmins[2] + nmaxs[2]) * 0.5f; + nradius2 = 0; + for (mesh = firstmesh;mesh;mesh = mesh->next) + { + for (i = 0, v = mesh->vertex3f;i < mesh->numverts;i++, v += 3) + { + VectorSubtract(v, ncenter, temp); + dist2 = DotProduct(temp, temp); + if (nradius2 < dist2) + nradius2 = dist2; + } + } + // return data + if (mins) + VectorCopy(nmins, mins); + if (maxs) + VectorCopy(nmaxs, maxs); + if (center) + VectorCopy(ncenter, center); + if (radius) + *radius = sqrt(nradius2); +} + +void Mod_ShadowMesh_Free(shadowmesh_t *mesh) +{ + shadowmesh_t *nextmesh; + for (;mesh;mesh = nextmesh) + { + if (mesh->element3i_indexbuffer) + R_Mesh_DestroyMeshBuffer(mesh->element3i_indexbuffer); + if (mesh->element3s_indexbuffer) + R_Mesh_DestroyMeshBuffer(mesh->element3s_indexbuffer); + if (mesh->vbo_vertexbuffer) + R_Mesh_DestroyMeshBuffer(mesh->vbo_vertexbuffer); + nextmesh = mesh->next; + Mem_Free(mesh); + } +} + +void Mod_CreateCollisionMesh(dp_model_t *mod) +{ + int k, numcollisionmeshtriangles; + qboolean usesinglecollisionmesh = false; + const msurface_t *surface = NULL; + + mempool_t *mempool = mod->mempool; + if (!mempool && mod->brush.parentmodel) + mempool = mod->brush.parentmodel->mempool; + // make a single combined collision mesh for physics engine use + // TODO rewrite this to use the collision brushes as source, to fix issues with e.g. common/caulk which creates no drawsurface + numcollisionmeshtriangles = 0; + for (k = 0;k < mod->nummodelsurfaces;k++) + { + surface = mod->data_surfaces + mod->firstmodelsurface + k; + if (!strcmp(surface->texture->name, "collision") || !strcmp(surface->texture->name, "collisionconvex")) // found collision mesh + { + usesinglecollisionmesh = true; + numcollisionmeshtriangles = surface->num_triangles; + break; + } + if (!(surface->texture->supercontents & SUPERCONTENTS_SOLID)) + continue; + numcollisionmeshtriangles += surface->num_triangles; + } + mod->brush.collisionmesh = Mod_ShadowMesh_Begin(mempool, numcollisionmeshtriangles * 3, numcollisionmeshtriangles, NULL, NULL, NULL, false, false, true); + if (usesinglecollisionmesh) + Mod_ShadowMesh_AddMesh(mempool, mod->brush.collisionmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); + else + { + for (k = 0;k < mod->nummodelsurfaces;k++) + { + surface = mod->data_surfaces + mod->firstmodelsurface + k; + if (!(surface->texture->supercontents & SUPERCONTENTS_SOLID)) + continue; + Mod_ShadowMesh_AddMesh(mempool, mod->brush.collisionmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); + } + } + mod->brush.collisionmesh = Mod_ShadowMesh_Finish(mempool, mod->brush.collisionmesh, false, false, false); +} + +#if 0 +static void Mod_GetTerrainVertex3fTexCoord2fFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int ix, int iy, float *vertex3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) +{ + float v[3], tc[3]; + v[0] = ix; + v[1] = iy; + if (ix >= 0 && iy >= 0 && ix < imagewidth && iy < imageheight) + v[2] = (imagepixels[((iy*imagewidth)+ix)*4+0] + imagepixels[((iy*imagewidth)+ix)*4+1] + imagepixels[((iy*imagewidth)+ix)*4+2]) * (1.0f / 765.0f); + else + v[2] = 0; + Matrix4x4_Transform(pixelstepmatrix, v, vertex3f); + Matrix4x4_Transform(pixeltexturestepmatrix, v, tc); + texcoord2f[0] = tc[0]; + texcoord2f[1] = tc[1]; +} + +static void Mod_GetTerrainVertexFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int ix, int iy, float *vertex3f, float *svector3f, float *tvector3f, float *normal3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) +{ + float vup[3], vdown[3], vleft[3], vright[3]; + float tcup[3], tcdown[3], tcleft[3], tcright[3]; + float sv[3], tv[3], nl[3]; + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy, vertex3f, texcoord2f, pixelstepmatrix, pixeltexturestepmatrix); + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy - 1, vup, tcup, pixelstepmatrix, pixeltexturestepmatrix); + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy + 1, vdown, tcdown, pixelstepmatrix, pixeltexturestepmatrix); + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix - 1, iy, vleft, tcleft, pixelstepmatrix, pixeltexturestepmatrix); + Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix + 1, iy, vright, tcright, pixelstepmatrix, pixeltexturestepmatrix); + Mod_BuildBumpVectors(vertex3f, vup, vright, texcoord2f, tcup, tcright, svector3f, tvector3f, normal3f); + Mod_BuildBumpVectors(vertex3f, vright, vdown, texcoord2f, tcright, tcdown, sv, tv, nl); + VectorAdd(svector3f, sv, svector3f); + VectorAdd(tvector3f, tv, tvector3f); + VectorAdd(normal3f, nl, normal3f); + Mod_BuildBumpVectors(vertex3f, vdown, vleft, texcoord2f, tcdown, tcleft, sv, tv, nl); + VectorAdd(svector3f, sv, svector3f); + VectorAdd(tvector3f, tv, tvector3f); + VectorAdd(normal3f, nl, normal3f); + Mod_BuildBumpVectors(vertex3f, vleft, vup, texcoord2f, tcleft, tcup, sv, tv, nl); + VectorAdd(svector3f, sv, svector3f); + VectorAdd(tvector3f, tv, tvector3f); + VectorAdd(normal3f, nl, normal3f); +} + +static void Mod_ConstructTerrainPatchFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int x1, int y1, int width, int height, int *element3i, int *neighbor3i, float *vertex3f, float *svector3f, float *tvector3f, float *normal3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) +{ + int x, y, ix, iy, *e; + e = element3i; + for (y = 0;y < height;y++) + { + for (x = 0;x < width;x++) + { + e[0] = (y + 1) * (width + 1) + (x + 0); + e[1] = (y + 0) * (width + 1) + (x + 0); + e[2] = (y + 1) * (width + 1) + (x + 1); + e[3] = (y + 0) * (width + 1) + (x + 0); + e[4] = (y + 0) * (width + 1) + (x + 1); + e[5] = (y + 1) * (width + 1) + (x + 1); + e += 6; + } + } + Mod_BuildTriangleNeighbors(neighbor3i, element3i, width*height*2); + for (y = 0, iy = y1;y < height + 1;y++, iy++) + for (x = 0, ix = x1;x < width + 1;x++, ix++, vertex3f += 3, texcoord2f += 2, svector3f += 3, tvector3f += 3, normal3f += 3) + Mod_GetTerrainVertexFromBGRA(imagepixels, imagewidth, imageheight, ix, iy, vertex3f, texcoord2f, svector3f, tvector3f, normal3f, pixelstepmatrix, pixeltexturestepmatrix); +} +#endif + +#if 0 +void Mod_Terrain_SurfaceRecurseChunk(dp_model_t *model, int stepsize, int x, int y) +{ + float mins[3]; + float maxs[3]; + float chunkwidth = min(stepsize, model->terrain.width - 1 - x); + float chunkheight = min(stepsize, model->terrain.height - 1 - y); + float viewvector[3]; + unsigned int firstvertex; + unsigned int *e; + float *v; + if (chunkwidth < 2 || chunkheight < 2) + return; + VectorSet(mins, model->terrain.mins[0] + x * stepsize * model->terrain.scale[0], model->terrain.mins[1] + y * stepsize * model->terrain.scale[1], model->terrain.mins[2]); + VectorSet(maxs, model->terrain.mins[0] + (x+1) * stepsize * model->terrain.scale[0], model->terrain.mins[1] + (y+1) * stepsize * model->terrain.scale[1], model->terrain.maxs[2]); + viewvector[0] = bound(mins[0], localvieworigin, maxs[0]) - model->terrain.vieworigin[0]; + viewvector[1] = bound(mins[1], localvieworigin, maxs[1]) - model->terrain.vieworigin[1]; + viewvector[2] = bound(mins[2], localvieworigin, maxs[2]) - model->terrain.vieworigin[2]; + if (stepsize > 1 && VectorLength(viewvector) < stepsize*model->terrain.scale[0]*r_terrain_lodscale.value) + { + // too close for this stepsize, emit as 4 chunks instead + stepsize /= 2; + Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x, y); + Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x+stepsize, y); + Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x, y+stepsize); + Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x+stepsize, y+stepsize); + return; + } + // emit the geometry at stepsize into our vertex buffer / index buffer + // we add two columns and two rows for skirt + outwidth = chunkwidth+2; + outheight = chunkheight+2; + outwidth2 = outwidth-1; + outheight2 = outheight-1; + outwidth3 = outwidth+1; + outheight3 = outheight+1; + firstvertex = numvertices; + e = model->terrain.element3i + numtriangles; + numtriangles += chunkwidth*chunkheight*2+chunkwidth*2*2+chunkheight*2*2; + v = model->terrain.vertex3f + numvertices; + numvertices += (chunkwidth+1)*(chunkheight+1)+(chunkwidth+1)*2+(chunkheight+1)*2; + // emit the triangles (note: the skirt is treated as two extra rows and two extra columns) + for (ty = 0;ty < outheight;ty++) + { + for (tx = 0;tx < outwidth;tx++) + { + *e++ = firstvertex + (ty )*outwidth3+(tx ); + *e++ = firstvertex + (ty )*outwidth3+(tx+1); + *e++ = firstvertex + (ty+1)*outwidth3+(tx+1); + *e++ = firstvertex + (ty )*outwidth3+(tx ); + *e++ = firstvertex + (ty+1)*outwidth3+(tx+1); + *e++ = firstvertex + (ty+1)*outwidth3+(tx ); + } + } + // TODO: emit surface vertices (x+tx*stepsize, y+ty*stepsize) + for (ty = 0;ty <= outheight;ty++) + { + skirtrow = ty == 0 || ty == outheight; + ry = y+bound(1, ty, outheight)*stepsize; + for (tx = 0;tx <= outwidth;tx++) + { + skirt = skirtrow || tx == 0 || tx == outwidth; + rx = x+bound(1, tx, outwidth)*stepsize; + v[0] = rx*scale[0]; + v[1] = ry*scale[1]; + v[2] = heightmap[ry*terrainwidth+rx]*scale[2]; + v += 3; + } + } + // TODO: emit skirt vertices +} + +void Mod_Terrain_UpdateSurfacesForViewOrigin(dp_model_t *model) +{ + for (y = 0;y < model->terrain.size[1];y += model->terrain. + Mod_Terrain_SurfaceRecurseChunk(model, model->terrain.maxstepsize, x, y); + Mod_Terrain_BuildChunk(model, +} +#endif + +static int Mod_LoadQ3Shaders_EnumerateWaveFunc(const char *s) +{ + int offset = 0; + if (!strncasecmp(s, "user", 4)) // parse stuff like "user1sin", always userfunc + { + offset = bound(0, s[4] - '0', 9); + offset = (offset + 1) << Q3WAVEFUNC_USER_SHIFT; + s += 4; + if(*s) + ++s; + } + if (!strcasecmp(s, "sin")) return offset | Q3WAVEFUNC_SIN; + if (!strcasecmp(s, "square")) return offset | Q3WAVEFUNC_SQUARE; + if (!strcasecmp(s, "triangle")) return offset | Q3WAVEFUNC_TRIANGLE; + if (!strcasecmp(s, "sawtooth")) return offset | Q3WAVEFUNC_SAWTOOTH; + if (!strcasecmp(s, "inversesawtooth")) return offset | Q3WAVEFUNC_INVERSESAWTOOTH; + if (!strcasecmp(s, "noise")) return offset | Q3WAVEFUNC_NOISE; + if (!strcasecmp(s, "none")) return offset | Q3WAVEFUNC_NONE; + Con_DPrintf("Mod_LoadQ3Shaders: unknown wavefunc %s\n", s); + return offset | Q3WAVEFUNC_NONE; +} + +void Mod_FreeQ3Shaders(void) +{ + Mem_FreePool(&q3shaders_mem); +} + +static void Q3Shader_AddToHash (q3shaderinfo_t* shader) +{ + unsigned short hash = CRC_Block_CaseInsensitive ((const unsigned char *)shader->name, strlen (shader->name)); + q3shader_hash_entry_t* entry = q3shader_data->hash + (hash % Q3SHADER_HASH_SIZE); + q3shader_hash_entry_t* lastEntry = NULL; + while (entry != NULL) + { + if (strcasecmp (entry->shader.name, shader->name) == 0) + { + // redeclaration + if(shader->dpshaderkill) + { + // killed shader is a redeclarion? we can safely ignore it + return; + } + else if(entry->shader.dpshaderkill) + { + // replace the old shader! + // this will skip the entry allocating part + // below and just replace the shader + break; + } + else + { + unsigned char *start, *end, *start2; + start = (unsigned char *) (&shader->Q3SHADERINFO_COMPARE_START); + end = ((unsigned char *) (&shader->Q3SHADERINFO_COMPARE_END)) + sizeof(shader->Q3SHADERINFO_COMPARE_END); + start2 = (unsigned char *) (&entry->shader.Q3SHADERINFO_COMPARE_START); + if(memcmp(start, start2, end - start)) + Con_DPrintf("Shader '%s' already defined, ignoring mismatching redeclaration\n", shader->name); + else + Con_DPrintf("Shader '%s' already defined\n", shader->name); + return; + } + } + lastEntry = entry; + entry = entry->chain; + } + if (entry == NULL) + { + if (lastEntry->shader.name[0] != 0) + { + /* Add to chain */ + q3shader_hash_entry_t* newEntry = (q3shader_hash_entry_t*) + Mem_ExpandableArray_AllocRecord (&q3shader_data->hash_entries); + + while (lastEntry->chain != NULL) lastEntry = lastEntry->chain; + lastEntry->chain = newEntry; + newEntry->chain = NULL; + lastEntry = newEntry; + } + /* else: head of chain, in hash entry array */ + entry = lastEntry; + } + memcpy (&entry->shader, shader, sizeof (q3shaderinfo_t)); +} + +extern cvar_t mod_noshader_default_offsetmapping; +extern cvar_t mod_q3shader_default_offsetmapping; +extern cvar_t mod_q3shader_default_offsetmapping_scale; +extern cvar_t mod_q3shader_default_offsetmapping_bias; +extern cvar_t mod_q3shader_default_polygonoffset; +extern cvar_t mod_q3shader_default_polygonfactor; +extern cvar_t mod_q3shader_force_addalpha; +extern cvar_t mod_q3shader_force_terrain_alphaflag; +void Mod_LoadQ3Shaders(void) +{ + int j; + int fileindex; + fssearch_t *search; + char *f; + const char *text; + q3shaderinfo_t shader; + q3shaderinfo_layer_t *layer; + int numparameters; + char parameter[TEXTURE_MAXFRAMES + 4][Q3PATHLENGTH]; + char *custsurfaceparmnames[256]; // VorteX: q3map2 has 64 but well, someone will need more + unsigned long custsurfaceflags[256]; + int numcustsurfaceflags; + qboolean dpshaderkill; + + Mod_FreeQ3Shaders(); + + q3shaders_mem = Mem_AllocPool("q3shaders", 0, NULL); + q3shader_data = (q3shader_data_t*)Mem_Alloc (q3shaders_mem, + sizeof (q3shader_data_t)); + Mem_ExpandableArray_NewArray (&q3shader_data->hash_entries, + q3shaders_mem, sizeof (q3shader_hash_entry_t), 256); + Mem_ExpandableArray_NewArray (&q3shader_data->char_ptrs, + q3shaders_mem, sizeof (char**), 256); + + // parse custinfoparms.txt + numcustsurfaceflags = 0; + if ((text = f = (char *)FS_LoadFile("scripts/custinfoparms.txt", tempmempool, false, NULL)) != NULL) + { + if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) + Con_DPrintf("scripts/custinfoparms.txt: contentflags section parsing error - expected \"{\", found \"%s\"\n", com_token); + else + { + while (COM_ParseToken_QuakeC(&text, false)) + if (!strcasecmp(com_token, "}")) + break; + // custom surfaceflags section + if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) + Con_DPrintf("scripts/custinfoparms.txt: surfaceflags section parsing error - expected \"{\", found \"%s\"\n", com_token); + else + { + while(COM_ParseToken_QuakeC(&text, false)) + { + if (!strcasecmp(com_token, "}")) + break; + // register surfaceflag + if (numcustsurfaceflags >= 256) + { + Con_Printf("scripts/custinfoparms.txt: surfaceflags section parsing error - max 256 surfaceflags exceeded\n"); + break; + } + // name + j = strlen(com_token)+1; + custsurfaceparmnames[numcustsurfaceflags] = (char *)Mem_Alloc(tempmempool, j); + strlcpy(custsurfaceparmnames[numcustsurfaceflags], com_token, j+1); + // value + if (COM_ParseToken_QuakeC(&text, false)) + custsurfaceflags[numcustsurfaceflags] = strtol(com_token, NULL, 0); + else + custsurfaceflags[numcustsurfaceflags] = 0; + numcustsurfaceflags++; + } + } + } + Mem_Free(f); + } + + // parse shaders + search = FS_Search("scripts/*.shader", true, false); + if (!search) + return; + for (fileindex = 0;fileindex < search->numfilenames;fileindex++) + { + text = f = (char *)FS_LoadFile(search->filenames[fileindex], tempmempool, false, NULL); + if (!f) + continue; + while (COM_ParseToken_QuakeC(&text, false)) + { + memset (&shader, 0, sizeof(shader)); + shader.name[0] = 0; + shader.surfaceparms = 0; + shader.surfaceflags = 0; + shader.textureflags = 0; + shader.numlayers = 0; + shader.lighting = false; + shader.vertexalpha = false; + shader.textureblendalpha = false; + shader.primarylayer = 0; + shader.backgroundlayer = 0; + shader.skyboxname[0] = 0; + shader.deforms[0].deform = Q3DEFORM_NONE; + shader.dpnortlight = false; + shader.dpshadow = false; + shader.dpnoshadow = false; + shader.dpmeshcollisions = false; + shader.dpshaderkill = false; + shader.dpreflectcube[0] = 0; + shader.reflectmin = 0; + shader.reflectmax = 1; + shader.refractfactor = 1; + Vector4Set(shader.refractcolor4f, 1, 1, 1, 1); + shader.reflectfactor = 1; + Vector4Set(shader.reflectcolor4f, 1, 1, 1, 1); + shader.r_water_wateralpha = 1; + shader.r_water_waterscroll[0] = 0; + shader.r_water_waterscroll[1] = 0; + shader.offsetmapping = (mod_q3shader_default_offsetmapping.value) ? OFFSETMAPPING_DEFAULT : OFFSETMAPPING_OFF; + shader.offsetscale = mod_q3shader_default_offsetmapping_scale.value; + shader.offsetbias = mod_q3shader_default_offsetmapping_bias.value; + shader.biaspolygonoffset = mod_q3shader_default_polygonoffset.value; + shader.biaspolygonfactor = mod_q3shader_default_polygonfactor.value; + shader.transparentsort = TRANSPARENTSORT_DISTANCE; + shader.specularscalemod = 1; + shader.specularpowermod = 1; + shader.rtlightambient = 0; + // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS + // JUST GREP FOR "specularscalemod = 1". + + strlcpy(shader.name, com_token, sizeof(shader.name)); + if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) + { + Con_DPrintf("%s parsing error - expected \"{\", found \"%s\"\n", search->filenames[fileindex], com_token); + break; + } + while (COM_ParseToken_QuakeC(&text, false)) + { + if (!strcasecmp(com_token, "}")) + break; + if (!strcasecmp(com_token, "{")) + { + static q3shaderinfo_layer_t dummy; + if (shader.numlayers < Q3SHADER_MAXLAYERS) + { + layer = shader.layers + shader.numlayers++; + } + else + { + // parse and process it anyway, just don't store it (so a map $lightmap or such stuff still is found) + memset(&dummy, 0, sizeof(dummy)); + layer = &dummy; + } + layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITY; + layer->alphagen.alphagen = Q3ALPHAGEN_IDENTITY; + layer->tcgen.tcgen = Q3TCGEN_TEXTURE; + layer->blendfunc[0] = GL_ONE; + layer->blendfunc[1] = GL_ZERO; + while (COM_ParseToken_QuakeC(&text, false)) + { + if (!strcasecmp(com_token, "}")) + break; + if (!strcasecmp(com_token, "\n")) + continue; + numparameters = 0; + for (j = 0;strcasecmp(com_token, "\n") && strcasecmp(com_token, "}");j++) + { + if (j < TEXTURE_MAXFRAMES + 4) + { + // remap dp_water to dpwater, dp_reflect to dpreflect, etc. + if(j == 0 && !strncasecmp(com_token, "dp_", 3)) + dpsnprintf(parameter[j], sizeof(parameter[j]), "dp%s", &com_token[3]); + else + strlcpy(parameter[j], com_token, sizeof(parameter[j])); + numparameters = j + 1; + } + if (!COM_ParseToken_QuakeC(&text, true)) + break; + } + //for (j = numparameters;j < TEXTURE_MAXFRAMES + 4;j++) + // parameter[j][0] = 0; + if (developer_insane.integer) + { + Con_DPrintf("%s %i: ", shader.name, shader.numlayers - 1); + for (j = 0;j < numparameters;j++) + Con_DPrintf(" %s", parameter[j]); + Con_DPrint("\n"); + } + if (numparameters >= 2 && !strcasecmp(parameter[0], "blendfunc")) + { + if (numparameters == 2) + { + if (!strcasecmp(parameter[1], "add")) + { + layer->blendfunc[0] = GL_ONE; + layer->blendfunc[1] = GL_ONE; + } + else if (!strcasecmp(parameter[1], "addalpha")) + { + layer->blendfunc[0] = GL_SRC_ALPHA; + layer->blendfunc[1] = GL_ONE; + } + else if (!strcasecmp(parameter[1], "filter")) + { + layer->blendfunc[0] = GL_DST_COLOR; + layer->blendfunc[1] = GL_ZERO; + } + else if (!strcasecmp(parameter[1], "blend")) + { + layer->blendfunc[0] = GL_SRC_ALPHA; + layer->blendfunc[1] = GL_ONE_MINUS_SRC_ALPHA; + } + } + else if (numparameters == 3) + { + int k; + for (k = 0;k < 2;k++) + { + if (!strcasecmp(parameter[k+1], "GL_ONE")) + layer->blendfunc[k] = GL_ONE; + else if (!strcasecmp(parameter[k+1], "GL_ZERO")) + layer->blendfunc[k] = GL_ZERO; + else if (!strcasecmp(parameter[k+1], "GL_SRC_COLOR")) + layer->blendfunc[k] = GL_SRC_COLOR; + else if (!strcasecmp(parameter[k+1], "GL_SRC_ALPHA")) + layer->blendfunc[k] = GL_SRC_ALPHA; + else if (!strcasecmp(parameter[k+1], "GL_DST_COLOR")) + layer->blendfunc[k] = GL_DST_COLOR; + else if (!strcasecmp(parameter[k+1], "GL_DST_ALPHA")) + layer->blendfunc[k] = GL_DST_ALPHA; + else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_SRC_COLOR")) + layer->blendfunc[k] = GL_ONE_MINUS_SRC_COLOR; + else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_SRC_ALPHA")) + layer->blendfunc[k] = GL_ONE_MINUS_SRC_ALPHA; + else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_DST_COLOR")) + layer->blendfunc[k] = GL_ONE_MINUS_DST_COLOR; + else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_DST_ALPHA")) + layer->blendfunc[k] = GL_ONE_MINUS_DST_ALPHA; + else + layer->blendfunc[k] = GL_ONE; // default in case of parsing error + } + } + } + if (numparameters >= 2 && !strcasecmp(parameter[0], "alphafunc")) + layer->alphatest = true; + if (numparameters >= 2 && (!strcasecmp(parameter[0], "map") || !strcasecmp(parameter[0], "clampmap"))) + { + if (!strcasecmp(parameter[0], "clampmap")) + layer->clampmap = true; + layer->numframes = 1; + layer->framerate = 1; + layer->texturename = (char**)Mem_ExpandableArray_AllocRecord ( + &q3shader_data->char_ptrs); + layer->texturename[0] = Mem_strdup (q3shaders_mem, parameter[1]); + if (!strcasecmp(parameter[1], "$lightmap")) + shader.lighting = true; + } + else if (numparameters >= 3 && (!strcasecmp(parameter[0], "animmap") || !strcasecmp(parameter[0], "animclampmap"))) + { + int i; + layer->numframes = min(numparameters - 2, TEXTURE_MAXFRAMES); + layer->framerate = atof(parameter[1]); + layer->texturename = (char **) Mem_Alloc (q3shaders_mem, sizeof (char*) * layer->numframes); + for (i = 0;i < layer->numframes;i++) + layer->texturename[i] = Mem_strdup (q3shaders_mem, parameter[i + 2]); + } + else if (numparameters >= 2 && !strcasecmp(parameter[0], "rgbgen")) + { + int i; + for (i = 0;i < numparameters - 2 && i < Q3RGBGEN_MAXPARMS;i++) + layer->rgbgen.parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "identity")) layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITY; + else if (!strcasecmp(parameter[1], "const")) layer->rgbgen.rgbgen = Q3RGBGEN_CONST; + else if (!strcasecmp(parameter[1], "entity")) layer->rgbgen.rgbgen = Q3RGBGEN_ENTITY; + else if (!strcasecmp(parameter[1], "exactvertex")) layer->rgbgen.rgbgen = Q3RGBGEN_EXACTVERTEX; + else if (!strcasecmp(parameter[1], "identitylighting")) layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITYLIGHTING; + else if (!strcasecmp(parameter[1], "lightingdiffuse")) layer->rgbgen.rgbgen = Q3RGBGEN_LIGHTINGDIFFUSE; + else if (!strcasecmp(parameter[1], "oneminusentity")) layer->rgbgen.rgbgen = Q3RGBGEN_ONEMINUSENTITY; + else if (!strcasecmp(parameter[1], "oneminusvertex")) layer->rgbgen.rgbgen = Q3RGBGEN_ONEMINUSVERTEX; + else if (!strcasecmp(parameter[1], "vertex")) layer->rgbgen.rgbgen = Q3RGBGEN_VERTEX; + else if (!strcasecmp(parameter[1], "wave")) + { + layer->rgbgen.rgbgen = Q3RGBGEN_WAVE; + layer->rgbgen.wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); + for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) + layer->rgbgen.waveparms[i] = atof(parameter[i+3]); + } + else Con_DPrintf("%s parsing warning: unknown rgbgen %s\n", search->filenames[fileindex], parameter[1]); + } + else if (numparameters >= 2 && !strcasecmp(parameter[0], "alphagen")) + { + int i; + for (i = 0;i < numparameters - 2 && i < Q3ALPHAGEN_MAXPARMS;i++) + layer->alphagen.parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "identity")) layer->alphagen.alphagen = Q3ALPHAGEN_IDENTITY; + else if (!strcasecmp(parameter[1], "const")) layer->alphagen.alphagen = Q3ALPHAGEN_CONST; + else if (!strcasecmp(parameter[1], "entity")) layer->alphagen.alphagen = Q3ALPHAGEN_ENTITY; + else if (!strcasecmp(parameter[1], "lightingspecular")) layer->alphagen.alphagen = Q3ALPHAGEN_LIGHTINGSPECULAR; + else if (!strcasecmp(parameter[1], "oneminusentity")) layer->alphagen.alphagen = Q3ALPHAGEN_ONEMINUSENTITY; + else if (!strcasecmp(parameter[1], "oneminusvertex")) layer->alphagen.alphagen = Q3ALPHAGEN_ONEMINUSVERTEX; + else if (!strcasecmp(parameter[1], "portal")) layer->alphagen.alphagen = Q3ALPHAGEN_PORTAL; + else if (!strcasecmp(parameter[1], "vertex")) layer->alphagen.alphagen = Q3ALPHAGEN_VERTEX; + else if (!strcasecmp(parameter[1], "wave")) + { + layer->alphagen.alphagen = Q3ALPHAGEN_WAVE; + layer->alphagen.wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); + for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) + layer->alphagen.waveparms[i] = atof(parameter[i+3]); + } + else Con_DPrintf("%s parsing warning: unknown alphagen %s\n", search->filenames[fileindex], parameter[1]); + } + else if (numparameters >= 2 && (!strcasecmp(parameter[0], "texgen") || !strcasecmp(parameter[0], "tcgen"))) + { + int i; + // observed values: tcgen environment + // no other values have been observed in real shaders + for (i = 0;i < numparameters - 2 && i < Q3TCGEN_MAXPARMS;i++) + layer->tcgen.parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "base")) layer->tcgen.tcgen = Q3TCGEN_TEXTURE; + else if (!strcasecmp(parameter[1], "texture")) layer->tcgen.tcgen = Q3TCGEN_TEXTURE; + else if (!strcasecmp(parameter[1], "environment")) layer->tcgen.tcgen = Q3TCGEN_ENVIRONMENT; + else if (!strcasecmp(parameter[1], "lightmap")) layer->tcgen.tcgen = Q3TCGEN_LIGHTMAP; + else if (!strcasecmp(parameter[1], "vector")) layer->tcgen.tcgen = Q3TCGEN_VECTOR; + else Con_DPrintf("%s parsing warning: unknown tcgen mode %s\n", search->filenames[fileindex], parameter[1]); + } + else if (numparameters >= 2 && !strcasecmp(parameter[0], "tcmod")) + { + int i, tcmodindex; + // observed values: + // tcmod rotate # + // tcmod scale # # + // tcmod scroll # # + // tcmod stretch sin # # # # + // tcmod stretch triangle # # # # + // tcmod transform # # # # # # + // tcmod turb # # # # + // tcmod turb sin # # # # (this is bogus) + // no other values have been observed in real shaders + for (tcmodindex = 0;tcmodindex < Q3MAXTCMODS;tcmodindex++) + if (!layer->tcmods[tcmodindex].tcmod) + break; + if (tcmodindex < Q3MAXTCMODS) + { + for (i = 0;i < numparameters - 2 && i < Q3TCMOD_MAXPARMS;i++) + layer->tcmods[tcmodindex].parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "entitytranslate")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_ENTITYTRANSLATE; + else if (!strcasecmp(parameter[1], "rotate")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_ROTATE; + else if (!strcasecmp(parameter[1], "scale")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_SCALE; + else if (!strcasecmp(parameter[1], "scroll")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_SCROLL; + else if (!strcasecmp(parameter[1], "page")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_PAGE; + else if (!strcasecmp(parameter[1], "stretch")) + { + layer->tcmods[tcmodindex].tcmod = Q3TCMOD_STRETCH; + layer->tcmods[tcmodindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); + for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) + layer->tcmods[tcmodindex].waveparms[i] = atof(parameter[i+3]); + } + else if (!strcasecmp(parameter[1], "transform")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_TRANSFORM; + else if (!strcasecmp(parameter[1], "turb")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_TURBULENT; + else Con_DPrintf("%s parsing warning: unknown tcmod mode %s\n", search->filenames[fileindex], parameter[1]); + } + else + Con_DPrintf("%s parsing warning: too many tcmods on one layer\n", search->filenames[fileindex]); + } + // break out a level if it was a closing brace (not using the character here to not confuse vim) + if (!strcasecmp(com_token, "}")) + break; + } + if (layer->rgbgen.rgbgen == Q3RGBGEN_LIGHTINGDIFFUSE || layer->rgbgen.rgbgen == Q3RGBGEN_VERTEX) + shader.lighting = true; + if (layer->alphagen.alphagen == Q3ALPHAGEN_VERTEX) + { + if (layer == shader.layers + 0) + { + // vertex controlled transparency + shader.vertexalpha = true; + } + else + { + // multilayer terrain shader or similar + shader.textureblendalpha = true; + if (mod_q3shader_force_terrain_alphaflag.integer) + shader.layers[0].texflags |= TEXF_ALPHA; + } + } + + if(mod_q3shader_force_addalpha.integer) + { + // for a long while, DP treated GL_ONE GL_ONE as GL_SRC_ALPHA GL_ONE + // this cvar brings back this behaviour + if(layer->blendfunc[0] == GL_ONE && layer->blendfunc[1] == GL_ONE) + layer->blendfunc[0] = GL_SRC_ALPHA; + } + + layer->texflags = 0; + if (layer->alphatest) + layer->texflags |= TEXF_ALPHA; + switch(layer->blendfunc[0]) + { + case GL_SRC_ALPHA: + case GL_ONE_MINUS_SRC_ALPHA: + layer->texflags |= TEXF_ALPHA; + break; + } + switch(layer->blendfunc[1]) + { + case GL_SRC_ALPHA: + case GL_ONE_MINUS_SRC_ALPHA: + layer->texflags |= TEXF_ALPHA; + break; + } + if (!(shader.surfaceparms & Q3SURFACEPARM_NOMIPMAPS)) + layer->texflags |= TEXF_MIPMAP; + if (!(shader.textureflags & Q3TEXTUREFLAG_NOPICMIP)) + layer->texflags |= TEXF_PICMIP | TEXF_COMPRESS; + if (layer->clampmap) + layer->texflags |= TEXF_CLAMP; + continue; + } + numparameters = 0; + for (j = 0;strcasecmp(com_token, "\n") && strcasecmp(com_token, "}");j++) + { + if (j < TEXTURE_MAXFRAMES + 4) + { + // remap dp_water to dpwater, dp_reflect to dpreflect, etc. + if(j == 0 && !strncasecmp(com_token, "dp_", 3)) + dpsnprintf(parameter[j], sizeof(parameter[j]), "dp%s", &com_token[3]); + else + strlcpy(parameter[j], com_token, sizeof(parameter[j])); + numparameters = j + 1; + } + if (!COM_ParseToken_QuakeC(&text, true)) + break; + } + //for (j = numparameters;j < TEXTURE_MAXFRAMES + 4;j++) + // parameter[j][0] = 0; + if (fileindex == 0 && !strcasecmp(com_token, "}")) + break; + if (developer_insane.integer) + { + Con_DPrintf("%s: ", shader.name); + for (j = 0;j < numparameters;j++) + Con_DPrintf(" %s", parameter[j]); + Con_DPrint("\n"); + } + if (numparameters < 1) + continue; + if (!strcasecmp(parameter[0], "surfaceparm") && numparameters >= 2) + { + if (!strcasecmp(parameter[1], "alphashadow")) + shader.surfaceparms |= Q3SURFACEPARM_ALPHASHADOW; + else if (!strcasecmp(parameter[1], "areaportal")) + shader.surfaceparms |= Q3SURFACEPARM_AREAPORTAL; + else if (!strcasecmp(parameter[1], "botclip")) + shader.surfaceparms |= Q3SURFACEPARM_BOTCLIP; + else if (!strcasecmp(parameter[1], "clusterportal")) + shader.surfaceparms |= Q3SURFACEPARM_CLUSTERPORTAL; + else if (!strcasecmp(parameter[1], "detail")) + shader.surfaceparms |= Q3SURFACEPARM_DETAIL; + else if (!strcasecmp(parameter[1], "donotenter")) + shader.surfaceparms |= Q3SURFACEPARM_DONOTENTER; + else if (!strcasecmp(parameter[1], "dust")) + shader.surfaceparms |= Q3SURFACEPARM_DUST; + else if (!strcasecmp(parameter[1], "hint")) + shader.surfaceparms |= Q3SURFACEPARM_HINT; + else if (!strcasecmp(parameter[1], "fog")) + shader.surfaceparms |= Q3SURFACEPARM_FOG; + else if (!strcasecmp(parameter[1], "lava")) + shader.surfaceparms |= Q3SURFACEPARM_LAVA; + else if (!strcasecmp(parameter[1], "lightfilter")) + shader.surfaceparms |= Q3SURFACEPARM_LIGHTFILTER; + else if (!strcasecmp(parameter[1], "lightgrid")) + shader.surfaceparms |= Q3SURFACEPARM_LIGHTGRID; + else if (!strcasecmp(parameter[1], "metalsteps")) + shader.surfaceparms |= Q3SURFACEPARM_METALSTEPS; + else if (!strcasecmp(parameter[1], "nodamage")) + shader.surfaceparms |= Q3SURFACEPARM_NODAMAGE; + else if (!strcasecmp(parameter[1], "nodlight")) + shader.surfaceparms |= Q3SURFACEPARM_NODLIGHT; + else if (!strcasecmp(parameter[1], "nodraw")) + shader.surfaceparms |= Q3SURFACEPARM_NODRAW; + else if (!strcasecmp(parameter[1], "nodrop")) + shader.surfaceparms |= Q3SURFACEPARM_NODROP; + else if (!strcasecmp(parameter[1], "noimpact")) + shader.surfaceparms |= Q3SURFACEPARM_NOIMPACT; + else if (!strcasecmp(parameter[1], "nolightmap")) + shader.surfaceparms |= Q3SURFACEPARM_NOLIGHTMAP; + else if (!strcasecmp(parameter[1], "nomarks")) + shader.surfaceparms |= Q3SURFACEPARM_NOMARKS; + else if (!strcasecmp(parameter[1], "nomipmaps")) + shader.surfaceparms |= Q3SURFACEPARM_NOMIPMAPS; + else if (!strcasecmp(parameter[1], "nonsolid")) + shader.surfaceparms |= Q3SURFACEPARM_NONSOLID; + else if (!strcasecmp(parameter[1], "origin")) + shader.surfaceparms |= Q3SURFACEPARM_ORIGIN; + else if (!strcasecmp(parameter[1], "playerclip")) + shader.surfaceparms |= Q3SURFACEPARM_PLAYERCLIP; + else if (!strcasecmp(parameter[1], "sky")) + shader.surfaceparms |= Q3SURFACEPARM_SKY; + else if (!strcasecmp(parameter[1], "slick")) + shader.surfaceparms |= Q3SURFACEPARM_SLICK; + else if (!strcasecmp(parameter[1], "slime")) + shader.surfaceparms |= Q3SURFACEPARM_SLIME; + else if (!strcasecmp(parameter[1], "structural")) + shader.surfaceparms |= Q3SURFACEPARM_STRUCTURAL; + else if (!strcasecmp(parameter[1], "trans")) + shader.surfaceparms |= Q3SURFACEPARM_TRANS; + else if (!strcasecmp(parameter[1], "water")) + shader.surfaceparms |= Q3SURFACEPARM_WATER; + else if (!strcasecmp(parameter[1], "pointlight")) + shader.surfaceparms |= Q3SURFACEPARM_POINTLIGHT; + else if (!strcasecmp(parameter[1], "antiportal")) + shader.surfaceparms |= Q3SURFACEPARM_ANTIPORTAL; + else if (!strcasecmp(parameter[1], "skip")) + ; // shader.surfaceparms |= Q3SURFACEPARM_SKIP; FIXME we don't have enough #defines for this any more, and the engine doesn't need this one anyway + else + { + // try custom surfaceparms + for (j = 0; j < numcustsurfaceflags; j++) + { + if (!strcasecmp(custsurfaceparmnames[j], parameter[1])) + { + shader.surfaceflags |= custsurfaceflags[j]; + break; + } + } + // failed all + if (j == numcustsurfaceflags) + Con_DPrintf("%s parsing warning: unknown surfaceparm \"%s\"\n", search->filenames[fileindex], parameter[1]); + } + } + else if (!strcasecmp(parameter[0], "dpshadow")) + shader.dpshadow = true; + else if (!strcasecmp(parameter[0], "dpnoshadow")) + shader.dpnoshadow = true; + else if (!strcasecmp(parameter[0], "dpnortlight")) + shader.dpnortlight = true; + else if (!strcasecmp(parameter[0], "dpreflectcube")) + strlcpy(shader.dpreflectcube, parameter[1], sizeof(shader.dpreflectcube)); + else if (!strcasecmp(parameter[0], "dpmeshcollisions")) + shader.dpmeshcollisions = true; + // this sets dpshaderkill to true if dpshaderkillifcvarzero was used, and to false if dpnoshaderkillifcvarzero was used + else if (((dpshaderkill = !strcasecmp(parameter[0], "dpshaderkillifcvarzero")) || !strcasecmp(parameter[0], "dpnoshaderkillifcvarzero")) && numparameters >= 2) + { + if (Cvar_VariableValue(parameter[1]) == 0.0f) + shader.dpshaderkill = dpshaderkill; + } + // this sets dpshaderkill to true if dpshaderkillifcvar was used, and to false if dpnoshaderkillifcvar was used + else if (((dpshaderkill = !strcasecmp(parameter[0], "dpshaderkillifcvar")) || !strcasecmp(parameter[0], "dpnoshaderkillifcvar")) && numparameters >= 2) + { + const char *op = NULL; + if (numparameters >= 3) + op = parameter[2]; + if(!op) + { + if (Cvar_VariableValue(parameter[1]) != 0.0f) + shader.dpshaderkill = dpshaderkill; + } + else if (numparameters >= 4 && !strcmp(op, "==")) + { + if (Cvar_VariableValue(parameter[1]) == atof(parameter[3])) + shader.dpshaderkill = dpshaderkill; + } + else if (numparameters >= 4 && !strcmp(op, "!=")) + { + if (Cvar_VariableValue(parameter[1]) != atof(parameter[3])) + shader.dpshaderkill = dpshaderkill; + } + else if (numparameters >= 4 && !strcmp(op, ">")) + { + if (Cvar_VariableValue(parameter[1]) > atof(parameter[3])) + shader.dpshaderkill = dpshaderkill; + } + else if (numparameters >= 4 && !strcmp(op, "<")) + { + if (Cvar_VariableValue(parameter[1]) < atof(parameter[3])) + shader.dpshaderkill = dpshaderkill; + } + else if (numparameters >= 4 && !strcmp(op, ">=")) + { + if (Cvar_VariableValue(parameter[1]) >= atof(parameter[3])) + shader.dpshaderkill = dpshaderkill; + } + else if (numparameters >= 4 && !strcmp(op, "<=")) + { + if (Cvar_VariableValue(parameter[1]) <= atof(parameter[3])) + shader.dpshaderkill = dpshaderkill; + } + else + { + Con_DPrintf("%s parsing warning: unknown dpshaderkillifcvar op \"%s\", or not enough arguments\n", search->filenames[fileindex], op); + } + } + else if (!strcasecmp(parameter[0], "sky") && numparameters >= 2) + { + // some q3 skies don't have the sky parm set + shader.surfaceparms |= Q3SURFACEPARM_SKY; + strlcpy(shader.skyboxname, parameter[1], sizeof(shader.skyboxname)); + } + else if (!strcasecmp(parameter[0], "skyparms") && numparameters >= 2) + { + // some q3 skies don't have the sky parm set + shader.surfaceparms |= Q3SURFACEPARM_SKY; + if (!atoi(parameter[1]) && strcasecmp(parameter[1], "-")) + strlcpy(shader.skyboxname, parameter[1], sizeof(shader.skyboxname)); + } + else if (!strcasecmp(parameter[0], "cull") && numparameters >= 2) + { + if (!strcasecmp(parameter[1], "disable") || !strcasecmp(parameter[1], "none") || !strcasecmp(parameter[1], "twosided")) + shader.textureflags |= Q3TEXTUREFLAG_TWOSIDED; + } + else if (!strcasecmp(parameter[0], "nomipmaps")) + shader.surfaceparms |= Q3SURFACEPARM_NOMIPMAPS; + else if (!strcasecmp(parameter[0], "nopicmip")) + shader.textureflags |= Q3TEXTUREFLAG_NOPICMIP; + else if (!strcasecmp(parameter[0], "polygonoffset")) + shader.textureflags |= Q3TEXTUREFLAG_POLYGONOFFSET; + else if (!strcasecmp(parameter[0], "dppolygonoffset")) + { + shader.textureflags |= Q3TEXTUREFLAG_POLYGONOFFSET; + if(numparameters >= 2) + { + shader.biaspolygonfactor = atof(parameter[1]); + if(numparameters >= 3) + shader.biaspolygonoffset = atof(parameter[2]); + else + shader.biaspolygonoffset = 0; + } + } + else if (!strcasecmp(parameter[0], "dptransparentsort") && numparameters >= 2) + { + shader.textureflags |= Q3TEXTUREFLAG_TRANSPARENTSORT; + if (!strcasecmp(parameter[1], "sky")) + shader.transparentsort = TRANSPARENTSORT_SKY; + else if (!strcasecmp(parameter[1], "distance")) + shader.transparentsort = TRANSPARENTSORT_DISTANCE; + else if (!strcasecmp(parameter[1], "hud")) + shader.transparentsort = TRANSPARENTSORT_HUD; + else + Con_DPrintf("%s parsing warning: unknown dptransparentsort category \"%s\", or not enough arguments\n", search->filenames[fileindex], parameter[1]); + } + else if (!strcasecmp(parameter[0], "dprefract") && numparameters >= 5) + { + shader.textureflags |= Q3TEXTUREFLAG_REFRACTION; + shader.refractfactor = atof(parameter[1]); + Vector4Set(shader.refractcolor4f, atof(parameter[2]), atof(parameter[3]), atof(parameter[4]), 1); + } + else if (!strcasecmp(parameter[0], "dpreflect") && numparameters >= 6) + { + shader.textureflags |= Q3TEXTUREFLAG_REFLECTION; + shader.reflectfactor = atof(parameter[1]); + Vector4Set(shader.reflectcolor4f, atof(parameter[2]), atof(parameter[3]), atof(parameter[4]), atof(parameter[5])); + } + else if (!strcasecmp(parameter[0], "dpcamera")) + { + shader.textureflags |= Q3TEXTUREFLAG_CAMERA; + } + else if (!strcasecmp(parameter[0], "dpwater") && numparameters >= 12) + { + shader.textureflags |= Q3TEXTUREFLAG_WATERSHADER; + shader.reflectmin = atof(parameter[1]); + shader.reflectmax = atof(parameter[2]); + shader.refractfactor = atof(parameter[3]); + shader.reflectfactor = atof(parameter[4]); + Vector4Set(shader.refractcolor4f, atof(parameter[5]), atof(parameter[6]), atof(parameter[7]), 1); + Vector4Set(shader.reflectcolor4f, atof(parameter[8]), atof(parameter[9]), atof(parameter[10]), 1); + shader.r_water_wateralpha = atof(parameter[11]); + } + else if (!strcasecmp(parameter[0], "dpwaterscroll") && numparameters >= 3) + { + shader.r_water_waterscroll[0] = 1/atof(parameter[1]); + shader.r_water_waterscroll[1] = 1/atof(parameter[2]); + } + else if (!strcasecmp(parameter[0], "dpglossintensitymod") && numparameters >= 2) + { + shader.specularscalemod = atof(parameter[1]); + } + else if (!strcasecmp(parameter[0], "dpglossexponentmod") && numparameters >= 2) + { + shader.specularpowermod = atof(parameter[1]); + } + else if (!strcasecmp(parameter[0], "dprtlightambient") && numparameters >= 2) + { + shader.rtlightambient = atof(parameter[1]); + } + else if (!strcasecmp(parameter[0], "dpoffsetmapping") && numparameters >= 2) + { + if (!strcasecmp(parameter[1], "disable") || !strcasecmp(parameter[1], "none") || !strcasecmp(parameter[1], "off")) + shader.offsetmapping = OFFSETMAPPING_OFF; + else if (!strcasecmp(parameter[1], "default") || !strcasecmp(parameter[1], "normal")) + shader.offsetmapping = OFFSETMAPPING_DEFAULT; + else if (!strcasecmp(parameter[1], "linear")) + shader.offsetmapping = OFFSETMAPPING_LINEAR; + else if (!strcasecmp(parameter[1], "relief")) + shader.offsetmapping = OFFSETMAPPING_RELIEF; + if (numparameters >= 3) + shader.offsetscale = atof(parameter[2]); + if (numparameters >= 5) + { + if(!strcasecmp(parameter[3], "bias")) + shader.offsetbias = atof(parameter[4]); + else if(!strcasecmp(parameter[3], "match")) + shader.offsetbias = 1.0f - atof(parameter[4]); + else if(!strcasecmp(parameter[3], "match8")) + shader.offsetbias = 1.0f - atof(parameter[4]) / 255.0f; + else if(!strcasecmp(parameter[3], "match16")) + shader.offsetbias = 1.0f - atof(parameter[4]) / 65535.0f; + } + } + else if (!strcasecmp(parameter[0], "deformvertexes") && numparameters >= 2) + { + int i, deformindex; + for (deformindex = 0;deformindex < Q3MAXDEFORMS;deformindex++) + if (!shader.deforms[deformindex].deform) + break; + if (deformindex < Q3MAXDEFORMS) + { + for (i = 0;i < numparameters - 2 && i < Q3DEFORM_MAXPARMS;i++) + shader.deforms[deformindex].parms[i] = atof(parameter[i+2]); + if (!strcasecmp(parameter[1], "projectionshadow")) shader.deforms[deformindex].deform = Q3DEFORM_PROJECTIONSHADOW; + else if (!strcasecmp(parameter[1], "autosprite" )) shader.deforms[deformindex].deform = Q3DEFORM_AUTOSPRITE; + else if (!strcasecmp(parameter[1], "autosprite2" )) shader.deforms[deformindex].deform = Q3DEFORM_AUTOSPRITE2; + else if (!strcasecmp(parameter[1], "text0" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT0; + else if (!strcasecmp(parameter[1], "text1" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT1; + else if (!strcasecmp(parameter[1], "text2" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT2; + else if (!strcasecmp(parameter[1], "text3" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT3; + else if (!strcasecmp(parameter[1], "text4" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT4; + else if (!strcasecmp(parameter[1], "text5" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT5; + else if (!strcasecmp(parameter[1], "text6" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT6; + else if (!strcasecmp(parameter[1], "text7" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT7; + else if (!strcasecmp(parameter[1], "bulge" )) shader.deforms[deformindex].deform = Q3DEFORM_BULGE; + else if (!strcasecmp(parameter[1], "normal" )) shader.deforms[deformindex].deform = Q3DEFORM_NORMAL; + else if (!strcasecmp(parameter[1], "wave" )) + { + shader.deforms[deformindex].deform = Q3DEFORM_WAVE; + shader.deforms[deformindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[3]); + for (i = 0;i < numparameters - 4 && i < Q3WAVEPARMS;i++) + shader.deforms[deformindex].waveparms[i] = atof(parameter[i+4]); + } + else if (!strcasecmp(parameter[1], "move" )) + { + shader.deforms[deformindex].deform = Q3DEFORM_MOVE; + shader.deforms[deformindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[5]); + for (i = 0;i < numparameters - 6 && i < Q3WAVEPARMS;i++) + shader.deforms[deformindex].waveparms[i] = atof(parameter[i+6]); + } + } + } + } + // hide this shader if a cvar said it should be killed + if (shader.dpshaderkill) + shader.numlayers = 0; + // pick the primary layer to render with + if (shader.numlayers) + { + shader.backgroundlayer = -1; + shader.primarylayer = 0; + // if lightmap comes first this is definitely an ordinary texture + // if the first two layers have the correct blendfuncs and use vertex alpha, it is a blended terrain shader + if ((shader.layers[shader.primarylayer].texturename != NULL) + && !strcasecmp(shader.layers[shader.primarylayer].texturename[0], "$lightmap")) + { + shader.backgroundlayer = -1; + shader.primarylayer = 1; + } + else if (shader.numlayers >= 2 + && shader.layers[1].alphagen.alphagen == Q3ALPHAGEN_VERTEX + && (shader.layers[0].blendfunc[0] == GL_ONE && shader.layers[0].blendfunc[1] == GL_ZERO && !shader.layers[0].alphatest) + && ((shader.layers[1].blendfunc[0] == GL_SRC_ALPHA && shader.layers[1].blendfunc[1] == GL_ONE_MINUS_SRC_ALPHA) + || (shader.layers[1].blendfunc[0] == GL_ONE && shader.layers[1].blendfunc[1] == GL_ZERO && shader.layers[1].alphatest))) + { + // terrain blending or other effects + shader.backgroundlayer = 0; + shader.primarylayer = 1; + } + } + // fix up multiple reflection types + if(shader.textureflags & Q3TEXTUREFLAG_WATERSHADER) + shader.textureflags &= ~(Q3TEXTUREFLAG_REFRACTION | Q3TEXTUREFLAG_REFLECTION | Q3TEXTUREFLAG_CAMERA); + + Q3Shader_AddToHash (&shader); + } + Mem_Free(f); + } + FS_FreeSearch(search); + // free custinfoparm values + for (j = 0; j < numcustsurfaceflags; j++) + Mem_Free(custsurfaceparmnames[j]); +} + +q3shaderinfo_t *Mod_LookupQ3Shader(const char *name) +{ + unsigned short hash; + q3shader_hash_entry_t* entry; + if (!q3shaders_mem) + Mod_LoadQ3Shaders(); + hash = CRC_Block_CaseInsensitive ((const unsigned char *)name, strlen (name)); + entry = q3shader_data->hash + (hash % Q3SHADER_HASH_SIZE); + while (entry != NULL) + { + if (strcasecmp (entry->shader.name, name) == 0) + return &entry->shader; + entry = entry->chain; + } + return NULL; +} + +qboolean Mod_LoadTextureFromQ3Shader(texture_t *texture, const char *name, qboolean warnmissing, qboolean fallback, int defaulttexflags) +{ + int j; + int texflagsmask, texflagsor; + qboolean success = true; + q3shaderinfo_t *shader; + if (!name) + name = ""; + strlcpy(texture->name, name, sizeof(texture->name)); + shader = name[0] ? Mod_LookupQ3Shader(name) : NULL; + + texflagsmask = ~0; + if(!(defaulttexflags & TEXF_PICMIP)) + texflagsmask &= ~TEXF_PICMIP; + if(!(defaulttexflags & TEXF_COMPRESS)) + texflagsmask &= ~TEXF_COMPRESS; + texflagsor = 0; + if(defaulttexflags & TEXF_ISWORLD) + texflagsor |= TEXF_ISWORLD; + if(defaulttexflags & TEXF_ISSPRITE) + texflagsor |= TEXF_ISSPRITE; + // unless later loaded from the shader + texture->offsetmapping = (mod_noshader_default_offsetmapping.value) ? OFFSETMAPPING_DEFAULT : OFFSETMAPPING_OFF; + texture->offsetscale = 1; + texture->offsetbias = 0; + texture->specularscalemod = 1; + texture->specularpowermod = 1; + texture->rtlightambient = 0; + texture->transparentsort = TRANSPARENTSORT_DISTANCE; + // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS + // JUST GREP FOR "specularscalemod = 1". + + if (shader) + { + if (developer_loading.integer) + Con_Printf("%s: loaded shader for %s\n", loadmodel->name, name); + + // allow disabling of picmip or compression by defaulttexflags + texture->textureflags = (shader->textureflags & texflagsmask) | texflagsor; + + if (shader->surfaceparms & Q3SURFACEPARM_SKY) + { + texture->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; + if (shader->skyboxname[0]) + { + // quake3 seems to append a _ to the skybox name, so this must do so as well + dpsnprintf(loadmodel->brush.skybox, sizeof(loadmodel->brush.skybox), "%s_", shader->skyboxname); + } + } + else if ((texture->surfaceflags & Q3SURFACEFLAG_NODRAW) || shader->numlayers == 0) + texture->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; + else + texture->basematerialflags = MATERIALFLAG_WALL; + + if (shader->layers[0].alphatest) + texture->basematerialflags |= MATERIALFLAG_ALPHATEST | MATERIALFLAG_NOSHADOW; + if (shader->textureflags & Q3TEXTUREFLAG_TWOSIDED) + texture->basematerialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_NOCULLFACE; + if (shader->textureflags & Q3TEXTUREFLAG_POLYGONOFFSET) + { + texture->biaspolygonoffset += shader->biaspolygonoffset; + texture->biaspolygonfactor += shader->biaspolygonfactor; + } + if (shader->textureflags & Q3TEXTUREFLAG_REFRACTION) + texture->basematerialflags |= MATERIALFLAG_REFRACTION; + if (shader->textureflags & Q3TEXTUREFLAG_REFLECTION) + texture->basematerialflags |= MATERIALFLAG_REFLECTION; + if (shader->textureflags & Q3TEXTUREFLAG_WATERSHADER) + texture->basematerialflags |= MATERIALFLAG_WATERSHADER; + if (shader->textureflags & Q3TEXTUREFLAG_CAMERA) + texture->basematerialflags |= MATERIALFLAG_CAMERA; + texture->customblendfunc[0] = GL_ONE; + texture->customblendfunc[1] = GL_ZERO; + texture->transparentsort = shader->transparentsort; + if (shader->numlayers > 0) + { + texture->customblendfunc[0] = shader->layers[0].blendfunc[0]; + texture->customblendfunc[1] = shader->layers[0].blendfunc[1]; +/* +Q3 shader blendfuncs actually used in the game (* = supported by DP) +* additive GL_ONE GL_ONE +additive weird GL_ONE GL_SRC_ALPHA +additive weird 2 GL_ONE GL_ONE_MINUS_SRC_ALPHA +* alpha GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA +alpha inverse GL_ONE_MINUS_SRC_ALPHA GL_SRC_ALPHA +brighten GL_DST_COLOR GL_ONE +brighten GL_ONE GL_SRC_COLOR +brighten weird GL_DST_COLOR GL_ONE_MINUS_DST_ALPHA +brighten weird 2 GL_DST_COLOR GL_SRC_ALPHA +* modulate GL_DST_COLOR GL_ZERO +* modulate GL_ZERO GL_SRC_COLOR +modulate inverse GL_ZERO GL_ONE_MINUS_SRC_COLOR +modulate inverse alpha GL_ZERO GL_SRC_ALPHA +modulate weird inverse GL_ONE_MINUS_DST_COLOR GL_ZERO +* modulate x2 GL_DST_COLOR GL_SRC_COLOR +* no blend GL_ONE GL_ZERO +nothing GL_ZERO GL_ONE +*/ + // if not opaque, figure out what blendfunc to use + if (shader->layers[0].blendfunc[0] != GL_ONE || shader->layers[0].blendfunc[1] != GL_ZERO) + { + if (shader->layers[0].blendfunc[0] == GL_ONE && shader->layers[0].blendfunc[1] == GL_ONE) + texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else if (shader->layers[0].blendfunc[0] == GL_SRC_ALPHA && shader->layers[0].blendfunc[1] == GL_ONE) + texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else if (shader->layers[0].blendfunc[0] == GL_SRC_ALPHA && shader->layers[0].blendfunc[1] == GL_ONE_MINUS_SRC_ALPHA) + texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else + texture->basematerialflags |= MATERIALFLAG_CUSTOMBLEND | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + } + } + if (!shader->lighting) + texture->basematerialflags |= MATERIALFLAG_FULLBRIGHT; + if (shader->primarylayer >= 0) + { + q3shaderinfo_layer_t* primarylayer = shader->layers + shader->primarylayer; + // copy over many primarylayer parameters + texture->rgbgen = primarylayer->rgbgen; + texture->alphagen = primarylayer->alphagen; + texture->tcgen = primarylayer->tcgen; + memcpy(texture->tcmods, primarylayer->tcmods, sizeof(texture->tcmods)); + // load the textures + texture->numskinframes = primarylayer->numframes; + texture->skinframerate = primarylayer->framerate; + for (j = 0;j < primarylayer->numframes;j++) + { + if(cls.state == ca_dedicated) + { + texture->skinframes[j] = NULL; + } + else if (!(texture->skinframes[j] = R_SkinFrame_LoadExternal(primarylayer->texturename[j], (primarylayer->texflags & texflagsmask) | texflagsor, false))) + { + Con_Printf("^1%s:^7 could not load texture ^3\"%s\"^7 (frame %i) for shader ^2\"%s\"\n", loadmodel->name, primarylayer->texturename[j], j, texture->name); + texture->skinframes[j] = R_SkinFrame_LoadMissing(); + } + } + } + if (shader->backgroundlayer >= 0) + { + q3shaderinfo_layer_t* backgroundlayer = shader->layers + shader->backgroundlayer; + // copy over one secondarylayer parameter + memcpy(texture->backgroundtcmods, backgroundlayer->tcmods, sizeof(texture->backgroundtcmods)); + // load the textures + texture->backgroundnumskinframes = backgroundlayer->numframes; + texture->backgroundskinframerate = backgroundlayer->framerate; + for (j = 0;j < backgroundlayer->numframes;j++) + { + if(cls.state == ca_dedicated) + { + texture->skinframes[j] = NULL; + } + else if (!(texture->backgroundskinframes[j] = R_SkinFrame_LoadExternal(backgroundlayer->texturename[j], (backgroundlayer->texflags & texflagsmask) | texflagsor, false))) + { + Con_Printf("^1%s:^7 could not load texture ^3\"%s\"^7 (background frame %i) for shader ^2\"%s\"\n", loadmodel->name, backgroundlayer->texturename[j], j, texture->name); + texture->backgroundskinframes[j] = R_SkinFrame_LoadMissing(); + } + } + } + if (shader->dpshadow) + texture->basematerialflags &= ~MATERIALFLAG_NOSHADOW; + if (shader->dpnoshadow) + texture->basematerialflags |= MATERIALFLAG_NOSHADOW; + if (shader->dpnortlight) + texture->basematerialflags |= MATERIALFLAG_NORTLIGHT; + if (shader->vertexalpha) + texture->basematerialflags |= MATERIALFLAG_ALPHAGEN_VERTEX; + memcpy(texture->deforms, shader->deforms, sizeof(texture->deforms)); + texture->reflectmin = shader->reflectmin; + texture->reflectmax = shader->reflectmax; + texture->refractfactor = shader->refractfactor; + Vector4Copy(shader->refractcolor4f, texture->refractcolor4f); + texture->reflectfactor = shader->reflectfactor; + Vector4Copy(shader->reflectcolor4f, texture->reflectcolor4f); + texture->r_water_wateralpha = shader->r_water_wateralpha; + Vector2Copy(shader->r_water_waterscroll, texture->r_water_waterscroll); + texture->offsetmapping = shader->offsetmapping; + texture->offsetscale = shader->offsetscale; + texture->offsetbias = shader->offsetbias; + texture->specularscalemod = shader->specularscalemod; + texture->specularpowermod = shader->specularpowermod; + texture->rtlightambient = shader->rtlightambient; + if (shader->dpreflectcube[0]) + texture->reflectcubetexture = R_GetCubemap(shader->dpreflectcube); + + // set up default supercontents (on q3bsp this is overridden by the q3bsp loader) + texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; + if (shader->surfaceparms & Q3SURFACEPARM_LAVA ) texture->supercontents = SUPERCONTENTS_LAVA ; + if (shader->surfaceparms & Q3SURFACEPARM_SLIME ) texture->supercontents = SUPERCONTENTS_SLIME ; + if (shader->surfaceparms & Q3SURFACEPARM_WATER ) texture->supercontents = SUPERCONTENTS_WATER ; + if (shader->surfaceparms & Q3SURFACEPARM_NONSOLID ) texture->supercontents = 0 ; + if (shader->surfaceparms & Q3SURFACEPARM_PLAYERCLIP ) texture->supercontents = SUPERCONTENTS_PLAYERCLIP ; + if (shader->surfaceparms & Q3SURFACEPARM_BOTCLIP ) texture->supercontents = SUPERCONTENTS_MONSTERCLIP ; + if (shader->surfaceparms & Q3SURFACEPARM_SKY ) texture->supercontents = SUPERCONTENTS_SKY ; + + // if (shader->surfaceparms & Q3SURFACEPARM_ALPHASHADOW ) texture->supercontents |= SUPERCONTENTS_ALPHASHADOW ; + // if (shader->surfaceparms & Q3SURFACEPARM_AREAPORTAL ) texture->supercontents |= SUPERCONTENTS_AREAPORTAL ; + // if (shader->surfaceparms & Q3SURFACEPARM_CLUSTERPORTAL) texture->supercontents |= SUPERCONTENTS_CLUSTERPORTAL; + // if (shader->surfaceparms & Q3SURFACEPARM_DETAIL ) texture->supercontents |= SUPERCONTENTS_DETAIL ; + if (shader->surfaceparms & Q3SURFACEPARM_DONOTENTER ) texture->supercontents |= SUPERCONTENTS_DONOTENTER ; + // if (shader->surfaceparms & Q3SURFACEPARM_FOG ) texture->supercontents |= SUPERCONTENTS_FOG ; + if (shader->surfaceparms & Q3SURFACEPARM_LAVA ) texture->supercontents |= SUPERCONTENTS_LAVA ; + // if (shader->surfaceparms & Q3SURFACEPARM_LIGHTFILTER ) texture->supercontents |= SUPERCONTENTS_LIGHTFILTER ; + // if (shader->surfaceparms & Q3SURFACEPARM_METALSTEPS ) texture->supercontents |= SUPERCONTENTS_METALSTEPS ; + // if (shader->surfaceparms & Q3SURFACEPARM_NODAMAGE ) texture->supercontents |= SUPERCONTENTS_NODAMAGE ; + // if (shader->surfaceparms & Q3SURFACEPARM_NODLIGHT ) texture->supercontents |= SUPERCONTENTS_NODLIGHT ; + // if (shader->surfaceparms & Q3SURFACEPARM_NODRAW ) texture->supercontents |= SUPERCONTENTS_NODRAW ; + if (shader->surfaceparms & Q3SURFACEPARM_NODROP ) texture->supercontents |= SUPERCONTENTS_NODROP ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOIMPACT ) texture->supercontents |= SUPERCONTENTS_NOIMPACT ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOLIGHTMAP ) texture->supercontents |= SUPERCONTENTS_NOLIGHTMAP ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOMARKS ) texture->supercontents |= SUPERCONTENTS_NOMARKS ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOMIPMAPS ) texture->supercontents |= SUPERCONTENTS_NOMIPMAPS ; + if (shader->surfaceparms & Q3SURFACEPARM_NONSOLID ) texture->supercontents &=~SUPERCONTENTS_SOLID ; + // if (shader->surfaceparms & Q3SURFACEPARM_ORIGIN ) texture->supercontents |= SUPERCONTENTS_ORIGIN ; + if (shader->surfaceparms & Q3SURFACEPARM_PLAYERCLIP ) texture->supercontents |= SUPERCONTENTS_PLAYERCLIP ; + if (shader->surfaceparms & Q3SURFACEPARM_SKY ) texture->supercontents |= SUPERCONTENTS_SKY ; + // if (shader->surfaceparms & Q3SURFACEPARM_SLICK ) texture->supercontents |= SUPERCONTENTS_SLICK ; + if (shader->surfaceparms & Q3SURFACEPARM_SLIME ) texture->supercontents |= SUPERCONTENTS_SLIME ; + // if (shader->surfaceparms & Q3SURFACEPARM_STRUCTURAL ) texture->supercontents |= SUPERCONTENTS_STRUCTURAL ; + // if (shader->surfaceparms & Q3SURFACEPARM_TRANS ) texture->supercontents |= SUPERCONTENTS_TRANS ; + if (shader->surfaceparms & Q3SURFACEPARM_WATER ) texture->supercontents |= SUPERCONTENTS_WATER ; + // if (shader->surfaceparms & Q3SURFACEPARM_POINTLIGHT ) texture->supercontents |= SUPERCONTENTS_POINTLIGHT ; + // if (shader->surfaceparms & Q3SURFACEPARM_HINT ) texture->supercontents |= SUPERCONTENTS_HINT ; + // if (shader->surfaceparms & Q3SURFACEPARM_DUST ) texture->supercontents |= SUPERCONTENTS_DUST ; + if (shader->surfaceparms & Q3SURFACEPARM_BOTCLIP ) texture->supercontents |= SUPERCONTENTS_BOTCLIP | SUPERCONTENTS_MONSTERCLIP; + // if (shader->surfaceparms & Q3SURFACEPARM_LIGHTGRID ) texture->supercontents |= SUPERCONTENTS_LIGHTGRID ; + // if (shader->surfaceparms & Q3SURFACEPARM_ANTIPORTAL ) texture->supercontents |= SUPERCONTENTS_ANTIPORTAL ; + + texture->surfaceflags = shader->surfaceflags; + if (shader->surfaceparms & Q3SURFACEPARM_ALPHASHADOW ) texture->surfaceflags |= Q3SURFACEFLAG_ALPHASHADOW ; + // if (shader->surfaceparms & Q3SURFACEPARM_AREAPORTAL ) texture->surfaceflags |= Q3SURFACEFLAG_AREAPORTAL ; + // if (shader->surfaceparms & Q3SURFACEPARM_CLUSTERPORTAL) texture->surfaceflags |= Q3SURFACEFLAG_CLUSTERPORTAL; + // if (shader->surfaceparms & Q3SURFACEPARM_DETAIL ) texture->surfaceflags |= Q3SURFACEFLAG_DETAIL ; + // if (shader->surfaceparms & Q3SURFACEPARM_DONOTENTER ) texture->surfaceflags |= Q3SURFACEFLAG_DONOTENTER ; + // if (shader->surfaceparms & Q3SURFACEPARM_FOG ) texture->surfaceflags |= Q3SURFACEFLAG_FOG ; + // if (shader->surfaceparms & Q3SURFACEPARM_LAVA ) texture->surfaceflags |= Q3SURFACEFLAG_LAVA ; + if (shader->surfaceparms & Q3SURFACEPARM_LIGHTFILTER ) texture->surfaceflags |= Q3SURFACEFLAG_LIGHTFILTER ; + if (shader->surfaceparms & Q3SURFACEPARM_METALSTEPS ) texture->surfaceflags |= Q3SURFACEFLAG_METALSTEPS ; + if (shader->surfaceparms & Q3SURFACEPARM_NODAMAGE ) texture->surfaceflags |= Q3SURFACEFLAG_NODAMAGE ; + if (shader->surfaceparms & Q3SURFACEPARM_NODLIGHT ) texture->surfaceflags |= Q3SURFACEFLAG_NODLIGHT ; + if (shader->surfaceparms & Q3SURFACEPARM_NODRAW ) texture->surfaceflags |= Q3SURFACEFLAG_NODRAW ; + // if (shader->surfaceparms & Q3SURFACEPARM_NODROP ) texture->surfaceflags |= Q3SURFACEFLAG_NODROP ; + if (shader->surfaceparms & Q3SURFACEPARM_NOIMPACT ) texture->surfaceflags |= Q3SURFACEFLAG_NOIMPACT ; + if (shader->surfaceparms & Q3SURFACEPARM_NOLIGHTMAP ) texture->surfaceflags |= Q3SURFACEFLAG_NOLIGHTMAP ; + if (shader->surfaceparms & Q3SURFACEPARM_NOMARKS ) texture->surfaceflags |= Q3SURFACEFLAG_NOMARKS ; + // if (shader->surfaceparms & Q3SURFACEPARM_NOMIPMAPS ) texture->surfaceflags |= Q3SURFACEFLAG_NOMIPMAPS ; + if (shader->surfaceparms & Q3SURFACEPARM_NONSOLID ) texture->surfaceflags |= Q3SURFACEFLAG_NONSOLID ; + // if (shader->surfaceparms & Q3SURFACEPARM_ORIGIN ) texture->surfaceflags |= Q3SURFACEFLAG_ORIGIN ; + // if (shader->surfaceparms & Q3SURFACEPARM_PLAYERCLIP ) texture->surfaceflags |= Q3SURFACEFLAG_PLAYERCLIP ; + if (shader->surfaceparms & Q3SURFACEPARM_SKY ) texture->surfaceflags |= Q3SURFACEFLAG_SKY ; + if (shader->surfaceparms & Q3SURFACEPARM_SLICK ) texture->surfaceflags |= Q3SURFACEFLAG_SLICK ; + // if (shader->surfaceparms & Q3SURFACEPARM_SLIME ) texture->surfaceflags |= Q3SURFACEFLAG_SLIME ; + // if (shader->surfaceparms & Q3SURFACEPARM_STRUCTURAL ) texture->surfaceflags |= Q3SURFACEFLAG_STRUCTURAL ; + // if (shader->surfaceparms & Q3SURFACEPARM_TRANS ) texture->surfaceflags |= Q3SURFACEFLAG_TRANS ; + // if (shader->surfaceparms & Q3SURFACEPARM_WATER ) texture->surfaceflags |= Q3SURFACEFLAG_WATER ; + if (shader->surfaceparms & Q3SURFACEPARM_POINTLIGHT ) texture->surfaceflags |= Q3SURFACEFLAG_POINTLIGHT ; + if (shader->surfaceparms & Q3SURFACEPARM_HINT ) texture->surfaceflags |= Q3SURFACEFLAG_HINT ; + if (shader->surfaceparms & Q3SURFACEPARM_DUST ) texture->surfaceflags |= Q3SURFACEFLAG_DUST ; + // if (shader->surfaceparms & Q3SURFACEPARM_BOTCLIP ) texture->surfaceflags |= Q3SURFACEFLAG_BOTCLIP ; + // if (shader->surfaceparms & Q3SURFACEPARM_LIGHTGRID ) texture->surfaceflags |= Q3SURFACEFLAG_LIGHTGRID ; + // if (shader->surfaceparms & Q3SURFACEPARM_ANTIPORTAL ) texture->surfaceflags |= Q3SURFACEFLAG_ANTIPORTAL ; + + if (shader->dpmeshcollisions) + texture->basematerialflags |= MATERIALFLAG_MESHCOLLISIONS; + if (shader->dpshaderkill && developer_extra.integer) + Con_DPrintf("^1%s:^7 killing shader ^3\"%s\" because of cvar\n", loadmodel->name, name); + } + else if (!strcmp(texture->name, "noshader") || !texture->name[0]) + { + if (developer_extra.integer) + Con_DPrintf("^1%s:^7 using fallback noshader material for ^3\"%s\"\n", loadmodel->name, name); + texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; + } + else if (!strcmp(texture->name, "common/nodraw") || !strcmp(texture->name, "textures/common/nodraw")) + { + if (developer_extra.integer) + Con_DPrintf("^1%s:^7 using fallback nodraw material for ^3\"%s\"\n", loadmodel->name, name); + texture->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; + texture->supercontents = SUPERCONTENTS_SOLID; + } + else + { + if (developer_extra.integer) + Con_DPrintf("^1%s:^7 No shader found for texture ^3\"%s\"\n", loadmodel->name, texture->name); + if (texture->surfaceflags & Q3SURFACEFLAG_NODRAW) + { + texture->basematerialflags |= MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; + texture->supercontents = SUPERCONTENTS_SOLID; + } + else if (texture->surfaceflags & Q3SURFACEFLAG_SKY) + { + texture->basematerialflags |= MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; + texture->supercontents = SUPERCONTENTS_SKY; + } + else + { + texture->basematerialflags |= MATERIALFLAG_WALL; + texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; + } + texture->numskinframes = 1; + if(cls.state == ca_dedicated) + { + texture->skinframes[0] = NULL; + success = false; + } + else + { + if (fallback) + { + if ((texture->skinframes[0] = R_SkinFrame_LoadExternal(texture->name, defaulttexflags, false))) + { + if(texture->skinframes[0]->hasalpha) + texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + } + else + success = false; + } + else + success = false; + if (!success && warnmissing) + Con_Printf("^1%s:^7 could not load texture ^3\"%s\"\n", loadmodel->name, texture->name); + } + } + // init the animation variables + texture->currentframe = texture; + if (texture->numskinframes < 1) + texture->numskinframes = 1; + if (!texture->skinframes[0]) + texture->skinframes[0] = R_SkinFrame_LoadMissing(); + texture->currentskinframe = texture->skinframes[0]; + texture->backgroundcurrentskinframe = texture->backgroundskinframes[0]; + return success; +} + +skinfile_t *Mod_LoadSkinFiles(void) +{ + int i, words, line, wordsoverflow; + char *text; + const char *data; + skinfile_t *skinfile = NULL, *first = NULL; + skinfileitem_t *skinfileitem; + char word[10][MAX_QPATH]; + char vabuf[1024]; + +/* +sample file: +U_bodyBox,models/players/Legoman/BikerA2.tga +U_RArm,models/players/Legoman/BikerA1.tga +U_LArm,models/players/Legoman/BikerA1.tga +U_armor,common/nodraw +U_sword,common/nodraw +U_shield,common/nodraw +U_homb,common/nodraw +U_backpack,common/nodraw +U_colcha,common/nodraw +tag_head, +tag_weapon, +tag_torso, +*/ + memset(word, 0, sizeof(word)); + for (i = 0;i < 256 && (data = text = (char *)FS_LoadFile(va(vabuf, sizeof(vabuf), "%s_%i.skin", loadmodel->name, i), tempmempool, true, NULL));i++) + { + // If it's the first file we parse + if (skinfile == NULL) + { + skinfile = (skinfile_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfile_t)); + first = skinfile; + } + else + { + skinfile->next = (skinfile_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfile_t)); + skinfile = skinfile->next; + } + skinfile->next = NULL; + + for(line = 0;;line++) + { + // parse line + if (!COM_ParseToken_QuakeC(&data, true)) + break; + if (!strcmp(com_token, "\n")) + continue; + words = 0; + wordsoverflow = false; + do + { + if (words < 10) + strlcpy(word[words++], com_token, sizeof (word[0])); + else + wordsoverflow = true; + } + while (COM_ParseToken_QuakeC(&data, true) && strcmp(com_token, "\n")); + if (wordsoverflow) + { + Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: line with too many statements, skipping\n", loadmodel->name, i, line); + continue; + } + // words is always >= 1 + if (!strcmp(word[0], "replace")) + { + if (words == 3) + { + if (developer_loading.integer) + Con_Printf("Mod_LoadSkinFiles: parsed mesh \"%s\" shader replacement \"%s\"\n", word[1], word[2]); + skinfileitem = (skinfileitem_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfileitem_t)); + skinfileitem->next = skinfile->items; + skinfile->items = skinfileitem; + strlcpy (skinfileitem->name, word[1], sizeof (skinfileitem->name)); + strlcpy (skinfileitem->replacement, word[2], sizeof (skinfileitem->replacement)); + } + else + Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: wrong number of parameters to command \"%s\", see documentation in DP_GFX_SKINFILES extension in dpextensions.qc\n", loadmodel->name, i, line, word[0]); + } + else if (words >= 2 && !strncmp(word[0], "tag_", 4)) + { + // tag name, like "tag_weapon," + // not used for anything (not even in Quake3) + } + else if (words >= 2 && !strcmp(word[1], ",")) + { + // mesh shader name, like "U_RArm,models/players/Legoman/BikerA1.tga" + if (developer_loading.integer) + Con_Printf("Mod_LoadSkinFiles: parsed mesh \"%s\" shader replacement \"%s\"\n", word[0], word[2]); + skinfileitem = (skinfileitem_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfileitem_t)); + skinfileitem->next = skinfile->items; + skinfile->items = skinfileitem; + strlcpy (skinfileitem->name, word[0], sizeof (skinfileitem->name)); + strlcpy (skinfileitem->replacement, word[2], sizeof (skinfileitem->replacement)); + } + else + Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: does not look like tag or mesh specification, or replace command, see documentation in DP_GFX_SKINFILES extension in dpextensions.qc\n", loadmodel->name, i, line); + } + Mem_Free(text); + } + if (i) + loadmodel->numskins = i; + return first; +} + +void Mod_FreeSkinFiles(skinfile_t *skinfile) +{ + skinfile_t *next; + skinfileitem_t *skinfileitem, *nextitem; + for (;skinfile;skinfile = next) + { + next = skinfile->next; + for (skinfileitem = skinfile->items;skinfileitem;skinfileitem = nextitem) + { + nextitem = skinfileitem->next; + Mem_Free(skinfileitem); + } + Mem_Free(skinfile); + } +} + +int Mod_CountSkinFiles(skinfile_t *skinfile) +{ + int i; + for (i = 0;skinfile;skinfile = skinfile->next, i++); + return i; +} + +void Mod_SnapVertices(int numcomponents, int numvertices, float *vertices, float snap) +{ + int i; + double isnap = 1.0 / snap; + for (i = 0;i < numvertices*numcomponents;i++) + vertices[i] = floor(vertices[i]*isnap)*snap; +} + +int Mod_RemoveDegenerateTriangles(int numtriangles, const int *inelement3i, int *outelement3i, const float *vertex3f) +{ + int i, outtriangles; + float edgedir1[3], edgedir2[3], temp[3]; + // a degenerate triangle is one with no width (thickness, surface area) + // these are characterized by having all 3 points colinear (along a line) + // or having two points identical + // the simplest check is to calculate the triangle's area + for (i = 0, outtriangles = 0;i < numtriangles;i++, inelement3i += 3) + { + // calculate first edge + VectorSubtract(vertex3f + inelement3i[1] * 3, vertex3f + inelement3i[0] * 3, edgedir1); + VectorSubtract(vertex3f + inelement3i[2] * 3, vertex3f + inelement3i[0] * 3, edgedir2); + CrossProduct(edgedir1, edgedir2, temp); + if (VectorLength2(temp) < 0.001f) + continue; // degenerate triangle (no area) + // valid triangle (has area) + VectorCopy(inelement3i, outelement3i); + outelement3i += 3; + outtriangles++; + } + return outtriangles; +} + +void Mod_VertexRangeFromElements(int numelements, const int *elements, int *firstvertexpointer, int *lastvertexpointer) +{ + int i, e; + int firstvertex, lastvertex; + if (numelements > 0 && elements) + { + firstvertex = lastvertex = elements[0]; + for (i = 1;i < numelements;i++) + { + e = elements[i]; + firstvertex = min(firstvertex, e); + lastvertex = max(lastvertex, e); + } + } + else + firstvertex = lastvertex = 0; + if (firstvertexpointer) + *firstvertexpointer = firstvertex; + if (lastvertexpointer) + *lastvertexpointer = lastvertex; +} + +void Mod_MakeSortedSurfaces(dp_model_t *mod) +{ + // make an optimal set of texture-sorted batches to draw... + int j, t; + int *firstsurfacefortexture; + int *numsurfacesfortexture; + if (!mod->sortedmodelsurfaces) + mod->sortedmodelsurfaces = (int *) Mem_Alloc(loadmodel->mempool, mod->nummodelsurfaces * sizeof(*mod->sortedmodelsurfaces)); + firstsurfacefortexture = (int *) Mem_Alloc(tempmempool, mod->num_textures * sizeof(*firstsurfacefortexture)); + numsurfacesfortexture = (int *) Mem_Alloc(tempmempool, mod->num_textures * sizeof(*numsurfacesfortexture)); + memset(numsurfacesfortexture, 0, mod->num_textures * sizeof(*numsurfacesfortexture)); + for (j = 0;j < mod->nummodelsurfaces;j++) + { + const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; + int t = (int)(surface->texture - mod->data_textures); + numsurfacesfortexture[t]++; + } + j = 0; + for (t = 0;t < mod->num_textures;t++) + { + firstsurfacefortexture[t] = j; + j += numsurfacesfortexture[t]; + } + for (j = 0;j < mod->nummodelsurfaces;j++) + { + const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; + int t = (int)(surface->texture - mod->data_textures); + mod->sortedmodelsurfaces[firstsurfacefortexture[t]++] = j + mod->firstmodelsurface; + } + Mem_Free(firstsurfacefortexture); + Mem_Free(numsurfacesfortexture); +} + +void Mod_BuildVBOs(void) +{ + if (!loadmodel->surfmesh.num_vertices) + return; + + if (gl_paranoid.integer && loadmodel->surfmesh.data_element3s && loadmodel->surfmesh.data_element3i) + { + int i; + for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) + { + if (loadmodel->surfmesh.data_element3s[i] != loadmodel->surfmesh.data_element3i[i]) + { + Con_Printf("Mod_BuildVBOs: element %u is incorrect (%u should be %u)\n", i, loadmodel->surfmesh.data_element3s[i], loadmodel->surfmesh.data_element3i[i]); + loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; + } + } + } + + // build r_vertexmesh_t array + // (compressed interleaved array for D3D) + if (!loadmodel->surfmesh.data_vertexmesh && vid.useinterleavedarrays) + { + int vertexindex; + int numvertices = loadmodel->surfmesh.num_vertices; + r_vertexmesh_t *vertexmesh; + loadmodel->surfmesh.data_vertexmesh = vertexmesh = (r_vertexmesh_t*)Mem_Alloc(loadmodel->mempool, numvertices * sizeof(r_vertexmesh_t)); + for (vertexindex = 0;vertexindex < numvertices;vertexindex++, vertexmesh++) + { + VectorCopy(loadmodel->surfmesh.data_vertex3f + 3*vertexindex, vertexmesh->vertex3f); + VectorScale(loadmodel->surfmesh.data_svector3f + 3*vertexindex, 1.0f, vertexmesh->svector3f); + VectorScale(loadmodel->surfmesh.data_tvector3f + 3*vertexindex, 1.0f, vertexmesh->tvector3f); + VectorScale(loadmodel->surfmesh.data_normal3f + 3*vertexindex, 1.0f, vertexmesh->normal3f); + if (loadmodel->surfmesh.data_lightmapcolor4f) + Vector4Copy(loadmodel->surfmesh.data_lightmapcolor4f + 4*vertexindex, vertexmesh->color4f); + Vector2Copy(loadmodel->surfmesh.data_texcoordtexture2f + 2*vertexindex, vertexmesh->texcoordtexture2f); + if (loadmodel->surfmesh.data_texcoordlightmap2f) + Vector2Scale(loadmodel->surfmesh.data_texcoordlightmap2f + 2*vertexindex, 1.0f, vertexmesh->texcoordlightmap2f); + if (loadmodel->surfmesh.data_skeletalindex4ub) + Vector4Copy(loadmodel->surfmesh.data_skeletalindex4ub + 4*vertexindex, vertexmesh->skeletalindex4ub); + if (loadmodel->surfmesh.data_skeletalweight4ub) + Vector4Copy(loadmodel->surfmesh.data_skeletalweight4ub + 4*vertexindex, vertexmesh->skeletalweight4ub); + } + } + + // upload short indices as a buffer + if (loadmodel->surfmesh.data_element3s && !loadmodel->surfmesh.data_element3s_indexbuffer) + loadmodel->surfmesh.data_element3s_indexbuffer = R_Mesh_CreateMeshBuffer(loadmodel->surfmesh.data_element3s, loadmodel->surfmesh.num_triangles * sizeof(short[3]), loadmodel->name, true, false, false, true); + + // upload int indices as a buffer + if (loadmodel->surfmesh.data_element3i && !loadmodel->surfmesh.data_element3i_indexbuffer && !loadmodel->surfmesh.data_element3s) + loadmodel->surfmesh.data_element3i_indexbuffer = R_Mesh_CreateMeshBuffer(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles * sizeof(int[3]), loadmodel->name, true, false, false, false); + + // only build a vbo if one has not already been created (this is important for brush models which load specially) + // vertex buffer is several arrays and we put them in the same buffer + // + // is this wise? the texcoordtexture2f array is used with dynamic + // vertex/svector/tvector/normal when rendering animated models, on the + // other hand animated models don't use a lot of vertices anyway... + if (!loadmodel->surfmesh.vbo_vertexbuffer && !vid.useinterleavedarrays) + { + size_t size; + unsigned char *mem; + size = 0; + loadmodel->surfmesh.vbooffset_vertexmesh = size;if (loadmodel->surfmesh.data_vertexmesh ) size += loadmodel->surfmesh.num_vertices * sizeof(r_vertexmesh_t); + loadmodel->surfmesh.vbooffset_vertex3f = size;if (loadmodel->surfmesh.data_vertex3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.vbooffset_svector3f = size;if (loadmodel->surfmesh.data_svector3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.vbooffset_tvector3f = size;if (loadmodel->surfmesh.data_tvector3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.vbooffset_normal3f = size;if (loadmodel->surfmesh.data_normal3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); + loadmodel->surfmesh.vbooffset_texcoordtexture2f = size;if (loadmodel->surfmesh.data_texcoordtexture2f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[2]); + loadmodel->surfmesh.vbooffset_texcoordlightmap2f = size;if (loadmodel->surfmesh.data_texcoordlightmap2f) size += loadmodel->surfmesh.num_vertices * sizeof(float[2]); + loadmodel->surfmesh.vbooffset_lightmapcolor4f = size;if (loadmodel->surfmesh.data_lightmapcolor4f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[4]); + loadmodel->surfmesh.vbooffset_skeletalindex4ub = size;if (loadmodel->surfmesh.data_skeletalindex4ub ) size += loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]); + loadmodel->surfmesh.vbooffset_skeletalweight4ub = size;if (loadmodel->surfmesh.data_skeletalweight4ub ) size += loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]); + mem = (unsigned char *)Mem_Alloc(tempmempool, size); + if (loadmodel->surfmesh.data_vertexmesh ) memcpy(mem + loadmodel->surfmesh.vbooffset_vertexmesh , loadmodel->surfmesh.data_vertexmesh , loadmodel->surfmesh.num_vertices * sizeof(r_vertexmesh_t)); + if (loadmodel->surfmesh.data_vertex3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_vertex3f , loadmodel->surfmesh.data_vertex3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); + if (loadmodel->surfmesh.data_svector3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_svector3f , loadmodel->surfmesh.data_svector3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); + if (loadmodel->surfmesh.data_tvector3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_tvector3f , loadmodel->surfmesh.data_tvector3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); + if (loadmodel->surfmesh.data_normal3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_normal3f , loadmodel->surfmesh.data_normal3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); + if (loadmodel->surfmesh.data_texcoordtexture2f ) memcpy(mem + loadmodel->surfmesh.vbooffset_texcoordtexture2f , loadmodel->surfmesh.data_texcoordtexture2f , loadmodel->surfmesh.num_vertices * sizeof(float[2])); + if (loadmodel->surfmesh.data_texcoordlightmap2f) memcpy(mem + loadmodel->surfmesh.vbooffset_texcoordlightmap2f, loadmodel->surfmesh.data_texcoordlightmap2f, loadmodel->surfmesh.num_vertices * sizeof(float[2])); + if (loadmodel->surfmesh.data_lightmapcolor4f ) memcpy(mem + loadmodel->surfmesh.vbooffset_lightmapcolor4f , loadmodel->surfmesh.data_lightmapcolor4f , loadmodel->surfmesh.num_vertices * sizeof(float[4])); + if (loadmodel->surfmesh.data_skeletalindex4ub ) memcpy(mem + loadmodel->surfmesh.vbooffset_skeletalindex4ub , loadmodel->surfmesh.data_skeletalindex4ub , loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4])); + if (loadmodel->surfmesh.data_skeletalweight4ub ) memcpy(mem + loadmodel->surfmesh.vbooffset_skeletalweight4ub , loadmodel->surfmesh.data_skeletalweight4ub , loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4])); + loadmodel->surfmesh.vbo_vertexbuffer = R_Mesh_CreateMeshBuffer(mem, size, loadmodel->name, false, false, false, false); + Mem_Free(mem); + } +} + +extern cvar_t mod_obj_orientation; +static void Mod_Decompile_OBJ(dp_model_t *model, const char *filename, const char *mtlfilename, const char *originalfilename) +{ + int submodelindex, vertexindex, surfaceindex, triangleindex, textureindex, countvertices = 0, countsurfaces = 0, countfaces = 0, counttextures = 0; + int a, b, c; + const char *texname; + const int *e; + const float *v, *vn, *vt; + size_t l; + size_t outbufferpos = 0; + size_t outbuffermax = 0x100000; + char *outbuffer = (char *) Z_Malloc(outbuffermax), *oldbuffer; + const msurface_t *surface; + const int maxtextures = 256; + char *texturenames = (char *) Z_Malloc(maxtextures * MAX_QPATH); + dp_model_t *submodel; + + // construct the mtllib file + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "# mtllib for %s exported by darkplaces engine\n", originalfilename); + if (l > 0) + outbufferpos += l; + for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->num_surfaces;surfaceindex++, surface++) + { + countsurfaces++; + countvertices += surface->num_vertices; + countfaces += surface->num_triangles; + texname = (surface->texture && surface->texture->name[0]) ? surface->texture->name : "default"; + for (textureindex = 0;textureindex < counttextures;textureindex++) + if (!strcmp(texturenames + textureindex * MAX_QPATH, texname)) + break; + if (textureindex < counttextures) + continue; // already wrote this material entry + if (textureindex >= maxtextures) + continue; // just a precaution + textureindex = counttextures++; + strlcpy(texturenames + textureindex * MAX_QPATH, texname, MAX_QPATH); + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "newmtl %s\nNs 96.078431\nKa 0 0 0\nKd 0.64 0.64 0.64\nKs 0.5 0.5 0.5\nNi 1\nd 1\nillum 2\nmap_Kd %s%s\n\n", texname, texname, strstr(texname, ".tga") ? "" : ".tga"); + if (l > 0) + outbufferpos += l; + } + + // write the mtllib file + FS_WriteFile(mtlfilename, outbuffer, outbufferpos); + + // construct the obj file + outbufferpos = 0; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "# model exported from %s by darkplaces engine\n# %i vertices, %i faces, %i surfaces\nmtllib %s\n", originalfilename, countvertices, countfaces, countsurfaces, mtlfilename); + if (l > 0) + outbufferpos += l; + + for (vertexindex = 0, v = model->surfmesh.data_vertex3f, vn = model->surfmesh.data_normal3f, vt = model->surfmesh.data_texcoordtexture2f;vertexindex < model->surfmesh.num_vertices;vertexindex++, v += 3, vn += 3, vt += 2) + { + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + if(mod_obj_orientation.integer) + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "v %f %f %f\nvn %f %f %f\nvt %f %f\n", v[0], v[2], v[1], vn[0], vn[2], vn[1], vt[0], 1-vt[1]); + else + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "v %f %f %f\nvn %f %f %f\nvt %f %f\n", v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1-vt[1]); + if (l > 0) + outbufferpos += l; + } + + for (submodelindex = 0;submodelindex < max(1, model->brush.numsubmodels);submodelindex++) + { + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "o %i\n", submodelindex); + if (l > 0) + outbufferpos += l; + submodel = model->brush.numsubmodels ? model->brush.submodels[submodelindex] : model; + for (surfaceindex = 0;surfaceindex < submodel->nummodelsurfaces;surfaceindex++) + { + surface = model->data_surfaces + submodel->sortedmodelsurfaces[surfaceindex]; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "usemtl %s\n", (surface->texture && surface->texture->name[0]) ? surface->texture->name : "default"); + if (l > 0) + outbufferpos += l; + for (triangleindex = 0, e = model->surfmesh.data_element3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + a = e[0]+1; + b = e[1]+1; + c = e[2]+1; + if(mod_obj_orientation.integer) + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", a,a,a,b,b,b,c,c,c); + else + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", a,a,a,c,c,c,b,b,b); + if (l > 0) + outbufferpos += l; + } + } + } + + // write the obj file + FS_WriteFile(filename, outbuffer, outbufferpos); + + // clean up + Z_Free(outbuffer); + Z_Free(texturenames); + + // print some stats + Con_Printf("Wrote %s (%i bytes, %i vertices, %i faces, %i surfaces with %i distinct textures)\n", filename, (int)outbufferpos, countvertices, countfaces, countsurfaces, counttextures); +} + +static void Mod_Decompile_SMD(dp_model_t *model, const char *filename, int firstpose, int numposes, qboolean writetriangles) +{ + int countnodes = 0, counttriangles = 0, countframes = 0; + int surfaceindex; + int triangleindex; + int transformindex; + int poseindex; + int cornerindex; + const int *e; + size_t l; + size_t outbufferpos = 0; + size_t outbuffermax = 0x100000; + char *outbuffer = (char *) Z_Malloc(outbuffermax), *oldbuffer; + const msurface_t *surface; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "version 1\nnodes\n"); + if (l > 0) + outbufferpos += l; + for (transformindex = 0;transformindex < model->num_bones;transformindex++) + { + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + countnodes++; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i \"%s\" %3i\n", transformindex, model->data_bones[transformindex].name, model->data_bones[transformindex].parent); + if (l > 0) + outbufferpos += l; + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\nskeleton\n"); + if (l > 0) + outbufferpos += l; + for (poseindex = 0;poseindex < numposes;poseindex++) + { + countframes++; + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "time %i\n", poseindex); + if (l > 0) + outbufferpos += l; + for (transformindex = 0;transformindex < model->num_bones;transformindex++) + { + float angles[3]; + float mtest[4][3]; + matrix4x4_t posematrix; + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + + // strangely the smd angles are for a transposed matrix, so we + // have to generate a transposed matrix, then convert that... + Matrix4x4_FromBonePose7s(&posematrix, model->num_posescale, model->data_poses7s + 7*(model->num_bones * poseindex + transformindex)); + Matrix4x4_ToArray12FloatGL(&posematrix, mtest[0]); + AnglesFromVectors(angles, mtest[0], mtest[2], false); + if (angles[0] >= 180) angles[0] -= 360; + if (angles[1] >= 180) angles[1] -= 360; + if (angles[2] >= 180) angles[2] -= 360; + +#if 0 +{ + float a = DEG2RAD(angles[ROLL]); + float b = DEG2RAD(angles[PITCH]); + float c = DEG2RAD(angles[YAW]); + float cy, sy, cp, sp, cr, sr; + float test[4][3]; + // smd matrix construction, for comparing + sy = sin(c); + cy = cos(c); + sp = sin(b); + cp = cos(b); + sr = sin(a); + cr = cos(a); + + test[0][0] = cp*cy; + test[0][1] = cp*sy; + test[0][2] = -sp; + test[1][0] = sr*sp*cy+cr*-sy; + test[1][1] = sr*sp*sy+cr*cy; + test[1][2] = sr*cp; + test[2][0] = (cr*sp*cy+-sr*-sy); + test[2][1] = (cr*sp*sy+-sr*cy); + test[2][2] = cr*cp; + test[3][0] = pose[9]; + test[3][1] = pose[10]; + test[3][2] = pose[11]; +} +#endif + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f\n", transformindex, mtest[3][0], mtest[3][1], mtest[3][2], DEG2RAD(angles[ROLL]), DEG2RAD(angles[PITCH]), DEG2RAD(angles[YAW])); + if (l > 0) + outbufferpos += l; + } + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\n"); + if (l > 0) + outbufferpos += l; + if (writetriangles) + { + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "triangles\n"); + if (l > 0) + outbufferpos += l; + for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->num_surfaces;surfaceindex++, surface++) + { + for (triangleindex = 0, e = model->surfmesh.data_element3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + counttriangles++; + if (outbufferpos >= outbuffermax >> 1) + { + outbuffermax *= 2; + oldbuffer = outbuffer; + outbuffer = (char *) Z_Malloc(outbuffermax); + memcpy(outbuffer, oldbuffer, outbufferpos); + Z_Free(oldbuffer); + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%s\n", surface->texture && surface->texture->name[0] ? surface->texture->name : "default.bmp"); + if (l > 0) + outbufferpos += l; + for (cornerindex = 0;cornerindex < 3;cornerindex++) + { + const int index = e[2-cornerindex]; + const float *v = model->surfmesh.data_vertex3f + index * 3; + const float *vn = model->surfmesh.data_normal3f + index * 3; + const float *vt = model->surfmesh.data_texcoordtexture2f + index * 2; + const int b = model->surfmesh.blends[index]; + if (b < model->num_bones) + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f\n" , b, v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1]); + else + { + const blendweights_t *w = model->surfmesh.data_blendweights + b - model->num_bones; + const unsigned char *wi = w->index; + const unsigned char *wf = w->influence; + if (wf[3]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 4 %i %f %i %f %i %f %i %f\n", wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f, wi[2], wf[2]/255.0f, wi[3], wf[3]/255.0f); + else if (wf[2]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 3 %i %f %i %f %i %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f, wi[2], wf[2]/255.0f); + else if (wf[1]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 2 %i %f %i %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f); + else l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1]); + } + if (l > 0) + outbufferpos += l; + } + } + } + l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\n"); + if (l > 0) + outbufferpos += l; + } + + FS_WriteFile(filename, outbuffer, outbufferpos); + Z_Free(outbuffer); + + Con_Printf("Wrote %s (%i bytes, %i nodes, %i frames, %i triangles)\n", filename, (int)outbufferpos, countnodes, countframes, counttriangles); +} + +/* +================ +Mod_Decompile_f + +decompiles a model to editable files +================ +*/ +static void Mod_Decompile_f(void) +{ + int i, j, k, l, first, count; + dp_model_t *mod; + char inname[MAX_QPATH]; + char outname[MAX_QPATH]; + char mtlname[MAX_QPATH]; + char basename[MAX_QPATH]; + char animname[MAX_QPATH]; + char animname2[MAX_QPATH]; + char zymtextbuffer[16384]; + char dpmtextbuffer[16384]; + char framegroupstextbuffer[16384]; + int zymtextsize = 0; + int dpmtextsize = 0; + int framegroupstextsize = 0; + char vabuf[1024]; + + if (Cmd_Argc() != 2) + { + Con_Print("usage: modeldecompile \n"); + return; + } + + strlcpy(inname, Cmd_Argv(1), sizeof(inname)); + FS_StripExtension(inname, basename, sizeof(basename)); + + mod = Mod_ForName(inname, false, true, inname[0] == '*' ? cl.model_name[1] : NULL); + if (!mod) + { + Con_Print("No such model\n"); + return; + } + if (mod->brush.submodel) + { + // if we're decompiling a submodel, be sure to give it a proper name based on its parent + FS_StripExtension(cl.model_name[1], outname, sizeof(outname)); + dpsnprintf(basename, sizeof(basename), "%s/%s", outname, mod->name); + outname[0] = 0; + } + if (!mod->surfmesh.num_triangles) + { + Con_Print("Empty model (or sprite)\n"); + return; + } + + // export OBJ if possible (not on sprites) + if (mod->surfmesh.num_triangles) + { + dpsnprintf(outname, sizeof(outname), "%s_decompiled.obj", basename); + dpsnprintf(mtlname, sizeof(mtlname), "%s_decompiled.mtl", basename); + Mod_Decompile_OBJ(mod, outname, mtlname, inname); + } + + // export SMD if possible (only for skeletal models) + if (mod->surfmesh.num_triangles && mod->num_bones) + { + dpsnprintf(outname, sizeof(outname), "%s_decompiled/ref1.smd", basename); + Mod_Decompile_SMD(mod, outname, 0, 1, true); + l = dpsnprintf(zymtextbuffer + zymtextsize, sizeof(zymtextbuffer) - zymtextsize, "output out.zym\nscale 1\norigin 0 0 0\nmesh ref1.smd\n"); + if (l > 0) zymtextsize += l; + l = dpsnprintf(dpmtextbuffer + dpmtextsize, sizeof(dpmtextbuffer) - dpmtextsize, "outputdir .\nmodel out\nscale 1\norigin 0 0 0\nscene ref1.smd\n"); + if (l > 0) dpmtextsize += l; + for (i = 0;i < mod->numframes;i = j) + { + strlcpy(animname, mod->animscenes[i].name, sizeof(animname)); + first = mod->animscenes[i].firstframe; + if (mod->animscenes[i].framecount > 1) + { + // framegroup anim + count = mod->animscenes[i].framecount; + j = i + 1; + } + else + { + // individual frame + // check for additional frames with same name + for (l = 0, k = strlen(animname);animname[l];l++) + if(animname[l] < '0' || animname[l] > '9') + k = l + 1; + if(k > 0 && animname[k-1] == '_') + --k; + animname[k] = 0; + count = mod->num_poses - first; + for (j = i + 1;j < mod->numframes;j++) + { + strlcpy(animname2, mod->animscenes[j].name, sizeof(animname2)); + for (l = 0, k = strlen(animname2);animname2[l];l++) + if(animname2[l] < '0' || animname2[l] > '9') + k = l + 1; + if(k > 0 && animname[k-1] == '_') + --k; + animname2[k] = 0; + if (strcmp(animname2, animname) || mod->animscenes[j].framecount > 1) + { + count = mod->animscenes[j].firstframe - first; + break; + } + } + // if it's only one frame, use the original frame name + if (j == i + 1) + strlcpy(animname, mod->animscenes[i].name, sizeof(animname)); + + } + dpsnprintf(outname, sizeof(outname), "%s_decompiled/%s.smd", basename, animname); + Mod_Decompile_SMD(mod, outname, first, count, false); + if (zymtextsize < (int)sizeof(zymtextbuffer) - 100) + { + l = dpsnprintf(zymtextbuffer + zymtextsize, sizeof(zymtextbuffer) - zymtextsize, "scene %s.smd fps %g %s\n", animname, mod->animscenes[i].framerate, mod->animscenes[i].loop ? "" : " noloop"); + if (l > 0) zymtextsize += l; + } + if (dpmtextsize < (int)sizeof(dpmtextbuffer) - 100) + { + l = dpsnprintf(dpmtextbuffer + dpmtextsize, sizeof(dpmtextbuffer) - dpmtextsize, "scene %s.smd fps %g %s\n", animname, mod->animscenes[i].framerate, mod->animscenes[i].loop ? "" : " noloop"); + if (l > 0) dpmtextsize += l; + } + if (framegroupstextsize < (int)sizeof(framegroupstextbuffer) - 100) + { + l = dpsnprintf(framegroupstextbuffer + framegroupstextsize, sizeof(framegroupstextbuffer) - framegroupstextsize, "%d %d %f %d // %s\n", first, count, mod->animscenes[i].framerate, mod->animscenes[i].loop, animname); + if (l > 0) framegroupstextsize += l; + } + } + if (zymtextsize) + FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_decompiled/out_zym.txt", basename), zymtextbuffer, (fs_offset_t)zymtextsize); + if (dpmtextsize) + FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_decompiled/out_dpm.txt", basename), dpmtextbuffer, (fs_offset_t)dpmtextsize); + if (framegroupstextsize) + FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_decompiled.framegroups", basename), framegroupstextbuffer, (fs_offset_t)framegroupstextsize); + } +} + +void Mod_AllocLightmap_Init(mod_alloclightmap_state_t *state, int width, int height) +{ + int y; + memset(state, 0, sizeof(*state)); + state->width = width; + state->height = height; + state->currentY = 0; + state->rows = (mod_alloclightmap_row_t *)Mem_Alloc(loadmodel->mempool, state->height * sizeof(*state->rows)); + for (y = 0;y < state->height;y++) + { + state->rows[y].currentX = 0; + state->rows[y].rowY = -1; + } +} + +void Mod_AllocLightmap_Reset(mod_alloclightmap_state_t *state) +{ + int y; + state->currentY = 0; + for (y = 0;y < state->height;y++) + { + state->rows[y].currentX = 0; + state->rows[y].rowY = -1; + } +} + +void Mod_AllocLightmap_Free(mod_alloclightmap_state_t *state) +{ + if (state->rows) + Mem_Free(state->rows); + memset(state, 0, sizeof(*state)); +} + +qboolean Mod_AllocLightmap_Block(mod_alloclightmap_state_t *state, int blockwidth, int blockheight, int *outx, int *outy) +{ + mod_alloclightmap_row_t *row; + int y; + + row = state->rows + blockheight; + if ((row->rowY < 0) || (row->currentX + blockwidth > state->width)) + { + if (state->currentY + blockheight <= state->height) + { + // use the current allocation position + row->rowY = state->currentY; + row->currentX = 0; + state->currentY += blockheight; + } + else + { + // find another position + for (y = blockheight;y < state->height;y++) + { + if ((state->rows[y].rowY >= 0) && (state->rows[y].currentX + blockwidth <= state->width)) + { + row = state->rows + y; + break; + } + } + if (y == state->height) + return false; + } + } + *outy = row->rowY; + *outx = row->currentX; + row->currentX += blockwidth; + + return true; +} + +typedef struct lightmapsample_s +{ + float pos[3]; + float sh1[4][3]; + float *vertex_color; + unsigned char *lm_bgr; + unsigned char *lm_dir; +} +lightmapsample_t; + +typedef struct lightmapvertex_s +{ + int index; + float pos[3]; + float normal[3]; + float texcoordbase[2]; + float texcoordlightmap[2]; + float lightcolor[4]; +} +lightmapvertex_t; + +typedef struct lightmaptriangle_s +{ + int triangleindex; + int surfaceindex; + int lightmapindex; + int axis; + int lmoffset[2]; + int lmsize[2]; + // 2D modelspace coordinates of min corner + // snapped to lightmap grid but not in grid coordinates + float lmbase[2]; + // 2D modelspace to lightmap coordinate scale + float lmscale[2]; + float vertex[3][3]; + float mins[3]; + float maxs[3]; +} +lightmaptriangle_t; + +typedef struct lightmaplight_s +{ + float origin[3]; + float radius; + float iradius; + float radius2; + float color[3]; + svbsp_t svbsp; +} +lightmaplight_t; + +lightmaptriangle_t *mod_generatelightmaps_lightmaptriangles; + +#define MAX_LIGHTMAPSAMPLES 64 +static int mod_generatelightmaps_numoffsets[3]; +static float mod_generatelightmaps_offsets[3][MAX_LIGHTMAPSAMPLES][3]; + +static int mod_generatelightmaps_numlights; +static lightmaplight_t *mod_generatelightmaps_lightinfo; + +extern cvar_t r_shadow_lightattenuationdividebias; +extern cvar_t r_shadow_lightattenuationlinearscale; + +static void Mod_GenerateLightmaps_LightPoint(dp_model_t *model, const vec3_t pos, vec3_t ambient, vec3_t diffuse, vec3_t lightdir) +{ + int i; + int index; + int result; + float relativepoint[3]; + float color[3]; + float dir[3]; + float dist; + float dist2; + float intensity; + float sample[5*3]; + float lightorigin[3]; + float lightradius; + float lightradius2; + float lightiradius; + float lightcolor[3]; + trace_t trace; + for (i = 0;i < 5*3;i++) + sample[i] = 0.0f; + for (index = 0;;index++) + { + result = R_Shadow_GetRTLightInfo(index, lightorigin, &lightradius, lightcolor); + if (result < 0) + break; + if (result == 0) + continue; + lightradius2 = lightradius * lightradius; + VectorSubtract(lightorigin, pos, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + lightiradius = 1.0f / lightradius; + dist = sqrt(dist2) * lightiradius; + intensity = (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); + if (intensity <= 0.0f) + continue; + if (model && model->TraceLine) + { + model->TraceLine(model, NULL, NULL, &trace, pos, lightorigin, SUPERCONTENTS_VISBLOCKERMASK); + if (trace.fraction < 1) + continue; + } + // scale down intensity to add to both ambient and diffuse + //intensity *= 0.5f; + VectorNormalize(relativepoint); + VectorScale(lightcolor, intensity, color); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity *= VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } + // calculate the direction we'll use to reduce the sample to a directional light source + VectorCopy(sample + 12, dir); + //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); + VectorNormalize(dir); + // extract the diffuse color along the chosen direction and scale it + diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]); + diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]); + diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]); + // subtract some of diffuse from ambient + VectorMA(sample, -0.333f, diffuse, ambient); + // store the normalized lightdir + VectorCopy(dir, lightdir); +} + +static void Mod_GenerateLightmaps_CreateLights_ComputeSVBSP_InsertSurfaces(const dp_model_t *model, svbsp_t *svbsp, const float *mins, const float *maxs) +{ + int surfaceindex; + int triangleindex; + const msurface_t *surface; + const float *vertex3f = model->surfmesh.data_vertex3f; + const int *element3i = model->surfmesh.data_element3i; + const int *e; + float v2[3][3]; + for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->nummodelsurfaces;surfaceindex++, surface++) + { + if (!BoxesOverlap(surface->mins, surface->maxs, mins, maxs)) + continue; + if (surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW) + continue; + for (triangleindex = 0, e = element3i + 3*surface->num_firsttriangle;triangleindex < surface->num_triangles;triangleindex++, e += 3) + { + VectorCopy(vertex3f + 3*e[0], v2[0]); + VectorCopy(vertex3f + 3*e[1], v2[1]); + VectorCopy(vertex3f + 3*e[2], v2[2]); + SVBSP_AddPolygon(svbsp, 3, v2[0], true, NULL, NULL, 0); + } + } +} + +static void Mod_GenerateLightmaps_CreateLights_ComputeSVBSP(dp_model_t *model, lightmaplight_t *lightinfo) +{ + int maxnodes = 1<<14; + svbsp_node_t *nodes; + float origin[3]; + float mins[3]; + float maxs[3]; + svbsp_t svbsp; + VectorSet(mins, lightinfo->origin[0] - lightinfo->radius, lightinfo->origin[1] - lightinfo->radius, lightinfo->origin[2] - lightinfo->radius); + VectorSet(maxs, lightinfo->origin[0] + lightinfo->radius, lightinfo->origin[1] + lightinfo->radius, lightinfo->origin[2] + lightinfo->radius); + VectorCopy(lightinfo->origin, origin); + nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, maxnodes * sizeof(*nodes)); + for (;;) + { + SVBSP_Init(&svbsp, origin, maxnodes, nodes); + Mod_GenerateLightmaps_CreateLights_ComputeSVBSP_InsertSurfaces(model, &svbsp, mins, maxs); + if (svbsp.ranoutofnodes) + { + maxnodes *= 16; + if (maxnodes > 1<<22) + { + Mem_Free(nodes); + return; + } + Mem_Free(nodes); + nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, maxnodes * sizeof(*nodes)); + } + else + break; + } + if (svbsp.numnodes > 0) + { + svbsp.nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, svbsp.numnodes * sizeof(*nodes)); + memcpy(svbsp.nodes, nodes, svbsp.numnodes * sizeof(*nodes)); + lightinfo->svbsp = svbsp; + } + Mem_Free(nodes); +} + +static void Mod_GenerateLightmaps_CreateLights(dp_model_t *model) +{ + int index; + int result; + lightmaplight_t *lightinfo; + float origin[3]; + float radius; + float color[3]; + mod_generatelightmaps_numlights = 0; + for (index = 0;;index++) + { + result = R_Shadow_GetRTLightInfo(index, origin, &radius, color); + if (result < 0) + break; + if (result > 0) + mod_generatelightmaps_numlights++; + } + if (mod_generatelightmaps_numlights > 0) + { + mod_generatelightmaps_lightinfo = (lightmaplight_t *)Mem_Alloc(tempmempool, mod_generatelightmaps_numlights * sizeof(*mod_generatelightmaps_lightinfo)); + lightinfo = mod_generatelightmaps_lightinfo; + for (index = 0;;index++) + { + result = R_Shadow_GetRTLightInfo(index, lightinfo->origin, &lightinfo->radius, lightinfo->color); + if (result < 0) + break; + if (result > 0) + lightinfo++; + } + } + for (index = 0, lightinfo = mod_generatelightmaps_lightinfo;index < mod_generatelightmaps_numlights;index++, lightinfo++) + { + lightinfo->iradius = 1.0f / lightinfo->radius; + lightinfo->radius2 = lightinfo->radius * lightinfo->radius; + // TODO: compute svbsp + Mod_GenerateLightmaps_CreateLights_ComputeSVBSP(model, lightinfo); + } +} + +static void Mod_GenerateLightmaps_DestroyLights(dp_model_t *model) +{ + int i; + if (mod_generatelightmaps_lightinfo) + { + for (i = 0;i < mod_generatelightmaps_numlights;i++) + if (mod_generatelightmaps_lightinfo[i].svbsp.nodes) + Mem_Free(mod_generatelightmaps_lightinfo[i].svbsp.nodes); + Mem_Free(mod_generatelightmaps_lightinfo); + } + mod_generatelightmaps_lightinfo = NULL; + mod_generatelightmaps_numlights = 0; +} + +static qboolean Mod_GenerateLightmaps_SamplePoint_SVBSP(const svbsp_t *svbsp, const float *pos) +{ + const svbsp_node_t *node; + const svbsp_node_t *nodes = svbsp->nodes; + int num = 0; + while (num >= 0) + { + node = nodes + num; + num = node->children[DotProduct(node->plane, pos) < node->plane[3]]; + } + return num == -1; // true if empty, false if solid (shadowed) +} + +static void Mod_GenerateLightmaps_SamplePoint(const float *pos, const float *normal, float *sample, int numoffsets, const float *offsets) +{ + int i; + float relativepoint[3]; + float color[3]; + float offsetpos[3]; + float dist; + float dist2; + float intensity; + int offsetindex; + int hits; + int tests; + const lightmaplight_t *lightinfo; + trace_t trace; + for (i = 0;i < 5*3;i++) + sample[i] = 0.0f; + for (i = 0, lightinfo = mod_generatelightmaps_lightinfo;i < mod_generatelightmaps_numlights;i++, lightinfo++) + { + //R_SampleRTLights(pos, sample, numoffsets, offsets); + VectorSubtract(lightinfo->origin, pos, relativepoint); + // don't accept light from behind a surface, it causes bad shading + if (normal && DotProduct(relativepoint, normal) <= 0) + continue; + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightinfo->radius2) + continue; + dist = sqrt(dist2) * lightinfo->iradius; + intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; + if (intensity <= 0) + continue; + if (cl.worldmodel && cl.worldmodel->TraceLine && numoffsets > 0) + { + hits = 0; + tests = 1; + if (Mod_GenerateLightmaps_SamplePoint_SVBSP(&lightinfo->svbsp, pos)) + hits++; + for (offsetindex = 1;offsetindex < numoffsets;offsetindex++) + { + VectorAdd(pos, offsets + 3*offsetindex, offsetpos); + if (!normal) + { + // for light grid we'd better check visibility of the offset point + cl.worldmodel->TraceLine(cl.worldmodel, NULL, NULL, &trace, pos, offsetpos, SUPERCONTENTS_VISBLOCKERMASK); + if (trace.fraction < 1) + VectorLerp(pos, trace.fraction, offsetpos, offsetpos); + } + tests++; + if (Mod_GenerateLightmaps_SamplePoint_SVBSP(&lightinfo->svbsp, offsetpos)) + hits++; + } + if (!hits) + continue; + // scale intensity according to how many rays succeeded + // we know one test is valid, half of the rest will fail... + //if (normal && tests > 1) + // intensity *= (tests - 1.0f) / tests; + intensity *= (float)hits / tests; + } + // scale down intensity to add to both ambient and diffuse + //intensity *= 0.5f; + VectorNormalize(relativepoint); + VectorScale(lightinfo->color, intensity, color); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity *= VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } +} + +static void Mod_GenerateLightmaps_LightmapSample(const float *pos, const float *normal, unsigned char *lm_bgr, unsigned char *lm_dir) +{ + float sample[5*3]; + float color[3]; + float dir[3]; + float f; + Mod_GenerateLightmaps_SamplePoint(pos, normal, sample, mod_generatelightmaps_numoffsets[0], mod_generatelightmaps_offsets[0][0]); + //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); + VectorCopy(sample + 12, dir); + VectorNormalize(dir); + //VectorAdd(dir, normal, dir); + //VectorNormalize(dir); + f = DotProduct(dir, normal); + f = max(0, f) * 255.0f; + VectorScale(sample, f, color); + //VectorCopy(normal, dir); + VectorSet(dir, (dir[0]+1.0f)*127.5f, (dir[1]+1.0f)*127.5f, (dir[2]+1.0f)*127.5f); + lm_bgr[0] = (unsigned char)bound(0.0f, color[2], 255.0f); + lm_bgr[1] = (unsigned char)bound(0.0f, color[1], 255.0f); + lm_bgr[2] = (unsigned char)bound(0.0f, color[0], 255.0f); + lm_bgr[3] = 255; + lm_dir[0] = (unsigned char)dir[2]; + lm_dir[1] = (unsigned char)dir[1]; + lm_dir[2] = (unsigned char)dir[0]; + lm_dir[3] = 255; +} + +static void Mod_GenerateLightmaps_VertexSample(const float *pos, const float *normal, float *vertex_color) +{ + float sample[5*3]; + Mod_GenerateLightmaps_SamplePoint(pos, normal, sample, mod_generatelightmaps_numoffsets[1], mod_generatelightmaps_offsets[1][0]); + VectorCopy(sample, vertex_color); +} + +static void Mod_GenerateLightmaps_GridSample(const float *pos, q3dlightgrid_t *s) +{ + float sample[5*3]; + float ambient[3]; + float diffuse[3]; + float dir[3]; + Mod_GenerateLightmaps_SamplePoint(pos, NULL, sample, mod_generatelightmaps_numoffsets[2], mod_generatelightmaps_offsets[2][0]); + // calculate the direction we'll use to reduce the sample to a directional light source + VectorCopy(sample + 12, dir); + //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); + VectorNormalize(dir); + // extract the diffuse color along the chosen direction and scale it + diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]) * 127.5f; + diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]) * 127.5f; + diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]) * 127.5f; + // scale the ambient from 0-2 to 0-255 and subtract some of diffuse + VectorScale(sample, 127.5f, ambient); + VectorMA(ambient, -0.333f, diffuse, ambient); + // encode to the grid format + s->ambientrgb[0] = (unsigned char)bound(0.0f, ambient[0], 255.0f); + s->ambientrgb[1] = (unsigned char)bound(0.0f, ambient[1], 255.0f); + s->ambientrgb[2] = (unsigned char)bound(0.0f, ambient[2], 255.0f); + s->diffusergb[0] = (unsigned char)bound(0.0f, diffuse[0], 255.0f); + s->diffusergb[1] = (unsigned char)bound(0.0f, diffuse[1], 255.0f); + s->diffusergb[2] = (unsigned char)bound(0.0f, diffuse[2], 255.0f); + if (dir[2] >= 0.99f) {s->diffusepitch = 0;s->diffuseyaw = 0;} + else if (dir[2] <= -0.99f) {s->diffusepitch = 128;s->diffuseyaw = 0;} + else {s->diffusepitch = (unsigned char)(acos(dir[2]) * (127.5f/M_PI));s->diffuseyaw = (unsigned char)(atan2(dir[1], dir[0]) * (127.5f/M_PI));} +} + +static void Mod_GenerateLightmaps_InitSampleOffsets(dp_model_t *model) +{ + float radius[3]; + float temp[3]; + int i, j; + memset(mod_generatelightmaps_offsets, 0, sizeof(mod_generatelightmaps_offsets)); + mod_generatelightmaps_numoffsets[0] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_lightmapsamples.integer); + mod_generatelightmaps_numoffsets[1] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_vertexsamples.integer); + mod_generatelightmaps_numoffsets[2] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_gridsamples.integer); + radius[0] = mod_generatelightmaps_lightmapradius.value; + radius[1] = mod_generatelightmaps_vertexradius.value; + radius[2] = mod_generatelightmaps_gridradius.value; + for (i = 0;i < 3;i++) + { + for (j = 1;j < mod_generatelightmaps_numoffsets[i];j++) + { + VectorRandom(temp); + VectorScale(temp, radius[i], mod_generatelightmaps_offsets[i][j]); + } + } +} + +static void Mod_GenerateLightmaps_DestroyLightmaps(dp_model_t *model) +{ + msurface_t *surface; + int surfaceindex; + int i; + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + surface->lightmaptexture = NULL; + surface->deluxemaptexture = NULL; + } + if (model->brushq3.data_lightmaps) + { + for (i = 0;i < model->brushq3.num_mergedlightmaps;i++) + if (model->brushq3.data_lightmaps[i]) + R_FreeTexture(model->brushq3.data_lightmaps[i]); + Mem_Free(model->brushq3.data_lightmaps); + model->brushq3.data_lightmaps = NULL; + } + if (model->brushq3.data_deluxemaps) + { + for (i = 0;i < model->brushq3.num_mergedlightmaps;i++) + if (model->brushq3.data_deluxemaps[i]) + R_FreeTexture(model->brushq3.data_deluxemaps[i]); + Mem_Free(model->brushq3.data_deluxemaps); + model->brushq3.data_deluxemaps = NULL; + } +} + +static void Mod_GenerateLightmaps_UnweldTriangles(dp_model_t *model) +{ + msurface_t *surface; + int surfaceindex; + int vertexindex; + int outvertexindex; + int i; + const int *e; + surfmesh_t oldsurfmesh; + size_t size; + unsigned char *data; + oldsurfmesh = model->surfmesh; + model->surfmesh.num_triangles = oldsurfmesh.num_triangles; + model->surfmesh.num_vertices = oldsurfmesh.num_triangles * 3; + size = 0; + size += model->surfmesh.num_vertices * sizeof(float[3]); + size += model->surfmesh.num_vertices * sizeof(float[3]); + size += model->surfmesh.num_vertices * sizeof(float[3]); + size += model->surfmesh.num_vertices * sizeof(float[3]); + size += model->surfmesh.num_vertices * sizeof(float[2]); + size += model->surfmesh.num_vertices * sizeof(float[2]); + size += model->surfmesh.num_vertices * sizeof(float[4]); + data = (unsigned char *)Mem_Alloc(model->mempool, size); + model->surfmesh.data_vertex3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); + model->surfmesh.data_normal3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); + model->surfmesh.data_svector3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); + model->surfmesh.data_tvector3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); + model->surfmesh.data_texcoordtexture2f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[2]); + model->surfmesh.data_texcoordlightmap2f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[2]); + model->surfmesh.data_lightmapcolor4f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[4]); + if (model->surfmesh.num_vertices > 65536) + model->surfmesh.data_element3s = NULL; + + if (model->surfmesh.data_element3i_indexbuffer) + R_Mesh_DestroyMeshBuffer(model->surfmesh.data_element3i_indexbuffer); + model->surfmesh.data_element3i_indexbuffer = NULL; + if (model->surfmesh.data_element3s_indexbuffer) + R_Mesh_DestroyMeshBuffer(model->surfmesh.data_element3s_indexbuffer); + model->surfmesh.data_element3s_indexbuffer = NULL; + if (model->surfmesh.vbo_vertexbuffer) + R_Mesh_DestroyMeshBuffer(model->surfmesh.vbo_vertexbuffer); + model->surfmesh.vbo_vertexbuffer = 0; + + // convert all triangles to unique vertex data + outvertexindex = 0; + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + surface->num_firstvertex = outvertexindex; + surface->num_vertices = surface->num_triangles*3; + e = oldsurfmesh.data_element3i + surface->num_firsttriangle*3; + for (i = 0;i < surface->num_triangles*3;i++) + { + vertexindex = e[i]; + model->surfmesh.data_vertex3f[outvertexindex*3+0] = oldsurfmesh.data_vertex3f[vertexindex*3+0]; + model->surfmesh.data_vertex3f[outvertexindex*3+1] = oldsurfmesh.data_vertex3f[vertexindex*3+1]; + model->surfmesh.data_vertex3f[outvertexindex*3+2] = oldsurfmesh.data_vertex3f[vertexindex*3+2]; + model->surfmesh.data_normal3f[outvertexindex*3+0] = oldsurfmesh.data_normal3f[vertexindex*3+0]; + model->surfmesh.data_normal3f[outvertexindex*3+1] = oldsurfmesh.data_normal3f[vertexindex*3+1]; + model->surfmesh.data_normal3f[outvertexindex*3+2] = oldsurfmesh.data_normal3f[vertexindex*3+2]; + model->surfmesh.data_svector3f[outvertexindex*3+0] = oldsurfmesh.data_svector3f[vertexindex*3+0]; + model->surfmesh.data_svector3f[outvertexindex*3+1] = oldsurfmesh.data_svector3f[vertexindex*3+1]; + model->surfmesh.data_svector3f[outvertexindex*3+2] = oldsurfmesh.data_svector3f[vertexindex*3+2]; + model->surfmesh.data_tvector3f[outvertexindex*3+0] = oldsurfmesh.data_tvector3f[vertexindex*3+0]; + model->surfmesh.data_tvector3f[outvertexindex*3+1] = oldsurfmesh.data_tvector3f[vertexindex*3+1]; + model->surfmesh.data_tvector3f[outvertexindex*3+2] = oldsurfmesh.data_tvector3f[vertexindex*3+2]; + model->surfmesh.data_texcoordtexture2f[outvertexindex*2+0] = oldsurfmesh.data_texcoordtexture2f[vertexindex*2+0]; + model->surfmesh.data_texcoordtexture2f[outvertexindex*2+1] = oldsurfmesh.data_texcoordtexture2f[vertexindex*2+1]; + if (oldsurfmesh.data_texcoordlightmap2f) + { + model->surfmesh.data_texcoordlightmap2f[outvertexindex*2+0] = oldsurfmesh.data_texcoordlightmap2f[vertexindex*2+0]; + model->surfmesh.data_texcoordlightmap2f[outvertexindex*2+1] = oldsurfmesh.data_texcoordlightmap2f[vertexindex*2+1]; + } + if (oldsurfmesh.data_lightmapcolor4f) + { + model->surfmesh.data_lightmapcolor4f[outvertexindex*4+0] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+0]; + model->surfmesh.data_lightmapcolor4f[outvertexindex*4+1] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+1]; + model->surfmesh.data_lightmapcolor4f[outvertexindex*4+2] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+2]; + model->surfmesh.data_lightmapcolor4f[outvertexindex*4+3] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+3]; + } + else + Vector4Set(model->surfmesh.data_lightmapcolor4f + 4*outvertexindex, 1, 1, 1, 1); + model->surfmesh.data_element3i[surface->num_firsttriangle*3+i] = outvertexindex; + outvertexindex++; + } + } + if (model->surfmesh.data_element3s) + for (i = 0;i < model->surfmesh.num_triangles*3;i++) + model->surfmesh.data_element3s[i] = model->surfmesh.data_element3i[i]; + + // find and update all submodels to use this new surfmesh data + for (i = 0;i < model->brush.numsubmodels;i++) + model->brush.submodels[i]->surfmesh = model->surfmesh; +} + +static void Mod_GenerateLightmaps_CreateTriangleInformation(dp_model_t *model) +{ + msurface_t *surface; + int surfaceindex; + int i; + int axis; + float normal[3]; + const int *e; + lightmaptriangle_t *triangle; + // generate lightmap triangle structs + mod_generatelightmaps_lightmaptriangles = (lightmaptriangle_t *)Mem_Alloc(model->mempool, model->surfmesh.num_triangles * sizeof(lightmaptriangle_t)); + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; + for (i = 0;i < surface->num_triangles;i++) + { + triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; + triangle->triangleindex = surface->num_firsttriangle+i; + triangle->surfaceindex = surfaceindex; + VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+0], triangle->vertex[0]); + VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+1], triangle->vertex[1]); + VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+2], triangle->vertex[2]); + // calculate bounds of triangle + triangle->mins[0] = min(triangle->vertex[0][0], min(triangle->vertex[1][0], triangle->vertex[2][0])); + triangle->mins[1] = min(triangle->vertex[0][1], min(triangle->vertex[1][1], triangle->vertex[2][1])); + triangle->mins[2] = min(triangle->vertex[0][2], min(triangle->vertex[1][2], triangle->vertex[2][2])); + triangle->maxs[0] = max(triangle->vertex[0][0], max(triangle->vertex[1][0], triangle->vertex[2][0])); + triangle->maxs[1] = max(triangle->vertex[0][1], max(triangle->vertex[1][1], triangle->vertex[2][1])); + triangle->maxs[2] = max(triangle->vertex[0][2], max(triangle->vertex[1][2], triangle->vertex[2][2])); + // pick an axial projection based on the triangle normal + TriangleNormal(triangle->vertex[0], triangle->vertex[1], triangle->vertex[2], normal); + axis = 0; + if (fabs(normal[1]) > fabs(normal[axis])) + axis = 1; + if (fabs(normal[2]) > fabs(normal[axis])) + axis = 2; + triangle->axis = axis; + } + } +} + +static void Mod_GenerateLightmaps_DestroyTriangleInformation(dp_model_t *model) +{ + if (mod_generatelightmaps_lightmaptriangles) + Mem_Free(mod_generatelightmaps_lightmaptriangles); + mod_generatelightmaps_lightmaptriangles = NULL; +} + +float lmaxis[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + +static void Mod_GenerateLightmaps_CreateLightmaps(dp_model_t *model) +{ + msurface_t *surface; + int surfaceindex; + int lightmapindex; + int lightmapnumber; + int i; + int j; + int k; + int x; + int y; + int axis; + int axis1; + int axis2; + int retry; + int pixeloffset; + float trianglenormal[3]; + float samplecenter[3]; + float samplenormal[3]; + float temp[3]; + float lmiscale[2]; + float slopex; + float slopey; + float slopebase; + float lmscalepixels; + float lmmins; + float lmmaxs; + float lm_basescalepixels; + int lm_borderpixels; + int lm_texturesize; + //int lm_maxpixels; + const int *e; + lightmaptriangle_t *triangle; + unsigned char *lightmappixels; + unsigned char *deluxemappixels; + mod_alloclightmap_state_t lmstate; + char vabuf[1024]; + + // generate lightmap projection information for all triangles + if (model->texturepool == NULL) + model->texturepool = R_AllocTexturePool(); + lm_basescalepixels = 1.0f / max(0.0001f, mod_generatelightmaps_unitspersample.value); + lm_borderpixels = mod_generatelightmaps_borderpixels.integer; + lm_texturesize = bound(lm_borderpixels*2+1, 64, (int)vid.maxtexturesize_2d); + //lm_maxpixels = lm_texturesize-(lm_borderpixels*2+1); + Mod_AllocLightmap_Init(&lmstate, lm_texturesize, lm_texturesize); + lightmapnumber = 0; + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; + lmscalepixels = lm_basescalepixels; + for (retry = 0;retry < 30;retry++) + { + // after a couple failed attempts, degrade quality to make it fit + if (retry > 1) + lmscalepixels *= 0.5f; + for (i = 0;i < surface->num_triangles;i++) + { + triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; + triangle->lightmapindex = lightmapnumber; + // calculate lightmap bounds in 3D pixel coordinates, limit size, + // pick two planar axes for projection + // lightmap coordinates here are in pixels + // lightmap projections are snapped to pixel grid explicitly, such + // that two neighboring triangles sharing an edge and projection + // axis will have identical sampl espacing along their shared edge + k = 0; + for (j = 0;j < 3;j++) + { + if (j == triangle->axis) + continue; + lmmins = floor(triangle->mins[j]*lmscalepixels)-lm_borderpixels; + lmmaxs = floor(triangle->maxs[j]*lmscalepixels)+lm_borderpixels; + triangle->lmsize[k] = (int)(lmmaxs-lmmins); + triangle->lmbase[k] = lmmins/lmscalepixels; + triangle->lmscale[k] = lmscalepixels; + k++; + } + if (!Mod_AllocLightmap_Block(&lmstate, triangle->lmsize[0], triangle->lmsize[1], &triangle->lmoffset[0], &triangle->lmoffset[1])) + break; + } + // if all fit in this texture, we're done with this surface + if (i == surface->num_triangles) + break; + // if we haven't maxed out the lightmap size yet, we retry the + // entire surface batch... + if (lm_texturesize * 2 <= min(mod_generatelightmaps_texturesize.integer, (int)vid.maxtexturesize_2d)) + { + lm_texturesize *= 2; + surfaceindex = -1; + lightmapnumber = 0; + Mod_AllocLightmap_Free(&lmstate); + Mod_AllocLightmap_Init(&lmstate, lm_texturesize, lm_texturesize); + break; + } + // if we have maxed out the lightmap size, and this triangle does + // not fit in the same texture as the rest of the surface, we have + // to retry the entire surface in a new texture (can only use one) + // with multiple retries, the lightmap quality degrades until it + // fits (or gives up) + if (surfaceindex > 0) + lightmapnumber++; + Mod_AllocLightmap_Reset(&lmstate); + } + } + lightmapnumber++; + Mod_AllocLightmap_Free(&lmstate); + + // now put triangles together into lightmap textures, and do not allow + // triangles of a surface to go into different textures (as that would + // require rewriting the surface list) + model->brushq3.deluxemapping_modelspace = true; + model->brushq3.deluxemapping = true; + model->brushq3.num_mergedlightmaps = lightmapnumber; + model->brushq3.data_lightmaps = (rtexture_t **)Mem_Alloc(model->mempool, model->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); + model->brushq3.data_deluxemaps = (rtexture_t **)Mem_Alloc(model->mempool, model->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); + lightmappixels = (unsigned char *)Mem_Alloc(tempmempool, model->brushq3.num_mergedlightmaps * lm_texturesize * lm_texturesize * 4); + deluxemappixels = (unsigned char *)Mem_Alloc(tempmempool, model->brushq3.num_mergedlightmaps * lm_texturesize * lm_texturesize * 4); + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; + for (i = 0;i < surface->num_triangles;i++) + { + triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; + TriangleNormal(triangle->vertex[0], triangle->vertex[1], triangle->vertex[2], trianglenormal); + VectorNormalize(trianglenormal); + VectorCopy(trianglenormal, samplenormal); // FIXME: this is supposed to be interpolated per pixel from vertices + axis = triangle->axis; + axis1 = axis == 0 ? 1 : 0; + axis2 = axis == 2 ? 1 : 2; + lmiscale[0] = 1.0f / triangle->lmscale[0]; + lmiscale[1] = 1.0f / triangle->lmscale[1]; + if (trianglenormal[axis] < 0) + VectorNegate(trianglenormal, trianglenormal); + CrossProduct(lmaxis[axis2], trianglenormal, temp);slopex = temp[axis] / temp[axis1]; + CrossProduct(lmaxis[axis1], trianglenormal, temp);slopey = temp[axis] / temp[axis2]; + slopebase = triangle->vertex[0][axis] - triangle->vertex[0][axis1]*slopex - triangle->vertex[0][axis2]*slopey; + for (j = 0;j < 3;j++) + { + float *t2f = model->surfmesh.data_texcoordlightmap2f + e[i*3+j]*2; + t2f[0] = ((triangle->vertex[j][axis1] - triangle->lmbase[0]) * triangle->lmscale[0] + triangle->lmoffset[0]) / lm_texturesize; + t2f[1] = ((triangle->vertex[j][axis2] - triangle->lmbase[1]) * triangle->lmscale[1] + triangle->lmoffset[1]) / lm_texturesize; +#if 0 + samplecenter[axis1] = (t2f[0]*lm_texturesize-triangle->lmoffset[0])*lmiscale[0] + triangle->lmbase[0]; + samplecenter[axis2] = (t2f[1]*lm_texturesize-triangle->lmoffset[1])*lmiscale[1] + triangle->lmbase[1]; + samplecenter[axis] = samplecenter[axis1]*slopex + samplecenter[axis2]*slopey + slopebase; + Con_Printf("%f:%f %f:%f %f:%f = %f %f\n", triangle->vertex[j][axis1], samplecenter[axis1], triangle->vertex[j][axis2], samplecenter[axis2], triangle->vertex[j][axis], samplecenter[axis], t2f[0], t2f[1]); +#endif + } + +#if 0 + switch (axis) + { + default: + case 0: + forward[0] = 0; + forward[1] = 1.0f / triangle->lmscale[0]; + forward[2] = 0; + left[0] = 0; + left[1] = 0; + left[2] = 1.0f / triangle->lmscale[1]; + up[0] = 1.0f; + up[1] = 0; + up[2] = 0; + origin[0] = 0; + origin[1] = triangle->lmbase[0]; + origin[2] = triangle->lmbase[1]; + break; + case 1: + forward[0] = 1.0f / triangle->lmscale[0]; + forward[1] = 0; + forward[2] = 0; + left[0] = 0; + left[1] = 0; + left[2] = 1.0f / triangle->lmscale[1]; + up[0] = 0; + up[1] = 1.0f; + up[2] = 0; + origin[0] = triangle->lmbase[0]; + origin[1] = 0; + origin[2] = triangle->lmbase[1]; + break; + case 2: + forward[0] = 1.0f / triangle->lmscale[0]; + forward[1] = 0; + forward[2] = 0; + left[0] = 0; + left[1] = 1.0f / triangle->lmscale[1]; + left[2] = 0; + up[0] = 0; + up[1] = 0; + up[2] = 1.0f; + origin[0] = triangle->lmbase[0]; + origin[1] = triangle->lmbase[1]; + origin[2] = 0; + break; + } + Matrix4x4_FromVectors(&backmatrix, forward, left, up, origin); +#endif +#define LM_DIST_EPSILON (1.0f / 32.0f) + for (y = 0;y < triangle->lmsize[1];y++) + { + pixeloffset = ((triangle->lightmapindex * lm_texturesize + y + triangle->lmoffset[1]) * lm_texturesize + triangle->lmoffset[0]) * 4; + for (x = 0;x < triangle->lmsize[0];x++, pixeloffset += 4) + { + samplecenter[axis1] = (x+0.5f)*lmiscale[0] + triangle->lmbase[0]; + samplecenter[axis2] = (y+0.5f)*lmiscale[1] + triangle->lmbase[1]; + samplecenter[axis] = samplecenter[axis1]*slopex + samplecenter[axis2]*slopey + slopebase; + VectorMA(samplecenter, 0.125f, samplenormal, samplecenter); + Mod_GenerateLightmaps_LightmapSample(samplecenter, samplenormal, lightmappixels + pixeloffset, deluxemappixels + pixeloffset); + } + } + } + } + + for (lightmapindex = 0;lightmapindex < model->brushq3.num_mergedlightmaps;lightmapindex++) + { + model->brushq3.data_lightmaps[lightmapindex] = R_LoadTexture2D(model->texturepool, va(vabuf, sizeof(vabuf), "lightmap%i", lightmapindex), lm_texturesize, lm_texturesize, lightmappixels + lightmapindex * lm_texturesize * lm_texturesize * 4, TEXTYPE_BGRA, TEXF_FORCELINEAR, -1, NULL); + model->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(model->texturepool, va(vabuf, sizeof(vabuf), "deluxemap%i", lightmapindex), lm_texturesize, lm_texturesize, deluxemappixels + lightmapindex * lm_texturesize * lm_texturesize * 4, TEXTYPE_BGRA, TEXF_FORCELINEAR, -1, NULL); + } + + if (lightmappixels) + Mem_Free(lightmappixels); + if (deluxemappixels) + Mem_Free(deluxemappixels); + + for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) + { + surface = model->data_surfaces + surfaceindex; + if (!surface->num_triangles) + continue; + lightmapindex = mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle].lightmapindex; + surface->lightmaptexture = model->brushq3.data_lightmaps[lightmapindex]; + surface->deluxemaptexture = model->brushq3.data_deluxemaps[lightmapindex]; + surface->lightmapinfo = NULL; + } + + model->brush.LightPoint = Mod_GenerateLightmaps_LightPoint; + model->brushq1.lightdata = NULL; + model->brushq1.lightmapupdateflags = NULL; + model->brushq1.firstrender = false; + model->brushq1.num_lightstyles = 0; + model->brushq1.data_lightstyleinfo = NULL; + for (i = 0;i < model->brush.numsubmodels;i++) + { + model->brush.submodels[i]->brushq1.lightmapupdateflags = NULL; + model->brush.submodels[i]->brushq1.firstrender = false; + model->brush.submodels[i]->brushq1.num_lightstyles = 0; + model->brush.submodels[i]->brushq1.data_lightstyleinfo = NULL; + } +} + +static void Mod_GenerateLightmaps_UpdateVertexColors(dp_model_t *model) +{ + int i; + for (i = 0;i < model->surfmesh.num_vertices;i++) + Mod_GenerateLightmaps_VertexSample(model->surfmesh.data_vertex3f + 3*i, model->surfmesh.data_normal3f + 3*i, model->surfmesh.data_lightmapcolor4f + 4*i); +} + +static void Mod_GenerateLightmaps_UpdateLightGrid(dp_model_t *model) +{ + int x; + int y; + int z; + int index = 0; + float pos[3]; + for (z = 0;z < model->brushq3.num_lightgrid_isize[2];z++) + { + pos[2] = (model->brushq3.num_lightgrid_imins[2] + z + 0.5f) * model->brushq3.num_lightgrid_cellsize[2]; + for (y = 0;y < model->brushq3.num_lightgrid_isize[1];y++) + { + pos[1] = (model->brushq3.num_lightgrid_imins[1] + y + 0.5f) * model->brushq3.num_lightgrid_cellsize[1]; + for (x = 0;x < model->brushq3.num_lightgrid_isize[0];x++, index++) + { + pos[0] = (model->brushq3.num_lightgrid_imins[0] + x + 0.5f) * model->brushq3.num_lightgrid_cellsize[0]; + Mod_GenerateLightmaps_GridSample(pos, model->brushq3.data_lightgrid + index); + } + } + } +} + +extern cvar_t mod_q3bsp_nolightmaps; +static void Mod_GenerateLightmaps(dp_model_t *model) +{ + //lightmaptriangle_t *lightmaptriangles = Mem_Alloc(model->mempool, model->surfmesh.num_triangles * sizeof(lightmaptriangle_t)); + dp_model_t *oldloadmodel = loadmodel; + loadmodel = model; + + Mod_GenerateLightmaps_InitSampleOffsets(model); + Mod_GenerateLightmaps_DestroyLightmaps(model); + Mod_GenerateLightmaps_UnweldTriangles(model); + Mod_GenerateLightmaps_CreateTriangleInformation(model); + Mod_GenerateLightmaps_CreateLights(model); + if(!mod_q3bsp_nolightmaps.integer) + Mod_GenerateLightmaps_CreateLightmaps(model); + Mod_GenerateLightmaps_UpdateVertexColors(model); + Mod_GenerateLightmaps_UpdateLightGrid(model); + Mod_GenerateLightmaps_DestroyLights(model); + Mod_GenerateLightmaps_DestroyTriangleInformation(model); + + loadmodel = oldloadmodel; +} + +static void Mod_GenerateLightmaps_f(void) +{ + if (Cmd_Argc() != 1) + { + Con_Printf("usage: mod_generatelightmaps\n"); + return; + } + if (!cl.worldmodel) + { + Con_Printf("no worldmodel loaded\n"); + return; + } + Mod_GenerateLightmaps(cl.worldmodel); +} diff --git a/app/jni/model_shared.h b/app/jni/model_shared.h new file mode 100644 index 0000000..dd5d5e0 --- /dev/null +++ b/app/jni/model_shared.h @@ -0,0 +1,1241 @@ +/* +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. + +*/ + +#ifndef MODEL_SHARED_H +#define MODEL_SHARED_H + +typedef enum synctype_e {ST_SYNC=0, ST_RAND } synctype_t; + +/* + +d*_t structures are on-disk representations +m*_t structures are in-memory + +*/ + +typedef enum modtype_e {mod_invalid, mod_brushq1, mod_sprite, mod_alias, mod_brushq2, mod_brushq3, mod_obj, mod_null} modtype_t; + +typedef struct animscene_s +{ + char name[32]; // for viewthing support + int firstframe; + int framecount; + int loop; // true or false + float framerate; +} +animscene_t; + +typedef struct skinframe_s +{ + rtexture_t *stain; // inverse modulate with background (used for decals and such) + rtexture_t *merged; // original texture without glow + rtexture_t *base; // original texture without pants/shirt/glow + rtexture_t *pants; // pants only (in greyscale) + rtexture_t *shirt; // shirt only (in greyscale) + rtexture_t *nmap; // normalmap (bumpmap for dot3) + rtexture_t *gloss; // glossmap (for dot3) + rtexture_t *glow; // glow only (fullbrights) + rtexture_t *fog; // alpha of the base texture (if not opaque) + rtexture_t *reflect; // colored mask for cubemap reflections + // accounting data for hash searches: + // the compare variables are used to identify internal skins from certain + // model formats + // (so that two q1bsp maps with the same texture name for different + // textures do not have any conflicts) + struct skinframe_s *next; // next on hash chain + char basename[MAX_QPATH]; // name of this + int textureflags; // texture flags to use + int comparewidth; + int compareheight; + int comparecrc; + // mark and sweep garbage collection, this value is updated to a new value + // on each level change for the used skinframes, if some are not used they + // are freed + int loadsequence; + // indicates whether this texture has transparent pixels + qboolean hasalpha; + // average texture color, if applicable + float avgcolor[4]; + // for mdl skins, we actually only upload on first use (many are never used, and they are almost never used in both base+pants+shirt and merged modes) + unsigned char *qpixels; + int qwidth; + int qheight; + qboolean qhascolormapping; + qboolean qgeneratebase; + qboolean qgeneratemerged; + qboolean qgeneratenmap; + qboolean qgenerateglow; +} +skinframe_t; + +struct md3vertex_s; +struct trivertx_s; +typedef struct texvecvertex_s +{ + signed char svec[3]; + signed char tvec[3]; +} +texvecvertex_t; + +typedef struct blendweights_s +{ + unsigned char index[4]; + unsigned char influence[4]; +} +blendweights_t; + +typedef struct r_vertexgeneric_s +{ + // 36 bytes + float vertex3f[3]; + float color4f[4]; + float texcoord2f[2]; +} +r_vertexgeneric_t; + +typedef struct r_vertexmesh_s +{ + // 88 bytes + float vertex3f[3]; + float color4f[4]; + float texcoordtexture2f[2]; + float texcoordlightmap2f[2]; + float svector3f[3]; + float tvector3f[3]; + float normal3f[3]; + unsigned char skeletalindex4ub[4]; + unsigned char skeletalweight4ub[4]; +} +r_vertexmesh_t; + +typedef struct r_meshbuffer_s +{ + int bufferobject; // OpenGL + void *devicebuffer; // Direct3D + size_t size; + qboolean isindexbuffer; + qboolean isuniformbuffer; + qboolean isdynamic; + qboolean isindex16; + char name[MAX_QPATH]; +} +r_meshbuffer_t; + +// used for mesh lists in q1bsp/q3bsp map models +// (the surfaces reference portions of these meshes) +typedef struct surfmesh_s +{ + // triangle data in system memory + int num_triangles; // number of triangles in the mesh + int *data_element3i; // int[tris*3] triangles of the mesh, 3 indices into vertex arrays for each + r_meshbuffer_t *data_element3i_indexbuffer; + size_t data_element3i_bufferoffset; + unsigned short *data_element3s; // unsigned short[tris*3] triangles of the mesh in unsigned short format (NULL if num_vertices > 65536) + r_meshbuffer_t *data_element3s_indexbuffer; + size_t data_element3s_bufferoffset; + int *data_neighbor3i; // int[tris*3] neighboring triangle on each edge (-1 if none) + // vertex data in system memory + int num_vertices; // number of vertices in the mesh + float *data_vertex3f; // float[verts*3] vertex locations + float *data_svector3f; // float[verts*3] direction of 'S' (right) texture axis for each vertex + float *data_tvector3f; // float[verts*3] direction of 'T' (down) texture axis for each vertex + float *data_normal3f; // float[verts*3] direction of 'R' (out) texture axis for each vertex + float *data_texcoordtexture2f; // float[verts*2] texcoords for surface texture + float *data_texcoordlightmap2f; // float[verts*2] texcoords for lightmap texture + float *data_lightmapcolor4f; + unsigned char *data_skeletalindex4ub; + unsigned char *data_skeletalweight4ub; + int *data_lightmapoffsets; // index into surface's lightmap samples for vertex lighting + r_vertexmesh_t *data_vertexmesh; // interleaved arrays for D3D + // vertex buffer object (stores geometry in video memory) + r_meshbuffer_t *vbo_vertexbuffer; + int vbooffset_vertex3f; + int vbooffset_svector3f; + int vbooffset_tvector3f; + int vbooffset_normal3f; + int vbooffset_texcoordtexture2f; + int vbooffset_texcoordlightmap2f; + int vbooffset_lightmapcolor4f; + int vbooffset_skeletalindex4ub; + int vbooffset_skeletalweight4ub; + int vbooffset_vertexmesh; + // morph blending, these are zero if model is skeletal or static + int num_morphframes; + struct md3vertex_s *data_morphmd3vertex; + struct trivertx_s *data_morphmdlvertex; + struct texvecvertex_s *data_morphtexvecvertex; + float *data_morphmd2framesize6f; + float num_morphmdlframescale[3]; + float num_morphmdlframetranslate[3]; + // skeletal blending, these are NULL if model is morph or static + struct blendweights_s *data_blendweights; + int num_blends; + unsigned short *blends; + // set if there is some kind of animation on this model + qboolean isanimated; + + // vertex and index buffers for rendering + r_meshbuffer_t *vertexmesh_vertexbuffer; +} +surfmesh_t; + +#define SHADOWMESHVERTEXHASH 1024 +typedef struct shadowmeshvertexhash_s +{ + struct shadowmeshvertexhash_s *next; +} +shadowmeshvertexhash_t; + +typedef struct shadowmesh_s +{ + // next mesh in chain + struct shadowmesh_s *next; + // used for light mesh (NULL on shadow mesh) + rtexture_t *map_diffuse; + rtexture_t *map_specular; + rtexture_t *map_normal; + // buffer sizes + int numverts, maxverts; + int numtriangles, maxtriangles; + // used always + float *vertex3f; + // used for light mesh (NULL on shadow mesh) + float *svector3f; + float *tvector3f; + float *normal3f; + float *texcoord2f; + // used always + int *element3i; + r_meshbuffer_t *element3i_indexbuffer; + int element3i_bufferoffset; + unsigned short *element3s; + r_meshbuffer_t *element3s_indexbuffer; + int element3s_bufferoffset; + // vertex/index buffers for rendering + // (created by Mod_ShadowMesh_Finish if possible) + r_vertexmesh_t *vertexmesh; // usually NULL + // used for shadow mapping cubemap side partitioning + int sideoffsets[6], sidetotals[6]; + // used for shadow mesh (NULL on light mesh) + int *neighbor3i; + // these are NULL after Mod_ShadowMesh_Finish is performed, only used + // while building meshes + shadowmeshvertexhash_t **vertexhashtable, *vertexhashentries; + r_meshbuffer_t *vbo_vertexbuffer; + int vbooffset_vertex3f; + int vbooffset_svector3f; + int vbooffset_tvector3f; + int vbooffset_normal3f; + int vbooffset_texcoord2f; + int vbooffset_vertexmesh; +} +shadowmesh_t; + +// various flags from shaders, used for special effects not otherwise classified +// TODO: support these features more directly +#define Q3TEXTUREFLAG_TWOSIDED 1 +#define Q3TEXTUREFLAG_NOPICMIP 16 +#define Q3TEXTUREFLAG_POLYGONOFFSET 32 +#define Q3TEXTUREFLAG_REFRACTION 256 +#define Q3TEXTUREFLAG_REFLECTION 512 +#define Q3TEXTUREFLAG_WATERSHADER 1024 +#define Q3TEXTUREFLAG_CAMERA 2048 +#define Q3TEXTUREFLAG_TRANSPARENTSORT 4096 + +#define Q3PATHLENGTH 64 +#define TEXTURE_MAXFRAMES 64 +#define Q3WAVEPARMS 4 +#define Q3DEFORM_MAXPARMS 3 +#define Q3SHADER_MAXLAYERS 2 // FIXME support more than that (currently only two are used, so why keep more in RAM?) +#define Q3RGBGEN_MAXPARMS 3 +#define Q3ALPHAGEN_MAXPARMS 1 +#define Q3TCGEN_MAXPARMS 6 +#define Q3TCMOD_MAXPARMS 6 +#define Q3MAXTCMODS 8 +#define Q3MAXDEFORMS 4 + +typedef enum q3wavefunc_e +{ + Q3WAVEFUNC_NONE, + Q3WAVEFUNC_INVERSESAWTOOTH, + Q3WAVEFUNC_NOISE, + Q3WAVEFUNC_SAWTOOTH, + Q3WAVEFUNC_SIN, + Q3WAVEFUNC_SQUARE, + Q3WAVEFUNC_TRIANGLE, + Q3WAVEFUNC_COUNT +} +q3wavefunc_e; +typedef int q3wavefunc_t; +#define Q3WAVEFUNC_USER_COUNT 4 +#define Q3WAVEFUNC_USER_SHIFT 8 // use 8 bits for wave func type + +typedef enum q3deform_e +{ + Q3DEFORM_NONE, + Q3DEFORM_PROJECTIONSHADOW, + Q3DEFORM_AUTOSPRITE, + Q3DEFORM_AUTOSPRITE2, + Q3DEFORM_TEXT0, + Q3DEFORM_TEXT1, + Q3DEFORM_TEXT2, + Q3DEFORM_TEXT3, + Q3DEFORM_TEXT4, + Q3DEFORM_TEXT5, + Q3DEFORM_TEXT6, + Q3DEFORM_TEXT7, + Q3DEFORM_BULGE, + Q3DEFORM_WAVE, + Q3DEFORM_NORMAL, + Q3DEFORM_MOVE, + Q3DEFORM_COUNT +} +q3deform_t; + +typedef enum q3rgbgen_e +{ + Q3RGBGEN_IDENTITY, + Q3RGBGEN_CONST, + Q3RGBGEN_ENTITY, + Q3RGBGEN_EXACTVERTEX, + Q3RGBGEN_IDENTITYLIGHTING, + Q3RGBGEN_LIGHTINGDIFFUSE, + Q3RGBGEN_ONEMINUSENTITY, + Q3RGBGEN_ONEMINUSVERTEX, + Q3RGBGEN_VERTEX, + Q3RGBGEN_WAVE, + Q3RGBGEN_COUNT +} +q3rgbgen_t; + +typedef enum q3alphagen_e +{ + Q3ALPHAGEN_IDENTITY, + Q3ALPHAGEN_CONST, + Q3ALPHAGEN_ENTITY, + Q3ALPHAGEN_LIGHTINGSPECULAR, + Q3ALPHAGEN_ONEMINUSENTITY, + Q3ALPHAGEN_ONEMINUSVERTEX, + Q3ALPHAGEN_PORTAL, + Q3ALPHAGEN_VERTEX, + Q3ALPHAGEN_WAVE, + Q3ALPHAGEN_COUNT +} +q3alphagen_t; + +typedef enum q3tcgen_e +{ + Q3TCGEN_NONE, + Q3TCGEN_TEXTURE, // very common + Q3TCGEN_ENVIRONMENT, // common + Q3TCGEN_LIGHTMAP, + Q3TCGEN_VECTOR, + Q3TCGEN_COUNT +} +q3tcgen_t; + +typedef enum q3tcmod_e +{ + Q3TCMOD_NONE, + Q3TCMOD_ENTITYTRANSLATE, + Q3TCMOD_ROTATE, + Q3TCMOD_SCALE, + Q3TCMOD_SCROLL, + Q3TCMOD_STRETCH, + Q3TCMOD_TRANSFORM, + Q3TCMOD_TURBULENT, + Q3TCMOD_PAGE, + Q3TCMOD_COUNT +} +q3tcmod_t; + +typedef struct q3shaderinfo_layer_rgbgen_s +{ + q3rgbgen_t rgbgen; + float parms[Q3RGBGEN_MAXPARMS]; + q3wavefunc_t wavefunc; + float waveparms[Q3WAVEPARMS]; +} +q3shaderinfo_layer_rgbgen_t; + +typedef struct q3shaderinfo_layer_alphagen_s +{ + q3alphagen_t alphagen; + float parms[Q3ALPHAGEN_MAXPARMS]; + q3wavefunc_t wavefunc; + float waveparms[Q3WAVEPARMS]; +} +q3shaderinfo_layer_alphagen_t; + +typedef struct q3shaderinfo_layer_tcgen_s +{ + q3tcgen_t tcgen; + float parms[Q3TCGEN_MAXPARMS]; +} +q3shaderinfo_layer_tcgen_t; + +typedef struct q3shaderinfo_layer_tcmod_s +{ + q3tcmod_t tcmod; + float parms[Q3TCMOD_MAXPARMS]; + q3wavefunc_t wavefunc; + float waveparms[Q3WAVEPARMS]; +} +q3shaderinfo_layer_tcmod_t; + +typedef struct q3shaderinfo_layer_s +{ + int alphatest; + int clampmap; + float framerate; + int numframes; + int texflags; + char** texturename; + int blendfunc[2]; + q3shaderinfo_layer_rgbgen_t rgbgen; + q3shaderinfo_layer_alphagen_t alphagen; + q3shaderinfo_layer_tcgen_t tcgen; + q3shaderinfo_layer_tcmod_t tcmods[Q3MAXTCMODS]; +} +q3shaderinfo_layer_t; + +typedef struct q3shaderinfo_deform_s +{ + q3deform_t deform; + float parms[Q3DEFORM_MAXPARMS]; + q3wavefunc_t wavefunc; + float waveparms[Q3WAVEPARMS]; +} +q3shaderinfo_deform_t; + +typedef enum dpoffsetmapping_technique_s +{ + OFFSETMAPPING_OFF, // none + OFFSETMAPPING_DEFAULT, // cvar-set + OFFSETMAPPING_LINEAR, // linear + OFFSETMAPPING_RELIEF // relief +}dpoffsetmapping_technique_t; + +typedef enum dptransparentsort_category_e +{ + TRANSPARENTSORT_SKY, + TRANSPARENTSORT_DISTANCE, + TRANSPARENTSORT_HUD, +}dptransparentsortcategory_t; + +typedef struct q3shaderinfo_s +{ + char name[Q3PATHLENGTH]; +#define Q3SHADERINFO_COMPARE_START surfaceparms + int surfaceparms; + int surfaceflags; + int textureflags; + int numlayers; + qboolean lighting; + qboolean vertexalpha; + qboolean textureblendalpha; + int primarylayer, backgroundlayer; + q3shaderinfo_layer_t layers[Q3SHADER_MAXLAYERS]; + char skyboxname[Q3PATHLENGTH]; + q3shaderinfo_deform_t deforms[Q3MAXDEFORMS]; + + // dp-specific additions: + + // shadow control + qboolean dpnortlight; + qboolean dpshadow; + qboolean dpnoshadow; + + // add collisions to all triangles of the surface + qboolean dpmeshcollisions; + + // kill shader based on cvar checks + qboolean dpshaderkill; + + // fake reflection + char dpreflectcube[Q3PATHLENGTH]; + + // reflection + float reflectmin; // when refraction is used, minimum amount of reflection (when looking straight down) + float reflectmax; // when refraction is used, maximum amount of reflection (when looking parallel to water) + float refractfactor; // amount of refraction distort (1.0 = like the cvar specifies) + vec4_t refractcolor4f; // color tint of refraction (including alpha factor) + float reflectfactor; // amount of reflection distort (1.0 = like the cvar specifies) + vec4_t reflectcolor4f; // color tint of reflection (including alpha factor) + float r_water_wateralpha; // additional wateralpha to apply when r_water is active + float r_water_waterscroll[2]; // water normalmapscrollblend - scale and speed + + // offsetmapping + dpoffsetmapping_technique_t offsetmapping; + float offsetscale; + float offsetbias; // 0 is normal, 1 leads to alpha 0 being neutral and alpha 1 pushing "out" + + // polygonoffset (only used if Q3TEXTUREFLAG_POLYGONOFFSET) + float biaspolygonoffset, biaspolygonfactor; + + // transparent sort category + dptransparentsortcategory_t transparentsort; + + // gloss + float specularscalemod; + float specularpowermod; + + // rtlighting ambient addition + float rtlightambient; +#define Q3SHADERINFO_COMPARE_END rtlightambient +} +q3shaderinfo_t; + +typedef enum texturelayertype_e +{ + TEXTURELAYERTYPE_INVALID, + TEXTURELAYERTYPE_LITTEXTURE, + TEXTURELAYERTYPE_TEXTURE, + TEXTURELAYERTYPE_FOG +} +texturelayertype_t; + +typedef struct texturelayer_s +{ + texturelayertype_t type; + qboolean depthmask; + int blendfunc1; + int blendfunc2; + rtexture_t *texture; + matrix4x4_t texmatrix; + vec4_t color; +} +texturelayer_t; + +typedef struct texture_s +{ + // q1bsp + // name + //char name[16]; + // size + unsigned int width, height; + // SURF_ flags + //unsigned int flags; + + // base material flags + int basematerialflags; + // current material flags (updated each bmodel render) + int currentmaterialflags; + + // PolygonOffset values for rendering this material + // (these are added to the r_refdef values and submodel values) + float biaspolygonfactor; + float biaspolygonoffset; + + // textures to use when rendering this material + skinframe_t *currentskinframe; + int numskinframes; + float skinframerate; + skinframe_t *skinframes[TEXTURE_MAXFRAMES]; + // background layer (for terrain texture blending) + skinframe_t *backgroundcurrentskinframe; + int backgroundnumskinframes; + float backgroundskinframerate; + skinframe_t *backgroundskinframes[TEXTURE_MAXFRAMES]; + + // total frames in sequence and alternate sequence + int anim_total[2]; + // direct pointers to each of the frames in the sequences + // (indexed as [alternate][frame]) + struct texture_s *anim_frames[2][10]; + // set if animated or there is an alternate frame set + // (this is an optimization in the renderer) + int animated; + + // renderer checks if this texture needs updating... + int update_lastrenderframe; + void *update_lastrenderentity; + // the current alpha of this texture (may be affected by r_wateralpha) + float currentalpha; + // the current texture frame in animation + struct texture_s *currentframe; + // current texture transform matrix (used for water scrolling) + matrix4x4_t currenttexmatrix; + matrix4x4_t currentbackgroundtexmatrix; + + // various q3 shader features + q3shaderinfo_layer_rgbgen_t rgbgen; + q3shaderinfo_layer_alphagen_t alphagen; + q3shaderinfo_layer_tcgen_t tcgen; + q3shaderinfo_layer_tcmod_t tcmods[Q3MAXTCMODS]; + q3shaderinfo_layer_tcmod_t backgroundtcmods[Q3MAXTCMODS]; + q3shaderinfo_deform_t deforms[Q3MAXDEFORMS]; + + qboolean colormapping; + rtexture_t *basetexture; // original texture without pants/shirt/glow + rtexture_t *pantstexture; // pants only (in greyscale) + rtexture_t *shirttexture; // shirt only (in greyscale) + rtexture_t *nmaptexture; // normalmap (bumpmap for dot3) + rtexture_t *glosstexture; // glossmap (for dot3) + rtexture_t *glowtexture; // glow only (fullbrights) + rtexture_t *fogtexture; // alpha of the base texture (if not opaque) + rtexture_t *reflectmasktexture; // mask for fake reflections + rtexture_t *reflectcubetexture; // fake reflections cubemap + rtexture_t *backgroundbasetexture; // original texture without pants/shirt/glow + rtexture_t *backgroundnmaptexture; // normalmap (bumpmap for dot3) + rtexture_t *backgroundglosstexture; // glossmap (for dot3) + rtexture_t *backgroundglowtexture; // glow only (fullbrights) + float specularscale; + float specularpower; + // color tint (colormod * currentalpha) used for rtlighting this material + float dlightcolor[3]; + // color tint (colormod * 2) used for lightmapped lighting on this material + // includes alpha as 4th component + // replaces role of gl_Color in GLSL shader + float lightmapcolor[4]; + + // from q3 shaders + int customblendfunc[2]; + + int currentnumlayers; + texturelayer_t currentlayers[16]; + + // q3bsp + char name[64]; + int surfaceflags; + int supercontents; + int textureflags; + + // reflection + float reflectmin; // when refraction is used, minimum amount of reflection (when looking straight down) + float reflectmax; // when refraction is used, maximum amount of reflection (when looking parallel to water) + float refractfactor; // amount of refraction distort (1.0 = like the cvar specifies) + vec4_t refractcolor4f; // color tint of refraction (including alpha factor) + float reflectfactor; // amount of reflection distort (1.0 = like the cvar specifies) + vec4_t reflectcolor4f; // color tint of reflection (including alpha factor) + float r_water_wateralpha; // additional wateralpha to apply when r_water is active + float r_water_waterscroll[2]; // scale and speed + int camera_entity; // entity number for use by cameras + + // offsetmapping + dpoffsetmapping_technique_t offsetmapping; + float offsetscale; + float offsetbias; + + // transparent sort category + dptransparentsortcategory_t transparentsort; + + // gloss + float specularscalemod; + float specularpowermod; + + // diffuse and ambient + float rtlightambient; +} + texture_t; + +typedef struct mtexinfo_s +{ + float vecs[2][4]; + texture_t *texture; + int flags; +} +mtexinfo_t; + +typedef struct msurface_lightmapinfo_s +{ + // texture mapping properties used by this surface + mtexinfo_t *texinfo; // q1bsp + // index into r_refdef.scene.lightstylevalue array, 255 means not used (black) + unsigned char styles[MAXLIGHTMAPS]; // q1bsp + // RGB lighting data [numstyles][height][width][3] + unsigned char *samples; // q1bsp + // RGB normalmap data [numstyles][height][width][3] + unsigned char *nmapsamples; // q1bsp + // stain to apply on lightmap (soot/dirt/blood/whatever) + unsigned char *stainsamples; // q1bsp + int texturemins[2]; // q1bsp + int extents[2]; // q1bsp + int lightmaporigin[2]; // q1bsp +} +msurface_lightmapinfo_t; + +struct q3deffect_s; +typedef struct msurface_s +{ + // bounding box for onscreen checks + vec3_t mins; + vec3_t maxs; + // the texture to use on the surface + texture_t *texture; + // the lightmap texture fragment to use on the rendering mesh + rtexture_t *lightmaptexture; + // the lighting direction texture fragment to use on the rendering mesh + rtexture_t *deluxemaptexture; + // lightmaptexture rebuild information not used in q3bsp + msurface_lightmapinfo_t *lightmapinfo; // q1bsp + // fog volume info in q3bsp + struct q3deffect_s *effect; // q3bsp + // mesh information for collisions (only used by q3bsp curves) + int num_firstcollisiontriangle; + int *deprecatedq3data_collisionelement3i; // q3bsp + float *deprecatedq3data_collisionvertex3f; // q3bsp + float *deprecatedq3data_collisionbbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles + float *deprecatedq3data_bbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles + + // surfaces own ranges of vertices and triangles in the model->surfmesh + int num_triangles; // number of triangles + int num_firsttriangle; // first triangle + int num_vertices; // number of vertices + int num_firstvertex; // first vertex + + // shadow volume building information + int num_firstshadowmeshtriangle; // index into model->brush.shadowmesh + + // mesh information for collisions (only used by q3bsp curves) + int num_collisiontriangles; // q3bsp + int num_collisionvertices; // q3bsp + int deprecatedq3num_collisionbboxstride; + int deprecatedq3num_bboxstride; + // FIXME: collisionmarkframe should be kept in a separate array + int deprecatedq3collisionmarkframe; // q3bsp // don't collide twice in one trace +} +msurface_t; + +#include "matrixlib.h" +#include "bih.h" + +#include "model_brush.h" +#include "model_sprite.h" +#include "model_alias.h" + +typedef struct model_sprite_s +{ + int sprnum_type; + mspriteframe_t *sprdata_frames; +} +model_sprite_t; + +struct trace_s; + +typedef struct model_brush_lightstyleinfo_s +{ + int style; + int value; + int numsurfaces; + int *surfacelist; +} +model_brush_lightstyleinfo_t; + +typedef struct model_brush_s +{ + // true if this model is a HalfLife .bsp file + qboolean ishlbsp; + // true if this model is a BSP2rmqe .bsp file (expanded 32bit bsp format for rmqe) + qboolean isbsp2rmqe; + // true if this model is a BSP2 .bsp file (expanded 32bit bsp format for DarkPlaces, others?) + qboolean isbsp2; + // string of entity definitions (.map format) + char *entities; + + // if not NULL this is a submodel + struct model_s *parentmodel; + // (this is the number of the submodel, an index into submodels) + int submodel; + + // number of submodels in this map (just used by server to know how many + // submodels to load) + int numsubmodels; + // pointers to each of the submodels + struct model_s **submodels; + + int num_planes; + mplane_t *data_planes; + + int num_nodes; + mnode_t *data_nodes; + + // visible leafs, not counting 0 (solid) + int num_visleafs; + // number of actual leafs (including 0 which is solid) + int num_leafs; + mleaf_t *data_leafs; + + int num_leafbrushes; + int *data_leafbrushes; + + int num_leafsurfaces; + int *data_leafsurfaces; + + int num_portals; + mportal_t *data_portals; + + int num_portalpoints; + mvertex_t *data_portalpoints; + + int num_brushes; + q3mbrush_t *data_brushes; + + int num_brushsides; + q3mbrushside_t *data_brushsides; + + // pvs + int num_pvsclusters; + int num_pvsclusterbytes; + unsigned char *data_pvsclusters; + // example + //pvschain = model->brush.data_pvsclusters + mycluster * model->brush.num_pvsclusterbytes; + //if (pvschain[thatcluster >> 3] & (1 << (thatcluster & 7))) + + // collision geometry for q3 curves + int num_collisionvertices; + int num_collisiontriangles; + float *data_collisionvertex3f; + int *data_collisionelement3i; + + // a mesh containing all shadow casting geometry for the whole model (including submodels), portions of this are referenced by each surface's num_firstshadowmeshtriangle + shadowmesh_t *shadowmesh; + + // a mesh containing all SUPERCONTENTS_SOLID surfaces for this model or submodel, for physics engines to use + shadowmesh_t *collisionmesh; + + // common functions + int (*SuperContentsFromNativeContents)(struct model_s *model, int nativecontents); + int (*NativeContentsFromSuperContents)(struct model_s *model, int supercontents); + unsigned char *(*GetPVS)(struct model_s *model, const vec3_t p); + int (*FatPVS)(struct model_s *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbufferlength, qboolean merge); + int (*BoxTouchingPVS)(struct model_s *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs); + int (*BoxTouchingLeafPVS)(struct model_s *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs); + int (*BoxTouchingVisibleLeafs)(struct model_s *model, const unsigned char *visibleleafs, const vec3_t mins, const vec3_t maxs); + int (*FindBoxClusters)(struct model_s *model, const vec3_t mins, const vec3_t maxs, int maxclusters, int *clusterlist); + void (*LightPoint)(struct model_s *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal); + void (*FindNonSolidLocation)(struct model_s *model, const vec3_t in, vec3_t out, vec_t radius); + mleaf_t *(*PointInLeaf)(struct model_s *model, const vec3_t p); + // these are actually only found on brushq1, but NULL is handled gracefully + void (*AmbientSoundLevelsForPoint)(struct model_s *model, const vec3_t p, unsigned char *out, int outsize); + void (*RoundUpToHullSize)(struct model_s *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs); + // trace a line of sight through this model (returns false if the line if sight is definitely blocked) + qboolean (*TraceLineOfSight)(struct model_s *model, const vec3_t start, const vec3_t end); + + char skybox[MAX_QPATH]; + + skinframe_t *solidskyskinframe; + skinframe_t *alphaskyskinframe; + + qboolean supportwateralpha; + + // QuakeWorld + int qw_md4sum; + int qw_md4sum2; +} +model_brush_t; + +typedef struct model_brushq1_s +{ + mmodel_t *submodels; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + mclipnode_t *clipnodes; + + hull_t hulls[MAX_MAP_HULLS]; + + int num_compressedpvs; + unsigned char *data_compressedpvs; + + int num_lightdata; + unsigned char *lightdata; + unsigned char *nmaplightdata; // deluxemap file + + // lightmap update chains for light styles + int num_lightstyles; + model_brush_lightstyleinfo_t *data_lightstyleinfo; + + // this contains bytes that are 1 if a surface needs its lightmap rebuilt + unsigned char *lightmapupdateflags; + qboolean firstrender; // causes all surface lightmaps to be loaded in first frame +} +model_brushq1_t; + +/* MSVC can't compile empty structs, so this is commented out for now +typedef struct model_brushq2_s +{ +} +model_brushq2_t; +*/ + +typedef struct model_brushq3_s +{ + int num_models; + q3dmodel_t *data_models; + + // used only during loading - freed after loading! + int num_vertices; + float *data_vertex3f; + float *data_normal3f; + float *data_texcoordtexture2f; + float *data_texcoordlightmap2f; + float *data_color4f; + + // freed after loading! + int num_triangles; + int *data_element3i; + + int num_effects; + q3deffect_t *data_effects; + + // lightmap textures + int num_originallightmaps; + int num_mergedlightmaps; + int num_lightmapmergedwidthpower; + int num_lightmapmergedheightpower; + int num_lightmapmergedwidthheightdeluxepower; + int num_lightmapmerge; + rtexture_t **data_lightmaps; + rtexture_t **data_deluxemaps; + + // voxel light data with directional shading + int num_lightgrid; + q3dlightgrid_t *data_lightgrid; + // size of each cell (may vary by map, typically 64 64 128) + float num_lightgrid_cellsize[3]; + // 1.0 / num_lightgrid_cellsize + float num_lightgrid_scale[3]; + // dimensions of the world model in lightgrid cells + int num_lightgrid_imins[3]; + int num_lightgrid_imaxs[3]; + int num_lightgrid_isize[3]; + // transform modelspace coordinates to lightgrid index + matrix4x4_t num_lightgrid_indexfromworld; + + // true if this q3bsp file has been detected as using deluxemapping + // (lightmap texture pairs, every odd one is never directly refernced, + // and contains lighting normals, not colors) + qboolean deluxemapping; + // true if the detected deluxemaps are the modelspace kind, rather than + // the faster tangentspace kind + qboolean deluxemapping_modelspace; + // size of lightmaps (128 by default, but may be another poweroftwo if + // external lightmaps are used (q3map2 -lightmapsize) + int lightmapsize; +} +model_brushq3_t; + +struct frameblend_s; +struct skeleton_s; + +typedef struct model_s +{ + // name and path of model, for example "progs/player.mdl" + char name[MAX_QPATH]; + // model needs to be loaded if this is false + qboolean loaded; + // set if the model is used in current map, models which are not, are purged + qboolean used; + // CRC of the file this model was loaded from, to reload if changed + unsigned int crc; + // mod_brush, mod_alias, mod_sprite + modtype_t type; + // memory pool for allocations + mempool_t *mempool; + // all models use textures... + rtexturepool_t *texturepool; + // EF_* flags (translated from the model file's different flags layout) + int effects; + // number of QC accessible frame(group)s in the model + int numframes; + // number of QC accessible skin(group)s in the model + int numskins; + // whether to randomize animated framegroups + synctype_t synctype; + // bounding box at angles '0 0 0' + vec3_t normalmins, normalmaxs; + // bounding box if yaw angle is not 0, but pitch and roll are + vec3_t yawmins, yawmaxs; + // bounding box if pitch or roll are used + vec3_t rotatedmins, rotatedmaxs; + // sphere radius, usable at any angles + float radius; + // squared sphere radius for easier comparisons + float radius2; + // skin animation info + animscene_t *skinscenes; // [numskins] + // skin animation info + animscene_t *animscenes; // [numframes] + // range of surface numbers in this (sub)model + int firstmodelsurface; + int nummodelsurfaces; + int *sortedmodelsurfaces; + // range of collision brush numbers in this (sub)model + int firstmodelbrush; + int nummodelbrushes; + // BIH (Bounding Interval Hierarchy) for this (sub)model + bih_t collision_bih; + bih_t render_bih; // if not set, use collision_bih instead for rendering purposes too + // for md3 models + int num_tags; + int num_tagframes; + aliastag_t *data_tags; + // for skeletal models + int num_bones; + aliasbone_t *data_bones; + float num_posescale; // scaling factor from origin in poses7s format (includes divide by 32767) + float num_poseinvscale; // scaling factor to origin in poses7s format (includes multiply by 32767) + int num_poses; + short *data_poses7s; // origin xyz, quat xyzw, unit length, values normalized to +/-32767 range + float *data_baseboneposeinverse; + // textures of this model + int num_textures; + int num_texturesperskin; + texture_t *data_textures; + qboolean wantnormals; + qboolean wanttangents; + // surfaces of this model + int num_surfaces; + msurface_t *data_surfaces; + // optional lightmapinfo data for surface lightmap updates + msurface_lightmapinfo_t *data_surfaces_lightmapinfo; + // all surfaces belong to this mesh + surfmesh_t surfmesh; + // data type of model + const char *modeldatatypestring; + // generates vertex data for a given frameblend + void(*AnimateVertices)(const struct model_s * RESTRICT model, const struct frameblend_s * RESTRICT frameblend, const struct skeleton_s *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); + // draw the model's sky polygons (only used by brush models) + void(*DrawSky)(struct entity_render_s *ent); + // draw refraction/reflection textures for the model's water polygons (only used by brush models) + void(*DrawAddWaterPlanes)(struct entity_render_s *ent); + // draw the model using lightmap/dlight shading + void(*Draw)(struct entity_render_s *ent); + // draw the model to the depth buffer (no color rendering at all) + void(*DrawDepth)(struct entity_render_s *ent); + // draw any enabled debugging effects on this model (such as showing triangles, normals, collision brushes...) + void(*DrawDebug)(struct entity_render_s *ent); + // draw geometry textures for deferred rendering + void(*DrawPrepass)(struct entity_render_s *ent); + // compile an optimized shadowmap mesh for the model based on light source + void(*CompileShadowMap)(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); + // draw depth into a shadowmap + void(*DrawShadowMap)(int side, struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs); + // gathers info on which clusters and surfaces are lit by light, as well as calculating a bounding box + void(*GetLightInfo)(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes); + // compile a shadow volume for the model based on light source + void(*CompileShadowVolume)(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); + // draw a shadow volume for the model based on light source + void(*DrawShadowVolume)(struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const vec3_t lightmins, const vec3_t lightmaxs); + // draw the lighting on a model (through stencil) + void(*DrawLight)(struct entity_render_s *ent, int numsurfaces, const int *surfacelist, const unsigned char *trispvs); + // trace a box against this model + void (*TraceBox)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask); + void (*TraceBrush)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, struct colbrushf_s *start, struct colbrushf_s *end, int hitsupercontentsmask); + // trace a box against this model + void (*TraceLine)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); + // trace a point against this model (like PointSuperContents) + void (*TracePoint)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask); + // find the supercontents value at a point in this model + int (*PointSuperContents)(struct model_s *model, int frame, const vec3_t point); + // trace a line against geometry in this model and report correct texture (used by r_shadow_bouncegrid) + void (*TraceLineAgainstSurfaces)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); + // fields belonging to some types of model + model_sprite_t sprite; + model_brush_t brush; + model_brushq1_t brushq1; + /* MSVC can't handle an empty struct, so this is commented out for now + model_brushq2_t brushq2; + */ + model_brushq3_t brushq3; + // flags this model for offseting sounds to the model center (used by brush models) + int soundfromcenter; + + // if set, the model contains light information (lightmap, or vertexlight) + qboolean lit; + float lightmapscale; +} +dp_model_t; + +//============================================================================ + +// model loading +extern dp_model_t *loadmodel; +extern unsigned char *mod_base; +// sky/water subdivision +//extern cvar_t gl_subdivide_size; +// texture fullbrights +extern cvar_t r_fullbrights; +extern cvar_t r_enableshadowvolumes; + +void Mod_Init (void); +void Mod_Reload (void); +dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk); +dp_model_t *Mod_FindName (const char *name, const char *parentname); +dp_model_t *Mod_ForName (const char *name, qboolean crash, qboolean checkdisk, const char *parentname); +void Mod_UnloadModel (dp_model_t *mod); + +void Mod_ClearUsed(void); +void Mod_PurgeUnused(void); +void Mod_RemoveStaleWorldModels(dp_model_t *skip); // only used during loading! + +extern dp_model_t *loadmodel; +extern char loadname[32]; // for hunk tags + +int Mod_BuildVertexRemapTableFromElements(int numelements, const int *elements, int numvertices, int *remapvertices); +void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles); +void Mod_ValidateElements(int *elements, int numtriangles, int firstvertex, int numverts, const char *filename, int fileline); +void Mod_BuildNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const int *elements, float *normal3f, qboolean areaweighting); +void Mod_BuildTextureVectorsFromNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const float *texcoord2f, const float *normal3f, const int *elements, float *svector3f, float *tvector3f, qboolean areaweighting); + +void Mod_AllocSurfMesh(mempool_t *mempool, int numvertices, int numtriangles, qboolean lightmapoffsets, qboolean vertexcolors, qboolean neighbors); +void Mod_MakeSortedSurfaces(dp_model_t *mod); + +// called specially by brush model loaders before generating submodels +// automatically called after model loader returns +void Mod_BuildVBOs(void); + +shadowmesh_t *Mod_ShadowMesh_Alloc(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable); +shadowmesh_t *Mod_ShadowMesh_ReAlloc(mempool_t *mempool, shadowmesh_t *oldmesh, int light, int neighbors); +int Mod_ShadowMesh_AddVertex(shadowmesh_t *mesh, float *vertex14f); +void Mod_ShadowMesh_AddTriangle(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, float *vertex14f); +void Mod_ShadowMesh_AddMesh(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *texcoord2f, int numtris, const int *element3i); +shadowmesh_t *Mod_ShadowMesh_Begin(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable); +shadowmesh_t *Mod_ShadowMesh_Finish(mempool_t *mempool, shadowmesh_t *firstmesh, qboolean light, qboolean neighbors, qboolean createvbo); +void Mod_ShadowMesh_CalcBBox(shadowmesh_t *firstmesh, vec3_t mins, vec3_t maxs, vec3_t center, float *radius); +void Mod_ShadowMesh_Free(shadowmesh_t *mesh); + +void Mod_CreateCollisionMesh(dp_model_t *mod); + +void Mod_FreeQ3Shaders(void); +void Mod_LoadQ3Shaders(void); +q3shaderinfo_t *Mod_LookupQ3Shader(const char *name); +qboolean Mod_LoadTextureFromQ3Shader(texture_t *texture, const char *name, qboolean warnmissing, qboolean fallback, int defaulttexflags); + +extern cvar_t r_mipskins; +extern cvar_t r_mipnormalmaps; + +typedef struct skinfileitem_s +{ + struct skinfileitem_s *next; + char name[MAX_QPATH]; + char replacement[MAX_QPATH]; +} +skinfileitem_t; + +typedef struct skinfile_s +{ + struct skinfile_s *next; + skinfileitem_t *items; +} +skinfile_t; + +skinfile_t *Mod_LoadSkinFiles(void); +void Mod_FreeSkinFiles(skinfile_t *skinfile); +int Mod_CountSkinFiles(skinfile_t *skinfile); +void Mod_BuildAliasSkinsFromSkinFiles(texture_t *skin, skinfile_t *skinfile, const char *meshname, const char *shadername); + +void Mod_SnapVertices(int numcomponents, int numvertices, float *vertices, float snap); +int Mod_RemoveDegenerateTriangles(int numtriangles, const int *inelement3i, int *outelement3i, const float *vertex3f); +void Mod_VertexRangeFromElements(int numelements, const int *elements, int *firstvertexpointer, int *lastvertexpointer); + +typedef struct mod_alloclightmap_row_s +{ + int rowY; + int currentX; +} +mod_alloclightmap_row_t; + +typedef struct mod_alloclightmap_state_s +{ + int width; + int height; + int currentY; + mod_alloclightmap_row_t *rows; +} +mod_alloclightmap_state_t; + +void Mod_AllocLightmap_Init(mod_alloclightmap_state_t *state, int width, int height); +void Mod_AllocLightmap_Free(mod_alloclightmap_state_t *state); +void Mod_AllocLightmap_Reset(mod_alloclightmap_state_t *state); +qboolean Mod_AllocLightmap_Block(mod_alloclightmap_state_t *state, int blockwidth, int blockheight, int *outx, int *outy); + +// bsp models +void Mod_BrushInit(void); +// used for talking to the QuakeC mainly +int Mod_Q1BSP_NativeContentsFromSuperContents(struct model_s *model, int supercontents); +int Mod_Q1BSP_SuperContentsFromNativeContents(struct model_s *model, int nativecontents); + +// a lot of model formats use the Q1BSP code, so here are the prototypes... +struct entity_render_s; +void R_Q1BSP_DrawAddWaterPlanes(struct entity_render_s *ent); +void R_Q1BSP_DrawSky(struct entity_render_s *ent); +void R_Q1BSP_Draw(struct entity_render_s *ent); +void R_Q1BSP_DrawDepth(struct entity_render_s *ent); +void R_Q1BSP_DrawDebug(struct entity_render_s *ent); +void R_Q1BSP_DrawPrepass(struct entity_render_s *ent); +void R_Q1BSP_GetLightInfo(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes); +void R_Q1BSP_CompileShadowMap(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); +void R_Q1BSP_DrawShadowMap(int side, struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs); +void R_Q1BSP_CompileShadowVolume(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); +void R_Q1BSP_DrawShadowVolume(struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const vec3_t lightmins, const vec3_t lightmaxs); +void R_Q1BSP_DrawLight(struct entity_render_s *ent, int numsurfaces, const int *surfacelist, const unsigned char *trispvs); + +// Collision optimization using Bounding Interval Hierarchy +void Mod_CollisionBIH_TracePoint(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask); +void Mod_CollisionBIH_TraceLine(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask); +void Mod_CollisionBIH_TraceBox(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask); +void Mod_CollisionBIH_TraceBrush(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, struct colbrushf_s *start, struct colbrushf_s *end, int hitsupercontentsmask); +void Mod_CollisionBIH_TracePoint_Mesh(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask); +int Mod_CollisionBIH_PointSuperContents_Mesh(struct model_s *model, int frame, const vec3_t point); +bih_t *Mod_MakeCollisionBIH(dp_model_t *model, qboolean userendersurfaces, bih_t *out); + +// alias models +struct frameblend_s; +struct skeleton_s; +void Mod_AliasInit(void); +int Mod_Alias_GetTagMatrix(const dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, int tagindex, matrix4x4_t *outmatrix); +int Mod_Alias_GetTagIndexForName(const dp_model_t *model, unsigned int skin, const char *tagname); +int Mod_Alias_GetExtendedTagInfoForIndex(const dp_model_t *model, unsigned int skin, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix); + +void Mod_Skeletal_FreeBuffers(void); + +// sprite models +void Mod_SpriteInit(void); + +// loaders +void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IBSP_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_MAP_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_OBJ_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDP0_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDP2_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDP3_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_ZYMOTICMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_DARKPLACESMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_PSKMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDSP_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_IDS2_Load(dp_model_t *mod, void *buffer, void *bufferend); +void Mod_INTERQUAKEMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); + +#endif // MODEL_SHARED_H + diff --git a/app/jni/model_sprite.c b/app/jni/model_sprite.c new file mode 100644 index 0000000..e24c842 --- /dev/null +++ b/app/jni/model_sprite.c @@ -0,0 +1,482 @@ +/* +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. + +*/ +// models.c -- model loading and caching + +// models are the only shared resource between a client and server running +// on the same machine. + +#include "quakedef.h" +#include "image.h" + +cvar_t r_mipsprites = {CVAR_SAVE, "r_mipsprites", "1", "mipmaps sprites so they render faster in the distance and do not display noise artifacts"}; +cvar_t r_labelsprites_scale = {CVAR_SAVE, "r_labelsprites_scale", "1", "global scale to apply to label sprites before conversion to HUD coordinates"}; +cvar_t r_labelsprites_roundtopixels = {CVAR_SAVE, "r_labelsprites_roundtopixels", "1", "try to make label sprites sharper by rounding their size to 0.5x or 1x and by rounding their position to whole pixels if possible"}; +cvar_t r_overheadsprites_perspective = {CVAR_SAVE, "r_overheadsprites_perspective", "5", "fake perspective effect for SPR_OVERHEAD sprites"}; +cvar_t r_overheadsprites_pushback = {CVAR_SAVE, "r_overheadsprites_pushback", "15", "how far to pull the SPR_OVERHEAD sprites toward the eye (used to avoid intersections with 3D models)"}; +cvar_t r_overheadsprites_scalex = {CVAR_SAVE, "r_overheadsprites_scalex", "1", "additional scale for overhead sprites for x axis"}; +cvar_t r_overheadsprites_scaley = {CVAR_SAVE, "r_overheadsprites_scaley", "1", "additional scale for overhead sprites for y axis"}; +cvar_t r_track_sprites = {CVAR_SAVE, "r_track_sprites", "1", "track SPR_LABEL* sprites by putting them as indicator at the screen border to rotate to"}; +cvar_t r_track_sprites_flags = {CVAR_SAVE, "r_track_sprites_flags", "1", "1: Rotate sprites accordingly, 2: Make it a continuous rotation"}; +cvar_t r_track_sprites_scalew = {CVAR_SAVE, "r_track_sprites_scalew", "1", "width scaling of tracked sprites"}; +cvar_t r_track_sprites_scaleh = {CVAR_SAVE, "r_track_sprites_scaleh", "1", "height scaling of tracked sprites"}; + +/* +=============== +Mod_SpriteInit +=============== +*/ +void Mod_SpriteInit (void) +{ + Cvar_RegisterVariable(&r_mipsprites); + Cvar_RegisterVariable(&r_labelsprites_scale); + Cvar_RegisterVariable(&r_labelsprites_roundtopixels); + Cvar_RegisterVariable(&r_overheadsprites_perspective); + Cvar_RegisterVariable(&r_overheadsprites_pushback); + Cvar_RegisterVariable(&r_overheadsprites_scalex); + Cvar_RegisterVariable(&r_overheadsprites_scaley); + Cvar_RegisterVariable(&r_track_sprites); + Cvar_RegisterVariable(&r_track_sprites_flags); + Cvar_RegisterVariable(&r_track_sprites_scalew); + Cvar_RegisterVariable(&r_track_sprites_scaleh); +} + +static void Mod_SpriteSetupTexture(texture_t *texture, skinframe_t *skinframe, qboolean fullbright, qboolean additive) +{ + if (!skinframe) + skinframe = R_SkinFrame_LoadMissing(); + texture->offsetmapping = OFFSETMAPPING_OFF; + texture->offsetscale = 1; + texture->offsetbias = 0; + texture->specularscalemod = 1; + texture->specularpowermod = 1; + texture->basematerialflags = MATERIALFLAG_WALL; + if (fullbright) + texture->basematerialflags |= MATERIALFLAG_FULLBRIGHT; + if (additive) + texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + else if (skinframe->hasalpha) + texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; + texture->currentmaterialflags = texture->basematerialflags; + texture->numskinframes = 1; + texture->currentskinframe = texture->skinframes[0] = skinframe; + texture->surfaceflags = 0; + texture->supercontents = SUPERCONTENTS_SOLID; + if (!(texture->basematerialflags & MATERIALFLAG_BLENDED)) + texture->supercontents |= SUPERCONTENTS_OPAQUE; + texture->transparentsort = TRANSPARENTSORT_DISTANCE; + // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS + // JUST GREP FOR "specularscalemod = 1". +} + +extern cvar_t gl_texturecompression_sprites; + +static void Mod_Sprite_SharedSetup(const unsigned char *datapointer, int version, const unsigned int *palette, qboolean additive) +{ + int i, j, groupframes, realframes, x, y, origin[2], width, height; + qboolean fullbright; + dspriteframetype_t *pinframetype; + dspriteframe_t *pinframe; + dspritegroup_t *pingroup; + dspriteinterval_t *pinintervals; + skinframe_t *skinframe; + float modelradius, interval; + char name[MAX_QPATH], fogname[MAX_QPATH]; + const void *startframes; + int texflags = (r_mipsprites.integer ? TEXF_MIPMAP : 0) | ((gl_texturecompression.integer && gl_texturecompression_sprites.integer) ? TEXF_COMPRESS : 0) | TEXF_ISSPRITE | TEXF_PICMIP | TEXF_ALPHA | TEXF_CLAMP; + modelradius = 0; + + if (loadmodel->numframes < 1) + Host_Error ("Mod_Sprite_SharedSetup: Invalid # of frames: %d", loadmodel->numframes); + + // LordHavoc: hack to allow sprites to be non-fullbright + fullbright = true; + for (i = 0;i < MAX_QPATH && loadmodel->name[i];i++) + if (loadmodel->name[i] == '!') + fullbright = false; + +// +// load the frames +// + startframes = datapointer; + realframes = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + pinframetype = (dspriteframetype_t *)datapointer; + datapointer += sizeof(dspriteframetype_t); + + if (LittleLong (pinframetype->type) == SPR_SINGLE) + groupframes = 1; + else + { + pingroup = (dspritegroup_t *)datapointer; + datapointer += sizeof(dspritegroup_t); + + groupframes = LittleLong(pingroup->numframes); + + datapointer += sizeof(dspriteinterval_t) * groupframes; + } + + for (j = 0;j < groupframes;j++) + { + pinframe = (dspriteframe_t *)datapointer; + if (version == SPRITE32_VERSION) + datapointer += sizeof(dspriteframe_t) + LittleLong(pinframe->width) * LittleLong(pinframe->height) * 4; + else //if (version == SPRITE_VERSION || version == SPRITEHL_VERSION) + datapointer += sizeof(dspriteframe_t) + LittleLong(pinframe->width) * LittleLong(pinframe->height); + } + realframes += groupframes; + } + + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); + loadmodel->sprite.sprdata_frames = (mspriteframe_t *)Mem_Alloc(loadmodel->mempool, sizeof(mspriteframe_t) * realframes); + loadmodel->num_textures = realframes; + loadmodel->num_texturesperskin = 1; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, sizeof(texture_t) * loadmodel->num_textures); + + datapointer = (unsigned char *)startframes; + realframes = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + pinframetype = (dspriteframetype_t *)datapointer; + datapointer += sizeof(dspriteframetype_t); + + if (LittleLong (pinframetype->type) == SPR_SINGLE) + { + groupframes = 1; + interval = 0.1f; + } + else + { + pingroup = (dspritegroup_t *)datapointer; + datapointer += sizeof(dspritegroup_t); + + groupframes = LittleLong(pingroup->numframes); + + pinintervals = (dspriteinterval_t *)datapointer; + datapointer += sizeof(dspriteinterval_t) * groupframes; + + interval = LittleFloat(pinintervals[0].interval); + if (interval < 0.01f) + Host_Error("Mod_Sprite_SharedSetup: invalid interval"); + } + + dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "frame %i", i); + loadmodel->animscenes[i].firstframe = realframes; + loadmodel->animscenes[i].framecount = groupframes; + loadmodel->animscenes[i].framerate = 1.0f / interval; + loadmodel->animscenes[i].loop = true; + + for (j = 0;j < groupframes;j++) + { + pinframe = (dspriteframe_t *)datapointer; + datapointer += sizeof(dspriteframe_t); + + origin[0] = LittleLong (pinframe->origin[0]); + origin[1] = LittleLong (pinframe->origin[1]); + width = LittleLong (pinframe->width); + height = LittleLong (pinframe->height); + + loadmodel->sprite.sprdata_frames[realframes].left = origin[0]; + loadmodel->sprite.sprdata_frames[realframes].right = origin[0] + width; + loadmodel->sprite.sprdata_frames[realframes].up = origin[1]; + loadmodel->sprite.sprdata_frames[realframes].down = origin[1] - height; + + x = (int)max(loadmodel->sprite.sprdata_frames[realframes].left * loadmodel->sprite.sprdata_frames[realframes].left, loadmodel->sprite.sprdata_frames[realframes].right * loadmodel->sprite.sprdata_frames[realframes].right); + y = (int)max(loadmodel->sprite.sprdata_frames[realframes].up * loadmodel->sprite.sprdata_frames[realframes].up, loadmodel->sprite.sprdata_frames[realframes].down * loadmodel->sprite.sprdata_frames[realframes].down); + if (modelradius < x + y) + modelradius = x + y; + + if (cls.state != ca_dedicated) + { + skinframe = NULL; + // note: Nehahra's null.spr has width == 0 and height == 0 + if (width > 0 && height > 0) + { + if (groupframes > 1) + { + dpsnprintf (name, sizeof(name), "%s_%i_%i", loadmodel->name, i, j); + dpsnprintf (fogname, sizeof(fogname), "%s_%i_%ifog", loadmodel->name, i, j); + } + else + { + dpsnprintf (name, sizeof(name), "%s_%i", loadmodel->name, i); + dpsnprintf (fogname, sizeof(fogname), "%s_%ifog", loadmodel->name, i); + } + if (!(skinframe = R_SkinFrame_LoadExternal(name, texflags | TEXF_COMPRESS, false))) + { + unsigned char *pixels = (unsigned char *) Mem_Alloc(loadmodel->mempool, width*height*4); + if (version == SPRITE32_VERSION) + { + for (x = 0;x < width*height;x++) + { + pixels[x*4+2] = datapointer[x*4+0]; + pixels[x*4+1] = datapointer[x*4+1]; + pixels[x*4+0] = datapointer[x*4+2]; + pixels[x*4+3] = datapointer[x*4+3]; + } + } + else //if (version == SPRITEHL_VERSION || version == SPRITE_VERSION) + Image_Copy8bitBGRA(datapointer, pixels, width*height, palette ? palette : palette_bgra_transparent); + skinframe = R_SkinFrame_LoadInternalBGRA(name, texflags, pixels, width, height, false); + // texflags |= TEXF_COMPRESS; + Mem_Free(pixels); + } + } + if (skinframe == NULL) + skinframe = R_SkinFrame_LoadMissing(); + Mod_SpriteSetupTexture(&loadmodel->data_textures[realframes], skinframe, fullbright, additive); + } + + if (version == SPRITE32_VERSION) + datapointer += width * height * 4; + else //if (version == SPRITE_VERSION || version == SPRITEHL_VERSION) + datapointer += width * height; + realframes++; + } + } + + modelradius = sqrt(modelradius); + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = loadmodel->yawmins[i] = loadmodel->rotatedmins[i] = -modelradius; + loadmodel->normalmaxs[i] = loadmodel->yawmaxs[i] = loadmodel->rotatedmaxs[i] = modelradius; + } + loadmodel->radius = modelradius; + loadmodel->radius2 = modelradius * modelradius; +} + +void Mod_IDSP_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int version; + const unsigned char *datapointer; + + datapointer = (unsigned char *)buffer; + + loadmodel->modeldatatypestring = "SPR1"; + + loadmodel->type = mod_sprite; + + loadmodel->DrawSky = NULL; + loadmodel->Draw = R_Model_Sprite_Draw; + loadmodel->DrawDepth = NULL; + loadmodel->CompileShadowVolume = NULL; + loadmodel->DrawShadowVolume = NULL; + loadmodel->DrawLight = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + + version = LittleLong(((dsprite_t *)buffer)->version); + if (version == SPRITE_VERSION || version == SPRITE32_VERSION) + { + dsprite_t *pinqsprite; + + pinqsprite = (dsprite_t *)datapointer; + datapointer += sizeof(dsprite_t); + + loadmodel->numframes = LittleLong (pinqsprite->numframes); + loadmodel->sprite.sprnum_type = LittleLong (pinqsprite->type); + loadmodel->synctype = (synctype_t)LittleLong (pinqsprite->synctype); + + Mod_Sprite_SharedSetup(datapointer, LittleLong (pinqsprite->version), NULL, false); + } + else if (version == SPRITEHL_VERSION) + { + int i, rendermode; + unsigned char palette[256][4]; + const unsigned char *in; + dspritehl_t *pinhlsprite; + + pinhlsprite = (dspritehl_t *)datapointer; + datapointer += sizeof(dspritehl_t); + + loadmodel->numframes = LittleLong (pinhlsprite->numframes); + loadmodel->sprite.sprnum_type = LittleLong (pinhlsprite->type); + loadmodel->synctype = (synctype_t)LittleLong (pinhlsprite->synctype); + rendermode = pinhlsprite->rendermode; + + in = datapointer; + datapointer += 2; + i = in[0] + in[1] * 256; + if (i != 256) + Host_Error ("Mod_IDSP_Load: unexpected number of palette colors %i (should be 256)", i); + in = datapointer; + datapointer += 768; + switch(rendermode) + { + case SPRHL_OPAQUE: + for (i = 0;i < 256;i++) + { + palette[i][2] = in[i*3+0]; + palette[i][1] = in[i*3+1]; + palette[i][0] = in[i*3+2]; + palette[i][3] = 255; + } + break; + case SPRHL_ADDITIVE: + for (i = 0;i < 256;i++) + { + palette[i][2] = in[i*3+0]; + palette[i][1] = in[i*3+1]; + palette[i][0] = in[i*3+2]; + palette[i][3] = 255; + } + // also passes additive == true to Mod_Sprite_SharedSetup + break; + case SPRHL_INDEXALPHA: + for (i = 0;i < 256;i++) + { + palette[i][2] = in[765]; + palette[i][1] = in[766]; + palette[i][0] = in[767]; + palette[i][3] = i; + in += 3; + } + break; + case SPRHL_ALPHATEST: + for (i = 0;i < 256;i++) + { + palette[i][2] = in[i*3+0]; + palette[i][1] = in[i*3+1]; + palette[i][0] = in[i*3+2]; + palette[i][3] = 255; + } + palette[255][0] = palette[255][1] = palette[255][2] = palette[255][3] = 0; + // should this use alpha test or alpha blend? (currently blend) + break; + default: + Host_Error("Mod_IDSP_Load: unknown texFormat (%i, should be 0, 1, 2, or 3)", i); + return; + } + + Mod_Sprite_SharedSetup(datapointer, LittleLong (pinhlsprite->version), (unsigned int *)(&palette[0][0]), rendermode == SPRHL_ADDITIVE); + } + else + Host_Error("Mod_IDSP_Load: %s has wrong version number (%i). Only %i (quake), %i (HalfLife), and %i (sprite32) supported", + loadmodel->name, version, SPRITE_VERSION, SPRITEHL_VERSION, SPRITE32_VERSION); + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || (loadmodel->animscenes && loadmodel->animscenes[0].framecount > 1); +} + + +void Mod_IDS2_Load(dp_model_t *mod, void *buffer, void *bufferend) +{ + int i, version; + qboolean fullbright; + const dsprite2_t *pinqsprite; + skinframe_t *skinframe; + float modelradius; + int texflags = (r_mipsprites.integer ? TEXF_MIPMAP : 0) | TEXF_ISSPRITE | TEXF_PICMIP | TEXF_COMPRESS | TEXF_ALPHA | TEXF_CLAMP; + + loadmodel->modeldatatypestring = "SPR2"; + + loadmodel->type = mod_sprite; + + loadmodel->DrawSky = NULL; + loadmodel->Draw = R_Model_Sprite_Draw; + loadmodel->DrawDepth = NULL; + loadmodel->CompileShadowVolume = NULL; + loadmodel->DrawShadowVolume = NULL; + loadmodel->DrawLight = NULL; + loadmodel->DrawAddWaterPlanes = NULL; + + pinqsprite = (dsprite2_t *)buffer; + + version = LittleLong(pinqsprite->version); + if (version != SPRITE2_VERSION) + Host_Error("Mod_IDS2_Load: %s has wrong version number (%i should be 2 (quake 2)", loadmodel->name, version); + + loadmodel->numframes = LittleLong (pinqsprite->numframes); + if (loadmodel->numframes < 1) + Host_Error ("Mod_IDS2_Load: Invalid # of frames: %d", loadmodel->numframes); + loadmodel->sprite.sprnum_type = SPR_VP_PARALLEL; + loadmodel->synctype = ST_SYNC; + + // LordHavoc: hack to allow sprites to be non-fullbright + fullbright = true; + for (i = 0;i < MAX_QPATH && loadmodel->name[i];i++) + if (loadmodel->name[i] == '!') + fullbright = false; + + loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); + loadmodel->sprite.sprdata_frames = (mspriteframe_t *)Mem_Alloc(loadmodel->mempool, sizeof(mspriteframe_t) * loadmodel->numframes); + loadmodel->num_textures = loadmodel->numframes; + loadmodel->num_texturesperskin = 1; + loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, sizeof(texture_t) * loadmodel->num_textures); + + modelradius = 0; + for (i = 0;i < loadmodel->numframes;i++) + { + int origin[2], x, y, width, height; + const dsprite2frame_t *pinframe; + mspriteframe_t *sprframe; + + dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "frame %i", i); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].framerate = 10; + loadmodel->animscenes[i].loop = true; + + pinframe = &pinqsprite->frames[i]; + + origin[0] = LittleLong (pinframe->origin_x); + origin[1] = LittleLong (pinframe->origin_y); + width = LittleLong (pinframe->width); + height = LittleLong (pinframe->height); + + sprframe = &loadmodel->sprite.sprdata_frames[i]; + + // note that sp2 origin[0] is positive, where as it is negative in + // spr/spr32/hlspr + sprframe->left = -origin[0]; + sprframe->right = -origin[0] + width; + sprframe->up = origin[1]; + sprframe->down = origin[1] - height; + + x = (int)max(sprframe->left * sprframe->left, sprframe->right * sprframe->right); + y = (int)max(sprframe->up * sprframe->up, sprframe->down * sprframe->down); + if (modelradius < x + y) + modelradius = x + y; + } + + if (cls.state != ca_dedicated) + { + for (i = 0;i < loadmodel->numframes;i++) + { + const dsprite2frame_t *pinframe; + pinframe = &pinqsprite->frames[i]; + if (!(skinframe = R_SkinFrame_LoadExternal(pinframe->name, texflags, false))) + { + Con_Printf("Mod_IDS2_Load: failed to load %s", pinframe->name); + skinframe = R_SkinFrame_LoadMissing(); + } + Mod_SpriteSetupTexture(&loadmodel->data_textures[i], skinframe, fullbright, false); + } + } + + modelradius = sqrt(modelradius); + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = loadmodel->yawmins[i] = loadmodel->rotatedmins[i] = -modelradius; + loadmodel->normalmaxs[i] = loadmodel->yawmaxs[i] = loadmodel->rotatedmaxs[i] = modelradius; + } + loadmodel->radius = modelradius; + loadmodel->radius2 = modelradius * modelradius; + + loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || (loadmodel->animscenes && loadmodel->animscenes[0].framecount > 1); +} diff --git a/app/jni/model_sprite.h b/app/jni/model_sprite.h new file mode 100644 index 0000000..f6408da --- /dev/null +++ b/app/jni/model_sprite.h @@ -0,0 +1,41 @@ +/* +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. + +*/ + +#ifndef MODEL_SPRITE_H +#define MODEL_SPRITE_H + +/* +============================================================================== + +SPRITE MODELS + +============================================================================== +*/ + +#include "spritegn.h" + +// FIXME: shorten these? +typedef struct mspriteframe_s +{ + float up, down, left, right; +} mspriteframe_t; + +#endif + diff --git a/app/jni/model_zymotic.h b/app/jni/model_zymotic.h new file mode 100644 index 0000000..d43f72c --- /dev/null +++ b/app/jni/model_zymotic.h @@ -0,0 +1,68 @@ + +#ifndef MODEL_ZYMOTIC_H +#define MODEL_ZYMOTIC_H + +typedef struct zymlump_s +{ + int start; + int length; +} zymlump_t; + +typedef struct zymtype1header_s +{ + char id[12]; // "ZYMOTICMODEL", length 12, no termination + int type; // 0 (vertex morph) 1 (skeletal pose) or 2 (skeletal scripted) + int filesize; // size of entire model file + float mins[3], maxs[3], radius; // for clipping uses + int numverts; + int numtris; + int numshaders; + int numbones; // this may be zero in the vertex morph format (undecided) + int numscenes; // 0 in skeletal scripted models + +// skeletal pose header + // lump offsets are relative to the file + zymlump_t lump_scenes; // zymscene_t scene[numscenes]; // name and other information for each scene (see zymscene struct) + zymlump_t lump_poses; // float pose[numposes][numbones][6]; // animation data + zymlump_t lump_bones; // zymbone_t bone[numbones]; + zymlump_t lump_vertbonecounts; // int vertbonecounts[numvertices]; // how many bones influence each vertex (separate mainly to make this compress better) + zymlump_t lump_verts; // zymvertex_t vert[numvertices]; // see vertex struct + zymlump_t lump_texcoords; // float texcoords[numvertices][2]; + zymlump_t lump_render; // int renderlist[rendersize]; // sorted by shader with run lengths (int count), shaders are sequentially used, each run can be used with glDrawElements (each triangle is 3 int indices) + zymlump_t lump_shaders; // char shadername[numshaders][32]; // shaders used on this model + zymlump_t lump_trizone; // byte trizone[numtris]; // see trizone explanation +} +zymtype1header_t; + +#define ZYMBONEFLAG_SHARED 1 + +typedef struct zymbone_s +{ + char name[32]; + int flags; + int parent; // parent bone number +} +zymbone_t; + +// normally the scene will loop, if this is set it will stay on the final frame +#define ZYMSCENEFLAG_NOLOOP 1 + +typedef struct zymscene_s +{ + char name[32]; + float mins[3], maxs[3], radius; // for clipping + float framerate; // the scene will animate at this framerate (in frames per second) + int flags; + int start, length; // range of poses +} +zymscene_t; + +typedef struct zymvertex_s +{ + int bonenum; + float origin[3]; +} +zymvertex_t; + +#endif + diff --git a/app/jni/modelgen.h b/app/jni/modelgen.h new file mode 100644 index 0000000..0ddb776 --- /dev/null +++ b/app/jni/modelgen.h @@ -0,0 +1,137 @@ +/* +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. + +*/ +// +// modelgen.h: header file for model generation program +// + +// ********************************************************* +// * This file must be identical in the modelgen directory * +// * and in the Quake directory, because it's used to * +// * pass data from one to the other via model files. * +// ********************************************************* + +#ifndef MODELGEN_H +#define MODELGEN_H + +#define ALIAS_VERSION 6 + +#define ALIAS_ONSEAM 0x0020 + +typedef enum aliasframetype_e { ALIAS_SINGLE=0, ALIAS_GROUP } aliasframetype_t; + +typedef enum aliasskintype_e { ALIAS_SKIN_SINGLE=0, ALIAS_SKIN_GROUP } aliasskintype_t; + +typedef struct mdl_s +{ + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + synctype_t synctype; + int flags; + float size; +} +mdl_t; + +// TODO: could be shorts + +typedef struct stvert_s +{ + int onseam; + int s; + int t; +} +stvert_t; + +typedef struct dtriangle_s +{ + int facesfront; + int vertindex[3]; +} +dtriangle_t; + +#define DT_FACES_FRONT 0x0010 + +// This mirrors trivert_t in trilib.h, is present so Quake knows how to +// load this data + +typedef struct trivertx_s +{ + unsigned char v[3]; + unsigned char lightnormalindex; +} +trivertx_t; + +typedef struct daliasframe_s +{ + trivertx_t bboxmin; // lightnormal isn't used + trivertx_t bboxmax; // lightnormal isn't used + char name[16]; // frame name from grabbing +} +daliasframe_t; + +typedef struct daliasgroup_s +{ + int numframes; + trivertx_t bboxmin; // lightnormal isn't used + trivertx_t bboxmax; // lightnormal isn't used +} +daliasgroup_t; + +typedef struct daliasskingroup_s +{ + int numskins; +} +daliasskingroup_t; + +typedef struct daliasinterval_s +{ + float interval; +} +daliasinterval_t; + +typedef struct daliasskininterval_s +{ + float interval; +} +daliasskininterval_t; + +typedef struct daliasframetype_s +{ + aliasframetype_t type; +} +daliasframetype_t; + +typedef struct daliasskintype_s +{ + aliasskintype_t type; +} +daliasskintype_t; + +#endif + diff --git a/app/jni/mprogdefs.h b/app/jni/mprogdefs.h new file mode 100644 index 0000000..c7993b7 --- /dev/null +++ b/app/jni/mprogdefs.h @@ -0,0 +1,22 @@ + +#ifndef MPROGDEFS_H +#define MPROGDEFS_H + +/* file generated by qcc, do not modify */ + +/* +typedef struct m_globalvars_s +{ + int pad[28]; + int self; +} m_globalvars_t; + +typedef struct m_entvars_s +{ +} m_entvars_t; + +#define M_PROGHEADER_CRC 10020 + +*/ + +#endif diff --git a/app/jni/mvm_cmds.c b/app/jni/mvm_cmds.c new file mode 100644 index 0000000..9a23640 --- /dev/null +++ b/app/jni/mvm_cmds.c @@ -0,0 +1,1577 @@ +#include "quakedef.h" + +#include "prvm_cmds.h" +#include "clvm_cmds.h" +#include "menu.h" +#include "csprogs.h" + +// TODO check which strings really should be engine strings + +//============================================================================ +// Menu + +const char *vm_m_extensions = +"BX_WAL_SUPPORT " +"DP_CINEMATIC_DPV " +"DP_CSQC_BINDMAPS " +"DP_CRYPTO " +"DP_GFX_FONTS " +"DP_GFX_FONTS_FREETYPE " +"DP_UTF8 " +"DP_FONT_VARIABLEWIDTH " +"DP_MENU_EXTRESPONSEPACKET " +"DP_QC_ASINACOSATANATAN2TAN " +"DP_QC_AUTOCVARS " +"DP_QC_CMD " +"DP_QC_CRC16 " +"DP_QC_CVAR_TYPE " +"DP_QC_CVAR_DESCRIPTION " +"DP_QC_DIGEST " +"DP_QC_DIGEST_SHA256 " +"DP_QC_FINDCHAIN_TOFIELD " +"DP_QC_I18N " +"DP_QC_LOG " +"DP_QC_RENDER_SCENE " +"DP_QC_SPRINTF " +"DP_QC_STRFTIME " +"DP_QC_STRINGBUFFERS " +"DP_QC_STRINGBUFFERS_CVARLIST " +"DP_QC_STRINGBUFFERS_EXT_WIP " +"DP_QC_STRINGCOLORFUNCTIONS " +"DP_QC_STRING_CASE_FUNCTIONS " +"DP_QC_STRREPLACE " +"DP_QC_TOKENIZEBYSEPARATOR " +"DP_QC_TOKENIZE_CONSOLE " +"DP_QC_UNLIMITEDTEMPSTRINGS " +"DP_QC_URI_ESCAPE " +"DP_QC_URI_GET " +"DP_QC_URI_POST " +"DP_QC_WHICHPACK " +"FTE_STRINGS " +; + +/* +========= +VM_M_setmousetarget + +setmousetarget(float target) +========= +*/ +static void VM_M_setmousetarget(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_setmousetarget); + + switch((int)PRVM_G_FLOAT(OFS_PARM0)) + { + case 1: + in_client_mouse = false; + break; + case 2: + in_client_mouse = true; + break; + default: + prog->error_cmd("VM_M_setmousetarget: wrong destination %f !",PRVM_G_FLOAT(OFS_PARM0)); + } +} + +/* +========= +VM_M_getmousetarget + +float getmousetarget +========= +*/ +static void VM_M_getmousetarget(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_M_getmousetarget); + + if(in_client_mouse) + PRVM_G_FLOAT(OFS_RETURN) = 2; + else + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + + + +/* +========= +VM_M_setkeydest + +setkeydest(float dest) +========= +*/ +static void VM_M_setkeydest(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_M_setkeydest); + + switch((int)PRVM_G_FLOAT(OFS_PARM0)) + { + case 0: + // key_game + key_dest = key_game; + break; + case 2: + // key_menu + key_dest = key_menu; + break; + case 3: + // key_menu_grabbed + key_dest = key_menu_grabbed; + break; + case 1: + // key_message + // key_dest = key_message + // break; + default: + prog->error_cmd("VM_M_setkeydest: wrong destination %f !", PRVM_G_FLOAT(OFS_PARM0)); + } +} + +/* +========= +VM_M_getkeydest + +float getkeydest +========= +*/ +static void VM_M_getkeydest(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_M_getkeydest); + + // key_game = 0, key_message = 1, key_menu = 2, key_menu_grabbed = 3, unknown = -1 + switch(key_dest) + { + case key_game: + PRVM_G_FLOAT(OFS_RETURN) = 0; + break; + case key_menu: + PRVM_G_FLOAT(OFS_RETURN) = 2; + break; + case key_menu_grabbed: + PRVM_G_FLOAT(OFS_RETURN) = 3; + break; + case key_message: + // not supported + // PRVM_G_FLOAT(OFS_RETURN) = 1; + // break; + default: + PRVM_G_FLOAT(OFS_RETURN) = -1; + } +} + + +/* +========= +VM_M_getresolution + +vector getresolution(float number) +========= +*/ +static void VM_M_getresolution(prvm_prog_t *prog) +{ + int nr, fs; + VM_SAFEPARMCOUNTRANGE(1, 2, VM_getresolution); + + nr = (int)PRVM_G_FLOAT(OFS_PARM0); + + fs = ((prog->argc <= 1) || ((int)PRVM_G_FLOAT(OFS_PARM1))); + + if(nr < 0 || nr >= (fs ? video_resolutions_count : video_resolutions_hardcoded_count)) + { + PRVM_G_VECTOR(OFS_RETURN)[0] = 0; + PRVM_G_VECTOR(OFS_RETURN)[1] = 0; + PRVM_G_VECTOR(OFS_RETURN)[2] = 0; + } + else + { + video_resolution_t *r = &((fs ? video_resolutions : video_resolutions_hardcoded)[nr]); + PRVM_G_VECTOR(OFS_RETURN)[0] = r->width; + PRVM_G_VECTOR(OFS_RETURN)[1] = r->height; + PRVM_G_VECTOR(OFS_RETURN)[2] = r->pixelheight; + } +} + +static void VM_M_getgamedirinfo(prvm_prog_t *prog) +{ + int nr, item; + VM_SAFEPARMCOUNT(2, VM_getgamedirinfo); + + nr = (int)PRVM_G_FLOAT(OFS_PARM0); + item = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; + + if(nr >= 0 && nr < fs_all_gamedirs_count) + { + if(item == 0) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, fs_all_gamedirs[nr].name ); + else if(item == 1) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, fs_all_gamedirs[nr].description ); + } +} + +/* +========= +VM_M_getserverliststat + +float getserverliststat(float type) +========= +*/ +/* + type: +0 serverlist_viewcount +1 serverlist_totalcount +2 masterquerycount +3 masterreplycount +4 serverquerycount +5 serverreplycount +6 sortfield +7 sortflags +*/ +static void VM_M_getserverliststat(prvm_prog_t *prog) +{ + int type; + VM_SAFEPARMCOUNT ( 1, VM_M_getserverliststat ); + + PRVM_G_FLOAT( OFS_RETURN ) = 0; + + type = (int)PRVM_G_FLOAT( OFS_PARM0 ); + switch(type) + { + case 0: + PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_viewcount; + return; + case 1: + PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_cachecount; + return; + case 2: + PRVM_G_FLOAT ( OFS_RETURN ) = masterquerycount; + return; + case 3: + PRVM_G_FLOAT ( OFS_RETURN ) = masterreplycount; + return; + case 4: + PRVM_G_FLOAT ( OFS_RETURN ) = serverquerycount; + return; + case 5: + PRVM_G_FLOAT ( OFS_RETURN ) = serverreplycount; + return; + case 6: + PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_sortbyfield; + return; + case 7: + PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_sortflags; + return; + default: + VM_Warning(prog, "VM_M_getserverliststat: bad type %i!\n", type ); + } +} + +/* +======================== +VM_M_resetserverlistmasks + +resetserverlistmasks() +======================== +*/ +static void VM_M_resetserverlistmasks(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_M_resetserverlistmasks); + ServerList_ResetMasks(); +} + + +/* +======================== +VM_M_setserverlistmaskstring + +setserverlistmaskstring(float mask, float fld, string str, float op) +0-511 and +512 - 1024 or +======================== +*/ +static void VM_M_setserverlistmaskstring(prvm_prog_t *prog) +{ + const char *str; + int masknr; + serverlist_mask_t *mask; + int field; + + VM_SAFEPARMCOUNT( 4, VM_M_setserverlistmaskstring ); + str = PRVM_G_STRING( OFS_PARM2 ); + + masknr = (int)PRVM_G_FLOAT( OFS_PARM0 ); + if( masknr >= 0 && masknr <= SERVERLIST_ANDMASKCOUNT ) + mask = &serverlist_andmasks[masknr]; + else if( masknr >= 512 && masknr - 512 <= SERVERLIST_ORMASKCOUNT ) + mask = &serverlist_ormasks[masknr - 512 ]; + else + { + VM_Warning(prog, "VM_M_setserverlistmaskstring: invalid mask number %i\n", masknr ); + return; + } + + field = (int) PRVM_G_FLOAT( OFS_PARM1 ); + + switch( field ) { + case SLIF_CNAME: + strlcpy( mask->info.cname, str, sizeof(mask->info.cname) ); + break; + case SLIF_NAME: + strlcpy( mask->info.name, str, sizeof(mask->info.name) ); + break; + case SLIF_QCSTATUS: + strlcpy( mask->info.qcstatus, str, sizeof(mask->info.qcstatus) ); + break; + case SLIF_PLAYERS: + strlcpy( mask->info.players, str, sizeof(mask->info.players) ); + break; + case SLIF_MAP: + strlcpy( mask->info.map, str, sizeof(mask->info.map) ); + break; + case SLIF_MOD: + strlcpy( mask->info.mod, str, sizeof(mask->info.mod) ); + break; + case SLIF_GAME: + strlcpy( mask->info.game, str, sizeof(mask->info.game) ); + break; + default: + VM_Warning(prog, "VM_M_setserverlistmaskstring: Bad field number %i passed!\n", field ); + return; + } + + mask->active = true; + mask->tests[field] = (serverlist_maskop_t)((int)PRVM_G_FLOAT( OFS_PARM3 )); +} + +/* +======================== +VM_M_setserverlistmasknumber + +setserverlistmasknumber(float mask, float fld, float num, float op) + +0-511 and +512 - 1024 or +======================== +*/ +static void VM_M_setserverlistmasknumber(prvm_prog_t *prog) +{ + int number; + serverlist_mask_t *mask; + int masknr; + int field; + VM_SAFEPARMCOUNT( 4, VM_M_setserverlistmasknumber ); + + masknr = (int)PRVM_G_FLOAT( OFS_PARM0 ); + if( masknr >= 0 && masknr <= SERVERLIST_ANDMASKCOUNT ) + mask = &serverlist_andmasks[masknr]; + else if( masknr >= 512 && masknr - 512 <= SERVERLIST_ORMASKCOUNT ) + mask = &serverlist_ormasks[masknr - 512 ]; + else + { + VM_Warning(prog, "VM_M_setserverlistmasknumber: invalid mask number %i\n", masknr ); + return; + } + + number = (int)PRVM_G_FLOAT( OFS_PARM2 ); + field = (int) PRVM_G_FLOAT( OFS_PARM1 ); + + switch( field ) { + case SLIF_MAXPLAYERS: + mask->info.maxplayers = number; + break; + case SLIF_NUMPLAYERS: + mask->info.numplayers = number; + break; + case SLIF_NUMBOTS: + mask->info.numbots = number; + break; + case SLIF_NUMHUMANS: + mask->info.numhumans = number; + break; + case SLIF_PING: + mask->info.ping = number; + break; + case SLIF_PROTOCOL: + mask->info.protocol = number; + break; + case SLIF_FREESLOTS: + mask->info.freeslots = number; + break; + case SLIF_ISFAVORITE: + mask->info.isfavorite = number != 0; + break; + default: + VM_Warning(prog, "VM_M_setserverlistmasknumber: Bad field number %i passed!\n", field ); + return; + } + + mask->active = true; + mask->tests[field] = (serverlist_maskop_t)((int)PRVM_G_FLOAT( OFS_PARM3 )); +} + + +/* +======================== +VM_M_resortserverlist + +resortserverlist +======================== +*/ +static void VM_M_resortserverlist(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_M_resortserverlist); + ServerList_RebuildViewList(); +} + +/* +========= +VM_M_getserverliststring + +string getserverliststring(float field, float hostnr) +========= +*/ +static void VM_M_getserverliststring(prvm_prog_t *prog) +{ + serverlist_entry_t *cache; + int hostnr; + + VM_SAFEPARMCOUNT(2, VM_M_getserverliststring); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + + hostnr = (int)PRVM_G_FLOAT(OFS_PARM1); + + if(hostnr < 0 || hostnr >= serverlist_viewcount) + { + Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); + return; + } + cache = ServerList_GetViewEntry(hostnr); + switch( (int) PRVM_G_FLOAT(OFS_PARM0) ) { + case SLIF_CNAME: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.cname ); + break; + case SLIF_NAME: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.name ); + break; + case SLIF_QCSTATUS: + PRVM_G_INT (OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.qcstatus ); + break; + case SLIF_PLAYERS: + PRVM_G_INT (OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.players ); + break; + case SLIF_GAME: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.game ); + break; + case SLIF_MOD: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.mod ); + break; + case SLIF_MAP: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.map ); + break; + // TODO remove this again + case 1024: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->line1 ); + break; + case 1025: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->line2 ); + break; + default: + Con_Print("VM_M_getserverliststring: bad field number passed!\n"); + } +} + +/* +========= +VM_M_getserverlistnumber + +float getserverlistnumber(float field, float hostnr) +========= +*/ +static void VM_M_getserverlistnumber(prvm_prog_t *prog) +{ + serverlist_entry_t *cache; + int hostnr; + + VM_SAFEPARMCOUNT(2, VM_M_getserverliststring); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + + hostnr = (int)PRVM_G_FLOAT(OFS_PARM1); + + if(hostnr < 0 || hostnr >= serverlist_viewcount) + { + Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); + return; + } + cache = ServerList_GetViewEntry(hostnr); + switch( (int) PRVM_G_FLOAT(OFS_PARM0) ) { + case SLIF_MAXPLAYERS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.maxplayers; + break; + case SLIF_NUMPLAYERS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numplayers; + break; + case SLIF_NUMBOTS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numbots; + break; + case SLIF_NUMHUMANS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numhumans; + break; + case SLIF_FREESLOTS: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.freeslots; + break; + case SLIF_PING: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.ping; + break; + case SLIF_PROTOCOL: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.protocol; + break; + case SLIF_ISFAVORITE: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.isfavorite; + break; + default: + Con_Print("VM_M_getserverlistnumber: bad field number passed!\n"); + } +} + +/* +======================== +VM_M_setserverlistsort + +setserverlistsort(float field, float flags) +======================== +*/ +static void VM_M_setserverlistsort(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT( 2, VM_M_setserverlistsort ); + + serverlist_sortbyfield = (serverlist_infofield_t)((int)PRVM_G_FLOAT( OFS_PARM0 )); + serverlist_sortflags = (int) PRVM_G_FLOAT( OFS_PARM1 ); +} + +/* +======================== +VM_M_refreshserverlist + +refreshserverlist() +======================== +*/ +static void VM_M_refreshserverlist(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT( 0, VM_M_refreshserverlist ); + ServerList_QueryList(false, true, false, false); +} + +/* +======================== +VM_M_getserverlistindexforkey + +float getserverlistindexforkey(string key) +======================== +*/ +static void VM_M_getserverlistindexforkey(prvm_prog_t *prog) +{ + const char *key; + VM_SAFEPARMCOUNT( 1, VM_M_getserverlistindexforkey ); + + key = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( prog, key ); + + if( !strcmp( key, "cname" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_CNAME; + else if( !strcmp( key, "ping" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PING; + else if( !strcmp( key, "game" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_GAME; + else if( !strcmp( key, "mod" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MOD; + else if( !strcmp( key, "map" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MAP; + else if( !strcmp( key, "name" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NAME; + else if( !strcmp( key, "qcstatus" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_QCSTATUS; + else if( !strcmp( key, "players" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PLAYERS; + else if( !strcmp( key, "maxplayers" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MAXPLAYERS; + else if( !strcmp( key, "numplayers" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMPLAYERS; + else if( !strcmp( key, "numbots" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMBOTS; + else if( !strcmp( key, "numhumans" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMHUMANS; + else if( !strcmp( key, "freeslots" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_FREESLOTS; + else if( !strcmp( key, "protocol" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PROTOCOL; + else if( !strcmp( key, "isfavorite" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_ISFAVORITE; + else + PRVM_G_FLOAT( OFS_RETURN ) = -1; +} + +/* +======================== +VM_M_addwantedserverlistkey + +addwantedserverlistkey(string key) +======================== +*/ +static void VM_M_addwantedserverlistkey(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT( 1, VM_M_addwantedserverlistkey ); +} + +/* +=============================================================================== +MESSAGE WRITING + +used only for client and menu +server uses VM_SV_... + +Write*(* data, float type, float to) + +=============================================================================== +*/ + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string + +static sizebuf_t *VM_M_WriteDest (prvm_prog_t *prog) +{ + int dest; + int destclient; + + if(!sv.active) + prog->error_cmd("VM_M_WriteDest: game is not server (%s)", prog->name); + + dest = (int)PRVM_G_FLOAT(OFS_PARM1); + switch (dest) + { + case MSG_BROADCAST: + return &sv.datagram; + + case MSG_ONE: + destclient = (int) PRVM_G_FLOAT(OFS_PARM2); + if (destclient < 0 || destclient >= svs.maxclients || !svs.clients[destclient].active || !svs.clients[destclient].netconnection) + prog->error_cmd("VM_clientcommand: %s: invalid client !", prog->name); + + return &svs.clients[destclient].netconnection->message; + + case MSG_ALL: + return &sv.reliable_datagram; + + case MSG_INIT: + return &sv.signon; + + default: + prog->error_cmd("WriteDest: bad destination"); + break; + } + + return NULL; +} + +static void VM_M_WriteByte (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteByte); + MSG_WriteByte (VM_M_WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM0)); +} + +static void VM_M_WriteChar (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteChar); + MSG_WriteChar (VM_M_WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM0)); +} + +static void VM_M_WriteShort (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteShort); + MSG_WriteShort (VM_M_WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM0)); +} + +static void VM_M_WriteLong (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteLong); + MSG_WriteLong (VM_M_WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM0)); +} + +static void VM_M_WriteAngle (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteAngle); + MSG_WriteAngle (VM_M_WriteDest(prog), PRVM_G_FLOAT(OFS_PARM0), sv.protocol); +} + +static void VM_M_WriteCoord (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteCoord); + MSG_WriteCoord (VM_M_WriteDest(prog), PRVM_G_FLOAT(OFS_PARM0), sv.protocol); +} + +static void VM_M_WriteString (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteString); + MSG_WriteString (VM_M_WriteDest(prog), PRVM_G_STRING(OFS_PARM0)); +} + +static void VM_M_WriteEntity (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_M_WriteEntity); + MSG_WriteShort (VM_M_WriteDest(prog), PRVM_G_EDICTNUM(OFS_PARM0)); +} + +/* +================= +VM_M_copyentity + +copies data from one entity to another + +copyentity(entity src, entity dst) +================= +*/ +static void VM_M_copyentity (prvm_prog_t *prog) +{ + prvm_edict_t *in, *out; + VM_SAFEPARMCOUNT(2,VM_M_copyentity); + in = PRVM_G_EDICT(OFS_PARM0); + out = PRVM_G_EDICT(OFS_PARM1); + memcpy(out->fields.fp, in->fields.fp, prog->entityfields * sizeof(prvm_vec_t)); +} + +//#66 vector() getmousepos (EXT_CSQC) +static void VM_M_getmousepos(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_M_getmousepos); + + if (key_consoleactive || (key_dest != key_menu && key_dest != key_menu_grabbed)) + VectorSet(PRVM_G_VECTOR(OFS_RETURN), 0, 0, 0); + else if (in_client_mouse) + VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_windowmouse_x * vid_conwidth.integer / vid.width, in_windowmouse_y * vid_conheight.integer / vid.height, 0); + else + VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height, 0); +} + +static void VM_M_crypto_getkeyfp(prvm_prog_t *prog) +{ + lhnetaddress_t addr; + const char *s; + char keyfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getkeyfp); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( prog, s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, keyfp, sizeof(keyfp), NULL, 0, NULL)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, keyfp ); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +static void VM_M_crypto_getidfp(prvm_prog_t *prog) +{ + lhnetaddress_t addr; + const char *s; + char idfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getidfp); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( prog, s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, idfp, sizeof(idfp), NULL)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, idfp ); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +static void VM_M_crypto_getencryptlevel(prvm_prog_t *prog) +{ + lhnetaddress_t addr; + const char *s; + int aeslevel; + char vabuf[1024]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getencryptlevel); + + s = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString( prog, s ); + + if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, NULL, 0, &aeslevel)) + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, aeslevel ? va(vabuf, sizeof(vabuf), "%d AES128", aeslevel) : "0"); + else + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; +} +static void VM_M_crypto_getmykeyfp(prvm_prog_t *prog) +{ + int i; + char keyfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); + + i = PRVM_G_FLOAT( OFS_PARM0 ); + switch(Crypto_RetrieveLocalKey(i, keyfp, sizeof(keyfp), NULL, 0, NULL)) + { + case -1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, ""); + break; + case 0: + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; + break; + default: + case 1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, keyfp); + break; + } +} +static void VM_M_crypto_getmyidfp(prvm_prog_t *prog) +{ + int i; + char idfp[FP64_SIZE + 1]; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); + + i = PRVM_G_FLOAT( OFS_PARM0 ); + switch(Crypto_RetrieveLocalKey(i, NULL, 0, idfp, sizeof(idfp), NULL)) + { + case -1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, ""); + break; + case 0: + PRVM_G_INT( OFS_RETURN ) = OFS_NULL; + break; + default: + case 1: + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, idfp); + break; + } +} +static void VM_M_crypto_getmyidstatus(prvm_prog_t *prog) +{ + int i; + qboolean issigned; + + VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); + + i = PRVM_G_FLOAT( OFS_PARM0 ); + switch(Crypto_RetrieveLocalKey(i, NULL, 0, NULL, 0, &issigned)) + { + case -1: + PRVM_G_FLOAT( OFS_RETURN ) = 0; // have no ID there + break; + case 0: + PRVM_G_FLOAT( OFS_RETURN ) = -1; // out of range + break; + default: + case 1: + PRVM_G_FLOAT( OFS_RETURN ) = issigned ? 2 : 1; + break; + } +} + +prvm_builtin_t vm_m_builtins[] = { +NULL, // #0 NULL function (not callable) +VM_checkextension, // #1 +VM_error, // #2 +VM_objerror, // #3 +VM_print, // #4 +VM_bprint, // #5 +VM_sprint, // #6 +VM_centerprint, // #7 +VM_normalize, // #8 +VM_vlen, // #9 +VM_vectoyaw, // #10 +VM_vectoangles, // #11 +VM_random, // #12 +VM_localcmd, // #13 +VM_cvar, // #14 +VM_cvar_set, // #15 +VM_dprint, // #16 +VM_ftos, // #17 +VM_fabs, // #18 +VM_vtos, // #19 +VM_etos, // #20 +VM_stof, // #21 +VM_spawn, // #22 +VM_remove, // #23 +VM_find, // #24 +VM_findfloat, // #25 +VM_findchain, // #26 +VM_findchainfloat, // #27 +VM_precache_file, // #28 +VM_precache_sound, // #29 +VM_coredump, // #30 +VM_traceon, // #31 +VM_traceoff, // #32 +VM_eprint, // #33 +VM_rint, // #34 +VM_floor, // #35 +VM_ceil, // #36 +VM_nextent, // #37 +VM_sin, // #38 +VM_cos, // #39 +VM_sqrt, // #40 +VM_randomvec, // #41 +VM_registercvar, // #42 +VM_min, // #43 +VM_max, // #44 +VM_bound, // #45 +VM_pow, // #46 +VM_M_copyentity, // #47 +VM_fopen, // #48 +VM_fclose, // #49 +VM_fgets, // #50 +VM_fputs, // #51 +VM_strlen, // #52 +VM_strcat, // #53 +VM_substring, // #54 +VM_stov, // #55 +VM_strzone, // #56 +VM_strunzone, // #57 +VM_tokenize, // #58 +VM_argv, // #59 +VM_isserver, // #60 +VM_clientcount, // #61 +VM_clientstate, // #62 +VM_clcommand, // #63 +VM_changelevel, // #64 +VM_localsound, // #65 +VM_M_getmousepos, // #66 +VM_gettime, // #67 +VM_loadfromdata, // #68 +VM_loadfromfile, // #69 +VM_modulo, // #70 +VM_cvar_string, // #71 +VM_crash, // #72 +VM_stackdump, // #73 +VM_search_begin, // #74 +VM_search_end, // #75 +VM_search_getsize, // #76 +VM_search_getfilename, // #77 +VM_chr, // #78 +VM_itof, // #79 +VM_ftoe, // #80 +VM_itof, // #81 isString +VM_altstr_count, // #82 +VM_altstr_prepare, // #83 +VM_altstr_get, // #84 +VM_altstr_set, // #85 +VM_altstr_ins, // #86 +VM_findflags, // #87 +VM_findchainflags, // #88 +VM_cvar_defstring, // #89 +// deactivate support for model rendering in the menu until someone has time to do it right [3/2/2008 Andreas] +#if 0 +VM_CL_setmodel, // #90 void(entity e, string m) setmodel (QUAKE) +VM_CL_precache_model, // #91 void(string s) precache_model (QUAKE) +VM_CL_setorigin, // #92 void(entity e, vector o) setorigin (QUAKE) +#else +NULL, +NULL, +NULL, +#endif +NULL, // #93 +NULL, // #94 +NULL, // #95 +NULL, // #96 +NULL, // #97 +NULL, // #98 +NULL, // #99 +NULL, // #100 +NULL, // #101 +NULL, // #102 +NULL, // #103 +NULL, // #104 +NULL, // #105 +NULL, // #106 +NULL, // #107 +NULL, // #108 +NULL, // #109 +NULL, // #110 +NULL, // #111 +NULL, // #112 +NULL, // #113 +NULL, // #114 +NULL, // #115 +NULL, // #116 +NULL, // #117 +NULL, // #118 +NULL, // #119 +NULL, // #120 +NULL, // #121 +NULL, // #122 +NULL, // #123 +NULL, // #124 +NULL, // #125 +NULL, // #126 +NULL, // #127 +NULL, // #128 +NULL, // #129 +NULL, // #130 +NULL, // #131 +NULL, // #132 +NULL, // #133 +NULL, // #134 +NULL, // #135 +NULL, // #136 +NULL, // #137 +NULL, // #138 +NULL, // #139 +NULL, // #140 +NULL, // #141 +NULL, // #142 +NULL, // #143 +NULL, // #144 +NULL, // #145 +NULL, // #146 +NULL, // #147 +NULL, // #148 +NULL, // #149 +NULL, // #150 +NULL, // #151 +NULL, // #152 +NULL, // #153 +NULL, // #154 +NULL, // #155 +NULL, // #156 +NULL, // #157 +NULL, // #158 +NULL, // #159 +NULL, // #160 +NULL, // #161 +NULL, // #162 +NULL, // #163 +NULL, // #164 +NULL, // #165 +NULL, // #166 +NULL, // #167 +NULL, // #168 +NULL, // #169 +NULL, // #170 +NULL, // #171 +NULL, // #172 +NULL, // #173 +NULL, // #174 +NULL, // #175 +NULL, // #176 +NULL, // #177 +NULL, // #178 +NULL, // #179 +NULL, // #180 +NULL, // #181 +NULL, // #182 +NULL, // #183 +NULL, // #184 +NULL, // #185 +NULL, // #186 +NULL, // #187 +NULL, // #188 +NULL, // #189 +NULL, // #190 +NULL, // #191 +NULL, // #192 +NULL, // #193 +NULL, // #194 +NULL, // #195 +NULL, // #196 +NULL, // #197 +NULL, // #198 +NULL, // #199 +NULL, // #200 +NULL, // #201 +NULL, // #202 +NULL, // #203 +NULL, // #204 +NULL, // #205 +NULL, // #206 +NULL, // #207 +NULL, // #208 +NULL, // #209 +NULL, // #210 +NULL, // #211 +NULL, // #212 +NULL, // #213 +NULL, // #214 +NULL, // #215 +NULL, // #216 +NULL, // #217 +NULL, // #218 +NULL, // #219 +NULL, // #220 +VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) +VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) +VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) +VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) +VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) +VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) +VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) +VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) +VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) +VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) +NULL, // #231 +NULL, // #232 +NULL, // #233 +NULL, // #234 +NULL, // #235 +NULL, // #236 +NULL, // #237 +NULL, // #238 +NULL, // #239 +NULL, // #240 +NULL, // #241 +NULL, // #242 +NULL, // #243 +NULL, // #244 +NULL, // #245 +NULL, // #246 +NULL, // #247 +NULL, // #248 +NULL, // #249 +NULL, // #250 +NULL, // #251 +NULL, // #252 +NULL, // #253 +NULL, // #254 +NULL, // #255 +NULL, // #256 +NULL, // #257 +NULL, // #258 +NULL, // #259 +NULL, // #260 +NULL, // #261 +NULL, // #262 +NULL, // #263 +NULL, // #264 +NULL, // #265 +NULL, // #266 +NULL, // #267 +NULL, // #268 +NULL, // #269 +NULL, // #270 +NULL, // #271 +NULL, // #272 +NULL, // #273 +NULL, // #274 +NULL, // #275 +NULL, // #276 +NULL, // #277 +NULL, // #278 +NULL, // #279 +NULL, // #280 +NULL, // #281 +NULL, // #282 +NULL, // #283 +NULL, // #284 +NULL, // #285 +NULL, // #286 +NULL, // #287 +NULL, // #288 +NULL, // #289 +NULL, // #290 +NULL, // #291 +NULL, // #292 +NULL, // #293 +NULL, // #294 +NULL, // #295 +NULL, // #296 +NULL, // #297 +NULL, // #298 +NULL, // #299 +// deactivate support for model rendering in the menu until someone has time to do it right [3/2/2008 Andreas] +#if 0 +// CSQC range #300-#399 +VM_CL_R_ClearScene, // #300 void() clearscene (DP_QC_RENDER_SCENE) +VM_CL_R_AddEntities, // #301 void(float mask) addentities (DP_QC_RENDER_SCENE) +VM_CL_R_AddEntity, // #302 void(entity ent) addentity (DP_QC_RENDER_SCENE) +VM_CL_R_SetView, // #303 float(float property, ...) setproperty (DP_QC_RENDER_SCENE) +VM_CL_R_RenderScene, // #304 void() renderscene (DP_QC_RENDER_SCENE) +VM_CL_R_AddDynamicLight, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (DP_QC_RENDER_SCENE) +VM_CL_R_PolygonBegin, // #306 void(string texturename, float flag[, float is2d, float lines]) R_BeginPolygon (DP_QC_RENDER_SCENE) +VM_CL_R_PolygonVertex, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex (DP_QC_RENDER_SCENE) +VM_CL_R_PolygonEnd, // #308 void() R_EndPolygon +NULL/*VM_CL_R_LoadWorldModel*/, // #309 void(string modelname) R_LoadWorldModel +// TODO: rearrange and merge all builtin lists and share as many extensions as possible between all VM instances [1/27/2008 Andreas] +VM_CL_setattachment, // #310 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) (DP_QC_RENDER_SCENE) +VM_CL_gettagindex, // #311 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) (DP_QC_RENDER_SCENE) +VM_CL_gettaginfo, // #312 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) (DP_QC_RENDER_SCENE) +#else +// CSQC range #300-#399 +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +NULL, +#endif +NULL, // #313 +NULL, // #314 +NULL, // #315 +NULL, // #316 +NULL, // #317 +NULL, // #318 +NULL, // #319 +NULL, // #320 +NULL, // #321 +NULL, // #322 +NULL, // #323 +NULL, // #324 +NULL, // #325 +NULL, // #326 +NULL, // #327 +NULL, // #328 +NULL, // #329 +NULL, // #330 +NULL, // #331 +NULL, // #332 +NULL, // #333 +NULL, // #334 +NULL, // #335 +NULL, // #336 +NULL, // #337 +NULL, // #338 +NULL, // #339 +VM_keynumtostring, // #340 string keynumtostring(float keynum) +VM_stringtokeynum, // #341 float stringtokeynum(string key) +VM_getkeybind, // #342 string(float keynum[, float bindmap]) getkeybind (EXT_CSQC) +NULL, // #343 +NULL, // #344 +NULL, // #345 +NULL, // #346 +NULL, // #347 +NULL, // #348 +VM_CL_isdemo, // #349 +NULL, // #350 +NULL, // #351 +NULL, // #352 +VM_wasfreed, // #353 float(entity ent) wasfreed +NULL, // #354 +VM_CL_videoplaying, // #355 +VM_findfont, // #356 float(string fontname) loadfont (DP_GFX_FONTS) +VM_loadfont, // #357 float(string fontname, string fontmaps, string sizes, float slot) loadfont (DP_GFX_FONTS) +NULL, // #358 +NULL, // #359 +NULL, // #360 +NULL, // #361 +NULL, // #362 +NULL, // #363 +NULL, // #364 +NULL, // #365 +NULL, // #366 +NULL, // #367 +NULL, // #368 +NULL, // #369 +NULL, // #370 +NULL, // #371 +NULL, // #372 +NULL, // #373 +NULL, // #374 +NULL, // #375 +NULL, // #376 +NULL, // #377 +NULL, // #378 +NULL, // #379 +NULL, // #380 +NULL, // #381 +NULL, // #382 +NULL, // #383 +NULL, // #384 +NULL, // #385 +NULL, // #386 +NULL, // #387 +NULL, // #388 +NULL, // #389 +NULL, // #390 +NULL, // #391 +NULL, // #392 +NULL, // #393 +NULL, // #394 +NULL, // #395 +NULL, // #396 +NULL, // #397 +NULL, // #398 +NULL, // #399 +NULL, // #400 +VM_M_WriteByte, // #401 +VM_M_WriteChar, // #402 +VM_M_WriteShort, // #403 +VM_M_WriteLong, // #404 +VM_M_WriteAngle, // #405 +VM_M_WriteCoord, // #406 +VM_M_WriteString, // #407 +VM_M_WriteEntity, // #408 +NULL, // #409 +NULL, // #410 +NULL, // #411 +NULL, // #412 +NULL, // #413 +NULL, // #414 +NULL, // #415 +NULL, // #416 +NULL, // #417 +NULL, // #418 +NULL, // #419 +NULL, // #420 +NULL, // #421 +NULL, // #422 +NULL, // #423 +NULL, // #424 +NULL, // #425 +NULL, // #426 +NULL, // #427 +NULL, // #428 +NULL, // #429 +NULL, // #430 +NULL, // #431 +NULL, // #432 +NULL, // #433 +NULL, // #434 +NULL, // #435 +NULL, // #436 +NULL, // #437 +NULL, // #438 +NULL, // #439 +VM_buf_create, // #440 float() buf_create (DP_QC_STRINGBUFFERS) +VM_buf_del, // #441 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) +VM_buf_getsize, // #442 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) +VM_buf_copy, // #443 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) +VM_buf_sort, // #444 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) +VM_buf_implode, // #445 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) +VM_bufstr_get, // #446 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) +VM_bufstr_set, // #447 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) +VM_bufstr_add, // #448 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) +VM_bufstr_free, // #449 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) +NULL, // #450 +VM_iscachedpic, // #451 draw functions... +VM_precache_pic, // #452 +VM_freepic, // #453 +VM_drawcharacter, // #454 +VM_drawstring, // #455 +VM_drawpic, // #456 +VM_drawfill, // #457 +VM_drawsetcliparea, // #458 +VM_drawresetcliparea, // #459 +VM_getimagesize, // #460 +VM_cin_open, // #461 +VM_cin_close, // #462 +VM_cin_setstate, // #463 +VM_cin_getstate, // #464 +VM_cin_restart, // #465 +VM_drawline, // #466 +VM_drawcolorcodedstring, // #467 +VM_stringwidth, // #468 +VM_drawsubpic, // #469 +VM_drawrotpic, // #470 +VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) +VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) +VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) +VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) +VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) +VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) +VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_QC_STRINGCOLORFUNCTIONS) +VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) +VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) +VM_strtolower, // #480 string(string s) VM_strtolower : DRESK - Return string as lowercase +VM_strtoupper, // #481 string(string s) VM_strtoupper : DRESK - Return string as uppercase +NULL, // #482 +NULL, // #483 +VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) +VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) +NULL, // #486 +VM_gecko_create, // #487 float gecko_create( string name ) +VM_gecko_destroy, // #488 void gecko_destroy( string name ) +VM_gecko_navigate, // #489 void gecko_navigate( string name, string URI ) +VM_gecko_keyevent, // #490 float gecko_keyevent( string name, float key, float eventtype ) +VM_gecko_movemouse, // #491 void gecko_mousemove( string name, float x, float y ) +VM_gecko_resize, // #492 void gecko_resize( string name, float w, float h ) +VM_gecko_get_texture_extent, // #493 vector gecko_get_texture_extent( string name ) +VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) +VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) +VM_numentityfields, // #496 float() numentityfields = #496; (QP_QC_ENTITYDATA) +VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (DP_QC_ENTITYDATA) +VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) +VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) +VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) +NULL, // #501 +NULL, // #502 +VM_whichpack, // #503 string(string) whichpack = #503; +NULL, // #504 +NULL, // #505 +NULL, // #506 +NULL, // #507 +NULL, // #508 +NULL, // #509 +VM_uri_escape, // #510 string(string in) uri_escape = #510; +VM_uri_unescape, // #511 string(string in) uri_unescape = #511; +VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) +VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) +VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) +VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) +VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) +NULL, // #519 +NULL, // #520 +NULL, // #521 +NULL, // #522 +NULL, // #523 +NULL, // #524 +NULL, // #525 +NULL, // #526 +NULL, // #527 +NULL, // #528 +NULL, // #529 +NULL, // #530 +NULL, // #531 +VM_log, // #532 +VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) +VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) +VM_buf_loadfile, // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_buf_writefile, // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_bufstr_find, // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_matchpattern, // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP) +NULL, // #539 +NULL, // #540 +NULL, // #541 +NULL, // #542 +NULL, // #543 +NULL, // #544 +NULL, // #545 +NULL, // #546 +NULL, // #547 +NULL, // #548 +NULL, // #549 +NULL, // #550 +NULL, // #551 +NULL, // #552 +NULL, // #553 +NULL, // #554 +NULL, // #555 +NULL, // #556 +NULL, // #557 +NULL, // #558 +NULL, // #559 +NULL, // #560 +NULL, // #561 +NULL, // #562 +NULL, // #563 +NULL, // #564 +NULL, // #565 +NULL, // #566 +NULL, // #567 +NULL, // #568 +NULL, // #569 +NULL, // #570 +NULL, // #571 +NULL, // #572 +NULL, // #573 +NULL, // #574 +NULL, // #575 +NULL, // #576 +NULL, // #577 +NULL, // #578 +NULL, // #579 +NULL, // #580 +NULL, // #581 +NULL, // #582 +NULL, // #583 +NULL, // #584 +NULL, // #585 +NULL, // #586 +NULL, // #587 +NULL, // #588 +NULL, // #589 +NULL, // #590 +NULL, // #591 +NULL, // #592 +NULL, // #593 +NULL, // #594 +NULL, // #595 +NULL, // #596 +NULL, // #597 +NULL, // #598 +NULL, // #599 +NULL, // #600 +VM_M_setkeydest, // #601 void setkeydest(float dest) +VM_M_getkeydest, // #602 float getkeydest(void) +VM_M_setmousetarget, // #603 void setmousetarget(float trg) +VM_M_getmousetarget, // #604 float getmousetarget(void) +VM_callfunction, // #605 void callfunction(...) +VM_writetofile, // #606 void writetofile(float fhandle, entity ent) +VM_isfunction, // #607 float isfunction(string function_name) +VM_M_getresolution, // #608 vector getresolution(float number, [float forfullscreen]) +VM_keynumtostring, // #609 string keynumtostring(float keynum) +VM_findkeysforcommand, // #610 string findkeysforcommand(string command[, float bindmap]) +VM_M_getserverliststat, // #611 float gethostcachevalue(float type) +VM_M_getserverliststring, // #612 string gethostcachestring(float type, float hostnr) +VM_parseentitydata, // #613 void parseentitydata(entity ent, string data) +VM_stringtokeynum, // #614 float stringtokeynum(string key) +VM_M_resetserverlistmasks, // #615 void resethostcachemasks(void) +VM_M_setserverlistmaskstring, // #616 void sethostcachemaskstring(float mask, float fld, string str, float op) +VM_M_setserverlistmasknumber, // #617 void sethostcachemasknumber(float mask, float fld, float num, float op) +VM_M_resortserverlist, // #618 void resorthostcache(void) +VM_M_setserverlistsort, // #619 void sethostcachesort(float fld, float descending) +VM_M_refreshserverlist, // #620 void refreshhostcache(void) +VM_M_getserverlistnumber, // #621 float gethostcachenumber(float fld, float hostnr) +VM_M_getserverlistindexforkey,// #622 float gethostcacheindexforkey(string key) +VM_M_addwantedserverlistkey, // #623 void addwantedhostcachekey(string key) +VM_CL_getextresponse, // #624 string getextresponse(void) +VM_netaddress_resolve, // #625 string netaddress_resolve(string, float) +VM_M_getgamedirinfo, // #626 string getgamedirinfo(float n, float prop) +VM_sprintf, // #627 string sprintf(string format, ...) +NULL, // #628 +NULL, // #629 +VM_setkeybind, // #630 float(float key, string bind[, float bindmap]) setkeybind +VM_getbindmaps, // #631 vector(void) getbindmap +VM_setbindmaps, // #632 float(vector bm) setbindmap +VM_M_crypto_getkeyfp, // #633 string(string addr) crypto_getkeyfp +VM_M_crypto_getidfp, // #634 string(string addr) crypto_getidfp +VM_M_crypto_getencryptlevel, // #635 string(string addr) crypto_getencryptlevel +VM_M_crypto_getmykeyfp, // #636 string(float addr) crypto_getmykeyfp +VM_M_crypto_getmyidfp, // #637 string(float addr) crypto_getmyidfp +NULL, // #638 +VM_digest_hex, // #639 +NULL, // #640 +VM_M_crypto_getmyidstatus, // #641 float(float i) crypto_getmyidstatus +NULL +}; + +const int vm_m_numbuiltins = sizeof(vm_m_builtins) / sizeof(prvm_builtin_t); + +void MVM_init_cmd(prvm_prog_t *prog) +{ + r_refdef_scene_t *scene; + + VM_Cmd_Init(prog); + VM_Polygons_Reset(prog); + + scene = R_GetScenePointer( RST_MENU ); + + memset (scene, 0, sizeof (*scene)); + + scene->maxtempentities = 128; + scene->tempentities = (entity_render_t*) Mem_Alloc(prog->progs_mempool, sizeof(entity_render_t) * scene->maxtempentities); + + scene->maxentities = MAX_EDICTS + 256 + 512; + scene->entities = (entity_render_t **)Mem_Alloc(prog->progs_mempool, sizeof(entity_render_t *) * scene->maxentities); + + scene->ambient = 32.0f; +} + +void MVM_reset_cmd(prvm_prog_t *prog) +{ + // note: the menu's render entities are automatically freed when the prog's pool is freed + + //VM_Cmd_Init(); + VM_Cmd_Reset(prog); + VM_Polygons_Reset(prog); +} diff --git a/app/jni/netconn.c b/app/jni/netconn.c new file mode 100644 index 0000000..52f931d --- /dev/null +++ b/app/jni/netconn.c @@ -0,0 +1,3766 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2002 Mathieu Olivier +Copyright (C) 2003 Forest Hale + +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 "thread.h" +#include "lhnet.h" + +#define HAVE_SNPRINTF +#define PREFER_PORTABLE_SNPRINTF +#include "snprintf.h" + +// for secure rcon authentication +#include "hmac.h" +#include "mdfour.h" +#include + +#define QWMASTER_PORT 27000 +#define DPMASTER_PORT 27950 + +// note this defaults on for dedicated servers, off for listen servers +cvar_t sv_public = {0, "sv_public", "0", "1: advertises this server on the master server (so that players can find it in the server browser); 0: allow direct queries only; -1: do not respond to direct queries; -2: do not allow anyone to connect; -3: already block at getchallenge level"}; +cvar_t sv_public_rejectreason = {0, "sv_public_rejectreason", "The server is closing.", "Rejection reason for connects when sv_public is -2"}; +static cvar_t sv_heartbeatperiod = {CVAR_SAVE, "sv_heartbeatperiod", "120", "how often to send heartbeat in seconds (only used if sv_public is 1)"}; +extern cvar_t sv_status_privacy; + +static cvar_t sv_masters [] = +{ + {CVAR_SAVE, "sv_master1", "", "user-chosen master server 1"}, + {CVAR_SAVE, "sv_master2", "", "user-chosen master server 2"}, + {CVAR_SAVE, "sv_master3", "", "user-chosen master server 3"}, + {CVAR_SAVE, "sv_master4", "", "user-chosen master server 4"}, + {0, "sv_masterextra1", "69.59.212.88", "ghdigital.com - default master server 1 (admin: LordHavoc)"}, // admin: LordHavoc + {0, "sv_masterextra2", "64.22.107.125", "dpmaster.deathmask.net - default master server 2 (admin: Willis)"}, // admin: Willis + {0, "sv_masterextra3", "92.62.40.73", "dpmaster.tchr.no - default master server 3 (admin: tChr)"}, // admin: tChr +#ifdef SUPPORTIPV6 + {0, "sv_masterextra4", "[2a03:4000:2:225::51:334d]:27950", "dpmaster.sudo.rm-f.org - default master server 4 (admin: divVerent)"}, // admin: divVerent +#endif + {0, NULL, NULL, NULL} +}; + +static cvar_t sv_qwmasters [] = +{ + {CVAR_SAVE, "sv_qwmaster1", "", "user-chosen qwmaster server 1"}, + {CVAR_SAVE, "sv_qwmaster2", "", "user-chosen qwmaster server 2"}, + {CVAR_SAVE, "sv_qwmaster3", "", "user-chosen qwmaster server 3"}, + {CVAR_SAVE, "sv_qwmaster4", "", "user-chosen qwmaster server 4"}, + {0, "sv_qwmasterextra1", "master.quakeservers.net:27000", "Global master server. (admin: unknown)"}, + {0, "sv_qwmasterextra2", "asgaard.morphos-team.net:27000", "Global master server. (admin: unknown)"}, + {0, "sv_qwmasterextra3", "qwmaster.ocrana.de:27000", "German master server. (admin: unknown)"}, + {0, "sv_qwmasterextra4", "masterserver.exhale.de:27000", "German master server. (admin: unknown)"}, + {0, "sv_qwmasterextra5", "qwmaster.fodquake.net:27000", "Global master server. (admin: unknown)"}, + {0, NULL, NULL, NULL} +}; + +static double nextheartbeattime = 0; + +sizebuf_t cl_message; +sizebuf_t sv_message; +static unsigned char cl_message_buf[NET_MAXMESSAGE]; +static unsigned char sv_message_buf[NET_MAXMESSAGE]; +char cl_readstring[MAX_INPUTLINE]; +char sv_readstring[MAX_INPUTLINE]; + +cvar_t net_messagetimeout = {0, "net_messagetimeout","300", "drops players who have not sent any packets for this many seconds"}; +cvar_t net_connecttimeout = {0, "net_connecttimeout","15", "after requesting a connection, the client must reply within this many seconds or be dropped (cuts down on connect floods). Must be above 10 seconds."}; +cvar_t net_connectfloodblockingtimeout = {0, "net_connectfloodblockingtimeout", "5", "when a connection packet is received, it will block all future connect packets from that IP address for this many seconds (cuts down on connect floods). Note that this does not include retries from the same IP; these are handled earlier and let in."}; +cvar_t net_challengefloodblockingtimeout = {0, "net_challengefloodblockingtimeout", "0.5", "when a challenge packet is received, it will block all future challenge packets from that IP address for this many seconds (cuts down on challenge floods). DarkPlaces clients retry once per second, so this should be <= 1. Failure here may lead to connect attempts failing."}; +cvar_t net_getstatusfloodblockingtimeout = {0, "net_getstatusfloodblockingtimeout", "1", "when a getstatus packet is received, it will block all future getstatus packets from that IP address for this many seconds (cuts down on getstatus floods). DarkPlaces retries every 4 seconds, and qstat retries once per second, so this should be <= 1. Failure here may lead to server not showing up in the server list."}; +cvar_t hostname = {CVAR_SAVE, "hostname", "UNNAMED", "server message to show in server browser"}; +cvar_t developer_networking = {0, "developer_networking", "0", "prints all received and sent packets (recommended only for debugging)"}; + +cvar_t cl_netlocalping = {0, "cl_netlocalping","0", "lags local loopback connection by this much ping time (useful to play more fairly on your own server with people with higher pings)"}; +static cvar_t cl_netpacketloss_send = {0, "cl_netpacketloss_send","0", "drops this percentage of outgoing packets, useful for testing network protocol robustness (jerky movement, prediction errors, etc)"}; +static cvar_t cl_netpacketloss_receive = {0, "cl_netpacketloss_receive","0", "drops this percentage of incoming packets, useful for testing network protocol robustness (jerky movement, effects failing to start, sounds failing to play, etc)"}; +static cvar_t net_slist_queriespersecond = {0, "net_slist_queriespersecond", "20", "how many server information requests to send per second"}; +static cvar_t net_slist_queriesperframe = {0, "net_slist_queriesperframe", "4", "maximum number of server information requests to send each rendered frame (guards against low framerates causing problems)"}; +static cvar_t net_slist_timeout = {0, "net_slist_timeout", "4", "how long to listen for a server information response before giving up"}; +static cvar_t net_slist_pause = {0, "net_slist_pause", "0", "when set to 1, the server list won't update until it is set back to 0"}; +static cvar_t net_slist_maxtries = {0, "net_slist_maxtries", "3", "how many times to ask the same server for information (more times gives better ping reports but takes longer)"}; +static cvar_t net_slist_favorites = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "net_slist_favorites", "", "contains a list of IP addresses and ports to always query explicitly"}; +static cvar_t net_tos_dscp = {CVAR_SAVE, "net_tos_dscp", "32", "DiffServ Codepoint for network sockets (may need game restart to apply)"}; +static cvar_t gameversion = {0, "gameversion", "0", "version of game data (mod-specific) to be sent to querying clients"}; +static cvar_t gameversion_min = {0, "gameversion_min", "-1", "minimum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"}; +static cvar_t gameversion_max = {0, "gameversion_max", "-1", "maximum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"}; +static cvar_t rcon_restricted_password = {CVAR_PRIVATE, "rcon_restricted_password", "", "password to authenticate rcon commands in restricted mode; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; +static cvar_t rcon_restricted_commands = {0, "rcon_restricted_commands", "", "allowed commands for rcon when the restricted mode password was used"}; +static cvar_t rcon_secure_maxdiff = {0, "rcon_secure_maxdiff", "5", "maximum time difference between rcon request and server system clock (to protect against replay attack)"}; +extern cvar_t rcon_secure; +extern cvar_t rcon_secure_challengetimeout; + +double masterquerytime = -1000; +int masterquerycount = 0; +int masterreplycount = 0; +int serverquerycount = 0; +int serverreplycount = 0; + +challenge_t challenge[MAX_CHALLENGES]; + +/// this is only false if there are still servers left to query +static qboolean serverlist_querysleep = true; +static qboolean serverlist_paused = false; +/// this is pushed a second or two ahead of realtime whenever a master server +/// reply is received, to avoid issuing queries while master replies are still +/// flooding in (which would make a mess of the ping times) +static double serverlist_querywaittime = 0; + +static int cl_numsockets; +static lhnetsocket_t *cl_sockets[16]; +static int sv_numsockets; +static lhnetsocket_t *sv_sockets[16]; + +netconn_t *netconn_list = NULL; +mempool_t *netconn_mempool = NULL; +void *netconn_mutex = NULL; + +cvar_t cl_netport = {0, "cl_port", "0", "forces client to use chosen port number if not 0"}; +cvar_t sv_netport = {0, "port", "26000", "server port for players to connect to"}; +cvar_t net_address = {0, "net_address", "", "network address to open ipv4 ports on (if empty, use default interfaces)"}; +cvar_t net_address_ipv6 = {0, "net_address_ipv6", "", "network address to open ipv6 ports on (if empty, use default interfaces)"}; + +char cl_net_extresponse[NET_EXTRESPONSE_MAX][1400]; +int cl_net_extresponse_count = 0; +int cl_net_extresponse_last = 0; + +char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400]; +int sv_net_extresponse_count = 0; +int sv_net_extresponse_last = 0; + +// ServerList interface +serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT]; +serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT]; + +serverlist_infofield_t serverlist_sortbyfield; +int serverlist_sortflags; + +int serverlist_viewcount = 0; +unsigned short serverlist_viewlist[SERVERLIST_VIEWLISTSIZE]; + +int serverlist_maxcachecount = 0; +int serverlist_cachecount = 0; +serverlist_entry_t *serverlist_cache = NULL; + +qboolean serverlist_consoleoutput; + +static int nFavorites = 0; +static lhnetaddress_t favorites[MAX_FAVORITESERVERS]; +static int nFavorites_idfp = 0; +static char favorites_idfp[MAX_FAVORITESERVERS][FP64_SIZE+1]; + +void NetConn_UpdateFavorites(void) +{ + const char *p; + nFavorites = 0; + nFavorites_idfp = 0; + p = net_slist_favorites.string; + while((size_t) nFavorites < sizeof(favorites) / sizeof(*favorites) && COM_ParseToken_Console(&p)) + { + if(com_token[0] != '[' && strlen(com_token) == FP64_SIZE && !strchr(com_token, '.')) + // currently 44 bytes, longest possible IPv6 address: 39 bytes, so this works + // (if v6 address contains port, it must start with '[') + { + strlcpy(favorites_idfp[nFavorites_idfp], com_token, sizeof(favorites_idfp[nFavorites_idfp])); + ++nFavorites_idfp; + } + else + { + if(LHNETADDRESS_FromString(&favorites[nFavorites], com_token, 26000)) + ++nFavorites; + } + } +} + +/// helper function to insert a value into the viewset +/// spare entries will be removed +static void _ServerList_ViewList_Helper_InsertBefore( int index, serverlist_entry_t *entry ) +{ + int i; + if( serverlist_viewcount < SERVERLIST_VIEWLISTSIZE ) { + i = serverlist_viewcount++; + } else { + i = SERVERLIST_VIEWLISTSIZE - 1; + } + + for( ; i > index ; i-- ) + serverlist_viewlist[ i ] = serverlist_viewlist[ i - 1 ]; + + serverlist_viewlist[index] = (int)(entry - serverlist_cache); +} + +/// we suppose serverlist_viewcount to be valid, ie > 0 +static void _ServerList_ViewList_Helper_Remove( int index ) +{ + serverlist_viewcount--; + for( ; index < serverlist_viewcount ; index++ ) + serverlist_viewlist[index] = serverlist_viewlist[index + 1]; +} + +/// \returns true if A should be inserted before B +static qboolean _ServerList_Entry_Compare( serverlist_entry_t *A, serverlist_entry_t *B ) +{ + int result = 0; // > 0 if for numbers A > B and for text if A < B + + if( serverlist_sortflags & SLSF_FAVORITESFIRST ) + { + if(A->info.isfavorite != B->info.isfavorite) + return A->info.isfavorite; + } + + switch( serverlist_sortbyfield ) { + case SLIF_PING: + result = A->info.ping - B->info.ping; + break; + case SLIF_MAXPLAYERS: + result = A->info.maxplayers - B->info.maxplayers; + break; + case SLIF_NUMPLAYERS: + result = A->info.numplayers - B->info.numplayers; + break; + case SLIF_NUMBOTS: + result = A->info.numbots - B->info.numbots; + break; + case SLIF_NUMHUMANS: + result = A->info.numhumans - B->info.numhumans; + break; + case SLIF_FREESLOTS: + result = A->info.freeslots - B->info.freeslots; + break; + case SLIF_PROTOCOL: + result = A->info.protocol - B->info.protocol; + break; + case SLIF_CNAME: + result = strcmp( B->info.cname, A->info.cname ); + break; + case SLIF_GAME: + result = strcasecmp( B->info.game, A->info.game ); + break; + case SLIF_MAP: + result = strcasecmp( B->info.map, A->info.map ); + break; + case SLIF_MOD: + result = strcasecmp( B->info.mod, A->info.mod ); + break; + case SLIF_NAME: + result = strcasecmp( B->info.name, A->info.name ); + break; + case SLIF_QCSTATUS: + result = strcasecmp( B->info.qcstatus, A->info.qcstatus ); // not really THAT useful, though + break; + case SLIF_ISFAVORITE: + result = !!B->info.isfavorite - !!A->info.isfavorite; + break; + default: + Con_DPrint( "_ServerList_Entry_Compare: Bad serverlist_sortbyfield!\n" ); + break; + } + + if (result != 0) + { + if( serverlist_sortflags & SLSF_DESCENDING ) + return result > 0; + else + return result < 0; + } + + // if the chosen sort key is identical, sort by index + // (makes this a stable sort, so that later replies from servers won't + // shuffle the servers around when they have the same ping) + return A < B; +} + +static qboolean _ServerList_CompareInt( int A, serverlist_maskop_t op, int B ) +{ + // This should actually be done with some intermediate and end-of-function return + switch( op ) { + case SLMO_LESS: + return A < B; + case SLMO_LESSEQUAL: + return A <= B; + case SLMO_EQUAL: + return A == B; + case SLMO_GREATER: + return A > B; + case SLMO_NOTEQUAL: + return A != B; + case SLMO_GREATEREQUAL: + case SLMO_CONTAINS: + case SLMO_NOTCONTAIN: + case SLMO_STARTSWITH: + case SLMO_NOTSTARTSWITH: + return A >= B; + default: + Con_DPrint( "_ServerList_CompareInt: Bad op!\n" ); + return false; + } +} + +static qboolean _ServerList_CompareStr( const char *A, serverlist_maskop_t op, const char *B ) +{ + int i; + char bufferA[ 1400 ], bufferB[ 1400 ]; // should be more than enough + COM_StringDecolorize(A, 0, bufferA, sizeof(bufferA), false); + for (i = 0;i < (int)sizeof(bufferA)-1 && bufferA[i];i++) + bufferA[i] = (bufferA[i] >= 'A' && bufferA[i] <= 'Z') ? (bufferA[i] + 'a' - 'A') : bufferA[i]; + bufferA[i] = 0; + for (i = 0;i < (int)sizeof(bufferB)-1 && B[i];i++) + bufferB[i] = (B[i] >= 'A' && B[i] <= 'Z') ? (B[i] + 'a' - 'A') : B[i]; + bufferB[i] = 0; + + // Same here, also using an intermediate & final return would be more appropriate + // A info B mask + switch( op ) { + case SLMO_CONTAINS: + return *bufferB && !!strstr( bufferA, bufferB ); // we want a real bool + case SLMO_NOTCONTAIN: + return !*bufferB || !strstr( bufferA, bufferB ); + case SLMO_STARTSWITH: + //Con_Printf("startsWith: %s %s\n", bufferA, bufferB); + return *bufferB && !memcmp(bufferA, bufferB, strlen(bufferB)); + case SLMO_NOTSTARTSWITH: + return !*bufferB || memcmp(bufferA, bufferB, strlen(bufferB)); + case SLMO_LESS: + return strcmp( bufferA, bufferB ) < 0; + case SLMO_LESSEQUAL: + return strcmp( bufferA, bufferB ) <= 0; + case SLMO_EQUAL: + return strcmp( bufferA, bufferB ) == 0; + case SLMO_GREATER: + return strcmp( bufferA, bufferB ) > 0; + case SLMO_NOTEQUAL: + return strcmp( bufferA, bufferB ) != 0; + case SLMO_GREATEREQUAL: + return strcmp( bufferA, bufferB ) >= 0; + default: + Con_DPrint( "_ServerList_CompareStr: Bad op!\n" ); + return false; + } +} + +static qboolean _ServerList_Entry_Mask( serverlist_mask_t *mask, serverlist_info_t *info ) +{ + if( !_ServerList_CompareInt( info->ping, mask->tests[SLIF_PING], mask->info.ping ) ) + return false; + if( !_ServerList_CompareInt( info->maxplayers, mask->tests[SLIF_MAXPLAYERS], mask->info.maxplayers ) ) + return false; + if( !_ServerList_CompareInt( info->numplayers, mask->tests[SLIF_NUMPLAYERS], mask->info.numplayers ) ) + return false; + if( !_ServerList_CompareInt( info->numbots, mask->tests[SLIF_NUMBOTS], mask->info.numbots ) ) + return false; + if( !_ServerList_CompareInt( info->numhumans, mask->tests[SLIF_NUMHUMANS], mask->info.numhumans ) ) + return false; + if( !_ServerList_CompareInt( info->freeslots, mask->tests[SLIF_FREESLOTS], mask->info.freeslots ) ) + return false; + if( !_ServerList_CompareInt( info->protocol, mask->tests[SLIF_PROTOCOL], mask->info.protocol )) + return false; + if( *mask->info.cname + && !_ServerList_CompareStr( info->cname, mask->tests[SLIF_CNAME], mask->info.cname ) ) + return false; + if( *mask->info.game + && !_ServerList_CompareStr( info->game, mask->tests[SLIF_GAME], mask->info.game ) ) + return false; + if( *mask->info.mod + && !_ServerList_CompareStr( info->mod, mask->tests[SLIF_MOD], mask->info.mod ) ) + return false; + if( *mask->info.map + && !_ServerList_CompareStr( info->map, mask->tests[SLIF_MAP], mask->info.map ) ) + return false; + if( *mask->info.name + && !_ServerList_CompareStr( info->name, mask->tests[SLIF_NAME], mask->info.name ) ) + return false; + if( *mask->info.qcstatus + && !_ServerList_CompareStr( info->qcstatus, mask->tests[SLIF_QCSTATUS], mask->info.qcstatus ) ) + return false; + if( *mask->info.players + && !_ServerList_CompareStr( info->players, mask->tests[SLIF_PLAYERS], mask->info.players ) ) + return false; + if( !_ServerList_CompareInt( info->isfavorite, mask->tests[SLIF_ISFAVORITE], mask->info.isfavorite )) + return false; + return true; +} + +static void ServerList_ViewList_Insert( serverlist_entry_t *entry ) +{ + int start, end, mid, i; + lhnetaddress_t addr; + + // reject incompatible servers + if( + entry->info.gameversion != gameversion.integer + && + !( + gameversion_min.integer >= 0 // min/max range set by user/mod? + && gameversion_max.integer >= 0 + && gameversion_min.integer <= entry->info.gameversion // version of server in min/max range? + && gameversion_max.integer >= entry->info.gameversion + ) + ) + return; + + // refresh the "favorite" status + entry->info.isfavorite = false; + if(LHNETADDRESS_FromString(&addr, entry->info.cname, 26000)) + { + char idfp[FP64_SIZE+1]; + for(i = 0; i < nFavorites; ++i) + { + if(LHNETADDRESS_Compare(&addr, &favorites[i]) == 0) + { + entry->info.isfavorite = true; + break; + } + } + if(Crypto_RetrieveHostKey(&addr, 0, NULL, 0, idfp, sizeof(idfp), NULL)) + { + for(i = 0; i < nFavorites_idfp; ++i) + { + if(!strcmp(idfp, favorites_idfp[i])) + { + entry->info.isfavorite = true; + break; + } + } + } + } + + // FIXME: change this to be more readable (...) + // now check whether it passes through the masks + for( start = 0 ; start < SERVERLIST_ANDMASKCOUNT && serverlist_andmasks[start].active; start++ ) + if( !_ServerList_Entry_Mask( &serverlist_andmasks[start], &entry->info ) ) + return; + + for( start = 0 ; start < SERVERLIST_ORMASKCOUNT && serverlist_ormasks[start].active ; start++ ) + if( _ServerList_Entry_Mask( &serverlist_ormasks[start], &entry->info ) ) + break; + if( start == SERVERLIST_ORMASKCOUNT || (start > 0 && !serverlist_ormasks[start].active) ) + return; + + if( !serverlist_viewcount ) { + _ServerList_ViewList_Helper_InsertBefore( 0, entry ); + return; + } + // ok, insert it, we just need to find out where exactly: + + // two special cases + // check whether to insert it as new first item + if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(0) ) ) { + _ServerList_ViewList_Helper_InsertBefore( 0, entry ); + return; + } // check whether to insert it as new last item + else if( !_ServerList_Entry_Compare( entry, ServerList_GetViewEntry(serverlist_viewcount - 1) ) ) { + _ServerList_ViewList_Helper_InsertBefore( serverlist_viewcount, entry ); + return; + } + start = 0; + end = serverlist_viewcount - 1; + while( end > start + 1 ) + { + mid = (start + end) / 2; + // test the item that lies in the middle between start and end + if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(mid) ) ) + // the item has to be in the upper half + end = mid; + else + // the item has to be in the lower half + start = mid; + } + _ServerList_ViewList_Helper_InsertBefore( start + 1, entry ); +} + +static void ServerList_ViewList_Remove( serverlist_entry_t *entry ) +{ + int i; + for( i = 0; i < serverlist_viewcount; i++ ) + { + if (ServerList_GetViewEntry(i) == entry) + { + _ServerList_ViewList_Helper_Remove(i); + break; + } + } +} + +void ServerList_RebuildViewList(void) +{ + int i; + + serverlist_viewcount = 0; + for( i = 0 ; i < serverlist_cachecount ; i++ ) { + serverlist_entry_t *entry = &serverlist_cache[i]; + // also display entries that are currently being refreshed [11/8/2007 Black] + if( entry->query == SQS_QUERIED || entry->query == SQS_REFRESHING ) + ServerList_ViewList_Insert( entry ); + } +} + +void ServerList_ResetMasks(void) +{ + int i; + + memset( &serverlist_andmasks, 0, sizeof( serverlist_andmasks ) ); + memset( &serverlist_ormasks, 0, sizeof( serverlist_ormasks ) ); + // numbots needs to be compared to -1 to always succeed + for(i = 0; i < SERVERLIST_ANDMASKCOUNT; ++i) + serverlist_andmasks[i].info.numbots = -1; + for(i = 0; i < SERVERLIST_ORMASKCOUNT; ++i) + serverlist_ormasks[i].info.numbots = -1; +} + +void ServerList_GetPlayerStatistics(int *numplayerspointer, int *maxplayerspointer) +{ + int i; + int numplayers = 0, maxplayers = 0; + for (i = 0;i < serverlist_cachecount;i++) + { + if (serverlist_cache[i].query == SQS_QUERIED) + { + numplayers += serverlist_cache[i].info.numhumans; + maxplayers += serverlist_cache[i].info.maxplayers; + } + } + *numplayerspointer = numplayers; + *maxplayerspointer = maxplayers; +} + +#if 0 +static void _ServerList_Test(void) +{ + int i; + if (serverlist_maxcachecount <= 1024) + { + serverlist_maxcachecount = 1024; + serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); + } + for( i = 0 ; i < 1024 ; i++ ) { + memset( &serverlist_cache[serverlist_cachecount], 0, sizeof( serverlist_entry_t ) ); + serverlist_cache[serverlist_cachecount].info.ping = 1000 + 1024 - i; + dpsnprintf( serverlist_cache[serverlist_cachecount].info.name, sizeof(serverlist_cache[serverlist_cachecount].info.name), "Black's ServerList Test %i", i ); + serverlist_cache[serverlist_cachecount].finished = true; + dpsnprintf( serverlist_cache[serverlist_cachecount].line1, sizeof(serverlist_cache[serverlist_cachecount].info.line1), "%i %s", serverlist_cache[serverlist_cachecount].info.ping, serverlist_cache[serverlist_cachecount].info.name ); + ServerList_ViewList_Insert( &serverlist_cache[serverlist_cachecount] ); + serverlist_cachecount++; + } +} +#endif + +void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryqw, qboolean consoleoutput) +{ + masterquerytime = realtime; + masterquerycount = 0; + masterreplycount = 0; + if( resetcache ) { + serverquerycount = 0; + serverreplycount = 0; + serverlist_cachecount = 0; + serverlist_viewcount = 0; + serverlist_maxcachecount = 0; + serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); + } else { + // refresh all entries + int n; + for( n = 0 ; n < serverlist_cachecount ; n++ ) { + serverlist_entry_t *entry = &serverlist_cache[ n ]; + entry->query = SQS_REFRESHING; + entry->querycounter = 0; + } + } + serverlist_consoleoutput = consoleoutput; + + //_ServerList_Test(); + + NetConn_QueryMasters(querydp, queryqw); +} + +// rest + +int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress) +{ + int length; + int i; + if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) + Thread_LockMutex(netconn_mutex); + length = LHNET_Read(mysocket, data, maxlength, peeraddress); + if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) + Thread_UnlockMutex(netconn_mutex); + if (length == 0) + return 0; + if (cl_netpacketloss_receive.integer) + for (i = 0;i < cl_numsockets;i++) + if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_receive.integer) + return 0; + if (developer_networking.integer) + { + char addressstring[128], addressstring2[128]; + LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true); + if (length > 0) + { + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i from %s:\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length, addressstring2); + Com_HexDumpToConsole((unsigned char *)data, length); + } + else + Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length); + } + return length; +} + +int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress) +{ + int ret; + int i; + if (cl_netpacketloss_send.integer) + for (i = 0;i < cl_numsockets;i++) + if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_send.integer) + return length; + if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) + Thread_LockMutex(netconn_mutex); + ret = LHNET_Write(mysocket, data, length, peeraddress); + if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) + Thread_UnlockMutex(netconn_mutex); + if (developer_networking.integer) + { + char addressstring[128], addressstring2[128]; + LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true); + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + Con_Printf("LHNET_Write(%p (%s), %p, %i, %p (%s)) = %i%s\n", (void *)mysocket, addressstring, (void *)data, length, (void *)peeraddress, addressstring2, length, ret == length ? "" : " (ERROR)"); + Com_HexDumpToConsole((unsigned char *)data, length); + } + return ret; +} + +int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress) +{ + // note this does not include the trailing NULL because we add that in the parser + return NetConn_Write(mysocket, string, (int)strlen(string), peeraddress); +} + +qboolean NetConn_CanSend(netconn_t *conn) +{ + conn->outgoing_packetcounter = (conn->outgoing_packetcounter + 1) % NETGRAPH_PACKETS; + conn->outgoing_netgraph[conn->outgoing_packetcounter].time = realtime; + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET; + conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes = NETGRAPH_NOPACKET; + if (realtime > conn->cleartime) + return true; + else + { + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_CHOKEDPACKET; + return false; + } +} + +int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, qboolean quakesignon_suppressreliables) +{ + int totallen = 0; + unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; + unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + + // if this packet was supposedly choked, but we find ourselves sending one + // anyway, make sure the size counting starts at zero + // (this mostly happens on level changes and disconnects and such) + if (conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes == NETGRAPH_CHOKEDPACKET) + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET; + + if (protocol == PROTOCOL_QUAKEWORLD) + { + int packetLen; + qboolean sendreliable; + + // note that it is ok to send empty messages to the qw server, + // otherwise it won't respond to us at all + + sendreliable = false; + // if the remote side dropped the last reliable message, resend it + if (conn->qw.incoming_acknowledged > conn->qw.last_reliable_sequence && conn->qw.incoming_reliable_acknowledged != conn->qw.reliable_sequence) + sendreliable = true; + // if the reliable transmit buffer is empty, copy the current message out + if (!conn->sendMessageLength && conn->message.cursize) + { + memcpy (conn->sendMessage, conn->message.data, conn->message.cursize); + conn->sendMessageLength = conn->message.cursize; + SZ_Clear(&conn->message); // clear the message buffer + conn->qw.reliable_sequence ^= 1; + sendreliable = true; + } + // outgoing unreliable packet number, and outgoing reliable packet number (0 or 1) + StoreLittleLong(sendbuffer, (unsigned int)conn->outgoing_unreliable_sequence | ((unsigned int)sendreliable<<31)); + // last received unreliable packet number, and last received reliable packet number (0 or 1) + StoreLittleLong(sendbuffer + 4, (unsigned int)conn->qw.incoming_sequence | ((unsigned int)conn->qw.incoming_reliable_sequence<<31)); + packetLen = 8; + conn->outgoing_unreliable_sequence++; + // client sends qport in every packet + if (conn == cls.netcon) + { + *((short *)(sendbuffer + 8)) = LittleShort(cls.qw_qport); + packetLen += 2; + // also update cls.qw_outgoing_sequence + cls.qw_outgoing_sequence = conn->outgoing_unreliable_sequence; + } + if (packetLen + (sendreliable ? conn->sendMessageLength : 0) > 1400) + { + Con_Printf ("NetConn_SendUnreliableMessage: reliable message too big %u\n", data->cursize); + return -1; + } + + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28; + + // add the reliable message if there is one + if (sendreliable) + { + conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += conn->sendMessageLength + 28; + memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength); + packetLen += conn->sendMessageLength; + conn->qw.last_reliable_sequence = conn->outgoing_unreliable_sequence; + } + + // add the unreliable message if possible + if (packetLen + data->cursize <= 1400) + { + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += data->cursize + 28; + memcpy(sendbuffer + packetLen, data->data, data->cursize); + packetLen += data->cursize; + } + + NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); + + conn->packetsSent++; + conn->unreliableMessagesSent++; + + totallen += packetLen + 28; + } + else + { + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + const void *sendme; + size_t sendmelen; + + // if a reliable message fragment has been lost, send it again + if (conn->sendMessageLength && (realtime - conn->lastSendTime) > 1.0) + { + if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + { + dataLen = conn->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_PACKETFRAGMENT; + eom = 0; + } + + packetLen = NET_HEADERSIZE + dataLen; + + StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom)); + StoreBigLong(sendbuffer + 4, conn->nq.sendSequence - 1); + memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); + + conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28; + + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if (sendme && NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress) == (int)sendmelen) + { + conn->lastSendTime = realtime; + conn->packetsReSent++; + } + + totallen += sendmelen + 28; + } + + // if we have a new reliable message to send, do so + if (!conn->sendMessageLength && conn->message.cursize && !quakesignon_suppressreliables) + { + if (conn->message.cursize > (int)sizeof(conn->sendMessage)) + { + Con_Printf("NetConn_SendUnreliableMessage: reliable message too big (%u > %u)\n", conn->message.cursize, (int)sizeof(conn->sendMessage)); + conn->message.overflowed = true; + return -1; + } + + if (developer_networking.integer && conn == cls.netcon) + { + Con_Print("client sending reliable message to server:\n"); + SZ_HexDumpToConsole(&conn->message); + } + + memcpy(conn->sendMessage, conn->message.data, conn->message.cursize); + conn->sendMessageLength = conn->message.cursize; + SZ_Clear(&conn->message); + + if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + { + dataLen = conn->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_PACKETFRAGMENT; + eom = 0; + } + + packetLen = NET_HEADERSIZE + dataLen; + + StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom)); + StoreBigLong(sendbuffer + 4, conn->nq.sendSequence); + memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); + + conn->nq.sendSequence++; + + conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28; + + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); + + conn->lastSendTime = realtime; + conn->packetsSent++; + conn->reliableMessagesSent++; + + totallen += sendmelen + 28; + } + + // if we have an unreliable message to send, do so + if (data->cursize) + { + packetLen = NET_HEADERSIZE + data->cursize; + + if (packetLen > (int)sizeof(sendbuffer)) + { + Con_Printf("NetConn_SendUnreliableMessage: message too big %u\n", data->cursize); + return -1; + } + + StoreBigLong(sendbuffer, packetLen | NETFLAG_UNRELIABLE); + StoreBigLong(sendbuffer + 4, conn->outgoing_unreliable_sequence); + memcpy(sendbuffer + NET_HEADERSIZE, data->data, data->cursize); + + conn->outgoing_unreliable_sequence++; + + conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28; + + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); + + conn->packetsSent++; + conn->unreliableMessagesSent++; + + totallen += sendmelen + 28; + } + } + + // delay later packets to obey rate limit + if (conn->cleartime < realtime - 0.1) + conn->cleartime = realtime - 0.1; + conn->cleartime = conn->cleartime + (double)totallen / (double)rate; + if (conn->cleartime < realtime) + conn->cleartime = realtime; + + return 0; +} + +qboolean NetConn_HaveClientPorts(void) +{ + return !!cl_numsockets; +} + +qboolean NetConn_HaveServerPorts(void) +{ + return !!sv_numsockets; +} + +void NetConn_CloseClientPorts(void) +{ + for (;cl_numsockets > 0;cl_numsockets--) + if (cl_sockets[cl_numsockets - 1]) + LHNET_CloseSocket(cl_sockets[cl_numsockets - 1]); +} + +static void NetConn_OpenClientPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport) +{ + lhnetaddress_t address; + lhnetsocket_t *s; + int success; + char addressstring2[1024]; + if (addressstring && addressstring[0]) + success = LHNETADDRESS_FromString(&address, addressstring, defaultport); + else + success = LHNETADDRESS_FromPort(&address, addresstype, defaultport); + if (success) + { + if ((s = LHNET_OpenSocket_Connectionless(&address))) + { + cl_sockets[cl_numsockets++] = s; + LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true); + if (addresstype != LHNETADDRESSTYPE_LOOP) + Con_Printf("Client opened a socket on address %s\n", addressstring2); + } + else + { + LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true); + Con_Printf("Client failed to open a socket on address %s\n", addressstring2); + } + } + else + Con_Printf("Client unable to parse address %s\n", addressstring); +} + +void NetConn_OpenClientPorts(void) +{ + int port; + NetConn_CloseClientPorts(); + + SV_LockThreadMutex(); // FIXME recursive? + Crypto_LoadKeys(); // client sockets + SV_UnlockThreadMutex(); + + port = bound(0, cl_netport.integer, 65535); + if (cl_netport.integer != port) + Cvar_SetValueQuick(&cl_netport, port); + if(port == 0) + Con_Printf("Client using an automatically assigned port\n"); + else + Con_Printf("Client using port %i\n", port); + NetConn_OpenClientPort(NULL, LHNETADDRESSTYPE_LOOP, 2); + NetConn_OpenClientPort(net_address.string, LHNETADDRESSTYPE_INET4, port); +#ifdef SUPPORTIPV6 + NetConn_OpenClientPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port); +#endif +} + +void NetConn_CloseServerPorts(void) +{ + for (;sv_numsockets > 0;sv_numsockets--) + if (sv_sockets[sv_numsockets - 1]) + LHNET_CloseSocket(sv_sockets[sv_numsockets - 1]); +} + +static qboolean NetConn_OpenServerPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport, int range) +{ + lhnetaddress_t address; + lhnetsocket_t *s; + int port; + char addressstring2[1024]; + int success; + + for (port = defaultport; port <= defaultport + range; port++) + { + if (addressstring && addressstring[0]) + success = LHNETADDRESS_FromString(&address, addressstring, port); + else + success = LHNETADDRESS_FromPort(&address, addresstype, port); + if (success) + { + if ((s = LHNET_OpenSocket_Connectionless(&address))) + { + sv_sockets[sv_numsockets++] = s; + LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true); + if (addresstype != LHNETADDRESSTYPE_LOOP) + Con_Printf("Server listening on address %s\n", addressstring2); + return true; + } + else + { + LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true); + Con_Printf("Server failed to open socket on address %s\n", addressstring2); + } + } + else + { + Con_Printf("Server unable to parse address %s\n", addressstring); + // if it cant parse one address, it wont be able to parse another for sure + return false; + } + } + return false; +} + +void NetConn_OpenServerPorts(int opennetports) +{ + int port; + NetConn_CloseServerPorts(); + + SV_LockThreadMutex(); // FIXME recursive? + Crypto_LoadKeys(); // server sockets + SV_UnlockThreadMutex(); + + NetConn_UpdateSockets(); + port = bound(0, sv_netport.integer, 65535); + if (port == 0) + port = 26000; + Con_Printf("Server using port %i\n", port); + if (sv_netport.integer != port) + Cvar_SetValueQuick(&sv_netport, port); + if (cls.state != ca_dedicated) + NetConn_OpenServerPort(NULL, LHNETADDRESSTYPE_LOOP, 1, 1); + if (opennetports) + { +#ifdef SUPPORTIPV6 + qboolean ip4success = NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100); + NetConn_OpenServerPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port, ip4success ? 1 : 100); +#else + NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100); +#endif + } + if (sv_numsockets == 0) + Host_Error("NetConn_OpenServerPorts: unable to open any ports!"); +} + +lhnetsocket_t *NetConn_ChooseClientSocketForAddress(lhnetaddress_t *address) +{ + int i, a = LHNETADDRESS_GetAddressType(address); + for (i = 0;i < cl_numsockets;i++) + if (cl_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == a) + return cl_sockets[i]; + return NULL; +} + +lhnetsocket_t *NetConn_ChooseServerSocketForAddress(lhnetaddress_t *address) +{ + int i, a = LHNETADDRESS_GetAddressType(address); + for (i = 0;i < sv_numsockets;i++) + if (sv_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(sv_sockets[i])) == a) + return sv_sockets[i]; + return NULL; +} + +netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress) +{ + netconn_t *conn; + conn = (netconn_t *)Mem_Alloc(netconn_mempool, sizeof(*conn)); + conn->mysocket = mysocket; + conn->peeraddress = *peeraddress; + conn->lastMessageTime = realtime; + conn->message.data = conn->messagedata; + conn->message.maxsize = sizeof(conn->messagedata); + conn->message.cursize = 0; + // LordHavoc: (inspired by ProQuake) use a short connect timeout to + // reduce effectiveness of connection request floods + conn->timeout = realtime + net_connecttimeout.value; + LHNETADDRESS_ToString(&conn->peeraddress, conn->address, sizeof(conn->address), true); + conn->next = netconn_list; + netconn_list = conn; + return conn; +} + +void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength); +void NetConn_Close(netconn_t *conn) +{ + netconn_t *c; + // remove connection from list + + // allow the client to reconnect immediately + NetConn_ClearFlood(&(conn->peeraddress), sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0])); + + if (conn == netconn_list) + netconn_list = conn->next; + else + { + for (c = netconn_list;c;c = c->next) + { + if (c->next == conn) + { + c->next = conn->next; + break; + } + } + // not found in list, we'll avoid crashing here... + if (!c) + return; + } + // free connection + Mem_Free(conn); +} + +static int clientport = -1; +static int clientport2 = -1; +static int hostport = -1; +void NetConn_UpdateSockets(void) +{ + int i, j; + + // TODO add logic to automatically close sockets if needed + LHNET_DefaultDSCP(net_tos_dscp.integer); + + if (cls.state != ca_dedicated) + { + if (clientport2 != cl_netport.integer) + { + clientport2 = cl_netport.integer; + if (cls.state == ca_connected) + Con_Print("Changing \"cl_port\" will not take effect until you reconnect.\n"); + } + if (cls.state == ca_disconnected && clientport != clientport2) + { + clientport = clientport2; + NetConn_CloseClientPorts(); + } + if (cl_numsockets == 0) + NetConn_OpenClientPorts(); + } + + if (hostport != sv_netport.integer) + { + hostport = sv_netport.integer; + if (sv.active) + Con_Print("Changing \"port\" will not take effect until \"map\" command is executed.\n"); + } + + for (j = 0;j < MAX_RCONS;j++) + { + i = (cls.rcon_ringpos + j + 1) % MAX_RCONS; + if(cls.rcon_commands[i][0]) + { + if(realtime > cls.rcon_timeout[i]) + { + char s[128]; + LHNETADDRESS_ToString(&cls.rcon_addresses[i], s, sizeof(s), true); + Con_Printf("rcon to %s (for command %s) failed: challenge request timed out\n", s, cls.rcon_commands[i]); + cls.rcon_commands[i][0] = 0; + --cls.rcon_trying; + break; + } + } + } +} + +static int NetConn_ReceivedMessage(netconn_t *conn, const unsigned char *data, size_t length, protocolversion_t protocol, double newtimeout) +{ + int originallength = length; + unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; + unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + unsigned char cryptoreadbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + if (length < 8) + return 0; + + if (protocol == PROTOCOL_QUAKEWORLD) + { + int sequence, sequence_ack; + int reliable_ack, reliable_message; + int count; + //int qport; + + sequence = LittleLong(*((int *)(data + 0))); + sequence_ack = LittleLong(*((int *)(data + 4))); + data += 8; + length -= 8; + + if (conn != cls.netcon) + { + // server only + if (length < 2) + return 0; + // TODO: use qport to identify that this client really is who they say they are? (and elsewhere in the code to identify the connection without a port match?) + //qport = LittleShort(*((int *)(data + 8))); + data += 2; + length -= 2; + } + + conn->packetsReceived++; + reliable_message = (sequence >> 31) & 1; + reliable_ack = (sequence_ack >> 31) & 1; + sequence &= ~(1<<31); + sequence_ack &= ~(1<<31); + if (sequence <= conn->qw.incoming_sequence) + { + //Con_DPrint("Got a stale datagram\n"); + return 0; + } + count = sequence - (conn->qw.incoming_sequence + 1); + if (count > 0) + { + conn->droppedDatagrams += count; + //Con_DPrintf("Dropped %u datagram(s)\n", count); + while (count--) + { + conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; + conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; + conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; + } + } + conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; + conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; + conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; + if (reliable_ack == conn->qw.reliable_sequence) + { + // received, now we will be able to send another reliable message + conn->sendMessageLength = 0; + conn->reliableMessagesReceived++; + } + conn->qw.incoming_sequence = sequence; + if (conn == cls.netcon) + cls.qw_incoming_sequence = conn->qw.incoming_sequence; + conn->qw.incoming_acknowledged = sequence_ack; + conn->qw.incoming_reliable_acknowledged = reliable_ack; + if (reliable_message) + conn->qw.incoming_reliable_sequence ^= 1; + conn->lastMessageTime = realtime; + conn->timeout = realtime + newtimeout; + conn->unreliableMessagesReceived++; + if (conn == cls.netcon) + { + SZ_Clear(&cl_message); + SZ_Write(&cl_message, data, length); + MSG_BeginReading(&cl_message); + } + else + { + SZ_Clear(&sv_message); + SZ_Write(&sv_message, data, length); + MSG_BeginReading(&sv_message); + } + return 2; + } + else + { + unsigned int count; + unsigned int flags; + unsigned int sequence; + size_t qlength; + const void *sendme; + size_t sendmelen; + + originallength = length; + data = (const unsigned char *) Crypto_DecryptPacket(&conn->crypto, data, length, cryptoreadbuffer, &length, sizeof(cryptoreadbuffer)); + if(!data) + return 0; + if(length < 8) + return 0; + + qlength = (unsigned int)BuffBigLong(data); + flags = qlength & ~NETFLAG_LENGTH_MASK; + qlength &= NETFLAG_LENGTH_MASK; + // control packets were already handled + if (!(flags & NETFLAG_CTL) && qlength == length) + { + sequence = BuffBigLong(data + 4); + conn->packetsReceived++; + data += 8; + length -= 8; + if (flags & NETFLAG_UNRELIABLE) + { + if (sequence >= conn->nq.unreliableReceiveSequence) + { + if (sequence > conn->nq.unreliableReceiveSequence) + { + count = sequence - conn->nq.unreliableReceiveSequence; + conn->droppedDatagrams += count; + //Con_DPrintf("Dropped %u datagram(s)\n", count); + while (count--) + { + conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; + conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; + conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; + } + } + conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; + conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; + conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; + conn->nq.unreliableReceiveSequence = sequence + 1; + conn->lastMessageTime = realtime; + conn->timeout = realtime + newtimeout; + conn->unreliableMessagesReceived++; + if (length > 0) + { + if (conn == cls.netcon) + { + SZ_Clear(&cl_message); + SZ_Write(&cl_message, data, length); + MSG_BeginReading(&cl_message); + } + else + { + SZ_Clear(&sv_message); + SZ_Write(&sv_message, data, length); + MSG_BeginReading(&sv_message); + } + return 2; + } + } + //else + // Con_DPrint("Got a stale datagram\n"); + return 1; + } + else if (flags & NETFLAG_ACK) + { + conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes += originallength + 28; + if (sequence == (conn->nq.sendSequence - 1)) + { + if (sequence == conn->nq.ackSequence) + { + conn->nq.ackSequence++; + if (conn->nq.ackSequence != conn->nq.sendSequence) + Con_DPrint("ack sequencing error\n"); + conn->lastMessageTime = realtime; + conn->timeout = realtime + newtimeout; + if (conn->sendMessageLength > MAX_PACKETFRAGMENT) + { + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + + conn->sendMessageLength -= MAX_PACKETFRAGMENT; + memmove(conn->sendMessage, conn->sendMessage+MAX_PACKETFRAGMENT, conn->sendMessageLength); + + if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) + { + dataLen = conn->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_PACKETFRAGMENT; + eom = 0; + } + + packetLen = NET_HEADERSIZE + dataLen; + + StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom)); + StoreBigLong(sendbuffer + 4, conn->nq.sendSequence); + memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); + + conn->nq.sendSequence++; + + sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if (sendme && NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress) == (int)sendmelen) + { + conn->lastSendTime = realtime; + conn->packetsSent++; + } + } + else + conn->sendMessageLength = 0; + } + //else + // Con_DPrint("Duplicate ACK received\n"); + } + //else + // Con_DPrint("Stale ACK received\n"); + return 1; + } + else if (flags & NETFLAG_DATA) + { + unsigned char temppacket[8]; + conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes += originallength + 28; + conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes += 8 + 28; + StoreBigLong(temppacket, 8 | NETFLAG_ACK); + StoreBigLong(temppacket + 4, sequence); + sendme = Crypto_EncryptPacket(&conn->crypto, temppacket, 8, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); + if(sendme) + NetConn_Write(conn->mysocket, sendme, sendmelen, &conn->peeraddress); + if (sequence == conn->nq.receiveSequence) + { + conn->lastMessageTime = realtime; + conn->timeout = realtime + newtimeout; + conn->nq.receiveSequence++; + if( conn->receiveMessageLength + length <= (int)sizeof( conn->receiveMessage ) ) { + memcpy(conn->receiveMessage + conn->receiveMessageLength, data, length); + conn->receiveMessageLength += length; + } else { + Con_Printf( "Reliable message (seq: %i) too big for message buffer!\n" + "Dropping the message!\n", sequence ); + conn->receiveMessageLength = 0; + return 1; + } + if (flags & NETFLAG_EOM) + { + conn->reliableMessagesReceived++; + length = conn->receiveMessageLength; + conn->receiveMessageLength = 0; + if (length > 0) + { + if (conn == cls.netcon) + { + SZ_Clear(&cl_message); + SZ_Write(&cl_message, conn->receiveMessage, length); + MSG_BeginReading(&cl_message); + } + else + { + SZ_Clear(&sv_message); + SZ_Write(&sv_message, conn->receiveMessage, length); + MSG_BeginReading(&sv_message); + } + return 2; + } + } + } + else + conn->receivedDuplicateCount++; + return 1; + } + } + } + return 0; +} + +static void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, protocolversion_t initialprotocol) +{ + crypto_t *crypto; + cls.connect_trying = false; + M_Update_Return_Reason(""); + // the connection request succeeded, stop current connection and set up a new connection + CL_Disconnect(); + // if we're connecting to a remote server, shut down any local server + if (LHNETADDRESS_GetAddressType(peeraddress) != LHNETADDRESSTYPE_LOOP && sv.active) + { + SV_LockThreadMutex(); + Host_ShutdownServer (); + SV_UnlockThreadMutex(); + } + // allocate a net connection to keep track of things + cls.netcon = NetConn_Open(mysocket, peeraddress); + crypto = &cls.crypto; + if(crypto && crypto->authenticated) + { + Crypto_ServerFinishInstance(&cls.netcon->crypto, crypto); + Con_Printf("%s connection to %s has been established: server is %s@%.*s, I am %.*s@%.*s\n", + crypto->use_aes ? "Encrypted" : "Authenticated", + cls.netcon->address, + crypto->server_idfp[0] ? crypto->server_idfp : "-", + crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-", + crypto_keyfp_recommended_length, crypto->client_idfp[0] ? crypto->client_idfp : "-", + crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-" + ); + } + Con_Printf("Connection accepted to %s\n", cls.netcon->address); + key_dest = key_game; + m_state = m_none; + cls.demonum = -1; // not in the demo loop now + cls.state = ca_connected; + cls.signon = 0; // need all the signon messages before playing + cls.protocol = initialprotocol; + // reset move sequence numbering on this new connection + cls.servermovesequence = 0; + if (cls.protocol == PROTOCOL_QUAKEWORLD) + Cmd_ForwardStringToServer("new"); + if (cls.protocol == PROTOCOL_QUAKE) + { + // write a keepalive (clc_nop) as it seems to greatly improve the + // chances of connecting to a netquake server + sizebuf_t msg; + unsigned char buf[4]; + memset(&msg, 0, sizeof(msg)); + msg.data = buf; + msg.maxsize = sizeof(buf); + MSG_WriteChar(&msg, clc_nop); + NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, false); + } +} + +int NetConn_IsLocalGame(void) +{ + if (cls.state == ca_connected && sv.active && cl.maxclients == 1) + return true; + return false; +} + +static int NetConn_ClientParsePacket_ServerList_ProcessReply(const char *addressstring) +{ + int n; + int pingtime; + serverlist_entry_t *entry = NULL; + + // search the cache for this server and update it + for (n = 0;n < serverlist_cachecount;n++) { + entry = &serverlist_cache[ n ]; + if (!strcmp(addressstring, entry->info.cname)) + break; + } + + if (n == serverlist_cachecount) + { + // LAN search doesnt require an answer from the master server so we wont + // know the ping nor will it be initialized already... + + // find a slot + if (serverlist_cachecount == SERVERLIST_TOTALSIZE) + return -1; + + if (serverlist_maxcachecount <= serverlist_cachecount) + { + serverlist_maxcachecount += 64; + serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); + } + entry = &serverlist_cache[n]; + + memset(entry, 0, sizeof(*entry)); + // store the data the engine cares about (address and ping) + strlcpy(entry->info.cname, addressstring, sizeof(entry->info.cname)); + entry->info.ping = 100000; + entry->querytime = realtime; + // if not in the slist menu we should print the server to console + if (serverlist_consoleoutput) + Con_Printf("querying %s\n", addressstring); + ++serverlist_cachecount; + } + // if this is the first reply from this server, count it as having replied + pingtime = (int)((realtime - entry->querytime) * 1000.0 + 0.5); + pingtime = bound(0, pingtime, 9999); + if (entry->query == SQS_REFRESHING) { + entry->info.ping = pingtime; + entry->query = SQS_QUERIED; + } else { + // convert to unsigned to catch the -1 + // I still dont like this but its better than the old 10000 magic ping number - as in easier to type and read :( [11/8/2007 Black] + entry->info.ping = min((unsigned) entry->info.ping, (unsigned) pingtime); + serverreplycount++; + } + + // other server info is updated by the caller + return n; +} + +static void NetConn_ClientParsePacket_ServerList_UpdateCache(int n) +{ + serverlist_entry_t *entry = &serverlist_cache[n]; + serverlist_info_t *info = &entry->info; + // update description strings for engine menu and console output + dpsnprintf(entry->line1, sizeof(serverlist_cache[n].line1), "^%c%5d^7 ^%c%3u^7/%3u %-65.65s", info->ping >= 300 ? '1' : (info->ping >= 200 ? '3' : '7'), (int)info->ping, ((info->numhumans > 0 && info->numhumans < info->maxplayers) ? (info->numhumans >= 4 ? '7' : '3') : '1'), info->numplayers, info->maxplayers, info->name); + dpsnprintf(entry->line2, sizeof(serverlist_cache[n].line2), "^4%-21.21s %-19.19s ^%c%-17.17s^4 %-20.20s", info->cname, info->game, + ( + info->gameversion != gameversion.integer + && + !( + gameversion_min.integer >= 0 // min/max range set by user/mod? + && gameversion_max.integer >= 0 + && gameversion_min.integer <= info->gameversion // version of server in min/max range? + && gameversion_max.integer >= info->gameversion + ) + ) ? '1' : '4', + info->mod, info->map); + if (entry->query == SQS_QUERIED) + { + if(!serverlist_paused) + ServerList_ViewList_Remove(entry); + } + // if not in the slist menu we should print the server to console (if wanted) + else if( serverlist_consoleoutput ) + Con_Printf("%s\n%s\n", serverlist_cache[n].line1, serverlist_cache[n].line2); + // and finally, update the view set + if(!serverlist_paused) + ServerList_ViewList_Insert( entry ); + // update the entry's state + serverlist_cache[n].query = SQS_QUERIED; +} + +// returns true, if it's sensible to continue the processing +static qboolean NetConn_ClientParsePacket_ServerList_PrepareQuery( int protocol, const char *ipstring, qboolean isfavorite ) { + int n; + serverlist_entry_t *entry; + + // ignore the rest of the message if the serverlist is full + if( serverlist_cachecount == SERVERLIST_TOTALSIZE ) + return false; + // also ignore it if we have already queried it (other master server response) + for( n = 0 ; n < serverlist_cachecount ; n++ ) + if( !strcmp( ipstring, serverlist_cache[ n ].info.cname ) ) + break; + + if( n < serverlist_cachecount ) { + // the entry has already been queried once or + return true; + } + + if (serverlist_maxcachecount <= n) + { + serverlist_maxcachecount += 64; + serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); + } + + entry = &serverlist_cache[n]; + + memset(entry, 0, sizeof(*entry)); + entry->protocol = protocol; + // store the data the engine cares about (address and ping) + strlcpy (entry->info.cname, ipstring, sizeof(entry->info.cname)); + + entry->info.isfavorite = isfavorite; + + // no, then reset the ping right away + entry->info.ping = -1; + // we also want to increase the serverlist_cachecount then + serverlist_cachecount++; + serverquerycount++; + + entry->query = SQS_QUERYING; + + return true; +} + +static void NetConn_ClientParsePacket_ServerList_ParseDPList(lhnetaddress_t *senderaddress, const unsigned char *data, int length, qboolean isextended) +{ + masterreplycount++; + if (serverlist_consoleoutput) + Con_Printf("received DarkPlaces %sserver list...\n", isextended ? "extended " : ""); + while (length >= 7) + { + char ipstring [128]; + + // IPv4 address + if (data[0] == '\\') + { + unsigned short port = data[5] * 256 + data[6]; + + if (port != 0 && (data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF || data[4] != 0xFF)) + dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%hu", data[1], data[2], data[3], data[4], port); + + // move on to next address in packet + data += 7; + length -= 7; + } + // IPv6 address + else if (data[0] == '/' && isextended && length >= 19) + { + unsigned short port = data[17] * 256 + data[18]; + + if (port != 0) + { +#ifdef WHY_JUST_WHY + const char *ifname; + char ifnamebuf[16]; + + /// \TODO: make some basic checks of the IP address (broadcast, ...) + + ifname = LHNETADDRESS_GetInterfaceName(senderaddress, ifnamebuf, sizeof(ifnamebuf)); + if (ifname != NULL) + { + dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x%%%s]:%hu", + (data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8], + (data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16], + ifname, port); + } + else +#endif + { + dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x]:%hu", + (data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8], + (data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16], + port); + } + } + + // move on to next address in packet + data += 19; + length -= 19; + } + else + { + Con_Print("Error while parsing the server list\n"); + break; + } + + if (serverlist_consoleoutput && developer_networking.integer) + Con_Printf("Requesting info from DarkPlaces server %s\n", ipstring); + + if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, ipstring, false ) ) { + break; + } + + } + + // begin or resume serverlist queries + serverlist_querysleep = false; + serverlist_querywaittime = realtime + 3; +} + +static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress) +{ + qboolean fromserver; + int ret, c; + const char *s; + char *string, addressstring2[128], ipstring[32]; + char stringbuf[16384]; + char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + size_t sendlength; + char infostringvalue[MAX_INPUTLINE]; + char vabuf[1024]; + + // quakeworld ingame packet + fromserver = cls.netcon && mysocket == cls.netcon->mysocket && !LHNETADDRESS_Compare(&cls.netcon->peeraddress, peeraddress); + + // convert the address to a string incase we need it + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + + if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255) + { + // received a command string - strip off the packaging and put it + // into our string buffer with NULL termination + data += 4; + length -= 4; + length = min(length, (int)sizeof(stringbuf) - 1); + memcpy(stringbuf, data, length); + stringbuf[length] = 0; + string = stringbuf; + + if (developer_networking.integer) + { + Con_Printf("NetConn_ClientParsePacket: %s sent us a command:\n", addressstring2); + Com_HexDumpToConsole(data, length); + } + + sendlength = sizeof(senddata) - 4; + switch(Crypto_ClientParsePacket(string, length, senddata+4, &sendlength, peeraddress)) + { + case CRYPTO_NOMATCH: + // nothing to do + break; + case CRYPTO_MATCH: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + break; + case CRYPTO_DISCARD: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + return true; + break; + case CRYPTO_REPLACE: + string = senddata+4; + length = sendlength; + break; + } + + if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.rcon_trying) + { + int i = 0, j; + for (j = 0;j < MAX_RCONS;j++) + { + // note: this value from i is used outside the loop too... + i = (cls.rcon_ringpos + j) % MAX_RCONS; + if(cls.rcon_commands[i][0]) + if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[i])) + break; + } + if (j < MAX_RCONS) + { + char buf[1500]; + char argbuf[1500]; + const char *e; + int n; + dpsnprintf(argbuf, sizeof(argbuf), "%s %s", string + 10, cls.rcon_commands[i]); + memcpy(buf, "\377\377\377\377srcon HMAC-MD4 CHALLENGE ", 29); + + e = strchr(rcon_password.string, ' '); + n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); + + if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 29), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n)) + { + int k; + buf[45] = ' '; + strlcpy(buf + 46, argbuf, sizeof(buf) - 46); + NetConn_Write(mysocket, buf, 46 + strlen(buf + 46), peeraddress); + cls.rcon_commands[i][0] = 0; + --cls.rcon_trying; + + for (k = 0;k < MAX_RCONS;k++) + if(cls.rcon_commands[k][0]) + if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[k])) + break; + if(k < MAX_RCONS) + { + int l; + NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", peeraddress); + // extend the timeout on other requests as we asked for a challenge + for (l = 0;l < MAX_RCONS;l++) + if(cls.rcon_commands[l][0]) + if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[l])) + cls.rcon_timeout[l] = realtime + rcon_secure_challengetimeout.value; + } + + return true; // we used up the challenge, so we can't use this oen for connecting now anyway + } + } + } + if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) + { + // darkplaces or quake3 + char protocolnames[1400]; + Protocol_Names(protocolnames, sizeof(protocolnames)); + Con_DPrintf("\"%s\" received, sending connect request back to %s\n", string, addressstring2); + M_Update_Return_Reason("Got challenge response"); + // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); + // TODO: add userinfo stuff here instead of using NQ commands? + NetConn_WriteString(mysocket, portable_va(vabuf, sizeof(vabuf), "\377\377\377\377connect\\protocol\\darkplaces 3\\protocols\\%s%s\\challenge\\%s", protocolnames, cls.connect_userinfo, string + 10), peeraddress); + return true; + } + if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying) + { + // darkplaces or quake3 + M_Update_Return_Reason("Accepted"); + NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_DARKPLACES3); + return true; + } + if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying) + { + char rejectreason[128]; + cls.connect_trying = false; + string += 7; + length = min(length - 7, (int)sizeof(rejectreason) - 1); + memcpy(rejectreason, string, length); + rejectreason[length] = 0; + M_Update_Return_Reason(rejectreason); + return true; + } + if (length >= 15 && !memcmp(string, "statusResponse\x0A", 15)) + { + serverlist_info_t *info; + char *p; + int n; + + string += 15; + // search the cache for this server and update it + n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); + if (n < 0) + return true; + + info = &serverlist_cache[n].info; + info->game[0] = 0; + info->mod[0] = 0; + info->map[0] = 0; + info->name[0] = 0; + info->qcstatus[0] = 0; + info->players[0] = 0; + info->protocol = -1; + info->numplayers = 0; + info->numbots = -1; + info->maxplayers = 0; + info->gameversion = 0; + + p = strchr(string, '\n'); + if(p) + { + *p = 0; // cut off the string there + ++p; + } + else + Con_Printf("statusResponse without players block?\n"); + + if ((s = InfoString_GetValue(string, "gamename" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->game, s, sizeof (info->game)); + if ((s = InfoString_GetValue(string, "modname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod )); + if ((s = InfoString_GetValue(string, "mapname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map )); + if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name)); + if ((s = InfoString_GetValue(string, "protocol" , infostringvalue, sizeof(infostringvalue))) != NULL) info->protocol = atoi(s); + if ((s = InfoString_GetValue(string, "clients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numplayers = atoi(s); + if ((s = InfoString_GetValue(string, "bots" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numbots = atoi(s); + if ((s = InfoString_GetValue(string, "sv_maxclients", infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s); + if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s); + if ((s = InfoString_GetValue(string, "qcstatus" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->qcstatus, s, sizeof(info->qcstatus)); + if (p != NULL) strlcpy(info->players, p, sizeof(info->players)); + info->numhumans = info->numplayers - max(0, info->numbots); + info->freeslots = info->maxplayers - info->numplayers; + + NetConn_ClientParsePacket_ServerList_UpdateCache(n); + + return true; + } + if (length >= 13 && !memcmp(string, "infoResponse\x0A", 13)) + { + serverlist_info_t *info; + int n; + + string += 13; + // search the cache for this server and update it + n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); + if (n < 0) + return true; + + info = &serverlist_cache[n].info; + info->game[0] = 0; + info->mod[0] = 0; + info->map[0] = 0; + info->name[0] = 0; + info->qcstatus[0] = 0; + info->players[0] = 0; + info->protocol = -1; + info->numplayers = 0; + info->numbots = -1; + info->maxplayers = 0; + info->gameversion = 0; + + if ((s = InfoString_GetValue(string, "gamename" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->game, s, sizeof (info->game)); + if ((s = InfoString_GetValue(string, "modname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod )); + if ((s = InfoString_GetValue(string, "mapname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map )); + if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name)); + if ((s = InfoString_GetValue(string, "protocol" , infostringvalue, sizeof(infostringvalue))) != NULL) info->protocol = atoi(s); + if ((s = InfoString_GetValue(string, "clients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numplayers = atoi(s); + if ((s = InfoString_GetValue(string, "bots" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numbots = atoi(s); + if ((s = InfoString_GetValue(string, "sv_maxclients", infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s); + if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s); + if ((s = InfoString_GetValue(string, "qcstatus" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->qcstatus, s, sizeof(info->qcstatus)); + info->numhumans = info->numplayers - max(0, info->numbots); + info->freeslots = info->maxplayers - info->numplayers; + + NetConn_ClientParsePacket_ServerList_UpdateCache(n); + + return true; + } + if (!strncmp(string, "getserversResponse\\", 19) && serverlist_cachecount < SERVERLIST_TOTALSIZE) + { + // Extract the IP addresses + data += 18; + length -= 18; + NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, false); + return true; + } + if (!strncmp(string, "getserversExtResponse", 21) && serverlist_cachecount < SERVERLIST_TOTALSIZE) + { + // Extract the IP addresses + data += 21; + length -= 21; + NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, true); + return true; + } + if (!memcmp(string, "d\n", 2) && serverlist_cachecount < SERVERLIST_TOTALSIZE) + { + // Extract the IP addresses + data += 2; + length -= 2; + masterreplycount++; + if (serverlist_consoleoutput) + Con_Printf("received QuakeWorld server list from %s...\n", addressstring2); + while (length >= 6 && (data[0] != 0xFF || data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF) && data[4] * 256 + data[5] != 0) + { + dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%u", data[0], data[1], data[2], data[3], data[4] * 256 + data[5]); + if (serverlist_consoleoutput && developer_networking.integer) + Con_Printf("Requesting info from QuakeWorld server %s\n", ipstring); + + if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_QUAKEWORLD, ipstring, false ) ) { + break; + } + + // move on to next address in packet + data += 6; + length -= 6; + } + // begin or resume serverlist queries + serverlist_querysleep = false; + serverlist_querywaittime = realtime + 3; + return true; + } + if (!strncmp(string, "extResponse ", 12)) + { + ++cl_net_extresponse_count; + if(cl_net_extresponse_count > NET_EXTRESPONSE_MAX) + cl_net_extresponse_count = NET_EXTRESPONSE_MAX; + cl_net_extresponse_last = (cl_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX; + portable_snprintf(cl_net_extresponse[cl_net_extresponse_last], sizeof(cl_net_extresponse[cl_net_extresponse_last]), "\"%s\" %s", addressstring2, string + 12); + return true; + } + if (!strncmp(string, "ping", 4)) + { + if (developer_extra.integer) + Con_DPrintf("Received ping from %s, sending ack\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress); + return true; + } + if (!strncmp(string, "ack", 3)) + return true; + // QuakeWorld compatibility + if (length > 1 && string[0] == 'c' && (string[1] == '-' || (string[1] >= '0' && string[1] <= '9')) && cls.connect_trying) + { + // challenge message + Con_Printf("challenge %s received, sending QuakeWorld connect request back to %s\n", string + 1, addressstring2); + M_Update_Return_Reason("Got QuakeWorld challenge response"); + cls.qw_qport = qport.integer; + // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); + NetConn_WriteString(mysocket, portable_va(vabuf, sizeof(vabuf), "\377\377\377\377connect %i %i %i \"%s%s\"\n", 28, cls.qw_qport, atoi(string + 1), cls.userinfo, cls.connect_userinfo), peeraddress); + return true; + } + if (length >= 1 && string[0] == 'j' && cls.connect_trying) + { + // accept message + M_Update_Return_Reason("QuakeWorld Accepted"); + NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_QUAKEWORLD); + return true; + } + if (length > 2 && !memcmp(string, "n\\", 2)) + { + serverlist_info_t *info; + int n; + + // qw server status + if (serverlist_consoleoutput && developer_networking.integer >= 2) + Con_Printf("QW server status from server at %s:\n%s\n", addressstring2, string + 1); + + string += 1; + // search the cache for this server and update it + n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); + if (n < 0) + return true; + + info = &serverlist_cache[n].info; + strlcpy(info->game, "QuakeWorld", sizeof(info->game)); + if ((s = InfoString_GetValue(string, "*gamedir" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod ));else info->mod[0] = 0; + if ((s = InfoString_GetValue(string, "map" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map ));else info->map[0] = 0; + if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name));else info->name[0] = 0; + info->protocol = 0; + info->numplayers = 0; // updated below + info->numhumans = 0; // updated below + if ((s = InfoString_GetValue(string, "maxclients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);else info->maxplayers = 0; + if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);else info->gameversion = 0; + + // count active players on server + // (we could gather more info, but we're just after the number) + s = strchr(string, '\n'); + if (s) + { + s++; + while (s < string + length) + { + for (;s < string + length && *s != '\n';s++) + ; + if (s >= string + length) + break; + info->numplayers++; + info->numhumans++; + s++; + } + } + + NetConn_ClientParsePacket_ServerList_UpdateCache(n); + + return true; + } + if (string[0] == 'n') + { + // qw print command + Con_Printf("QW print command from server at %s:\n%s\n", addressstring2, string + 1); + } + // we may not have liked the packet, but it was a command packet, so + // we're done processing this packet now + return true; + } + // quakeworld ingame packet + if (fromserver && cls.protocol == PROTOCOL_QUAKEWORLD && length >= 8 && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2) + { + ret = 0; + CL_ParseServerMessage(); + return ret; + } + // netquake control packets, supported for compatibility only + if (length >= 5 && BuffBigLong(data) == ((int)NETFLAG_CTL | length) && !ENCRYPTION_REQUIRED) + { + int n; + serverlist_info_t *info; + + data += 4; + length -= 4; + SZ_Clear(&cl_message); + SZ_Write(&cl_message, data, length); + MSG_BeginReading(&cl_message); + c = MSG_ReadByte(&cl_message); + switch (c) + { + case CCREP_ACCEPT: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREP_ACCEPT from %s.\n", addressstring2); + if (cls.connect_trying) + { + lhnetaddress_t clientportaddress; + clientportaddress = *peeraddress; + LHNETADDRESS_SetPort(&clientportaddress, MSG_ReadLong(&cl_message)); + // extra ProQuake stuff + if (length >= 6) + cls.proquake_servermod = MSG_ReadByte(&cl_message); // MOD_PROQUAKE + else + cls.proquake_servermod = 0; + if (length >= 7) + cls.proquake_serverversion = MSG_ReadByte(&cl_message); // version * 10 + else + cls.proquake_serverversion = 0; + if (length >= 8) + cls.proquake_serverflags = MSG_ReadByte(&cl_message); // flags (mainly PQF_CHEATFREE) + else + cls.proquake_serverflags = 0; + if (cls.proquake_servermod == 1) + Con_Printf("Connected to ProQuake %.1f server, enabling precise aim\n", cls.proquake_serverversion / 10.0f); + // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) + InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); + M_Update_Return_Reason("Accepted"); + NetConn_ConnectionEstablished(mysocket, &clientportaddress, PROTOCOL_QUAKE); + } + break; + case CCREP_REJECT: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREP_REJECT from %s.\n", addressstring2); + cls.connect_trying = false; + M_Update_Return_Reason((char *)MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); + break; + case CCREP_SERVER_INFO: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREP_SERVER_INFO from %s.\n", addressstring2); + // LordHavoc: because the quake server may report weird addresses + // we just ignore it and keep the real address + MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); + // search the cache for this server and update it + n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); + if (n < 0) + break; + + info = &serverlist_cache[n].info; + strlcpy(info->game, "Quake", sizeof(info->game)); + strlcpy(info->mod , "", sizeof(info->mod)); // mod name is not specified + strlcpy(info->name, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->name)); + strlcpy(info->map , MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->map)); + info->numplayers = MSG_ReadByte(&cl_message); + info->maxplayers = MSG_ReadByte(&cl_message); + info->protocol = MSG_ReadByte(&cl_message); + + NetConn_ClientParsePacket_ServerList_UpdateCache(n); + + break; + case CCREP_RCON: // RocketGuy: ProQuake rcon support + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREP_RCON from %s.\n", addressstring2); + + Con_Printf("%s\n", MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); + break; + case CCREP_PLAYER_INFO: + // we got a CCREP_PLAYER_INFO?? + //if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: received CCREP_PLAYER_INFO from %s.\n", addressstring2); + break; + case CCREP_RULE_INFO: + // we got a CCREP_RULE_INFO?? + //if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: received CCREP_RULE_INFO from %s.\n", addressstring2); + break; + default: + break; + } + SZ_Clear(&cl_message); + // we may not have liked the packet, but it was a valid control + // packet, so we're done processing this packet now + return true; + } + ret = 0; + if (fromserver && length >= (int)NET_HEADERSIZE && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2) + CL_ParseServerMessage(); + return ret; +} + +void NetConn_QueryQueueFrame(void) +{ + int index; + int queries; + int maxqueries; + double timeouttime; + static double querycounter = 0; + + if(!net_slist_pause.integer && serverlist_paused) + ServerList_RebuildViewList(); + serverlist_paused = net_slist_pause.integer != 0; + + if (serverlist_querysleep) + return; + + // apply a cool down time after master server replies, + // to avoid messing up the ping times on the servers + if (serverlist_querywaittime > realtime) + return; + + // each time querycounter reaches 1.0 issue a query + querycounter += cl.realframetime * net_slist_queriespersecond.value; + maxqueries = (int)querycounter; + maxqueries = bound(0, maxqueries, net_slist_queriesperframe.integer); + querycounter -= maxqueries; + + if( maxqueries == 0 ) { + return; + } + + // scan serverlist and issue queries as needed + serverlist_querysleep = true; + + timeouttime = realtime - net_slist_timeout.value; + for( index = 0, queries = 0 ; index < serverlist_cachecount && queries < maxqueries ; index++ ) + { + serverlist_entry_t *entry = &serverlist_cache[ index ]; + if( entry->query != SQS_QUERYING && entry->query != SQS_REFRESHING ) + { + continue; + } + + serverlist_querysleep = false; + if( entry->querycounter != 0 && entry->querytime > timeouttime ) + { + continue; + } + + if( entry->querycounter != (unsigned) net_slist_maxtries.integer ) + { + lhnetaddress_t address; + int socket; + + LHNETADDRESS_FromString(&address, entry->info.cname, 0); + if (entry->protocol == PROTOCOL_QUAKEWORLD) + { + for (socket = 0; socket < cl_numsockets ; socket++) + NetConn_WriteString(cl_sockets[socket], "\377\377\377\377status\n", &address); + } + else + { + for (socket = 0; socket < cl_numsockets ; socket++) + NetConn_WriteString(cl_sockets[socket], "\377\377\377\377getstatus", &address); + } + + // update the entry fields + entry->querytime = realtime; + entry->querycounter++; + + // if not in the slist menu we should print the server to console + if (serverlist_consoleoutput) + Con_Printf("querying %25s (%i. try)\n", entry->info.cname, entry->querycounter); + + queries++; + } + else + { + // have we tried to refresh this server? + if( entry->query == SQS_REFRESHING ) { + // yes, so update the reply count (since its not responding anymore) + serverreplycount--; + if(!serverlist_paused) + ServerList_ViewList_Remove(entry); + } + entry->query = SQS_TIMEDOUT; + } + } +} + +void NetConn_ClientFrame(void) +{ + int i, length; + lhnetaddress_t peeraddress; + unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; + NetConn_UpdateSockets(); + if (cls.connect_trying && cls.connect_nextsendtime < realtime) + { + if (cls.connect_remainingtries == 0) + M_Update_Return_Reason("Connect: Waiting 10 seconds for reply"); + cls.connect_nextsendtime = realtime + 1; + cls.connect_remainingtries--; + if (cls.connect_remainingtries <= -10) + { + cls.connect_trying = false; + M_Update_Return_Reason("Connect: Failed"); + return; + } + // try challenge first (newer DP server or QW) + NetConn_WriteString(cls.connect_mysocket, "\377\377\377\377getchallenge", &cls.connect_address); + // then try netquake as a fallback (old server, or netquake) + SZ_Clear(&cl_message); + // save space for the header, filled in later + MSG_WriteLong(&cl_message, 0); + MSG_WriteByte(&cl_message, CCREQ_CONNECT); + MSG_WriteString(&cl_message, "QUAKE"); + MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION); + // extended proquake stuff + MSG_WriteByte(&cl_message, 1); // mod = MOD_PROQUAKE + // this version matches ProQuake 3.40, the first version to support + // the NAT fix, and it only supports the NAT fix for ProQuake 3.40 or + // higher clients, so we pretend we are that version... + MSG_WriteByte(&cl_message, 34); // version * 10 + MSG_WriteByte(&cl_message, 0); // flags + MSG_WriteLong(&cl_message, 0); // password + // write the packetsize now... + StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(cls.connect_mysocket, cl_message.data, cl_message.cursize, &cls.connect_address); + SZ_Clear(&cl_message); + } + for (i = 0;i < cl_numsockets;i++) + { + while (cl_sockets[i] && (length = NetConn_Read(cl_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0) + { +// R_TimeReport("clientreadnetwork"); + NetConn_ClientParsePacket(cl_sockets[i], readbuffer, length, &peeraddress); +// R_TimeReport("clientparsepacket"); + } + } + NetConn_QueryQueueFrame(); + if (cls.netcon && realtime > cls.netcon->timeout && !sv.active) + { + Con_Print("Connection timed out\n"); + CL_Disconnect(); + SV_LockThreadMutex(); + Host_ShutdownServer (); + SV_UnlockThreadMutex(); + } +} + +static void NetConn_BuildChallengeString(char *buffer, int bufferlength) +{ + int i; + char c; + for (i = 0;i < bufferlength - 1;i++) + { + do + { + c = rand () % (127 - 33) + 33; + } while (c == '\\' || c == ';' || c == '"' || c == '%' || c == '/'); + buffer[i] = c; + } + buffer[i] = 0; +} + +/// (div0) build the full response only if possible; better a getinfo response than no response at all if getstatus won't fit +static qboolean NetConn_BuildStatusResponse(const char* challenge, char* out_msg, size_t out_size, qboolean fullstatus) +{ + prvm_prog_t *prog = SVVM_prog; + char qcstatus[256]; + unsigned int nb_clients = 0, nb_bots = 0, i; + int length; + char teambuf[3]; + const char *crypto_idstring; + const char *str; + + // How many clients are there? + for (i = 0;i < (unsigned int)svs.maxclients;i++) + { + if (svs.clients[i].active) + { + nb_clients++; + if (!svs.clients[i].netconnection) + nb_bots++; + } + } + + *qcstatus = 0; + str = PRVM_GetString(prog, PRVM_serverglobalstring(worldstatus)); + if(str && *str) + { + char *p; + const char *q; + p = qcstatus; + for(q = str; *q && (size_t)(p - qcstatus) < (sizeof(qcstatus) - 1); ++q) + if(*q != '\\' && *q != '\n') + *p++ = *q; + *p = 0; + } + + /// \TODO: we should add more information for the full status string + crypto_idstring = Crypto_GetInfoResponseDataString(); + length = portable_snprintf(out_msg, out_size, + "\377\377\377\377%s\x0A" + "\\gamename\\%s\\modname\\%s\\gameversion\\%d\\sv_maxclients\\%d" + "\\clients\\%d\\bots\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d" + "%s%s" + "%s%s" + "%s%s" + "%s", + fullstatus ? "statusResponse" : "infoResponse", + gamename, com_modname, gameversion.integer, svs.maxclients, + nb_clients, nb_bots, sv.worldbasename, hostname.string, NET_PROTOCOL_VERSION, + *qcstatus ? "\\qcstatus\\" : "", qcstatus, + challenge ? "\\challenge\\" : "", challenge ? challenge : "", + crypto_idstring ? "\\d0_blind_id\\" : "", crypto_idstring ? crypto_idstring : "", + fullstatus ? "\n" : ""); + + // Make sure it fits in the buffer + if (length < 0) + goto bad; + + if (fullstatus) + { + char *ptr; + int left; + int savelength; + + savelength = length; + + ptr = out_msg + length; + left = (int)out_size - length; + + for (i = 0;i < (unsigned int)svs.maxclients;i++) + { + client_t *cl = &svs.clients[i]; + if (cl->active) + { + int nameind, cleanind, pingvalue; + char curchar; + char cleanname [sizeof(cl->name)]; + const char *str; + prvm_edict_t *ed; + + // Remove all characters '"' and '\' in the player name + nameind = 0; + cleanind = 0; + do + { + curchar = cl->name[nameind++]; + if (curchar != '"' && curchar != '\\') + { + cleanname[cleanind++] = curchar; + if (cleanind == sizeof(cleanname) - 1) + break; + } + } while (curchar != '\0'); + cleanname[cleanind] = 0; // cleanind is always a valid index even at this point + + pingvalue = (int)(cl->ping * 1000.0f); + if(cl->netconnection) + pingvalue = bound(1, pingvalue, 9999); + else + pingvalue = 0; + + *qcstatus = 0; + ed = PRVM_EDICT_NUM(i + 1); + str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus)); + if(str && *str) + { + char *p; + const char *q; + p = qcstatus; + for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q) + if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q)) + *p++ = *q; + *p = 0; + } + + if ((gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) && (teamplay.integer > 0)) + { + if(cl->frags == -666) // spectator + strlcpy(teambuf, " 0", sizeof(teambuf)); + else if(cl->colors == 0x44) // red team + strlcpy(teambuf, " 1", sizeof(teambuf)); + else if(cl->colors == 0xDD) // blue team + strlcpy(teambuf, " 2", sizeof(teambuf)); + else if(cl->colors == 0xCC) // yellow team + strlcpy(teambuf, " 3", sizeof(teambuf)); + else if(cl->colors == 0x99) // pink team + strlcpy(teambuf, " 4", sizeof(teambuf)); + else + strlcpy(teambuf, " 0", sizeof(teambuf)); + } + else + *teambuf = 0; + + // note: team number is inserted according to SoF2 protocol + if(*qcstatus) + length = dpsnprintf(ptr, left, "%s %d%s \"%s\"\n", + qcstatus, + pingvalue, + teambuf, + cleanname); + else + length = dpsnprintf(ptr, left, "%d %d%s \"%s\"\n", + cl->frags, + pingvalue, + teambuf, + cleanname); + + if(length < 0) + { + // out of space? + // turn it into an infoResponse! + out_msg[savelength] = 0; + memcpy(out_msg + 4, "infoResponse\x0A", 13); + memmove(out_msg + 17, out_msg + 19, savelength - 19); + break; + } + left -= length; + ptr += length; + } + } + } + + return true; + +bad: + return false; +} + +static qboolean NetConn_PreventFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength, double floodtime, qboolean renew) +{ + size_t floodslotnum, bestfloodslotnum; + double bestfloodtime; + lhnetaddress_t noportpeeraddress; + // see if this is a connect flood + noportpeeraddress = *peeraddress; + LHNETADDRESS_SetPort(&noportpeeraddress, 0); + bestfloodslotnum = 0; + bestfloodtime = floodlist[bestfloodslotnum].lasttime; + for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++) + { + if (bestfloodtime >= floodlist[floodslotnum].lasttime) + { + bestfloodtime = floodlist[floodslotnum].lasttime; + bestfloodslotnum = floodslotnum; + } + if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0) + { + // this address matches an ongoing flood address + if (realtime < floodlist[floodslotnum].lasttime + floodtime) + { + if(renew) + { + // renew the ban on this address so it does not expire + // until the flood has subsided + floodlist[floodslotnum].lasttime = realtime; + } + //Con_Printf("Flood detected!\n"); + return true; + } + // the flood appears to have subsided, so allow this + bestfloodslotnum = floodslotnum; // reuse the same slot + break; + } + } + // begin a new timeout on this address + floodlist[bestfloodslotnum].address = noportpeeraddress; + floodlist[bestfloodslotnum].lasttime = realtime; + //Con_Printf("Flood detection initiated!\n"); + return false; +} + +void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength) +{ + size_t floodslotnum; + lhnetaddress_t noportpeeraddress; + // see if this is a connect flood + noportpeeraddress = *peeraddress; + LHNETADDRESS_SetPort(&noportpeeraddress, 0); + for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++) + { + if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0) + { + // this address matches an ongoing flood address + // remove the ban + floodlist[floodslotnum].address.addresstype = LHNETADDRESSTYPE_NONE; + floodlist[floodslotnum].lasttime = 0; + //Con_Printf("Flood cleared!\n"); + } + } +} + +typedef qboolean (*rcon_matchfunc_t) (lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen); + +static qboolean hmac_mdfour_time_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) +{ + char mdfourbuf[16]; + long t1, t2; + + t1 = (long) time(NULL); + t2 = strtol(s, NULL, 0); + if(abs(t1 - t2) > rcon_secure_maxdiff.integer) + return false; + + if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, strlen(password))) + return false; + + return !memcmp(mdfourbuf, hash, 16); +} + +static qboolean hmac_mdfour_challenge_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) +{ + char mdfourbuf[16]; + int i; + + if(slen < (int)(sizeof(challenge[0].string)) - 1) + return false; + + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strncmp(challenge[i].string, s, sizeof(challenge[0].string) - 1)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) + return false; + + if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, strlen(password))) + return false; + + if(memcmp(mdfourbuf, hash, 16)) + return false; + + // unmark challenge to prevent replay attacks + challenge[i].time = 0; + + return true; +} + +static qboolean plaintext_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) +{ + return !strcmp(password, hash); +} + +/// returns a string describing the user level, or NULL for auth failure +static const char *RCon_Authenticate(lhnetaddress_t *peeraddress, const char *password, const char *s, const char *endpos, rcon_matchfunc_t comparator, const char *cs, int cslen) +{ + const char *text, *userpass_start, *userpass_end, *userpass_startpass; + static char buf[MAX_INPUTLINE]; + qboolean hasquotes; + qboolean restricted = false; + qboolean have_usernames = false; + char vabuf[1024]; + + userpass_start = rcon_password.string; + while((userpass_end = strchr(userpass_start, ' '))) + { + have_usernames = true; + strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1)); + if(buf[0]) + if(comparator(peeraddress, buf, password, cs, cslen)) + goto allow; + userpass_start = userpass_end + 1; + } + if(userpass_start[0]) + { + userpass_end = userpass_start + strlen(userpass_start); + if(comparator(peeraddress, userpass_start, password, cs, cslen)) + goto allow; + } + + restricted = true; + have_usernames = false; + userpass_start = rcon_restricted_password.string; + while((userpass_end = strchr(userpass_start, ' '))) + { + have_usernames = true; + strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1)); + if(buf[0]) + if(comparator(peeraddress, buf, password, cs, cslen)) + goto check; + userpass_start = userpass_end + 1; + } + if(userpass_start[0]) + { + userpass_end = userpass_start + strlen(userpass_start); + if(comparator(peeraddress, userpass_start, password, cs, cslen)) + goto check; + } + + return NULL; // DENIED + +check: + for(text = s; text != endpos; ++text) + if((signed char) *text > 0 && ((signed char) *text < (signed char) ' ' || *text == ';')) + return NULL; // block possible exploits against the parser/alias expansion + + while(s != endpos) + { + size_t l = strlen(s); + if(l) + { + hasquotes = (strchr(s, '"') != NULL); + // sorry, we can't allow these substrings in wildcard expressions, + // as they can mess with the argument counts + text = rcon_restricted_commands.string; + while(COM_ParseToken_Console(&text)) + { + // com_token now contains a pattern to check for... + if(strchr(com_token, '*') || strchr(com_token, '?')) // wildcard expression, * can only match a SINGLE argument + { + if(!hasquotes) + if(matchpattern_with_separator(s, com_token, true, " ", true)) // note how we excluded tab, newline etc. above + goto match; + } + else if(strchr(com_token, ' ')) // multi-arg expression? must match in whole + { + if(!strcmp(com_token, s)) + goto match; + } + else // single-arg expression? must match the beginning of the command + { + if(!strcmp(com_token, s)) + goto match; + if(!memcmp(va(vabuf, sizeof(vabuf), "%s ", com_token), s, strlen(com_token) + 1)) + goto match; + } + } + // if we got here, nothing matched! + return NULL; + } +match: + s += l + 1; + } + +allow: + userpass_startpass = strchr(userpass_start, ':'); + if(have_usernames && userpass_startpass && userpass_startpass < userpass_end) + return va(vabuf, sizeof(vabuf), "%srcon (username %.*s)", restricted ? "restricted " : "", (int)(userpass_startpass-userpass_start), userpass_start); + + return va(vabuf, sizeof(vabuf), "%srcon", restricted ? "restricted " : ""); +} + +static void RCon_Execute(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, const char *addressstring2, const char *userlevel, const char *s, const char *endpos, qboolean proquakeprotocol) +{ + if(userlevel) + { + // looks like a legitimate rcon command with the correct password + const char *s_ptr = s; + Con_Printf("server received %s command from %s: ", userlevel, host_client ? host_client->name : addressstring2); + while(s_ptr != endpos) + { + size_t l = strlen(s_ptr); + if(l) + Con_Printf(" %s;", s_ptr); + s_ptr += l + 1; + } + Con_Printf("\n"); + + if (!host_client || !host_client->netconnection || LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP) + Con_Rcon_Redirect_Init(mysocket, peeraddress, proquakeprotocol); + while(s != endpos) + { + size_t l = strlen(s); + if(l) + { + client_t *host_client_save = host_client; + Cmd_ExecuteString(s, src_command, true); + host_client = host_client_save; + // in case it is a command that changes host_client (like restart) + } + s += l + 1; + } + Con_Rcon_Redirect_End(); + } + else + { + Con_Printf("server denied rcon access to %s\n", host_client ? host_client->name : addressstring2); + } +} + +static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress) +{ + int i, ret, clientnum, best; + double besttime; + client_t *client; + char *s, *string, response[1400], addressstring2[128]; + static char stringbuf[16384]; // server only + qboolean islocal = (LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP); + char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; + size_t sendlength, response_len; + char infostringvalue[MAX_INPUTLINE]; + char vabuf[1024]; + + if (!sv.active) + return false; + + // convert the address to a string incase we need it + LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); + + // see if we can identify the sender as a local player + // (this is necessary for rcon to send a reliable reply if the client is + // actually on the server, not sending remotely) + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if (host_client->netconnection && host_client->netconnection->mysocket == mysocket && !LHNETADDRESS_Compare(&host_client->netconnection->peeraddress, peeraddress)) + break; + if (i == svs.maxclients) + host_client = NULL; + + if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255) + { + // received a command string - strip off the packaging and put it + // into our string buffer with NULL termination + data += 4; + length -= 4; + length = min(length, (int)sizeof(stringbuf) - 1); + memcpy(stringbuf, data, length); + stringbuf[length] = 0; + string = stringbuf; + + if (developer_extra.integer) + { + Con_Printf("NetConn_ServerParsePacket: %s sent us a command:\n", addressstring2); + Com_HexDumpToConsole(data, length); + } + + sendlength = sizeof(senddata) - 4; + switch(Crypto_ServerParsePacket(string, length, senddata+4, &sendlength, peeraddress)) + { + case CRYPTO_NOMATCH: + // nothing to do + break; + case CRYPTO_MATCH: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + break; + case CRYPTO_DISCARD: + if(sendlength) + { + memcpy(senddata, "\377\377\377\377", 4); + NetConn_Write(mysocket, senddata, sendlength+4, peeraddress); + } + return true; + break; + case CRYPTO_REPLACE: + string = senddata+4; + length = sendlength; + break; + } + + if (length >= 12 && !memcmp(string, "getchallenge", 12) && (islocal || sv_public.integer > -3)) + { + for (i = 0, best = 0, besttime = realtime;i < MAX_CHALLENGES;i++) + { + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address)) + break; + if (besttime > challenge[i].time) + besttime = challenge[best = i].time; + } + // if we did not find an exact match, choose the oldest and + // update address and string + if (i == MAX_CHALLENGES) + { + i = best; + challenge[i].address = *peeraddress; + NetConn_BuildChallengeString(challenge[i].string, sizeof(challenge[i].string)); + } + else + { + // flood control: drop if requesting challenge too often + if(challenge[i].time > realtime - net_challengefloodblockingtimeout.value) + return true; + } + challenge[i].time = realtime; + // send the challenge + portable_snprintf(response, sizeof(response), "\377\377\377\377challenge %s", challenge[i].string); + response_len = strlen(response) + 1; + Crypto_ServerAppendToChallenge(string, length, response, &response_len, sizeof(response)); + NetConn_Write(mysocket, response, response_len, peeraddress); + return true; + } + if (length > 8 && !memcmp(string, "connect\\", 8)) + { + crypto_t *crypto = Crypto_ServerGetInstance(peeraddress); + string += 7; + length -= 7; + + if(crypto && crypto->authenticated) + { + // no need to check challenge + if(crypto_developer.integer) + { + Con_Printf("%s connection to %s is being established: client is %s@%.*s, I am %.*s@%.*s\n", + crypto->use_aes ? "Encrypted" : "Authenticated", + addressstring2, + crypto->client_idfp[0] ? crypto->client_idfp : "-", + crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-", + crypto_keyfp_recommended_length, crypto->server_idfp[0] ? crypto->server_idfp : "-", + crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-" + ); + } + } + else + { + if ((s = InfoString_GetValue(string, "challenge", infostringvalue, sizeof(infostringvalue)))) + { + // validate the challenge + for (i = 0;i < MAX_CHALLENGES;i++) + if(challenge[i].time > 0) + if (!LHNETADDRESS_Compare(peeraddress, &challenge[i].address) && !strcmp(challenge[i].string, s)) + break; + // if the challenge is not recognized, drop the packet + if (i == MAX_CHALLENGES) + return true; + } + } + + if((s = InfoString_GetValue(string, "message", infostringvalue, sizeof(infostringvalue)))) + Con_DPrintf("Connecting client %s sent us the message: %s\n", addressstring2, s); + + if(!(islocal || sv_public.integer > -2)) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject %s\" to %s.\n", sv_public_rejectreason.string, addressstring2); + NetConn_WriteString(mysocket, portable_va(vabuf, sizeof(vabuf), "\377\377\377\377reject %s", sv_public_rejectreason.string), peeraddress); + return true; + } + + // check engine protocol + if(!(s = InfoString_GetValue(string, "protocol", infostringvalue, sizeof(infostringvalue))) || strcmp(s, "darkplaces 3")) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Wrong game protocol.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Wrong game protocol.", peeraddress); + return true; + } + + // see if this is a duplicate connection request or a disconnected + // client who is rejoining to the same client slot + for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) + { + if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0) + { + // this is a known client... + if(crypto && crypto->authenticated) + { + // reject if changing key! + if(client->netconnection->crypto.authenticated) + { + if( + strcmp(client->netconnection->crypto.client_idfp, crypto->client_idfp) + || + strcmp(client->netconnection->crypto.server_idfp, crypto->server_idfp) + || + strcmp(client->netconnection->crypto.client_keyfp, crypto->client_keyfp) + || + strcmp(client->netconnection->crypto.server_keyfp, crypto->server_keyfp) + ) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to change key of crypto.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to change key of crypto.", peeraddress); + return true; + } + } + } + else + { + // reject if downgrading! + if(client->netconnection->crypto.authenticated) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to downgrade crypto.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to downgrade crypto.", peeraddress); + return true; + } + } + if (client->begun) + { + // client crashed and is coming back, + // keep their stuff intact + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&client->netconnection->crypto, crypto); + SV_SendServerinfo(client); + } + else + { + // client is still trying to connect, + // so we send a duplicate reply + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending duplicate accept to %s.\n", addressstring2); + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&client->netconnection->crypto, crypto); + NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); + } + return true; + } + } + + if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true)) + return true; + + // find an empty client slot for this new client + for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) + { + netconn_t *conn; + if (!client->active && (conn = NetConn_Open(mysocket, peeraddress))) + { + // allocated connection + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", conn->address); + NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); + // now set up the client + if(crypto && crypto->authenticated) + Crypto_ServerFinishInstance(&conn->crypto, crypto); + SV_ConnectClient(clientnum, conn); + NetConn_Heartbeat(1); + return true; + } + } + + // no empty slots found - server is full + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending \"reject Server is full.\" to %s.\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377reject Server is full.", peeraddress); + + return true; + } + if (length >= 7 && !memcmp(string, "getinfo", 7) && (islocal || sv_public.integer > -1)) + { + const char *challenge = NULL; + + if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false)) + return true; + + // If there was a challenge in the getinfo message + if (length > 8 && string[7] == ' ') + challenge = string + 8; + + if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), false)) + { + if (developer_extra.integer) + Con_DPrintf("Sending reply to master %s - %s\n", addressstring2, response); + NetConn_WriteString(mysocket, response, peeraddress); + } + return true; + } + if (length >= 9 && !memcmp(string, "getstatus", 9) && (islocal || sv_public.integer > -1)) + { + const char *challenge = NULL; + + if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false)) + return true; + + // If there was a challenge in the getinfo message + if (length > 10 && string[9] == ' ') + challenge = string + 10; + + if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), true)) + { + if (developer_extra.integer) + Con_DPrintf("Sending reply to client %s - %s\n", addressstring2, response); + NetConn_WriteString(mysocket, response, peeraddress); + } + return true; + } + if (length >= 37 && !memcmp(string, "srcon HMAC-MD4 TIME ", 20)) + { + char *password = string + 20; + char *timeval = string + 37; + char *s = strchr(timeval, ' '); + char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it + const char *userlevel; + + if(rcon_secure.integer > 1) + return true; + + if(!s) + return true; // invalid packet + ++s; + + userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_time_matching, timeval, endpos - timeval - 1); // not including the appended \0 into the HMAC + RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); + return true; + } + if (length >= 42 && !memcmp(string, "srcon HMAC-MD4 CHALLENGE ", 25)) + { + char *password = string + 25; + char *challenge = string + 42; + char *s = strchr(challenge, ' '); + char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it + const char *userlevel; + if(!s) + return true; // invalid packet + ++s; + + userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_challenge_matching, challenge, endpos - challenge - 1); // not including the appended \0 into the HMAC + RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); + return true; + } + if (length >= 5 && !memcmp(string, "rcon ", 5)) + { + int i; + char *s = string + 5; + char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it + char password[64]; + + if(rcon_secure.integer > 0) + return true; + + for (i = 0;!ISWHITESPACE(*s);s++) + if (i < (int)sizeof(password) - 1) + password[i++] = *s; + if(ISWHITESPACE(*s) && s != endpos) // skip leading ugly space + ++s; + password[i] = 0; + if (!ISWHITESPACE(password[0])) + { + const char *userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0); + RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); + } + return true; + } + if (!strncmp(string, "extResponse ", 12)) + { + ++sv_net_extresponse_count; + if(sv_net_extresponse_count > NET_EXTRESPONSE_MAX) + sv_net_extresponse_count = NET_EXTRESPONSE_MAX; + sv_net_extresponse_last = (sv_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX; + portable_snprintf(sv_net_extresponse[sv_net_extresponse_last], sizeof(sv_net_extresponse[sv_net_extresponse_last]), "'%s' %s", addressstring2, string + 12); + return true; + } + if (!strncmp(string, "ping", 4)) + { + if (developer_extra.integer) + Con_DPrintf("Received ping from %s, sending ack\n", addressstring2); + NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress); + return true; + } + if (!strncmp(string, "ack", 3)) + return true; + // we may not have liked the packet, but it was a command packet, so + // we're done processing this packet now + return true; + } + // netquake control packets, supported for compatibility only, and only + // when running game protocols that are normally served via this connection + // protocol + // (this protects more modern protocols against being used for + // Quake packet flood Denial Of Service attacks) + if (length >= 5 && (i = BuffBigLong(data)) && (i & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (i & NETFLAG_LENGTH_MASK) == length && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) && !ENCRYPTION_REQUIRED) + { + int c; + int protocolnumber; + const char *protocolname; + data += 4; + length -= 4; + SZ_Clear(&sv_message); + SZ_Write(&sv_message, data, length); + MSG_BeginReading(&sv_message); + c = MSG_ReadByte(&sv_message); + switch (c) + { + case CCREQ_CONNECT: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_CONNECT from %s.\n", addressstring2); + if(!(islocal || sv_public.integer > -2)) + { + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"%s\" to %s.\n", sv_public_rejectreason.string, addressstring2); + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_REJECT); + MSG_WriteString(&sv_message, va(vabuf, sizeof(vabuf), "%s\n", sv_public_rejectreason.string)); + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + break; + } + + protocolname = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)); + protocolnumber = MSG_ReadByte(&sv_message); + if (strcmp(protocolname, "QUAKE") || protocolnumber != NET_PROTOCOL_VERSION) + { + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Incompatible version.\" to %s.\n", addressstring2); + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_REJECT); + MSG_WriteString(&sv_message, "Incompatible version.\n"); + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + break; + } + + // see if this connect request comes from a known client + for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) + { + if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0) + { + // this is either a duplicate connection request + // or coming back from a timeout + // (if so, keep their stuff intact) + + crypto_t *crypto = Crypto_ServerGetInstance(peeraddress); + if((crypto && crypto->authenticated) || client->netconnection->crypto.authenticated) + { + if (developer_extra.integer) + Con_Printf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Attempt to downgrade crypto.\" to %s.\n", addressstring2); + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_REJECT); + MSG_WriteString(&sv_message, "Attempt to downgrade crypto.\n"); + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + return true; + } + + // send a reply + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending duplicate CCREP_ACCEPT to %s.\n", addressstring2); + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_ACCEPT); + MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(client->netconnection->mysocket))); + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + + // if client is already spawned, re-send the + // serverinfo message as they'll need it to play + if (client->begun) + SV_SendServerinfo(client); + return true; + } + } + + // this is a new client, check for connection flood + if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true)) + break; + + // find a slot for the new client + for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) + { + netconn_t *conn; + if (!client->active && (client->netconnection = conn = NetConn_Open(mysocket, peeraddress)) != NULL) + { + // connect to the client + // everything is allocated, just fill in the details + strlcpy (conn->address, addressstring2, sizeof (conn->address)); + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_ACCEPT to %s.\n", addressstring2); + // send back the info about the server connection + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_ACCEPT); + MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(conn->mysocket))); + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + // now set up the client struct + SV_ConnectClient(clientnum, conn); + NetConn_Heartbeat(1); + return true; + } + } + + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Server is full.\" to %s.\n", addressstring2); + // no room; try to let player know + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_REJECT); + MSG_WriteString(&sv_message, "Server is full.\n"); + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + break; + case CCREQ_SERVER_INFO: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_SERVER_INFO from %s.\n", addressstring2); + if(!(islocal || sv_public.integer > -1)) + break; + + if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false)) + break; + + if (sv.active && !strcmp(MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), "QUAKE")) + { + int numclients; + char myaddressstring[128]; + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_SERVER_INFO to %s.\n", addressstring2); + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_SERVER_INFO); + LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), myaddressstring, sizeof(myaddressstring), true); + MSG_WriteString(&sv_message, myaddressstring); + MSG_WriteString(&sv_message, hostname.string); + MSG_WriteString(&sv_message, sv.name); + // How many clients are there? + for (i = 0, numclients = 0;i < svs.maxclients;i++) + if (svs.clients[i].active) + numclients++; + MSG_WriteByte(&sv_message, numclients); + MSG_WriteByte(&sv_message, svs.maxclients); + MSG_WriteByte(&sv_message, NET_PROTOCOL_VERSION); + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + } + break; + case CCREQ_PLAYER_INFO: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_PLAYER_INFO from %s.\n", addressstring2); + if(!(islocal || sv_public.integer > -1)) + break; + + if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false)) + break; + + if (sv.active) + { + int playerNumber, activeNumber, clientNumber; + client_t *client; + + playerNumber = MSG_ReadByte(&sv_message); + activeNumber = -1; + for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++) + if (client->active && ++activeNumber == playerNumber) + break; + if (clientNumber != svs.maxclients) + { + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_PLAYER_INFO); + MSG_WriteByte(&sv_message, playerNumber); + MSG_WriteString(&sv_message, client->name); + MSG_WriteLong(&sv_message, client->colors); + MSG_WriteLong(&sv_message, client->frags); + MSG_WriteLong(&sv_message, (int)(realtime - client->connecttime)); + if(sv_status_privacy.integer) + MSG_WriteString(&sv_message, client->netconnection ? "hidden" : "botclient"); + else + MSG_WriteString(&sv_message, client->netconnection ? client->netconnection->address : "botclient"); + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + } + } + break; + case CCREQ_RULE_INFO: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RULE_INFO from %s.\n", addressstring2); + if(!(islocal || sv_public.integer > -1)) + break; + + // no flood check here, as it only returns one cvar for one cvar and clients may iterate quickly + + if (sv.active) + { + char *prevCvarName; + cvar_t *var; + + // find the search start location + prevCvarName = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)); + var = Cvar_FindVarAfter(prevCvarName, CVAR_NOTIFY); + + // send the response + SZ_Clear(&sv_message); + // save space for the header, filled in later + MSG_WriteLong(&sv_message, 0); + MSG_WriteByte(&sv_message, CCREP_RULE_INFO); + if (var) + { + MSG_WriteString(&sv_message, var->name); + MSG_WriteString(&sv_message, var->string); + } + StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); + SZ_Clear(&sv_message); + } + break; + case CCREQ_RCON: + if (developer_extra.integer) + Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RCON from %s.\n", addressstring2); + if (sv.active && !rcon_secure.integer) + { + char password[2048]; + char cmd[2048]; + char *s; + char *endpos; + const char *userlevel; + strlcpy(password, MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), sizeof(password)); + strlcpy(cmd, MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), sizeof(cmd)); + s = cmd; + endpos = cmd + strlen(cmd) + 1; // one behind the NUL, so adding strlen+1 will eventually reach it + userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0); + RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, true); + return true; + } + break; + default: + break; + } + SZ_Clear(&sv_message); + // we may not have liked the packet, but it was a valid control + // packet, so we're done processing this packet now + return true; + } + if (host_client) + { + if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length, sv.protocol, host_client->begun ? net_messagetimeout.value : net_connecttimeout.value)) == 2) + { + SV_ReadClientMessage(); + return ret; + } + } + return 0; +} + +void NetConn_ServerFrame(void) +{ + int i, length; + lhnetaddress_t peeraddress; + unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; + for (i = 0;i < sv_numsockets;i++) + while (sv_sockets[i] && (length = NetConn_Read(sv_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0) + NetConn_ServerParsePacket(sv_sockets[i], readbuffer, length, &peeraddress); + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + // never timeout loopback connections + if (host_client->netconnection && realtime > host_client->netconnection->timeout && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP) + { + Con_Printf("Client \"%s\" connection timed out\n", host_client->name); + SV_DropClient(false); + } + } +} + +void NetConn_SleepMicroseconds(int microseconds) +{ + LHNET_SleepUntilPacket_Microseconds(microseconds); +} + +void NetConn_QueryMasters(qboolean querydp, qboolean queryqw) +{ + int i, j; + int masternum; + lhnetaddress_t masteraddress; + lhnetaddress_t broadcastaddress; + char request[256]; + + if (serverlist_cachecount >= SERVERLIST_TOTALSIZE) + return; + + // 26000 is the default quake server port, servers on other ports will not + // be found + // note this is IPv4-only, I doubt there are IPv6-only LANs out there + LHNETADDRESS_FromString(&broadcastaddress, "255.255.255.255", 26000); + + if (querydp) + { + for (i = 0;i < cl_numsockets;i++) + { + if (cl_sockets[i]) + { + const char *cmdname, *extraoptions; + int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])); + + if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af) + { + // search LAN for Quake servers + SZ_Clear(&cl_message); + // save space for the header, filled in later + MSG_WriteLong(&cl_message, 0); + MSG_WriteByte(&cl_message, CCREQ_SERVER_INFO); + MSG_WriteString(&cl_message, "QUAKE"); + MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION); + StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK)); + NetConn_Write(cl_sockets[i], cl_message.data, cl_message.cursize, &broadcastaddress); + SZ_Clear(&cl_message); + + // search LAN for DarkPlaces servers + NetConn_WriteString(cl_sockets[i], "\377\377\377\377getstatus", &broadcastaddress); + } + + // build the getservers message to send to the dpmaster master servers + if (LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == LHNETADDRESSTYPE_INET6) + { + cmdname = "getserversExt"; + extraoptions = " ipv4 ipv6"; // ask for IPv4 and IPv6 servers + } + else + { + cmdname = "getservers"; + extraoptions = ""; + } + portable_snprintf(request, sizeof(request), "\377\377\377\377%s %s %u empty full%s", cmdname, gamename, NET_PROTOCOL_VERSION, extraoptions); + + // search internet + for (masternum = 0;sv_masters[masternum].name;masternum++) + { + if (sv_masters[masternum].string && sv_masters[masternum].string[0] && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == af) + { + masterquerycount++; + NetConn_WriteString(cl_sockets[i], request, &masteraddress); + } + } + + // search favorite servers + for(j = 0; j < nFavorites; ++j) + { + if(LHNETADDRESS_GetAddressType(&favorites[j]) == af) + { + if(LHNETADDRESS_ToString(&favorites[j], request, sizeof(request), true)) + NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, request, true ); + } + } + } + } + } + + // only query QuakeWorld servers when the user wants to + if (queryqw) + { + for (i = 0;i < cl_numsockets;i++) + { + if (cl_sockets[i]) + { + int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])); + + if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af) + { + // search LAN for QuakeWorld servers + NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &broadcastaddress); + + // build the getservers message to send to the qwmaster master servers + // note this has no -1 prefix, and the trailing nul byte is sent + portable_snprintf(request, sizeof(request), "c\n"); + } + + // search internet + for (masternum = 0;sv_qwmasters[masternum].name;masternum++) + { + if (sv_qwmasters[masternum].string && LHNETADDRESS_FromString(&masteraddress, sv_qwmasters[masternum].string, QWMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i]))) + { + if (m_state != m_slist) + { + char lookupstring[128]; + LHNETADDRESS_ToString(&masteraddress, lookupstring, sizeof(lookupstring), true); + Con_Printf("Querying master %s (resolved from %s)\n", lookupstring, sv_qwmasters[masternum].string); + } + masterquerycount++; + NetConn_Write(cl_sockets[i], request, (int)strlen(request) + 1, &masteraddress); + } + } + + // search favorite servers + for(j = 0; j < nFavorites; ++j) + { + if(LHNETADDRESS_GetAddressType(&favorites[j]) == af) + { + if(LHNETADDRESS_ToString(&favorites[j], request, sizeof(request), true)) + { + NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &favorites[j]); + NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_QUAKEWORLD, request, true ); + } + } + } + } + } + } + if (!masterquerycount) + { + Con_Print("Unable to query master servers, no suitable network sockets active.\n"); + M_Update_Return_Reason("No network"); + } +} + +void NetConn_Heartbeat(int priority) +{ + lhnetaddress_t masteraddress; + int masternum; + lhnetsocket_t *mysocket; + + // if it's a state change (client connected), limit next heartbeat to no + // more than 30 sec in the future + if (priority == 1 && nextheartbeattime > realtime + 30.0) + nextheartbeattime = realtime + 30.0; + + // limit heartbeatperiod to 30 to 270 second range, + // lower limit is to avoid abusing master servers with excess traffic, + // upper limit is to avoid timing out on the master server (which uses + // 300 sec timeout) + if (sv_heartbeatperiod.value < 30) + Cvar_SetValueQuick(&sv_heartbeatperiod, 30); + if (sv_heartbeatperiod.value > 270) + Cvar_SetValueQuick(&sv_heartbeatperiod, 270); + + // make advertising optional and don't advertise singleplayer games, and + // only send a heartbeat as often as the admin wants + if (sv.active && sv_public.integer > 0 && svs.maxclients >= 2 && (priority > 1 || realtime > nextheartbeattime)) + { + nextheartbeattime = realtime + sv_heartbeatperiod.value; + for (masternum = 0;sv_masters[masternum].name;masternum++) + if (sv_masters[masternum].string && sv_masters[masternum].string[0] && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT) && (mysocket = NetConn_ChooseServerSocketForAddress(&masteraddress))) + NetConn_WriteString(mysocket, "\377\377\377\377heartbeat DarkPlaces\x0A", &masteraddress); + } +} + +static void Net_Heartbeat_f(void) +{ + if (sv.active) + NetConn_Heartbeat(2); + else + Con_Print("No server running, can not heartbeat to master server.\n"); +} + +static void PrintStats(netconn_t *conn) +{ + if ((cls.state == ca_connected && cls.protocol == PROTOCOL_QUAKEWORLD) || (sv.active && sv.protocol == PROTOCOL_QUAKEWORLD)) + Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->outgoing_unreliable_sequence, conn->qw.incoming_sequence); + else + Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->nq.sendSequence, conn->nq.receiveSequence); + Con_Printf("unreliable messages sent = %i\n", conn->unreliableMessagesSent); + Con_Printf("unreliable messages recv = %i\n", conn->unreliableMessagesReceived); + Con_Printf("reliable messages sent = %i\n", conn->reliableMessagesSent); + Con_Printf("reliable messages received = %i\n", conn->reliableMessagesReceived); + Con_Printf("packetsSent = %i\n", conn->packetsSent); + Con_Printf("packetsReSent = %i\n", conn->packetsReSent); + Con_Printf("packetsReceived = %i\n", conn->packetsReceived); + Con_Printf("receivedDuplicateCount = %i\n", conn->receivedDuplicateCount); + Con_Printf("droppedDatagrams = %i\n", conn->droppedDatagrams); +} + +void Net_Stats_f(void) +{ + netconn_t *conn; + Con_Print("connections =\n"); + for (conn = netconn_list;conn;conn = conn->next) + PrintStats(conn); +} + +void Net_Refresh_f(void) +{ + if (m_state != m_slist) { + Con_Print("Sending new requests to master servers\n"); + ServerList_QueryList(false, true, false, true); + Con_Print("Listening for replies...\n"); + } else + ServerList_QueryList(false, true, false, false); +} + +void Net_Slist_f(void) +{ + ServerList_ResetMasks(); + serverlist_sortbyfield = SLIF_PING; + serverlist_sortflags = 0; + if (m_state != m_slist) { + Con_Print("Sending requests to master servers\n"); + ServerList_QueryList(true, true, false, true); + Con_Print("Listening for replies...\n"); + } else + ServerList_QueryList(true, true, false, false); +} + +void Net_SlistQW_f(void) +{ + ServerList_ResetMasks(); + serverlist_sortbyfield = SLIF_PING; + serverlist_sortflags = 0; + if (m_state != m_slist) { + Con_Print("Sending requests to master servers\n"); + ServerList_QueryList(true, false, true, true); + serverlist_consoleoutput = true; + Con_Print("Listening for replies...\n"); + } else + ServerList_QueryList(true, false, true, false); +} + +void NetConn_Init(void) +{ + int i; + lhnetaddress_t tempaddress; + netconn_mempool = Mem_AllocPool("network connections", 0, NULL); + Cmd_AddCommand("net_stats", Net_Stats_f, "print network statistics"); + Cmd_AddCommand("net_slist", Net_Slist_f, "query dp master servers and print all server information"); + Cmd_AddCommand("net_slistqw", Net_SlistQW_f, "query qw master servers and print all server information"); + Cmd_AddCommand("net_refresh", Net_Refresh_f, "query dp master servers and refresh all server information"); + Cmd_AddCommand("heartbeat", Net_Heartbeat_f, "send a heartbeat to the master server (updates your server information)"); + Cvar_RegisterVariable(&rcon_restricted_password); + Cvar_RegisterVariable(&rcon_restricted_commands); + Cvar_RegisterVariable(&rcon_secure_maxdiff); + Cvar_RegisterVariable(&net_slist_queriespersecond); + Cvar_RegisterVariable(&net_slist_queriesperframe); + Cvar_RegisterVariable(&net_slist_timeout); + Cvar_RegisterVariable(&net_slist_maxtries); + Cvar_RegisterVariable(&net_slist_favorites); + Cvar_RegisterVariable(&net_slist_pause); + if(LHNET_DefaultDSCP(-1) >= 0) // register cvar only if supported + Cvar_RegisterVariable(&net_tos_dscp); + Cvar_RegisterVariable(&net_messagetimeout); + Cvar_RegisterVariable(&net_connecttimeout); + Cvar_RegisterVariable(&net_connectfloodblockingtimeout); + Cvar_RegisterVariable(&net_challengefloodblockingtimeout); + Cvar_RegisterVariable(&net_getstatusfloodblockingtimeout); + Cvar_RegisterVariable(&cl_netlocalping); + Cvar_RegisterVariable(&cl_netpacketloss_send); + Cvar_RegisterVariable(&cl_netpacketloss_receive); + Cvar_RegisterVariable(&hostname); + Cvar_RegisterVariable(&developer_networking); + Cvar_RegisterVariable(&cl_netport); + Cvar_RegisterVariable(&sv_netport); + Cvar_RegisterVariable(&net_address); + Cvar_RegisterVariable(&net_address_ipv6); + Cvar_RegisterVariable(&sv_public); + Cvar_RegisterVariable(&sv_public_rejectreason); + Cvar_RegisterVariable(&sv_heartbeatperiod); + for (i = 0;sv_masters[i].name;i++) + Cvar_RegisterVariable(&sv_masters[i]); + Cvar_RegisterVariable(&gameversion); + Cvar_RegisterVariable(&gameversion_min); + Cvar_RegisterVariable(&gameversion_max); +// COMMANDLINEOPTION: Server: -ip sets the ip address of this machine for purposes of networking (default 0.0.0.0 also known as INADDR_ANY), use only if you have multiple network adapters and need to choose one specifically. + if ((i = COM_CheckParm("-ip")) && i + 1 < com_argc) + { + if (LHNETADDRESS_FromString(&tempaddress, com_argv[i + 1], 0) == 1) + { + Con_Printf("-ip option used, setting net_address to \"%s\"\n", com_argv[i + 1]); + Cvar_SetQuick(&net_address, com_argv[i + 1]); + } + else + Con_Printf("-ip option used, but unable to parse the address \"%s\"\n", com_argv[i + 1]); + } +// COMMANDLINEOPTION: Server: -port sets the port to use for a server (default 26000, the same port as QUAKE itself), useful if you host multiple servers on your machine + if (((i = COM_CheckParm("-port")) || (i = COM_CheckParm("-ipport")) || (i = COM_CheckParm("-udpport"))) && i + 1 < com_argc) + { + i = atoi(com_argv[i + 1]); + if (i >= 0 && i < 65536) + { + Con_Printf("-port option used, setting port cvar to %i\n", i); + Cvar_SetValueQuick(&sv_netport, i); + } + else + Con_Printf("-port option used, but %i is not a valid port number\n", i); + } + cl_numsockets = 0; + sv_numsockets = 0; + cl_message.data = cl_message_buf; + cl_message.maxsize = sizeof(cl_message_buf); + cl_message.cursize = 0; + sv_message.data = sv_message_buf; + sv_message.maxsize = sizeof(sv_message_buf); + sv_message.cursize = 0; + LHNET_Init(); + if (Thread_HasThreads()) + netconn_mutex = Thread_CreateMutex(); +} + +void NetConn_Shutdown(void) +{ + NetConn_CloseClientPorts(); + NetConn_CloseServerPorts(); + LHNET_Shutdown(); + if (netconn_mutex) + Thread_DestroyMutex(netconn_mutex); + netconn_mutex = NULL; +} + diff --git a/app/jni/netconn.h b/app/jni/netconn.h new file mode 100644 index 0000000..69b0de5 --- /dev/null +++ b/app/jni/netconn.h @@ -0,0 +1,469 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2003 Forest Hale + +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. + +*/ + +#ifndef NET_H +#define NET_H + +#include "lhnet.h" + +#define NET_HEADERSIZE (2 * sizeof(unsigned int)) + +// NetHeader flags +#define NETFLAG_LENGTH_MASK 0x0000ffff +#define NETFLAG_DATA 0x00010000 +#define NETFLAG_ACK 0x00020000 +#define NETFLAG_NAK 0x00040000 +#define NETFLAG_EOM 0x00080000 +#define NETFLAG_UNRELIABLE 0x00100000 +#define NETFLAG_CTL 0x80000000 +#define NETFLAG_CRYPTO 0x40000000 + + +#define NET_PROTOCOL_VERSION 3 +#define NET_EXTRESPONSE_MAX 16 + +/// \page netconn The network info/connection protocol. +/// It is used to find Quake +/// servers, get info about them, and connect to them. Once connected, the +/// Quake game protocol (documented elsewhere) is used. +/// +/// +/// General notes:\code +/// game_name is currently always "QUAKE", but is there so this same protocol +/// can be used for future games as well; can you say Quake2? +/// +/// CCREQ_CONNECT +/// string game_name "QUAKE" +/// byte net_protocol_version NET_PROTOCOL_VERSION +/// +/// CCREQ_SERVER_INFO +/// string game_name "QUAKE" +/// byte net_protocol_version NET_PROTOCOL_VERSION +/// +/// CCREQ_PLAYER_INFO +/// byte player_number +/// +/// CCREQ_RULE_INFO +/// string rule +/// +/// CCREQ_RCON +/// string password +/// string command +/// +/// +/// +/// CCREP_ACCEPT +/// long port +/// +/// CCREP_REJECT +/// string reason +/// +/// CCREP_SERVER_INFO +/// string server_address +/// string host_name +/// string level_name +/// byte current_players +/// byte max_players +/// byte protocol_version NET_PROTOCOL_VERSION +/// +/// CCREP_PLAYER_INFO +/// byte player_number +/// string name +/// long colors +/// long frags +/// long connect_time +/// string address +/// +/// CCREP_RULE_INFO +/// string rule +/// string value +/// +/// CCREP_RCON +/// string reply +/// \endcode +/// \note +/// There are two address forms used above. The short form is just a +/// port number. The address that goes along with the port is defined as +/// "whatever address you receive this reponse from". This lets us use +/// the host OS to solve the problem of multiple host addresses (possibly +/// with no routing between them); the host will use the right address +/// when we reply to the inbound connection request. The long from is +/// a full address and port in a string. It is used for returning the +/// address of a server that is not running locally. + +#define CCREQ_CONNECT 0x01 +#define CCREQ_SERVER_INFO 0x02 +#define CCREQ_PLAYER_INFO 0x03 +#define CCREQ_RULE_INFO 0x04 +#define CCREQ_RCON 0x05 // RocketGuy: ProQuake rcon support + +#define CCREP_ACCEPT 0x81 +#define CCREP_REJECT 0x82 +#define CCREP_SERVER_INFO 0x83 +#define CCREP_PLAYER_INFO 0x84 +#define CCREP_RULE_INFO 0x85 +#define CCREP_RCON 0x86 // RocketGuy: ProQuake rcon support + +typedef struct netgraphitem_s +{ + double time; + int reliablebytes; + int unreliablebytes; + int ackbytes; +} +netgraphitem_t; + +typedef struct netconn_s +{ + struct netconn_s *next; + + lhnetsocket_t *mysocket; + lhnetaddress_t peeraddress; + + // this is mostly identical to qsocket_t from quake + + /// if this time is reached, kick off peer + double connecttime; + double timeout; + double lastMessageTime; + double lastSendTime; + + /// writing buffer to send to peer as the next reliable message + /// can be added to at any time, copied into sendMessage buffer when it is + /// possible to send a reliable message and then cleared + /// @{ + sizebuf_t message; + unsigned char messagedata[NET_MAXMESSAGE]; + /// @} + + /// reliable message that is currently sending + /// (for building fragments) + int sendMessageLength; + unsigned char sendMessage[NET_MAXMESSAGE]; + + /// reliable message that is currently being received + /// (for putting together fragments) + int receiveMessageLength; + unsigned char receiveMessage[NET_MAXMESSAGE]; + + /// used by both NQ and QW protocols + unsigned int outgoing_unreliable_sequence; + + struct netconn_nq_s + { + unsigned int ackSequence; + unsigned int sendSequence; + + unsigned int receiveSequence; + unsigned int unreliableReceiveSequence; + } + nq; + struct netconn_qw_s + { + // QW protocol + qboolean fatal_error; + + float last_received; // for timeouts + + // the statistics are cleared at each client begin, because + // the server connecting process gives a bogus picture of the data + float frame_latency; // rolling average + float frame_rate; + + int drop_count; ///< dropped packets, cleared each level + int good_count; ///< cleared each level + + int qport; + + // sequencing variables + int incoming_sequence; + int incoming_acknowledged; + int incoming_reliable_acknowledged; ///< single bit + + int incoming_reliable_sequence; ///< single bit, maintained local + + int reliable_sequence; ///< single bit + int last_reliable_sequence; ///< sequence number of last send + } + qw; + + // bandwidth estimator + double cleartime; // if realtime > nc->cleartime, free to go + + // this tracks packet loss and packet sizes on the most recent packets + // used by shownetgraph feature +#define NETGRAPH_PACKETS 256 +#define NETGRAPH_NOPACKET 0 +#define NETGRAPH_LOSTPACKET -1 +#define NETGRAPH_CHOKEDPACKET -2 + int incoming_packetcounter; + netgraphitem_t incoming_netgraph[NETGRAPH_PACKETS]; + int outgoing_packetcounter; + netgraphitem_t outgoing_netgraph[NETGRAPH_PACKETS]; + + char address[128]; + crypto_t crypto; + + // statistic counters + int packetsSent; + int packetsReSent; + int packetsReceived; + int receivedDuplicateCount; + int droppedDatagrams; + int unreliableMessagesSent; + int unreliableMessagesReceived; + int reliableMessagesSent; + int reliableMessagesReceived; +} netconn_t; + +extern netconn_t *netconn_list; +extern mempool_t *netconn_mempool; + +extern cvar_t hostname; +extern cvar_t developer_networking; + +#define SERVERLIST_VIEWLISTSIZE SERVERLIST_TOTALSIZE + +typedef enum serverlist_maskop_e +{ + // SLMO_CONTAINS is the default for strings + // SLMO_GREATEREQUAL is the default for numbers (also used when OP == CONTAINS or NOTCONTAINS + SLMO_CONTAINS, + SLMO_NOTCONTAIN, + + SLMO_LESSEQUAL, + SLMO_LESS, + SLMO_EQUAL, + SLMO_GREATER, + SLMO_GREATEREQUAL, + SLMO_NOTEQUAL, + SLMO_STARTSWITH, + SLMO_NOTSTARTSWITH +} serverlist_maskop_t; + +/// struct with all fields that you can search for or sort by +typedef struct serverlist_info_s +{ + /// address for connecting + char cname[128]; + /// ping time for sorting servers + int ping; + /// name of the game + char game[32]; + /// name of the mod + char mod[32]; + /// name of the map + char map[32]; + /// name of the session + char name[128]; + /// qc-defined short status string + char qcstatus[128]; + /// frags/ping/name list (if they fit in the packet) + char players[1400]; + /// max client number + int maxplayers; + /// number of currently connected players (including bots) + int numplayers; + /// number of currently connected players that are bots + int numbots; + /// number of currently connected players that are not bots + int numhumans; + /// number of free slots + int freeslots; + /// protocol version + int protocol; + /// game data version + /// (an integer that is used for filtering incompatible servers, + /// not filterable by QC) + int gameversion; + /// favorite server flag + qboolean isfavorite; +} serverlist_info_t; + +typedef enum +{ + SLIF_CNAME, + SLIF_PING, + SLIF_GAME, + SLIF_MOD, + SLIF_MAP, + SLIF_NAME, + SLIF_MAXPLAYERS, + SLIF_NUMPLAYERS, + SLIF_PROTOCOL, + SLIF_NUMBOTS, + SLIF_NUMHUMANS, + SLIF_FREESLOTS, + SLIF_QCSTATUS, + SLIF_PLAYERS, + SLIF_ISFAVORITE, + SLIF_COUNT +} serverlist_infofield_t; + +typedef enum +{ + SLSF_DESCENDING = 1, + SLSF_FAVORITESFIRST = 2 +} serverlist_sortflags_t; + +typedef enum +{ + SQS_NONE = 0, + SQS_QUERYING, + SQS_QUERIED, + SQS_TIMEDOUT, + SQS_REFRESHING +} serverlist_query_state; + +typedef struct serverlist_entry_s +{ + /// used to determine whether this entry should be included into the final view + serverlist_query_state query; + /// used to count the number of times the host has tried to query this server already + unsigned querycounter; + /// used to calculate ping when update comes in + double querytime; + /// query protocol to use on this server, may be PROTOCOL_QUAKEWORLD or PROTOCOL_DARKPLACES7 + int protocol; + + serverlist_info_t info; + + // legacy stuff + char line1[128]; + char line2[128]; +} serverlist_entry_t; + +typedef struct serverlist_mask_s +{ + qboolean active; + serverlist_maskop_t tests[SLIF_COUNT]; + serverlist_info_t info; +} serverlist_mask_t; + +#define ServerList_GetCacheEntry(x) (&serverlist_cache[(x)]) +#define ServerList_GetViewEntry(x) (ServerList_GetCacheEntry(serverlist_viewlist[(x)])) + +extern serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT]; +extern serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT]; + +extern serverlist_infofield_t serverlist_sortbyfield; +extern int serverlist_sortflags; // not using the enum, as it is a bitmask + +#if SERVERLIST_TOTALSIZE > 65536 +#error too many servers, change type of index array +#endif +extern int serverlist_viewcount; +extern unsigned short serverlist_viewlist[SERVERLIST_VIEWLISTSIZE]; + +extern int serverlist_cachecount; +extern serverlist_entry_t *serverlist_cache; + +extern qboolean serverlist_consoleoutput; + +void ServerList_GetPlayerStatistics(int *numplayerspointer, int *maxplayerspointer); + +//============================================================================ +// +// public network functions +// +//============================================================================ + +extern char cl_net_extresponse[NET_EXTRESPONSE_MAX][1400]; +extern int cl_net_extresponse_count; +extern int cl_net_extresponse_last; + +extern char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400]; +extern int sv_net_extresponse_count; +extern int sv_net_extresponse_last; + +extern double masterquerytime; +extern int masterquerycount; +extern int masterreplycount; +extern int serverquerycount; +extern int serverreplycount; + +extern sizebuf_t cl_message; +extern sizebuf_t sv_message; +extern char cl_readstring[MAX_INPUTLINE]; +extern char sv_readstring[MAX_INPUTLINE]; + +extern cvar_t sv_public; + +extern cvar_t cl_netlocalping; + +extern cvar_t cl_netport; +extern cvar_t sv_netport; +extern cvar_t net_address; +extern cvar_t net_address_ipv6; + +qboolean NetConn_CanSend(netconn_t *conn); +int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, qboolean quakesignon_suppressreliables); +qboolean NetConn_HaveClientPorts(void); +qboolean NetConn_HaveServerPorts(void); +void NetConn_CloseClientPorts(void); +void NetConn_OpenClientPorts(void); +void NetConn_CloseServerPorts(void); +void NetConn_OpenServerPorts(int opennetports); +void NetConn_UpdateSockets(void); +lhnetsocket_t *NetConn_ChooseClientSocketForAddress(lhnetaddress_t *address); +lhnetsocket_t *NetConn_ChooseServerSocketForAddress(lhnetaddress_t *address); +void NetConn_Init(void); +void NetConn_Shutdown(void); +netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress); +void NetConn_Close(netconn_t *conn); +void NetConn_Listen(qboolean state); +int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress); +int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress); +int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress); +int NetConn_IsLocalGame(void); +void NetConn_ClientFrame(void); +void NetConn_ServerFrame(void); +void NetConn_SleepMicroseconds(int microseconds); +void NetConn_QueryMasters(qboolean querydp, qboolean queryqw); +void NetConn_Heartbeat(int priority); +void NetConn_QueryQueueFrame(void); +void Net_Stats_f(void); +void Net_Slist_f(void); +void Net_SlistQW_f(void); +void Net_Refresh_f(void); + +/// ServerList interface (public) +/// manually refresh the view set, do this after having changed the mask or any other flag +void ServerList_RebuildViewList(void); +void ServerList_ResetMasks(void); +void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryqw, qboolean consoleoutput); + +/// called whenever net_slist_favorites changes +void NetConn_UpdateFavorites(void); + +#define MAX_CHALLENGES 128 +typedef struct challenge_s +{ + lhnetaddress_t address; + double time; + char string[12]; +} +challenge_t; + +extern challenge_t challenge[MAX_CHALLENGES]; + +#endif + diff --git a/app/jni/palette.c b/app/jni/palette.c new file mode 100644 index 0000000..9fb36de --- /dev/null +++ b/app/jni/palette.c @@ -0,0 +1,336 @@ + +#include "quakedef.h" + +cvar_t r_colormap_palette = {0, "r_colormap_palette", "gfx/colormap_palette.lmp", "name of a palette lmp file to override the shirt/pants colors of player models. It consists of 16 shirt colors, 16 scoreboard shirt colors, 16 pants colors and 16 scoreboard pants colors"}; + +unsigned char palette_rgb[256][3]; +unsigned char palette_rgb_pantscolormap[16][3]; +unsigned char palette_rgb_shirtcolormap[16][3]; +unsigned char palette_rgb_pantsscoreboard[16][3]; +unsigned char palette_rgb_shirtscoreboard[16][3]; + +unsigned int palette_bgra_complete[256]; +unsigned int palette_bgra_font[256]; +unsigned int palette_bgra_alpha[256]; +unsigned int palette_bgra_nocolormap[256]; +unsigned int palette_bgra_nocolormapnofullbrights[256]; +unsigned int palette_bgra_nofullbrights[256]; +unsigned int palette_bgra_onlyfullbrights[256]; +unsigned int palette_bgra_pantsaswhite[256]; +unsigned int palette_bgra_shirtaswhite[256]; +unsigned int palette_bgra_transparent[256]; +unsigned int palette_bgra_embeddedpic[256]; +unsigned char palette_featureflags[256]; + +// John Carmack said the quake palette.lmp can be considered public domain because it is not an important asset to id, so I include it here as a fallback if no external palette file is found. +unsigned char host_quakepal[768] = +{ +// marked: colormap colors: cb = (colormap & 0xF0);cb += (cb >= 128 && cb < 224) ? 4 : 12; +// 0x0* + 0,0,0, 15,15,15, 31,31,31, 47,47,47, 63,63,63, 75,75,75, 91,91,91, 107,107,107, + 123,123,123, 139,139,139, 155,155,155, 171,171,171, 187,187,187, 203,203,203, 219,219,219, 235,235,235, +// 0x1* 0 ^ + 15,11,7, 23,15,11, 31,23,11, 39,27,15, 47,35,19, 55,43,23, 63,47,23, 75,55,27, + 83,59,27, 91,67,31, 99,75,31, 107,83,31, 115,87,31, 123,95,35, 131,103,35, 143,111,35, +// 0x2* 1 ^ + 11,11,15, 19,19,27, 27,27,39, 39,39,51, 47,47,63, 55,55,75, 63,63,87, 71,71,103, + 79,79,115, 91,91,127, 99,99,139, 107,107,151, 115,115,163, 123,123,175, 131,131,187, 139,139,203, +// 0x3* 2 ^ + 0,0,0, 7,7,0, 11,11,0, 19,19,0, 27,27,0, 35,35,0, 43,43,7, 47,47,7, + 55,55,7, 63,63,7, 71,71,7, 75,75,11, 83,83,11, 91,91,11, 99,99,11, 107,107,15, +// 0x4* 3 ^ + 7,0,0, 15,0,0, 23,0,0, 31,0,0, 39,0,0, 47,0,0, 55,0,0, 63,0,0, + 71,0,0, 79,0,0, 87,0,0, 95,0,0, 103,0,0, 111,0,0, 119,0,0, 127,0,0, +// 0x5* 4 ^ + 19,19,0, 27,27,0, 35,35,0, 47,43,0, 55,47,0, 67,55,0, 75,59,7, 87,67,7, + 95,71,7, 107,75,11, 119,83,15, 131,87,19, 139,91,19, 151,95,27, 163,99,31, 175,103,35, +// 0x6* 5 ^ + 35,19,7, 47,23,11, 59,31,15, 75,35,19, 87,43,23, 99,47,31, 115,55,35, 127,59,43, + 143,67,51, 159,79,51, 175,99,47, 191,119,47, 207,143,43, 223,171,39, 239,203,31, 255,243,27, +// 0x7* 6 ^ + 11,7,0, 27,19,0, 43,35,15, 55,43,19, 71,51,27, 83,55,35, 99,63,43, 111,71,51, + 127,83,63, 139,95,71, 155,107,83, 167,123,95, 183,135,107, 195,147,123, 211,163,139, 227,179,151, +// 0x8* 7 ^ v 8 + 171,139,163, 159,127,151, 147,115,135, 139,103,123, 127,91,111, 119,83,99, 107,75,87, 95,63,75, + 87,55,67, 75,47,55, 67,39,47, 55,31,35, 43,23,27, 35,19,19, 23,11,11, 15,7,7, +// 0x9* 9 v + 187,115,159, 175,107,143, 163,95,131, 151,87,119, 139,79,107, 127,75,95, 115,67,83, 107,59,75, + 95,51,63, 83,43,55, 71,35,43, 59,31,35, 47,23,27, 35,19,19, 23,11,11, 15,7,7, +// 0xA* 10 v + 219,195,187, 203,179,167, 191,163,155, 175,151,139, 163,135,123, 151,123,111, 135,111,95, 123,99,83, + 107,87,71, 95,75,59, 83,63,51, 67,51,39, 55,43,31, 39,31,23, 27,19,15, 15,11,7, +// 0xB* 11 v + 111,131,123, 103,123,111, 95,115,103, 87,107,95, 79,99,87, 71,91,79, 63,83,71, 55,75,63, + 47,67,55, 43,59,47, 35,51,39, 31,43,31, 23,35,23, 15,27,19, 11,19,11, 7,11,7, +// 0xC* 12 v + 255,243,27, 239,223,23, 219,203,19, 203,183,15, 187,167,15, 171,151,11, 155,131,7, 139,115,7, + 123,99,7, 107,83,0, 91,71,0, 75,55,0, 59,43,0, 43,31,0, 27,15,0, 11,7,0, +// 0xD* 13 v + 0,0,255, 11,11,239, 19,19,223, 27,27,207, 35,35,191, 43,43,175, 47,47,159, 47,47,143, + 47,47,127, 47,47,111, 47,47,95, 43,43,79, 35,35,63, 27,27,47, 19,19,31, 11,11,15, +// 0xE* + 43,0,0, 59,0,0, 75,7,0, 95,7,0, 111,15,0, 127,23,7, 147,31,7, 163,39,11, + 183,51,15, 195,75,27, 207,99,43, 219,127,59, 227,151,79, 231,171,95, 239,191,119, 247,211,139, +// 0xF* 14 ^ + 167,123,59, 183,155,55, 199,195,55, 231,227,87, 127,191,255, 171,231,255, 215,255,255, 103,0,0, + 139,0,0, 179,0,0, 215,0,0, 255,0,0, 255,243,147, 255,247,199, 255,255,255, 159,91,83 +}; // 15 ^ + +static void Palette_SetupSpecialPalettes(void) +{ + int i; + int fullbright_start, fullbright_end; + int pants_start, pants_end; + int shirt_start, shirt_end; + int reversed_start, reversed_end; + int transparentcolor; + unsigned char *colormap; + fs_offset_t filesize; + union + { + int i; + unsigned char b[4]; + } + u; + + colormap = FS_LoadFile("gfx/colormap.lmp", tempmempool, true, &filesize); + if (colormap && filesize >= 16385) + fullbright_start = 256 - colormap[16384]; + else + fullbright_start = 256; + if (colormap) + Mem_Free(colormap); + fullbright_end = 256; + pants_start = 96; + pants_end = 112; + shirt_start = 16; + shirt_end = 32; + reversed_start = 128; + reversed_end = 224; + transparentcolor = 255; + + for (i = 0;i < 256;i++) + palette_featureflags[i] = PALETTEFEATURE_STANDARD; + for (i = reversed_start;i < reversed_end;i++) + palette_featureflags[i] = PALETTEFEATURE_REVERSED; + for (i = pants_start;i < pants_end;i++) + palette_featureflags[i] = PALETTEFEATURE_PANTS; + for (i = shirt_start;i < shirt_end;i++) + palette_featureflags[i] = PALETTEFEATURE_SHIRT; + for (i = fullbright_start;i < fullbright_end;i++) + palette_featureflags[i] = PALETTEFEATURE_GLOW; + palette_featureflags[0] = PALETTEFEATURE_ZERO; + palette_featureflags[transparentcolor] = PALETTEFEATURE_TRANSPARENT; + + for (i = 0;i < 256;i++) + palette_bgra_transparent[i] = palette_bgra_complete[i]; + palette_bgra_transparent[transparentcolor] = 0; + + for (i = 0;i < fullbright_start;i++) + palette_bgra_nofullbrights[i] = palette_bgra_complete[i]; + for (i = fullbright_start;i < fullbright_end;i++) + palette_bgra_nofullbrights[i] = palette_bgra_complete[0]; + + for (i = 0;i < 256;i++) + palette_bgra_onlyfullbrights[i] = 0; + for (i = fullbright_start;i < fullbright_end;i++) + palette_bgra_onlyfullbrights[i] = palette_bgra_complete[i]; + + for (i = 0;i < 256;i++) + palette_bgra_nocolormapnofullbrights[i] = palette_bgra_complete[i]; + for (i = pants_start;i < pants_end;i++) + palette_bgra_nocolormapnofullbrights[i] = 0; + for (i = shirt_start;i < shirt_end;i++) + palette_bgra_nocolormapnofullbrights[i] = 0; + for (i = fullbright_start;i < fullbright_end;i++) + palette_bgra_nocolormapnofullbrights[i] = 0; + + for (i = 0;i < 256;i++) + palette_bgra_nocolormap[i] = palette_bgra_complete[i]; + for (i = pants_start;i < pants_end;i++) + palette_bgra_nocolormap[i] = 0; + for (i = shirt_start;i < shirt_end;i++) + palette_bgra_nocolormap[i] = 0; + + for (i = 0;i < 256;i++) + palette_bgra_pantsaswhite[i] = 0; + for (i = pants_start;i < pants_end;i++) + { + if (i >= reversed_start && i < reversed_end) + palette_bgra_pantsaswhite[i] = palette_bgra_complete[15 - (i - pants_start)]; + else + palette_bgra_pantsaswhite[i] = palette_bgra_complete[i - pants_start]; + } + + for (i = 0;i < 256;i++) + palette_bgra_shirtaswhite[i] = 0; + for (i = shirt_start;i < shirt_end;i++) + { + if (i >= reversed_start && i < reversed_end) + palette_bgra_shirtaswhite[i] = palette_bgra_complete[15 - (i - shirt_start)]; + else + palette_bgra_shirtaswhite[i] = palette_bgra_complete[i - shirt_start]; + } + + for (i = 0;i < 256;i++) + palette_bgra_alpha[i] = 0xFFFFFFFF; + u.i = 0xFFFFFFFF; + u.b[3] = 0; + palette_bgra_alpha[transparentcolor] = u.i; + + for (i = 0;i < 256;i++) + palette_bgra_font[i] = palette_bgra_complete[i]; + palette_bgra_font[0] = 0; +} + +void BuildGammaTable8(float prescale, float gamma, float scale, float base, float contrastboost, unsigned char *out, int rampsize) +{ + int i, adjusted; + double invgamma; + double t, d; + + invgamma = 1.0 / gamma; + prescale /= (double) (rampsize - 1); + for (i = 0;i < rampsize;i++) + { + t = i * prescale; + d = ((contrastboost - 1) * t + 1); + if(d == 0) + t = 0; // we could just as well assume 1 here, depending on which side of the division by zero we want to be + else + t = contrastboost * t / d; + adjusted = (int) (255.0 * (pow(t, invgamma) * scale + base) + 0.5); + out[i] = bound(0, adjusted, 255); + } +} + +void BuildGammaTable16(float prescale, float gamma, float scale, float base, float contrastboost, unsigned short *out, int rampsize) +{ + int i, adjusted; + double invgamma; + double t; + + invgamma = 1.0 / gamma; + prescale /= (double) (rampsize - 1); + for (i = 0;i < rampsize;i++) + { + t = i * prescale; + t = contrastboost * t / ((contrastboost - 1) * t + 1); + adjusted = (int) (65535.0 * (pow(t, invgamma) * scale + base) + 0.5); + out[i] = bound(0, adjusted, 65535); + } +} + +static void Palette_Shutdown(void) +{ +} + +static void Palette_NewMap(void) +{ +} + +static void Palette_Load(void) +{ + int i; + unsigned char *out; + float gamma, scale, base; + fs_offset_t filesize; + unsigned char *palfile; + unsigned char texturegammaramp[256]; + union + { + unsigned char b[4]; + unsigned int i; + } + bgra; + + gamma = 1; + scale = 1; + base = 0; +// COMMANDLINEOPTION: Client: -texgamma sets the quake palette gamma, allowing you to make quake textures brighter/darker, not recommended + i = COM_CheckParm("-texgamma"); + if (i) + gamma = atof(com_argv[i + 1]); +// COMMANDLINEOPTION: Client: -texcontrast sets the quake palette contrast, allowing you to make quake textures brighter/darker, not recommended + i = COM_CheckParm("-texcontrast"); + if (i) + scale = atof(com_argv[i + 1]); +// COMMANDLINEOPTION: Client: -texbrightness sets the quake palette brightness (brightness of black), allowing you to make quake textures brighter/darker, not recommended + i = COM_CheckParm("-texbrightness"); + if (i) + base = atof(com_argv[i + 1]); + gamma = bound(0.01, gamma, 10.0); + scale = bound(0.01, scale, 10.0); + base = bound(0, base, 0.95); + + BuildGammaTable8(1.0f, gamma, scale, base, 1, texturegammaramp, 256); + + palfile = (unsigned char *)FS_LoadFile ("gfx/palette.lmp", tempmempool, false, &filesize); + if (palfile && filesize >= 768) + memcpy(palette_rgb, palfile, 768); + else + { + Con_DPrint("Couldn't load gfx/palette.lmp, falling back on internal palette\n"); + memcpy(palette_rgb, host_quakepal, 768); + } + if (palfile) + Mem_Free(palfile); + + out = (unsigned char *) palette_bgra_complete; // palette is accessed as 32bit for speed reasons, but is created as 8bit bytes + for (i = 0;i < 256;i++) + { + out[i*4+2] = texturegammaramp[palette_rgb[i][0]]; + out[i*4+1] = texturegammaramp[palette_rgb[i][1]]; + out[i*4+0] = texturegammaramp[palette_rgb[i][2]]; + out[i*4+3] = 255; + } + + if(*r_colormap_palette.string) + palfile = (unsigned char *)FS_LoadFile (r_colormap_palette.string, tempmempool, false, &filesize); + else + palfile = NULL; + + if (palfile && filesize >= 48*2) + { + memcpy(palette_rgb_shirtcolormap[0], palfile, 48); + memcpy(palette_rgb_shirtscoreboard[0], palfile + 48, 48); + } + else + { + for(i = 0;i < 16;i++) + { + VectorCopy(palette_rgb[(i << 4) | ((i >= 8 && i <= 13) ? 0x04 : 0x0C)], palette_rgb_shirtcolormap[i]); + VectorCopy(palette_rgb[(i << 4) | 0x08], palette_rgb_shirtscoreboard[i]); + } + } + + if (palfile && filesize >= 48*4) + { + memcpy(palette_rgb_pantscolormap[0], palfile + 48*2, 48); + memcpy(palette_rgb_pantsscoreboard[0], palfile + 48*3, 48); + } + else + { + memcpy(palette_rgb_pantscolormap, palette_rgb_shirtcolormap, sizeof(palette_rgb_pantscolormap)); + memcpy(palette_rgb_pantsscoreboard, palette_rgb_shirtscoreboard, sizeof(palette_rgb_pantsscoreboard)); + } + + if(palfile) + Mem_Free(palfile); + + memset(palette_bgra_embeddedpic, 0, sizeof(palette_bgra_embeddedpic)); + for (i = '1';i <= '7';i++) + { + Vector4Set(bgra.b, 255, 255, 255, (i - '0') * 255 / 7); + palette_bgra_embeddedpic[i] = bgra.i; + } + + Palette_SetupSpecialPalettes(); +} + +void Palette_Init(void) +{ + R_RegisterModule("Palette", Palette_Load, Palette_Shutdown, Palette_NewMap, NULL, NULL); + Cvar_RegisterVariable(&r_colormap_palette); + Palette_Load(); +} diff --git a/app/jni/palette.h b/app/jni/palette.h new file mode 100644 index 0000000..f904465 --- /dev/null +++ b/app/jni/palette.h @@ -0,0 +1,39 @@ + +#ifndef PALLETE_H +#define PALLETE_H + +#define PALETTEFEATURE_STANDARD 1 +#define PALETTEFEATURE_REVERSED 2 +#define PALETTEFEATURE_PANTS 4 +#define PALETTEFEATURE_SHIRT 8 +#define PALETTEFEATURE_GLOW 16 +#define PALETTEFEATURE_ZERO 32 +#define PALETTEFEATURE_TRANSPARENT 128 + +extern unsigned char palette_rgb[256][3]; +extern unsigned char palette_rgb_pantscolormap[16][3]; +extern unsigned char palette_rgb_shirtcolormap[16][3]; +extern unsigned char palette_rgb_pantsscoreboard[16][3]; +extern unsigned char palette_rgb_shirtscoreboard[16][3]; + +extern unsigned int palette_bgra_complete[256]; +extern unsigned int palette_bgra_font[256]; +extern unsigned int palette_bgra_alpha[256]; +extern unsigned int palette_bgra_nocolormap[256]; +extern unsigned int palette_bgra_nocolormapnofullbrights[256]; +extern unsigned int palette_bgra_nofullbrights[256]; +extern unsigned int palette_bgra_onlyfullbrights[256]; +extern unsigned int palette_bgra_pantsaswhite[256]; +extern unsigned int palette_bgra_shirtaswhite[256]; +extern unsigned int palette_bgra_transparent[256]; +extern unsigned int palette_bgra_embeddedpic[256]; +extern unsigned char palette_featureflags[256]; + +// used by hardware gamma functions in vid_* files +void BuildGammaTable8(float prescale, float gamma, float scale, float base, float contrastboost, unsigned char *out, int rampsize); +void BuildGammaTable16(float prescale, float gamma, float scale, float base, float contrastboost, unsigned short *out, int rampsize); + +void Palette_Init(void); + +#endif + diff --git a/app/jni/polygon.c b/app/jni/polygon.c new file mode 100644 index 0000000..99ceb0b --- /dev/null +++ b/app/jni/polygon.c @@ -0,0 +1,310 @@ + +/* +Polygon clipping routines written by Forest Hale and placed into public domain. +*/ + +#include +#include "polygon.h" + +void PolygonF_QuadForPlane(float *outpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float quadsize) +{ + float d, quadright[3], quadup[3]; + if (fabs(planenormalz) > fabs(planenormalx) && fabs(planenormalz) > fabs(planenormaly)) + { + quadup[0] = 1; + quadup[1] = 0; + quadup[2] = 0; + } + else + { + quadup[0] = 0; + quadup[1] = 0; + quadup[2] = 1; + } + // d = -DotProduct(quadup, planenormal); + d = -(quadup[0] * planenormalx + quadup[1] * planenormaly + quadup[2] * planenormalz); + // VectorMA(quadup, d, planenormal, quadup); + quadup[0] += d * planenormalx; + quadup[1] += d * planenormaly; + quadup[2] += d * planenormalz; + // VectorNormalize(quadup); + d = (float)(1.0 / sqrt(quadup[0] * quadup[0] + quadup[1] * quadup[1] + quadup[2] * quadup[2])); + quadup[0] *= d; + quadup[1] *= d; + quadup[2] *= d; + // CrossProduct(quadup,planenormal,quadright); + quadright[0] = quadup[1] * planenormalz - quadup[2] * planenormaly; + quadright[1] = quadup[2] * planenormalx - quadup[0] * planenormalz; + quadright[2] = quadup[0] * planenormaly - quadup[1] * planenormalx; + // make the points + outpoints[0] = planedist * planenormalx - quadsize * quadright[0] + quadsize * quadup[0]; + outpoints[1] = planedist * planenormaly - quadsize * quadright[1] + quadsize * quadup[1]; + outpoints[2] = planedist * planenormalz - quadsize * quadright[2] + quadsize * quadup[2]; + outpoints[3] = planedist * planenormalx + quadsize * quadright[0] + quadsize * quadup[0]; + outpoints[4] = planedist * planenormaly + quadsize * quadright[1] + quadsize * quadup[1]; + outpoints[5] = planedist * planenormalz + quadsize * quadright[2] + quadsize * quadup[2]; + outpoints[6] = planedist * planenormalx + quadsize * quadright[0] - quadsize * quadup[0]; + outpoints[7] = planedist * planenormaly + quadsize * quadright[1] - quadsize * quadup[1]; + outpoints[8] = planedist * planenormalz + quadsize * quadright[2] - quadsize * quadup[2]; + outpoints[9] = planedist * planenormalx - quadsize * quadright[0] - quadsize * quadup[0]; + outpoints[10] = planedist * planenormaly - quadsize * quadright[1] - quadsize * quadup[1]; + outpoints[11] = planedist * planenormalz - quadsize * quadright[2] - quadsize * quadup[2]; +} + +void PolygonD_QuadForPlane(double *outpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double quadsize) +{ + double d, quadright[3], quadup[3]; + if (fabs(planenormalz) > fabs(planenormalx) && fabs(planenormalz) > fabs(planenormaly)) + { + quadup[0] = 1; + quadup[1] = 0; + quadup[2] = 0; + } + else + { + quadup[0] = 0; + quadup[1] = 0; + quadup[2] = 1; + } + // d = -DotProduct(quadup, planenormal); + d = -(quadup[0] * planenormalx + quadup[1] * planenormaly + quadup[2] * planenormalz); + // VectorMA(quadup, d, planenormal, quadup); + quadup[0] += d * planenormalx; + quadup[1] += d * planenormaly; + quadup[2] += d * planenormalz; + // VectorNormalize(quadup); + d = 1.0 / sqrt(quadup[0] * quadup[0] + quadup[1] * quadup[1] + quadup[2] * quadup[2]); + quadup[0] *= d; + quadup[1] *= d; + quadup[2] *= d; + // CrossProduct(quadup,planenormal,quadright); + quadright[0] = quadup[1] * planenormalz - quadup[2] * planenormaly; + quadright[1] = quadup[2] * planenormalx - quadup[0] * planenormalz; + quadright[2] = quadup[0] * planenormaly - quadup[1] * planenormalx; + // make the points + outpoints[0] = planedist * planenormalx - quadsize * quadright[0] + quadsize * quadup[0]; + outpoints[1] = planedist * planenormaly - quadsize * quadright[1] + quadsize * quadup[1]; + outpoints[2] = planedist * planenormalz - quadsize * quadright[2] + quadsize * quadup[2]; + outpoints[3] = planedist * planenormalx + quadsize * quadright[0] + quadsize * quadup[0]; + outpoints[4] = planedist * planenormaly + quadsize * quadright[1] + quadsize * quadup[1]; + outpoints[5] = planedist * planenormalz + quadsize * quadright[2] + quadsize * quadup[2]; + outpoints[6] = planedist * planenormalx + quadsize * quadright[0] - quadsize * quadup[0]; + outpoints[7] = planedist * planenormaly + quadsize * quadright[1] - quadsize * quadup[1]; + outpoints[8] = planedist * planenormalz + quadsize * quadright[2] - quadsize * quadup[2]; + outpoints[9] = planedist * planenormalx - quadsize * quadright[0] - quadsize * quadup[0]; + outpoints[10] = planedist * planenormaly - quadsize * quadright[1] - quadsize * quadup[1]; + outpoints[11] = planedist * planenormalz - quadsize * quadright[2] - quadsize * quadup[2]; +} + +int PolygonF_Clip(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints) +{ + int i, frontcount = 0; + const float *n, *p; + float frac, pdist, ndist; + if (innumpoints < 1) + return 0; + n = inpoints; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + for(i = 0;i < innumpoints;i++) + { + p = n; + pdist = ndist; + n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + if (pdist >= -epsilon) + { + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0]; + *outfrontpoints++ = p[1]; + *outfrontpoints++ = p[2]; + } + frontcount++; + } + if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) + { + frac = pdist / (pdist - ndist); + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); + *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); + *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); + } + frontcount++; + } + } + return frontcount; +} + +int PolygonD_Clip(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints) +{ + int i, frontcount = 0; + const double *n, *p; + double frac, pdist, ndist; + if (innumpoints < 1) + return 0; + n = inpoints; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + for(i = 0;i < innumpoints;i++) + { + p = n; + pdist = ndist; + n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + if (pdist >= -epsilon) + { + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0]; + *outfrontpoints++ = p[1]; + *outfrontpoints++ = p[2]; + } + frontcount++; + } + if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) + { + frac = pdist / (pdist - ndist); + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); + *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); + *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); + } + frontcount++; + } + } + return frontcount; +} + +void PolygonF_Divide(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, float *outbackpoints, int *neededbackpoints, int *oncountpointer) +{ + int i, frontcount = 0, backcount = 0, oncount = 0; + const float *n, *p; + float frac, pdist, ndist; + if (innumpoints) + { + n = inpoints; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + for(i = 0;i < innumpoints;i++) + { + p = n; + pdist = ndist; + n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + if (pdist >= -epsilon) + { + if (pdist <= epsilon) + oncount++; + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0]; + *outfrontpoints++ = p[1]; + *outfrontpoints++ = p[2]; + } + frontcount++; + } + if (pdist <= epsilon) + { + if (backcount < outbackmaxpoints) + { + *outbackpoints++ = p[0]; + *outbackpoints++ = p[1]; + *outbackpoints++ = p[2]; + } + backcount++; + } + if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) + { + oncount++; + frac = pdist / (pdist - ndist); + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); + *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); + *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); + } + frontcount++; + if (backcount < outbackmaxpoints) + { + *outbackpoints++ = p[0] + frac * (n[0] - p[0]); + *outbackpoints++ = p[1] + frac * (n[1] - p[1]); + *outbackpoints++ = p[2] + frac * (n[2] - p[2]); + } + backcount++; + } + } + } + if (neededfrontpoints) + *neededfrontpoints = frontcount; + if (neededbackpoints) + *neededbackpoints = backcount; + if (oncountpointer) + *oncountpointer = oncount; +} + +void PolygonD_Divide(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, double *outbackpoints, int *neededbackpoints, int *oncountpointer) +{ + int i = 0, frontcount = 0, backcount = 0, oncount = 0; + const double *n, *p; + double frac, pdist, ndist; + if (innumpoints) + { + n = inpoints; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + for(i = 0;i < innumpoints;i++) + { + p = n; + pdist = ndist; + n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; + ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; + if (pdist >= -epsilon) + { + if (pdist <= epsilon) + oncount++; + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0]; + *outfrontpoints++ = p[1]; + *outfrontpoints++ = p[2]; + } + frontcount++; + } + if (pdist <= epsilon) + { + if (backcount < outbackmaxpoints) + { + *outbackpoints++ = p[0]; + *outbackpoints++ = p[1]; + *outbackpoints++ = p[2]; + } + backcount++; + } + if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) + { + oncount++; + frac = pdist / (pdist - ndist); + if (frontcount < outfrontmaxpoints) + { + *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); + *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); + *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); + } + frontcount++; + if (backcount < outbackmaxpoints) + { + *outbackpoints++ = p[0] + frac * (n[0] - p[0]); + *outbackpoints++ = p[1] + frac * (n[1] - p[1]); + *outbackpoints++ = p[2] + frac * (n[2] - p[2]); + } + backcount++; + } + } + } + if (neededfrontpoints) + *neededfrontpoints = frontcount; + if (neededbackpoints) + *neededbackpoints = backcount; + if (oncountpointer) + *oncountpointer = oncount; +} + diff --git a/app/jni/polygon.h b/app/jni/polygon.h new file mode 100644 index 0000000..e8bf2e8 --- /dev/null +++ b/app/jni/polygon.h @@ -0,0 +1,16 @@ + +#ifndef POLYGON_H +#define POLYGON_H + +/* +Polygon clipping routines written by Forest Hale and placed into public domain. +*/ + +void PolygonF_QuadForPlane(float *outpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float quadsize); +void PolygonD_QuadForPlane(double *outpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double quadsize); +int PolygonF_Clip(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints); +int PolygonD_Clip(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints); +void PolygonF_Divide(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, float *outbackpoints, int *neededbackpoints, int *oncountpointer); +void PolygonD_Divide(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, double *outbackpoints, int *neededbackpoints, int *oncountpointer); + +#endif diff --git a/app/jni/portals.c b/app/jni/portals.c new file mode 100644 index 0000000..2872a8c --- /dev/null +++ b/app/jni/portals.c @@ -0,0 +1,480 @@ + +#include "quakedef.h" +#include "polygon.h" +#include "portals.h" + +#define MAXRECURSIVEPORTALPLANES 1024 +#define MAXRECURSIVEPORTALS 256 + +static tinyplane_t portalplanes[MAXRECURSIVEPORTALPLANES]; +static int ranoutofportalplanes; +static int ranoutofportals; +static float portaltemppoints[2][256][3]; +static float portaltemppoints2[256][3]; +static int portal_markid = 0; +static float boxpoints[4*3]; + +static int Portal_PortalThroughPortalPlanes(tinyplane_t *clipplanes, int clipnumplanes, float *targpoints, int targnumpoints, float *out, int maxpoints) +{ + int numpoints = targnumpoints, i, w; + if (numpoints < 1) + return numpoints; + if (maxpoints > 256) + maxpoints = 256; + w = 0; + memcpy(&portaltemppoints[w][0][0], targpoints, numpoints * 3 * sizeof(float)); + for (i = 0;i < clipnumplanes && numpoints > 0;i++) + { + PolygonF_Divide(numpoints, &portaltemppoints[w][0][0], clipplanes[i].normal[0], clipplanes[i].normal[1], clipplanes[i].normal[2], clipplanes[i].dist, 1.0f/32.0f, 256, &portaltemppoints[1-w][0][0], &numpoints, 0, NULL, NULL, NULL); + w = 1-w; + numpoints = min(numpoints, 256); + } + numpoints = min(numpoints, maxpoints); + if (numpoints > 0) + memcpy(out, &portaltemppoints[w][0][0], numpoints * 3 * sizeof(float)); + return numpoints; +} + +static int Portal_RecursiveFlowSearch (mleaf_t *leaf, vec3_t eye, int firstclipplane, int numclipplanes) +{ + mportal_t *p; + int newpoints, i, prev; + vec3_t center, v1, v2; + tinyplane_t *newplanes; + + if (leaf->portalmarkid == portal_markid) + return true; + + // follow portals into other leafs + for (p = leaf->portals;p;p = p->next) + { + // only flow through portals facing away from the viewer + if (PlaneDiff(eye, (&p->plane)) < 0) + { + newpoints = Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, (float *) p->points, p->numpoints, &portaltemppoints2[0][0], 256); + if (newpoints < 3) + continue; + else if (firstclipplane + numclipplanes + newpoints > MAXRECURSIVEPORTALPLANES) + ranoutofportalplanes = true; + else + { + // find the center by averaging + VectorClear(center); + for (i = 0;i < newpoints;i++) + VectorAdd(center, portaltemppoints2[i], center); + // ixtable is a 1.0f / N table + VectorScale(center, ixtable[newpoints], center); + // calculate the planes, and make sure the polygon can see it's own center + newplanes = &portalplanes[firstclipplane + numclipplanes]; + for (prev = newpoints - 1, i = 0;i < newpoints;prev = i, i++) + { + VectorSubtract(eye, portaltemppoints2[i], v1); + VectorSubtract(portaltemppoints2[prev], portaltemppoints2[i], v2); + CrossProduct(v1, v2, newplanes[i].normal); + VectorNormalize(newplanes[i].normal); + newplanes[i].dist = DotProduct(eye, newplanes[i].normal); + if (DotProduct(newplanes[i].normal, center) <= newplanes[i].dist) + { + // polygon can't see it's own center, discard and use parent portal + break; + } + } + if (i == newpoints) + { + if (Portal_RecursiveFlowSearch(p->past, eye, firstclipplane + numclipplanes, newpoints)) + return true; + } + else + { + if (Portal_RecursiveFlowSearch(p->past, eye, firstclipplane, numclipplanes)) + return true; + } + } + } + } + + return false; +} + +static void Portal_PolygonRecursiveMarkLeafs(mnode_t *node, float *polypoints, int numpoints) +{ + int i, front; + float *p; + +loc0: + if (!node->plane) + { + ((mleaf_t *)node)->portalmarkid = portal_markid; + return; + } + + front = 0; + for (i = 0, p = polypoints;i < numpoints;i++, p += 3) + { + if (DotProduct(p, node->plane->normal) > node->plane->dist) + front++; + } + if (front > 0) + { + if (front == numpoints) + { + node = node->children[0]; + goto loc0; + } + else + Portal_PolygonRecursiveMarkLeafs(node->children[0], polypoints, numpoints); + } + node = node->children[1]; + goto loc0; +} + +int Portal_CheckPolygon(dp_model_t *model, vec3_t eye, float *polypoints, int numpoints) +{ + int i, prev, returnvalue; + mleaf_t *eyeleaf; + vec3_t center, v1, v2; + + // if there is no model, it can not block visibility + if (model == NULL || !model->brush.PointInLeaf) + return true; + + portal_markid++; + + Portal_PolygonRecursiveMarkLeafs(model->brush.data_nodes, polypoints, numpoints); + + eyeleaf = model->brush.PointInLeaf(model, eye); + + // find the center by averaging + VectorClear(center); + for (i = 0;i < numpoints;i++) + VectorAdd(center, (&polypoints[i * 3]), center); + // ixtable is a 1.0f / N table + VectorScale(center, ixtable[numpoints], center); + + // calculate the planes, and make sure the polygon can see it's own center + for (prev = numpoints - 1, i = 0;i < numpoints;prev = i, i++) + { + VectorSubtract(eye, (&polypoints[i * 3]), v1); + VectorSubtract((&polypoints[prev * 3]), (&polypoints[i * 3]), v2); + CrossProduct(v1, v2, portalplanes[i].normal); + VectorNormalize(portalplanes[i].normal); + portalplanes[i].dist = DotProduct(eye, portalplanes[i].normal); + if (DotProduct(portalplanes[i].normal, center) <= portalplanes[i].dist) + { + // polygon can't see it's own center, discard + return false; + } + } + + ranoutofportalplanes = false; + ranoutofportals = false; + + returnvalue = Portal_RecursiveFlowSearch(eyeleaf, eye, 0, numpoints); + + if (ranoutofportalplanes) + Con_Printf("Portal_RecursiveFlowSearch: ran out of %d plane stack when recursing through portals\n", MAXRECURSIVEPORTALPLANES); + if (ranoutofportals) + Con_Printf("Portal_RecursiveFlowSearch: ran out of %d portal stack when recursing through portals\n", MAXRECURSIVEPORTALS); + + return returnvalue; +} + +#define Portal_MinsBoxPolygon(axis, axisvalue, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) \ +{\ + if (eye[(axis)] < ((axisvalue) - 0.5f))\ + {\ + boxpoints[ 0] = x1;boxpoints[ 1] = y1;boxpoints[ 2] = z1;\ + boxpoints[ 3] = x2;boxpoints[ 4] = y2;boxpoints[ 5] = z2;\ + boxpoints[ 6] = x3;boxpoints[ 7] = y3;boxpoints[ 8] = z3;\ + boxpoints[ 9] = x4;boxpoints[10] = y4;boxpoints[11] = z4;\ + if (Portal_CheckPolygon(model, eye, boxpoints, 4))\ + return true;\ + }\ +} + +#define Portal_MaxsBoxPolygon(axis, axisvalue, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) \ +{\ + if (eye[(axis)] > ((axisvalue) + 0.5f))\ + {\ + boxpoints[ 0] = x1;boxpoints[ 1] = y1;boxpoints[ 2] = z1;\ + boxpoints[ 3] = x2;boxpoints[ 4] = y2;boxpoints[ 5] = z2;\ + boxpoints[ 6] = x3;boxpoints[ 7] = y3;boxpoints[ 8] = z3;\ + boxpoints[ 9] = x4;boxpoints[10] = y4;boxpoints[11] = z4;\ + if (Portal_CheckPolygon(model, eye, boxpoints, 4))\ + return true;\ + }\ +} + +int Portal_CheckBox(dp_model_t *model, vec3_t eye, vec3_t a, vec3_t b) +{ + if (eye[0] >= (a[0] - 1.0f) && eye[0] < (b[0] + 1.0f) + && eye[1] >= (a[1] - 1.0f) && eye[1] < (b[1] + 1.0f) + && eye[2] >= (a[2] - 1.0f) && eye[2] < (b[2] + 1.0f)) + return true; + + Portal_MinsBoxPolygon + ( + 0, a[0], + a[0], a[1], a[2], + a[0], b[1], a[2], + a[0], b[1], b[2], + a[0], a[1], b[2] + ); + Portal_MaxsBoxPolygon + ( + 0, b[0], + b[0], b[1], a[2], + b[0], a[1], a[2], + b[0], a[1], b[2], + b[0], b[1], b[2] + ); + Portal_MinsBoxPolygon + ( + 1, a[1], + b[0], a[1], a[2], + a[0], a[1], a[2], + a[0], a[1], b[2], + b[0], a[1], b[2] + ); + Portal_MaxsBoxPolygon + ( + 1, b[1], + a[0], b[1], a[2], + b[0], b[1], a[2], + b[0], b[1], b[2], + a[0], b[1], b[2] + ); + Portal_MinsBoxPolygon + ( + 2, a[2], + a[0], a[1], a[2], + b[0], a[1], a[2], + b[0], b[1], a[2], + a[0], b[1], a[2] + ); + Portal_MaxsBoxPolygon + ( + 2, b[2], + b[0], a[1], b[2], + a[0], a[1], b[2], + a[0], b[1], b[2], + b[0], b[1], b[2] + ); + + return false; +} + +typedef struct portalrecursioninfo_s +{ + int exact; + int numfrustumplanes; + vec3_t boxmins; + vec3_t boxmaxs; + int numsurfaces; + int *surfacelist; + unsigned char *surfacepvs; + int numleafs; + unsigned char *visitingleafpvs; // used to prevent infinite loops + int *leaflist; + unsigned char *leafpvs; + unsigned char *shadowtrispvs; + unsigned char *lighttrispvs; + dp_model_t *model; + vec3_t eye; + float *updateleafsmins; + float *updateleafsmaxs; +} +portalrecursioninfo_t; + +static void Portal_RecursiveFlow (portalrecursioninfo_t *info, mleaf_t *leaf, int firstclipplane, int numclipplanes) +{ + mportal_t *p; + int newpoints, i, prev; + float dist; + vec3_t center; + tinyplane_t *newplanes; + int leafindex = leaf - info->model->brush.data_leafs; + + if (CHECKPVSBIT(info->visitingleafpvs, leafindex)) + return; // recursive loop of leafs (cmc.bsp for megatf coop) + + SETPVSBIT(info->visitingleafpvs, leafindex); + + for (i = 0;i < 3;i++) + { + if (info->updateleafsmins && info->updateleafsmins[i] > leaf->mins[i]) info->updateleafsmins[i] = leaf->mins[i]; + if (info->updateleafsmaxs && info->updateleafsmaxs[i] < leaf->maxs[i]) info->updateleafsmaxs[i] = leaf->maxs[i]; + } + + + if (info->leafpvs) + { + if (!CHECKPVSBIT(info->leafpvs, leafindex)) + { + SETPVSBIT(info->leafpvs, leafindex); + info->leaflist[info->numleafs++] = leafindex; + } + } + + // mark surfaces in leaf that can be seen through portal + if (leaf->numleafsurfaces && info->surfacepvs) + { + for (i = 0;i < leaf->numleafsurfaces;i++) + { + int surfaceindex = leaf->firstleafsurface[i]; + msurface_t *surface = info->model->data_surfaces + surfaceindex; + if (BoxesOverlap(surface->mins, surface->maxs, info->boxmins, info->boxmaxs)) + { + qboolean insidebox = BoxInsideBox(surface->mins, surface->maxs, info->boxmins, info->boxmaxs); + qboolean addedtris = false; + int t, tend; + const int *elements; + const float *vertex3f; + float v[9]; + vertex3f = info->model->surfmesh.data_vertex3f; + elements = (info->model->surfmesh.data_element3i + 3 * surface->num_firsttriangle); + for (t = surface->num_firsttriangle, tend = t + surface->num_triangles;t < tend;t++, elements += 3) + { + VectorCopy(vertex3f + elements[0] * 3, v + 0); + VectorCopy(vertex3f + elements[1] * 3, v + 3); + VectorCopy(vertex3f + elements[2] * 3, v + 6); + if (PointInfrontOfTriangle(info->eye, v + 0, v + 3, v + 6) + && (insidebox || TriangleBBoxOverlapsBox(v, v + 3, v + 6, info->boxmins, info->boxmaxs)) + && (!info->exact || Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, v, 3, &portaltemppoints2[0][0], 256) > 0)) + { + addedtris = true; + if (info->shadowtrispvs) + SETPVSBIT(info->shadowtrispvs, t); + if (info->lighttrispvs) + SETPVSBIT(info->lighttrispvs, t); + } + } + if (addedtris && !CHECKPVSBIT(info->surfacepvs, surfaceindex)) + { + SETPVSBIT(info->surfacepvs, surfaceindex); + info->surfacelist[info->numsurfaces++] = surfaceindex; + } + } + } + } + + // follow portals into other leafs + for (p = leaf->portals;p;p = p->next) + { + // only flow through portals facing the viewer + dist = PlaneDiff(info->eye, (&p->plane)); + if (dist < 0 && BoxesOverlap(p->past->mins, p->past->maxs, info->boxmins, info->boxmaxs)) + { + newpoints = Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, (float *) p->points, p->numpoints, &portaltemppoints2[0][0], 256); + if (newpoints < 3) + continue; + else if (firstclipplane + numclipplanes + newpoints > MAXRECURSIVEPORTALPLANES) + ranoutofportalplanes = true; + else + { + // find the center by averaging + VectorClear(center); + for (i = 0;i < newpoints;i++) + VectorAdd(center, portaltemppoints2[i], center); + // ixtable is a 1.0f / N table + VectorScale(center, ixtable[newpoints], center); + // calculate the planes, and make sure the polygon can see its own center + newplanes = &portalplanes[firstclipplane + numclipplanes]; + for (prev = newpoints - 1, i = 0;i < newpoints;prev = i, i++) + { + TriangleNormal(portaltemppoints2[prev], portaltemppoints2[i], info->eye, newplanes[i].normal); + VectorNormalize(newplanes[i].normal); + newplanes[i].dist = DotProduct(info->eye, newplanes[i].normal); + if (DotProduct(newplanes[i].normal, center) <= newplanes[i].dist) + { + // polygon can't see its own center, discard and use parent portal + break; + } + } + if (i == newpoints) + Portal_RecursiveFlow(info, p->past, firstclipplane + numclipplanes, newpoints); + else + Portal_RecursiveFlow(info, p->past, firstclipplane, numclipplanes); + } + } + } + + CLEARPVSBIT(info->visitingleafpvs, leafindex); +} + +static void Portal_RecursiveFindLeafForFlow(portalrecursioninfo_t *info, mnode_t *node) +{ + if (node->plane) + { + float f = DotProduct(info->eye, node->plane->normal) - node->plane->dist; + if (f > -0.1) + Portal_RecursiveFindLeafForFlow(info, node->children[0]); + if (f < 0.1) + Portal_RecursiveFindLeafForFlow(info, node->children[1]); + } + else + { + mleaf_t *leaf = (mleaf_t *)node; + if (leaf->clusterindex >= 0) + Portal_RecursiveFlow(info, leaf, 0, info->numfrustumplanes); + } +} + +void Portal_Visibility(dp_model_t *model, const vec3_t eye, int *leaflist, unsigned char *leafpvs, int *numleafspointer, int *surfacelist, unsigned char *surfacepvs, int *numsurfacespointer, const mplane_t *frustumplanes, int numfrustumplanes, int exact, const float *boxmins, const float *boxmaxs, float *updateleafsmins, float *updateleafsmaxs, unsigned char *shadowtrispvs, unsigned char *lighttrispvs, unsigned char *visitingleafpvs) +{ + int i; + portalrecursioninfo_t info; + + // if there is no model, it can not block visibility + if (model == NULL) + { + Con_Print("Portal_Visibility: NULL model\n"); + return; + } + + if (!model->brush.data_nodes) + { + Con_Print("Portal_Visibility: not a brush model\n"); + return; + } + + // put frustum planes (if any) into tinyplane format at start of buffer + for (i = 0;i < numfrustumplanes;i++) + { + VectorCopy(frustumplanes[i].normal, portalplanes[i].normal); + portalplanes[i].dist = frustumplanes[i].dist; + } + + ranoutofportalplanes = false; + ranoutofportals = false; + + VectorCopy(boxmins, info.boxmins); + VectorCopy(boxmaxs, info.boxmaxs); + info.exact = exact; + info.numsurfaces = 0; + info.surfacelist = surfacelist; + info.surfacepvs = surfacepvs; + info.numleafs = 0; + info.visitingleafpvs = visitingleafpvs; + info.leaflist = leaflist; + info.leafpvs = leafpvs; + info.model = model; + VectorCopy(eye, info.eye); + info.numfrustumplanes = numfrustumplanes; + info.updateleafsmins = updateleafsmins; + info.updateleafsmaxs = updateleafsmaxs; + info.shadowtrispvs = shadowtrispvs; + info.lighttrispvs = lighttrispvs; + + Portal_RecursiveFindLeafForFlow(&info, model->brush.data_nodes); + + if (ranoutofportalplanes) + Con_Printf("Portal_RecursiveFlow: ran out of %d plane stack when recursing through portals\n", MAXRECURSIVEPORTALPLANES); + if (ranoutofportals) + Con_Printf("Portal_RecursiveFlow: ran out of %d portal stack when recursing through portals\n", MAXRECURSIVEPORTALS); + if (numsurfacespointer) + *numsurfacespointer = info.numsurfaces; + if (numleafspointer) + *numleafspointer = info.numleafs; +} + diff --git a/app/jni/portals.h b/app/jni/portals.h new file mode 100644 index 0000000..fb61328 --- /dev/null +++ b/app/jni/portals.h @@ -0,0 +1,10 @@ + +#ifndef PORTALS_H +#define PORTALS_H + +int Portal_CheckPolygon(dp_model_t *model, vec3_t eye, float *polypoints, int numpoints); +int Portal_CheckBox(dp_model_t *model, vec3_t eye, vec3_t a, vec3_t b); +void Portal_Visibility(dp_model_t *model, const vec3_t eye, int *leaflist, unsigned char *leafpvs, int *numleafspointer, int *surfacelist, unsigned char *surfacepvs, int *numsurfacespointer, const mplane_t *frustumplanes, int numfrustumplanes, int exact, const float *boxmins, const float *boxmaxs, float *updateleafsmins, float *updateleafsmaxs, unsigned char *shadowtrispvs, unsigned char *lighttrispvs, unsigned char *visitingleafpvs); + +#endif + diff --git a/app/jni/pr_comp.h b/app/jni/pr_comp.h new file mode 100644 index 0000000..d1643b6 --- /dev/null +++ b/app/jni/pr_comp.h @@ -0,0 +1,225 @@ +/* +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. + +*/ + +// this file is shared by quake and qcc + +#ifndef PR_COMP_H +#define PR_COMP_H + +typedef unsigned int func_t; +typedef int string_t; + +typedef enum etype_e {ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_pointer} etype_t; + + +#define OFS_NULL 0 +#define OFS_RETURN 1 +#define OFS_PARM0 4 // leave 3 ofs for each parm to hold vectors +#define OFS_PARM1 7 +#define OFS_PARM2 10 +#define OFS_PARM3 13 +#define OFS_PARM4 16 +#define OFS_PARM5 19 +#define OFS_PARM6 22 +#define OFS_PARM7 25 +#define RESERVED_OFS 28 + + +typedef enum opcode_e +{ + OP_DONE, + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_ADD_F, + OP_ADD_V, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_FNC, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_FNC, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_LOAD_F, + OP_LOAD_V, + OP_LOAD_S, + OP_LOAD_ENT, + OP_LOAD_FLD, + OP_LOAD_FNC, + + OP_ADDRESS, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_FLD, + OP_STORE_FNC, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, + OP_STOREP_FLD, + OP_STOREP_FNC, + + OP_RETURN, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + OP_NOT_FNC, + OP_IF, + OP_IFNOT, + OP_CALL0, + OP_CALL1, + OP_CALL2, + OP_CALL3, + OP_CALL4, + OP_CALL5, + OP_CALL6, + OP_CALL7, + OP_CALL8, + OP_STATE, + OP_GOTO, + OP_AND, + OP_OR, + + OP_BITAND, + OP_BITOR +} +opcode_t; + + +typedef struct statement_s +{ + unsigned short op; + signed short a,b,c; +} +dstatement_t; + +typedef struct ddef_s +{ + unsigned short type; // if DEF_SAVEGLOBGAL bit is set + // the variable needs to be saved in savegames + unsigned short ofs; + int s_name; +} +ddef_t; +#define DEF_SAVEGLOBAL (1<<15) + +#define MAX_PARMS 8 + +typedef struct dfunction_s +{ + int first_statement; // negative numbers are builtins + int parm_start; + int locals; // total ints of parms + locals + + int profile; // runtime + + int s_name; + int s_file; // source file defined in + + int numparms; + unsigned char parm_size[MAX_PARMS]; +} +dfunction_t; + +typedef struct mfunction_s +{ + int first_statement; // negative numbers are builtins + int parm_start; + int locals; // total ints of parms + locals + + // these are doubles so that they can count up to 54bits or so rather than 32bit + double tprofile; // realtime in this function + double tbprofile; // realtime in builtins called by this function (NOTE: builtins also have a tprofile!) + double profile; // runtime + double builtinsprofile; // cost of builtin functions called by this function + double callcount; // times the functions has been called since the last profile call + double totaltime; // total execution time of this function DIRECTLY FROM THE ENGINE + double tprofile_total; // runtime (NOTE: tbprofile_total makes no real sense, so not accumulating that) + double profile_total; // runtime + double builtinsprofile_total; // cost of builtin functions called by this function + int recursion; + + int s_name; + int s_file; // source file defined in + + int numparms; + unsigned char parm_size[MAX_PARMS]; +} +mfunction_t; + +typedef struct mstatement_s +{ + opcode_t op; + int operand[3]; // always a global or -1 for unused + int jumpabsolute; // only used by IF, IFNOT, GOTO +} +mstatement_t; + + +#define PROG_VERSION 6 +typedef struct dprograms_s +{ + int version; + int crc; // check of header file + + int ofs_statements; + int numstatements; // statement 0 is an error + + int ofs_globaldefs; + int numglobaldefs; + + int ofs_fielddefs; + int numfielddefs; + + int ofs_functions; + int numfunctions; // function 0 is an empty + + int ofs_strings; + int numstrings; // first string is a null string + + int ofs_globals; + int numglobals; + + int entityfields; +} +dprograms_t; + +#endif + diff --git a/app/jni/progdefs.h b/app/jni/progdefs.h new file mode 100644 index 0000000..7086dbd --- /dev/null +++ b/app/jni/progdefs.h @@ -0,0 +1,170 @@ +/* +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. + +*/ + +/* file generated by qcc, do not modify */ + +#ifndef PROGDEFS_H +#define PROGDEFS_H + +typedef struct globalvars_s +{ + int pad[28]; + int self; + int other; + int world; + float time; + float frametime; + float force_retouch; + string_t mapname; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float total_secrets; + float total_monsters; + float found_secrets; + float killed_monsters; + float parm1; + float parm2; + float parm3; + float parm4; + float parm5; + float parm6; + float parm7; + float parm8; + float parm9; + float parm10; + float parm11; + float parm12; + float parm13; + float parm14; + float parm15; + float parm16; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + int trace_ent; + float trace_inopen; + float trace_inwater; + int msg_entity; + func_t main; + func_t StartFrame; + func_t PlayerPreThink; + func_t PlayerPostThink; + func_t ClientKill; + func_t ClientConnect; + func_t PutClientInServer; + func_t ClientDisconnect; + func_t SetNewParms; + func_t SetChangeParms; +} globalvars_t; + +typedef struct entvars_s +{ + float modelindex; + vec3_t absmin; + vec3_t absmax; + float ltime; + float movetype; + float solid; + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t angles; + vec3_t avelocity; + vec3_t punchangle; + string_t classname; + string_t model; + float frame; + float skin; + float effects; + vec3_t mins; + vec3_t maxs; + vec3_t size; + func_t touch; + func_t use; + func_t think; + func_t blocked; + float nextthink; + int groundentity; + float health; + float frags; + float weapon; + string_t weaponmodel; + float weaponframe; + float currentammo; + float ammo_shells; + float ammo_nails; + float ammo_rockets; + float ammo_cells; + float items; + float takedamage; + int chain; + float deadflag; + vec3_t view_ofs; + float button0; + float button1; + float button2; + float impulse; + float fixangle; + vec3_t v_angle; + float idealpitch; + string_t netname; + int enemy; + float flags; + float colormap; + float team; + float max_health; + float teleport_time; + float armortype; + float armorvalue; + float waterlevel; + float watertype; + float ideal_yaw; + float yaw_speed; + int aiment; + int goalentity; + float spawnflags; + string_t target; + string_t targetname; + float dmg_take; + float dmg_save; + int dmg_inflictor; + int owner; + vec3_t movedir; + string_t message; + float sounds; + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; +} entvars_t; + +#define PROGHEADER_CRC 5927 +#define PROGHEADER_CRC_TENEBRAE 32401 + +#endif + diff --git a/app/jni/progs.h b/app/jni/progs.h new file mode 100644 index 0000000..ed09789 --- /dev/null +++ b/app/jni/progs.h @@ -0,0 +1,151 @@ +/* +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. + +*/ + +#ifndef PROGS_H +#define PROGS_H +#include "pr_comp.h" // defs shared with qcc + +#define ENTITYGRIDAREAS 16 +#define MAX_ENTITYCLUSTERS 16 + +#define GEOMTYPE_NONE -1 +#define GEOMTYPE_SOLID 0 +#define GEOMTYPE_BOX 1 +#define GEOMTYPE_SPHERE 2 +#define GEOMTYPE_CAPSULE 3 +#define GEOMTYPE_TRIMESH 4 +#define GEOMTYPE_CYLINDER 5 +#define GEOMTYPE_CAPSULE_X 6 +#define GEOMTYPE_CAPSULE_Y 7 +#define GEOMTYPE_CAPSULE_Z 8 +#define GEOMTYPE_CYLINDER_X 9 +#define GEOMTYPE_CYLINDER_Y 10 +#define GEOMTYPE_CYLINDER_Z 11 + +#define JOINTTYPE_NONE 0 +#define JOINTTYPE_POINT 1 +#define JOINTTYPE_HINGE 2 +#define JOINTTYPE_SLIDER 3 +#define JOINTTYPE_UNIVERSAL 4 +#define JOINTTYPE_HINGE2 5 +#define JOINTTYPE_FIXED -1 + +#define FORCETYPE_NONE 0 +#define FORCETYPE_FORCE 1 +#define FORCETYPE_FORCEATPOS 2 +#define FORCETYPE_TORQUE 3 + +#define ODEFUNC_ENABLE 1 +#define ODEFUNC_DISABLE 2 +#define ODEFUNC_FORCE 3 +#define ODEFUNC_TORQUE 4 + +typedef struct edict_odefunc_s +{ + int type; + vec3_t v1; + vec3_t v2; + struct edict_odefunc_s *next; +}edict_odefunc_t; + +typedef struct edict_engineprivate_s +{ + // true if this edict is unused + qboolean free; + // sv.time when the object was freed (to prevent early reuse which could + // mess up client interpolation or obscure severe QuakeC bugs) + float freetime; + // mark for the leak detector + int mark; + // place in the code where it was allocated (for the leak detector) + const char *allocation_origin; + // initially false to prevent projectiles from moving on their first frame + // (even if they were spawned by an synchronous client think) + qboolean move; + + // cached cluster links for quick stationary object visibility checking + vec3_t cullmins, cullmaxs; + int pvs_numclusters; + int pvs_clusterlist[MAX_ENTITYCLUSTERS]; + + // physics grid areas this edict is linked into + link_t areagrid[ENTITYGRIDAREAS]; + // since the areagrid can have multiple references to one entity, + // we should avoid extensive checking on entities already encountered + int areagridmarknumber; + // mins/maxs passed to World_LinkEdict + vec3_t areamins, areamaxs; + + // PROTOCOL_QUAKE, PROTOCOL_QUAKEDP, PROTOCOL_NEHAHRAMOVIE, PROTOCOL_QUAKEWORLD + // baseline values + entity_state_t baseline; + + // LordHavoc: gross hack to make floating items still work + int suspendedinairflag; + + // cached position to avoid redundant SV_CheckWaterTransition calls on monsters + qboolean waterposition_forceupdate; // force an update on this entity (set by SV_PushMove code for moving water entities) + vec3_t waterposition_origin; // updates whenever this changes + + // used by PushMove to keep track of where objects were before they were + // moved, in case they need to be moved back + vec3_t moved_from; + vec3_t moved_fromangles; + + framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + skeleton_t skeleton; + + // physics parameters + qboolean ode_physics; + void *ode_body; + void *ode_geom; + void *ode_joint; + float *ode_vertex3f; + int *ode_element3i; + int ode_numvertices; + int ode_numtriangles; + edict_odefunc_t *ode_func; + vec3_t ode_mins; + vec3_t ode_maxs; + vec3_t ode_scale; + vec_t ode_mass; + float ode_friction; + vec3_t ode_origin; + vec3_t ode_velocity; + vec3_t ode_angles; + vec3_t ode_avelocity; + qboolean ode_gravity; + int ode_modelindex; + vec_t ode_movelimit; // smallest component of (maxs[]-mins[]) + matrix4x4_t ode_offsetmatrix; + matrix4x4_t ode_offsetimatrix; + int ode_joint_type; + int ode_joint_enemy; + int ode_joint_aiment; + vec3_t ode_joint_origin; // joint anchor + vec3_t ode_joint_angles; // joint axis + vec3_t ode_joint_velocity; // second joint axis + vec3_t ode_joint_movedir; // parameters + void *ode_massbuf; +} +edict_engineprivate_t; + +#endif diff --git a/app/jni/progsvm.h b/app/jni/progsvm.h new file mode 100644 index 0000000..9ca932b --- /dev/null +++ b/app/jni/progsvm.h @@ -0,0 +1,886 @@ +/* +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. + +*/ +/* +This is a try to make the vm more generic, it is mainly based on the progs.h file. +For the license refer to progs.h. + +Generic means, less as possible hard-coded links with the other parts of the engine. +This means no edict_engineprivate struct usage, etc. +The code uses void pointers instead. +*/ + +#ifndef PROGSVM_H +#define PROGSVM_H + +#include "pr_comp.h" // defs shared with qcc +#include "progdefs.h" // generated by program cdefs +#include "clprogdefs.h" // generated by program cdefs + +#ifndef DP_SMALLMEMORY +//#define PROFILING +#endif + +typedef struct prvm_stack_s +{ + int s; + mfunction_t *f; + double tprofile_acc; + double profile_acc; + double builtinsprofile_acc; +} prvm_stack_t; + + +typedef union prvm_eval_s +{ + prvm_int_t string; + prvm_vec_t _float; + prvm_vec_t vector[3]; + prvm_int_t function; + prvm_int_t ivector[3]; + prvm_int_t _int; + prvm_int_t edict; +} prvm_eval_t; + +typedef struct prvm_required_field_s +{ + int type; + const char *name; +} prvm_required_field_t; + + +// AK: I dont call it engine private cause it doesnt really belongs to the engine +// it belongs to prvm. +typedef struct prvm_edict_private_s +{ + qboolean free; + float freetime; + int mark; // used during leaktest (0 = unref, >0 = referenced); special values during server physics: +#define PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN -1 +#define PRVM_EDICT_MARK_SETORIGIN_CAUGHT -2 + const char *allocation_origin; +} prvm_edict_private_t; + +typedef struct prvm_edict_s +{ + // engine-private fields (stored in dynamically resized array) + //edict_engineprivate_t *e; + union + { + prvm_edict_private_t *required; + prvm_vec_t *fp; + prvm_int_t *ip; + // FIXME: this server pointer really means world, not server + // (it is used by both server qc and client qc, but not menu qc) + edict_engineprivate_t *server; + // add other private structs as you desire + // new structs have to start with the elements of prvm_edit_private_t + // e.g. a new struct has to either look like this: + // typedef struct server_edict_private_s { + // prvm_edict_private_t base; + // vec3_t moved_from; + // vec3_t moved_fromangles; + // ... } server_edict_private_t; + // or: + // typedef struct server_edict_private_s { + // qboolean free; + // float freetime; + // vec3_t moved_from; + // vec3_t moved_fromangles; + // ... } server_edict_private_t; + // However, the first one should be preferred. + } priv; + // QuakeC fields (stored in dynamically resized array) + union + { + prvm_vec_t *fp; + prvm_int_t *ip; +// entvars_t *server; +// cl_entvars_t *client; + } fields; +} prvm_edict_t; + +#define VMPOLYGONS_MAXPOINTS 64 + +typedef struct vmpolygons_triangle_s +{ + rtexture_t *texture; + int drawflag; + qboolean hasalpha; + unsigned short elements[3]; +} vmpolygons_triangle_t; + +typedef struct vmpolygons_s +{ + mempool_t *pool; + qboolean initialized; + + int max_vertices; + int num_vertices; + float *data_vertex3f; + float *data_color4f; + float *data_texcoord2f; + + int max_triangles; + int num_triangles; + vmpolygons_triangle_t *data_triangles; + unsigned short *data_sortedelement3s; + + qboolean begin_active; + int begin_draw2d; + rtexture_t *begin_texture; + int begin_drawflag; + int begin_vertices; + float begin_vertex[VMPOLYGONS_MAXPOINTS][3]; + float begin_color[VMPOLYGONS_MAXPOINTS][4]; + float begin_texcoord[VMPOLYGONS_MAXPOINTS][2]; + qboolean begin_texture_hasalpha; +} vmpolygons_t; + +extern prvm_eval_t prvm_badvalue; + +#define PRVM_alledictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_alledictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_alledictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_alledictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_alledictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_allglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_allglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_allglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_allglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_allglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_allfunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_drawedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_drawglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_drawglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_drawglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_drawglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_drawglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_drawfunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_gameedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_gameglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_gameglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_gameglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_gameglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_gameglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_gamefunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_serveredictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serveredictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serveredictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serveredictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serveredictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_serverglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_serverglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_serverglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_serverglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_serverglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_serverfunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_clientedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_clientglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_clientglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_clientglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_clientglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_clientglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_clientfunction(funcname) (prog->funcoffsets.funcname) + +#define PRVM_menuedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) +#define PRVM_menuglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) +#define PRVM_menuglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) +#define PRVM_menuglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) +#define PRVM_menuglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) +#define PRVM_menuglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) +#define PRVM_menufunction(funcname) (prog->funcoffsets.funcname) + +#if 1 +#define PRVM_EDICTFIELDVALUE(ed, fieldoffset) ((fieldoffset) < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)((ed)->fields.fp + (fieldoffset))) +#define PRVM_EDICTFIELDFLOAT(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->_float) +#define PRVM_EDICTFIELDVECTOR(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->vector) +#define PRVM_EDICTFIELDSTRING(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->string) +#define PRVM_EDICTFIELDEDICT(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->edict) +#define PRVM_EDICTFIELDFUNCTION(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->function) +#define PRVM_GLOBALFIELDVALUE(fieldoffset) ((fieldoffset) < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)(prog->globals.fp + (fieldoffset))) +#define PRVM_GLOBALFIELDFLOAT(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->_float) +#define PRVM_GLOBALFIELDVECTOR(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->vector) +#define PRVM_GLOBALFIELDSTRING(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->string) +#define PRVM_GLOBALFIELDEDICT(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->edict) +#define PRVM_GLOBALFIELDFUNCTION(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->function) +#else +#define PRVM_EDICTFIELDVALUE(ed, fieldoffset) ((prvm_eval_t *)(ed->fields.fp + fieldoffset)) +#define PRVM_EDICTFIELDFLOAT(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->_float) +#define PRVM_EDICTFIELDVECTOR(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->vector) +#define PRVM_EDICTFIELDSTRING(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->string) +#define PRVM_EDICTFIELDEDICT(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->edict) +#define PRVM_EDICTFIELDFUNCTION(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->function) +#define PRVM_GLOBALFIELDVALUE(fieldoffset) ((prvm_eval_t *)(prog->globals.fp + fieldoffset)) +#define PRVM_GLOBALFIELDFLOAT(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->_float) +#define PRVM_GLOBALFIELDVECTOR(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->vector) +#define PRVM_GLOBALFIELDSTRING(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->string) +#define PRVM_GLOBALFIELDEDICT(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->edict) +#define PRVM_GLOBALFIELDFUNCTION(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->function) +#endif + +//============================================================================ +#define PRVM_OP_STATE 1 + +#ifdef DP_SMALLMEMORY +#define PRVM_MAX_STACK_DEPTH 128 +#define PRVM_LOCALSTACK_SIZE 2048 + +#define PRVM_MAX_OPENFILES 16 +#define PRVM_MAX_OPENSEARCHES 8 +#else +#define PRVM_MAX_STACK_DEPTH 1024 +#define PRVM_LOCALSTACK_SIZE 16384 + +#define PRVM_MAX_OPENFILES 256 +#define PRVM_MAX_OPENSEARCHES 128 +#endif + +struct prvm_prog_s; +typedef void (*prvm_builtin_t) (struct prvm_prog_s *prog); + +// NOTE: field offsets use -1 for NULL +typedef struct prvm_prog_fieldoffsets_s +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) int x; +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +} +prvm_prog_fieldoffsets_t; + +// NOTE: global offsets use -1 for NULL +typedef struct prvm_prog_globaloffsets_s +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) int x; +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +} +prvm_prog_globaloffsets_t; + +// NOTE: function offsets use 0 for NULL +typedef struct prvm_prog_funcoffsets_s +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) int x; +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +} +prvm_prog_funcoffsets_t; + +// stringbuffer flags +#define STRINGBUFFER_SAVED 1 // saved in savegames +#define STRINGBUFFER_QCFLAGS 1 // allowed to be set by QC +#define STRINGBUFFER_TEMP 128 // internal use ONLY +typedef struct prvm_stringbuffer_s +{ + int max_strings; + int num_strings; + char **strings; + const char *origin; + unsigned char flags; +} +prvm_stringbuffer_t; + +// [INIT] variables flagged with this token can be initialized by 'you' +// NOTE: external code has to create and free the mempools but everything else is done by prvm ! +typedef struct prvm_prog_s +{ + double starttime; // system time when PRVM_Prog_Load was called + double profiletime; // system time when last PRVM_CallProfile was called (or PRVM_Prog_Load initially) + unsigned int id; // increasing unique id of progs instance + mfunction_t *functions; + char *strings; + int stringssize; + ddef_t *fielddefs; + ddef_t *globaldefs; + mstatement_t *statements; + int entityfields; // number of vec_t fields in progs (some variables are 3) + int entityfieldsarea; // LordHavoc: equal to max_edicts * entityfields (for bounds checking) + + // loaded values from the disk format + int progs_version; + int progs_crc; + int progs_numstatements; + int progs_numglobaldefs; + int progs_numfielddefs; + int progs_numfunctions; + int progs_numstrings; + int progs_numglobals; + int progs_entityfields; + + // real values in memory (some modified by loader) + int numstatements; + int numglobaldefs; + int numfielddefs; + int numfunctions; + int numstrings; + int numglobals; + + int *statement_linenums; // NULL if not available + + double *statement_profile; // only incremented if prvm_statementprofiling is on + + union { + prvm_vec_t *fp; + prvm_int_t *ip; +// globalvars_t *server; +// cl_globalvars_t *client; + } globals; + + int maxknownstrings; + int numknownstrings; + // this is updated whenever a string is removed or added + // (simple optimization of the free string search) + int firstfreeknownstring; + const char **knownstrings; + unsigned char *knownstrings_freeable; + const char **knownstrings_origin; + const char ***stringshash; + + memexpandablearray_t stringbuffersarray; + + // all memory allocations related to this vm_prog (code, edicts, strings) + mempool_t *progs_mempool; // [INIT] + + prvm_builtin_t *builtins; // [INIT] + int numbuiltins; // [INIT] + + int argc; + + int trace; + int break_statement; + int break_stack_index; + int watch_global; + etype_t watch_global_type; + prvm_eval_t watch_global_value; + int watch_edict; + int watch_field; + etype_t watch_field_type; + prvm_eval_t watch_edictfield_value; + + mfunction_t *xfunction; + int xstatement; + + // stacktrace writes into stack[MAX_STACK_DEPTH] + // thus increase the array, so depth wont be overwritten + prvm_stack_t stack[PRVM_MAX_STACK_DEPTH+1]; + int depth; + + prvm_int_t localstack[PRVM_LOCALSTACK_SIZE]; + int localstack_used; + + unsigned short filecrc; + + //============================================================================ + // until this point everything also exists (with the pr_ prefix) in the old vm + + qfile_t *openfiles[PRVM_MAX_OPENFILES]; + const char * openfiles_origin[PRVM_MAX_OPENFILES]; + fssearch_t *opensearches[PRVM_MAX_OPENSEARCHES]; + const char * opensearches_origin[PRVM_MAX_OPENSEARCHES]; + skeleton_t *skeletons[MAX_EDICTS]; + + // buffer for storing all tempstrings created during one invocation of ExecuteProgram + sizebuf_t tempstringsbuf; + + // LordHavoc: moved this here to clean up things that relied on prvm_prog_list too much + // FIXME: make VM_CL_R_Polygon functions use Debug_Polygon functions? + vmpolygons_t vmpolygons; + + // copies of some vars that were former read from sv + int num_edicts; + // number of edicts for which space has been (should be) allocated + int max_edicts; // [INIT] + // used instead of the constant MAX_EDICTS + int limit_edicts; // [INIT] + + // number of reserved edicts (allocated from 1) + int reserved_edicts; // [INIT] + + prvm_edict_t *edicts; + prvm_vec_t *edictsfields; + void *edictprivate; + + // size of the engine private struct + int edictprivate_size; // [INIT] + + prvm_prog_fieldoffsets_t fieldoffsets; + prvm_prog_globaloffsets_t globaloffsets; + prvm_prog_funcoffsets_t funcoffsets; + + // allow writing to world entity fields, this is set by server init and + // cleared before first server frame + qboolean allowworldwrites; + + // name of the prog, e.g. "Server", "Client" or "Menu" (used for text output) + const char *name; // [INIT] + + // flag - used to store general flags like PRVM_GE_SELF, etc. + int flag; + + const char *extensionstring; // [INIT] + + qboolean loadintoworld; // [INIT] + + // used to indicate whether a prog is loaded + qboolean loaded; + qboolean leaktest_active; + + // translation buffer (only needs to be freed on unloading progs, type is private to prvm_edict.c) + void *po; + + // printed together with backtraces + const char *statestring; + +// prvm_builtin_mem_t *mem_list; + +// now passed as parameter of PRVM_LoadProgs +// char **required_func; +// int numrequiredfunc; + + //============================================================================ + + ddef_t *self; // if self != 0 then there is a global self + + //============================================================================ + // function pointers + + void (*begin_increase_edicts)(struct prvm_prog_s *prog); // [INIT] used by PRVM_MEM_Increase_Edicts + void (*end_increase_edicts)(struct prvm_prog_s *prog); // [INIT] + + void (*init_edict)(struct prvm_prog_s *prog, prvm_edict_t *edict); // [INIT] used by PRVM_ED_ClearEdict + void (*free_edict)(struct prvm_prog_s *prog, prvm_edict_t *ed); // [INIT] used by PRVM_ED_Free + + void (*count_edicts)(struct prvm_prog_s *prog); // [INIT] used by PRVM_ED_Count_f + + qboolean (*load_edict)(struct prvm_prog_s *prog, prvm_edict_t *ent); // [INIT] used by PRVM_ED_LoadFromFile + + void (*init_cmd)(struct prvm_prog_s *prog); // [INIT] used by PRVM_InitProg + void (*reset_cmd)(struct prvm_prog_s *prog); // [INIT] used by PRVM_ResetProg + + void (*error_cmd)(const char *format, ...) DP_FUNC_PRINTF(1); // [INIT] + + void (*ExecuteProgram)(struct prvm_prog_s *prog, func_t fnum, const char *errormessage); // pointer to one of the *VM_ExecuteProgram functions +} prvm_prog_t; + +typedef enum prvm_progindex_e +{ + PRVM_PROG_SERVER, + PRVM_PROG_CLIENT, + PRVM_PROG_MENU, + PRVM_PROG_MAX +} +prvm_progindex_t; + +extern prvm_prog_t prvm_prog_list[PRVM_PROG_MAX]; +prvm_prog_t *PRVM_ProgFromString(const char *str); +prvm_prog_t *PRVM_FriendlyProgFromString(const char *str); // for console commands (prints error if name unknown and returns NULL, prints error if prog not loaded and returns NULL) +#define PRVM_GetProg(n) (&prvm_prog_list[(n)]) +#define PRVM_ProgLoaded(n) (PRVM_GetProg(n)->loaded) +#define SVVM_prog (&prvm_prog_list[PRVM_PROG_SERVER]) +#define CLVM_prog (&prvm_prog_list[PRVM_PROG_CLIENT]) +#define MVM_prog (&prvm_prog_list[PRVM_PROG_MENU]) + +//============================================================================ +// prvm_cmds part + +extern prvm_builtin_t vm_sv_builtins[]; +extern prvm_builtin_t vm_cl_builtins[]; +extern prvm_builtin_t vm_m_builtins[]; + +extern const int vm_sv_numbuiltins; +extern const int vm_cl_numbuiltins; +extern const int vm_m_numbuiltins; + +extern const char * vm_sv_extensions; // client also uses this +extern const char * vm_m_extensions; + +void SVVM_init_cmd(prvm_prog_t *prog); +void SVVM_reset_cmd(prvm_prog_t *prog); + +void CLVM_init_cmd(prvm_prog_t *prog); +void CLVM_reset_cmd(prvm_prog_t *prog); + +void MVM_init_cmd(prvm_prog_t *prog); +void MVM_reset_cmd(prvm_prog_t *prog); + +void VM_Cmd_Init(prvm_prog_t *prog); +void VM_Cmd_Reset(prvm_prog_t *prog); +//============================================================================ + +void PRVM_Init (void); + +#ifdef PROFILING +void SVVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage); +void CLVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage); +void MVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage); +#else +#define SVVM_ExecuteProgram PRVM_ExecuteProgram +#define CLVM_ExecuteProgram PRVM_ExecuteProgram +#define MVM_ExecuteProgram PRVM_ExecuteProgram +void PRVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage); +#endif + +#define PRVM_Alloc(buffersize) Mem_Alloc(prog->progs_mempool, buffersize) +#define PRVM_Free(buffer) Mem_Free(buffer) + +void PRVM_Profile (prvm_prog_t *prog, int maxfunctions, double mintime, int sortby); +void PRVM_Profile_f (void); +void PRVM_ChildProfile_f (void); +void PRVM_CallProfile_f (void); +void PRVM_PrintFunction_f (void); + +void PRVM_PrintState(prvm_prog_t *prog, int stack_index); +void PRVM_Crash(prvm_prog_t *prog); +void PRVM_ShortStackTrace(prvm_prog_t *prog, char *buf, size_t bufsize); +const char *PRVM_AllocationOrigin(prvm_prog_t *prog); + +ddef_t *PRVM_ED_FindField(prvm_prog_t *prog, const char *name); +ddef_t *PRVM_ED_FindGlobal(prvm_prog_t *prog, const char *name); +mfunction_t *PRVM_ED_FindFunction(prvm_prog_t *prog, const char *name); + +int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *name); +int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *name); +func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *name); +#define PRVM_ED_FindFieldOffset_FromStruct(st, field) prog->fieldoffsets . field = ((int *)(&((st *)NULL)-> field ) - ((int *)NULL)) +#define PRVM_ED_FindGlobalOffset_FromStruct(st, field) prog->globaloffsets . field = ((int *)(&((st *)NULL)-> field ) - ((int *)NULL)) + +void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog); + +qboolean PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e); +prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog); +void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed); +void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e); + +void PRVM_PrintFunctionStatements(prvm_prog_t *prog, const char *name); +void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname); +void PRVM_ED_Write(prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed); +const char *PRVM_ED_ParseEdict(prvm_prog_t *prog, const char *data, prvm_edict_t *ent); + +void PRVM_ED_WriteGlobals(prvm_prog_t *prog, qfile_t *f); +void PRVM_ED_ParseGlobals(prvm_prog_t *prog, const char *data); + +void PRVM_ED_LoadFromFile(prvm_prog_t *prog, const char *data); + +unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline); +#define PRVM_EDICT(n) (((unsigned)(n) < (unsigned int)prog->max_edicts) ? (unsigned int)(n) : PRVM_EDICT_NUM_ERROR(prog, (unsigned int)(n), __FILE__, __LINE__)) +#define PRVM_EDICT_NUM(n) (prog->edicts + PRVM_EDICT(n)) + +//int NUM_FOR_EDICT_ERROR(prvm_edict_t *e); +#define PRVM_NUM_FOR_EDICT(e) ((int)((prvm_edict_t *)(e) - prog->edicts)) +//int PRVM_NUM_FOR_EDICT(prvm_edict_t *e); + +#define PRVM_NEXT_EDICT(e) ((e) + 1) + +#define PRVM_EDICT_TO_PROG(e) (PRVM_NUM_FOR_EDICT(e)) +//int PRVM_EDICT_TO_PROG(prvm_edict_t *e); +#define PRVM_PROG_TO_EDICT(n) (PRVM_EDICT_NUM(n)) +//prvm_edict_t *PRVM_PROG_TO_EDICT(int n); + +//============================================================================ + +#define PRVM_G_FLOAT(o) (prog->globals.fp[o]) +#define PRVM_G_INT(o) (prog->globals.ip[o]) +#define PRVM_G_EDICT(o) (PRVM_PROG_TO_EDICT(prog->globals.ip[o])) +#define PRVM_G_EDICTNUM(o) PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(o)) +#define PRVM_G_VECTOR(o) (&prog->globals.fp[o]) +#define PRVM_G_STRING(o) (PRVM_GetString(prog, prog->globals.ip[o])) +//#define PRVM_G_FUNCTION(prog, o) (prog->globals.ip[o]) + +// FIXME: make these go away? +#define PRVM_E_FLOAT(e,o) (e->fields.fp[o]) +#define PRVM_E_INT(e,o) (e->fields.ip[o]) +//#define PRVM_E_VECTOR(e,o) (&(e->fields.fp[o])) +#define PRVM_E_STRING(e,o) (PRVM_GetString(prog, e->fields.ip[o])) + +extern int prvm_type_size[8]; // for consistency : I think a goal of this sub-project is to +// make the new vm mostly independent from the old one, thus if it's necessary, I copy everything + +void PRVM_Init_Exec(prvm_prog_t *prog); + +void PRVM_ED_PrintEdicts_f (void); +void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname); + +const char *PRVM_GetString(prvm_prog_t *prog, int num); +int PRVM_SetEngineString(prvm_prog_t *prog, const char *s); +const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s); +int PRVM_SetTempString(prvm_prog_t *prog, const char *s); +int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer); +void PRVM_FreeString(prvm_prog_t *prog, int num); + +ddef_t *PRVM_ED_FieldAtOfs(prvm_prog_t *prog, int ofs); +qboolean PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash); +char *PRVM_UglyValueString(prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength); +char *PRVM_GlobalString(prvm_prog_t *prog, int ofs, char *line, size_t linelength); +char *PRVM_GlobalStringNoContents(prvm_prog_t *prog, int ofs, char *line, size_t linelength); + +//============================================================================ + +/* +Initializing a vm: +Call InitProg with the num +Set up the fields marked with [INIT] in the prog struct +Load a program with LoadProgs +*/ +// Load expects to be called right after Reset +void PRVM_Prog_Init(prvm_prog_t *prog); +void PRVM_Prog_Load(prvm_prog_t *prog, const char *filename, unsigned char *data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global); +void PRVM_Prog_Reset(prvm_prog_t *prog); + +void PRVM_StackTrace(prvm_prog_t *prog); +void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text); +void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n); + +void VM_Warning(prvm_prog_t *prog, const char *fmt, ...) DP_FUNC_PRINTF(2); + +void VM_GenerateFrameGroupBlend(prvm_prog_t *prog, framegroupblend_t *framegroupblend, const prvm_edict_t *ed); +void VM_FrameBlendFromFrameGroupBlend(frameblend_t *frameblend, const framegroupblend_t *framegroupblend, const dp_model_t *model, double curtime); +void VM_UpdateEdictSkeleton(prvm_prog_t *prog, prvm_edict_t *ed, const dp_model_t *edmodel, const frameblend_t *frameblend); +void VM_RemoveEdictSkeleton(prvm_prog_t *prog, prvm_edict_t *ed); + +#endif diff --git a/app/jni/protocol.c b/app/jni/protocol.c new file mode 100644 index 0000000..db2de2f --- /dev/null +++ b/app/jni/protocol.c @@ -0,0 +1,3387 @@ +#include "quakedef.h" + +#define ENTITYSIZEPROFILING_START(msg, num) \ + int entityprofiling_startsize = msg->cursize + +#define ENTITYSIZEPROFILING_END(msg, num) \ + if(developer_networkentities.integer >= 2) \ + { \ + prvm_edict_t *ed = prog->edicts + num; \ + Con_Printf("sent entity update of size %d for a %s\n", (msg->cursize - entityprofiling_startsize), PRVM_serveredictstring(ed, classname) ? PRVM_GetString(prog, PRVM_serveredictstring(ed, classname)) : "(no classname)"); \ + } + +// this is 88 bytes (must match entity_state_t in protocol.h) +entity_state_t defaultstate = +{ + // ! means this is not sent to client + 0,//double time; // ! time this state was built (used on client for interpolation) + {0,0,0},//float netcenter[3]; // ! for network prioritization, this is the center of the bounding box (which may differ from the origin) + {0,0,0},//float origin[3]; + {0,0,0},//float angles[3]; + 0,//int effects; + 0,//unsigned int customizeentityforclient; // ! + 0,//unsigned short number; // entity number this state is for + 0,//unsigned short modelindex; + 0,//unsigned short frame; + 0,//unsigned short tagentity; + 0,//unsigned short specialvisibilityradius; // ! larger if it has effects/light + 0,//unsigned short viewmodelforclient; // ! + 0,//unsigned short exteriormodelforclient; // ! not shown if first person viewing from this entity, shown in all other cases + 0,//unsigned short nodrawtoclient; // ! + 0,//unsigned short drawonlytoclient; // ! + 0,//unsigned short traileffectnum; + {0,0,0,0},//unsigned short light[4]; // color*256 (0.00 to 255.996), and radius*1 + ACTIVE_NOT,//unsigned char active; // true if a valid state + 0,//unsigned char lightstyle; + 0,//unsigned char lightpflags; + 0,//unsigned char colormap; + 0,//unsigned char skin; // also chooses cubemap for rtlights if lightpflags & LIGHTPFLAGS_FULLDYNAMIC + 255,//unsigned char alpha; + 16,//unsigned char scale; + 0,//unsigned char glowsize; + 254,//unsigned char glowcolor; + 0,//unsigned char flags; + 0,//unsigned char internaleffects; // INTEF_FLAG1QW and so on + 0,//unsigned char tagindex; + {32, 32, 32},//unsigned char colormod[3]; + {32, 32, 32},//unsigned char glowmod[3]; +}; + +// LordHavoc: I own protocol ranges 96, 97, 3500-3599 + +struct protocolversioninfo_s +{ + int number; + protocolversion_t version; + const char *name; +} +protocolversioninfo[] = +{ + { 3504, PROTOCOL_DARKPLACES7 , "DP7"}, + { 3503, PROTOCOL_DARKPLACES6 , "DP6"}, + { 3502, PROTOCOL_DARKPLACES5 , "DP5"}, + { 3501, PROTOCOL_DARKPLACES4 , "DP4"}, + { 3500, PROTOCOL_DARKPLACES3 , "DP3"}, + { 97, PROTOCOL_DARKPLACES2 , "DP2"}, + { 96, PROTOCOL_DARKPLACES1 , "DP1"}, + { 15, PROTOCOL_QUAKEDP , "QUAKEDP"}, + { 15, PROTOCOL_QUAKE , "QUAKE"}, + { 28, PROTOCOL_QUAKEWORLD , "QW"}, + { 250, PROTOCOL_NEHAHRAMOVIE, "NEHAHRAMOVIE"}, + {10000, PROTOCOL_NEHAHRABJP , "NEHAHRABJP"}, + {10001, PROTOCOL_NEHAHRABJP2 , "NEHAHRABJP2"}, + {10002, PROTOCOL_NEHAHRABJP3 , "NEHAHRABJP3"}, + { 0, PROTOCOL_UNKNOWN , NULL} +}; + +protocolversion_t Protocol_EnumForName(const char *s) +{ + int i; + for (i = 0;protocolversioninfo[i].name;i++) + if (!strcasecmp(s, protocolversioninfo[i].name)) + return protocolversioninfo[i].version; + return PROTOCOL_UNKNOWN; +} + +const char *Protocol_NameForEnum(protocolversion_t p) +{ + int i; + for (i = 0;protocolversioninfo[i].name;i++) + if (protocolversioninfo[i].version == p) + return protocolversioninfo[i].name; + return "UNKNOWN"; +} + +protocolversion_t Protocol_EnumForNumber(int n) +{ + int i; + for (i = 0;protocolversioninfo[i].name;i++) + if (protocolversioninfo[i].number == n) + return protocolversioninfo[i].version; + return PROTOCOL_UNKNOWN; +} + +int Protocol_NumberForEnum(protocolversion_t p) +{ + int i; + for (i = 0;protocolversioninfo[i].name;i++) + if (protocolversioninfo[i].version == p) + return protocolversioninfo[i].number; + return 0; +} + +void Protocol_Names(char *buffer, size_t buffersize) +{ + int i; + if (buffersize < 1) + return; + buffer[0] = 0; + for (i = 0;protocolversioninfo[i].name;i++) + { + if (i > 1) + strlcat(buffer, " ", buffersize); + strlcat(buffer, protocolversioninfo[i].name, buffersize); + } +} + +void EntityFrameQuake_ReadEntity(int bits) +{ + int num; + entity_t *ent; + entity_state_t s; + + if (bits & U_MOREBITS) + bits |= (MSG_ReadByte(&cl_message)<<8); + if ((bits & U_EXTEND1) && cls.protocol != PROTOCOL_NEHAHRAMOVIE) + { + bits |= MSG_ReadByte(&cl_message) << 16; + if (bits & U_EXTEND2) + bits |= MSG_ReadByte(&cl_message) << 24; + } + + if (bits & U_LONGENTITY) + num = (unsigned short) MSG_ReadShort(&cl_message); + else + num = MSG_ReadByte(&cl_message); + + if (num >= MAX_EDICTS) + Host_Error("EntityFrameQuake_ReadEntity: entity number (%i) >= MAX_EDICTS (%i)", num, MAX_EDICTS); + if (num < 1) + Host_Error("EntityFrameQuake_ReadEntity: invalid entity number (%i)", num); + + if (cl.num_entities <= num) + { + cl.num_entities = num + 1; + if (num >= cl.max_entities) + CL_ExpandEntities(num); + } + + ent = cl.entities + num; + + // note: this inherits the 'active' state of the baseline chosen + // (state_baseline is always active, state_current may not be active if + // the entity was missing in the last frame) + if (bits & U_DELTA) + s = ent->state_current; + else + { + s = ent->state_baseline; + s.active = ACTIVE_NETWORK; + } + + cl.isquakeentity[num] = true; + if (cl.lastquakeentity < num) + cl.lastquakeentity = num; + s.number = num; + s.time = cl.mtime[0]; + s.flags = 0; + if (bits & U_MODEL) + { + if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) + s.modelindex = (unsigned short) MSG_ReadShort(&cl_message); + else + s.modelindex = (s.modelindex & 0xFF00) | MSG_ReadByte(&cl_message); + } + if (bits & U_FRAME) s.frame = (s.frame & 0xFF00) | MSG_ReadByte(&cl_message); + if (bits & U_COLORMAP) s.colormap = MSG_ReadByte(&cl_message); + if (bits & U_SKIN) s.skin = MSG_ReadByte(&cl_message); + if (bits & U_EFFECTS) s.effects = (s.effects & 0xFF00) | MSG_ReadByte(&cl_message); + if (bits & U_ORIGIN1) s.origin[0] = MSG_ReadCoord(&cl_message, cls.protocol); + if (bits & U_ANGLE1) s.angles[0] = MSG_ReadAngle(&cl_message, cls.protocol); + if (bits & U_ORIGIN2) s.origin[1] = MSG_ReadCoord(&cl_message, cls.protocol); + if (bits & U_ANGLE2) s.angles[1] = MSG_ReadAngle(&cl_message, cls.protocol); + if (bits & U_ORIGIN3) s.origin[2] = MSG_ReadCoord(&cl_message, cls.protocol); + if (bits & U_ANGLE3) s.angles[2] = MSG_ReadAngle(&cl_message, cls.protocol); + if (bits & U_STEP) s.flags |= RENDER_STEP; + if (bits & U_ALPHA) s.alpha = MSG_ReadByte(&cl_message); + if (bits & U_SCALE) s.scale = MSG_ReadByte(&cl_message); + if (bits & U_EFFECTS2) s.effects = (s.effects & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); + if (bits & U_GLOWSIZE) s.glowsize = MSG_ReadByte(&cl_message); + if (bits & U_GLOWCOLOR) s.glowcolor = MSG_ReadByte(&cl_message); + if (bits & U_COLORMOD) {int c = MSG_ReadByte(&cl_message);s.colormod[0] = (unsigned char)(((c >> 5) & 7) * (32.0f / 7.0f));s.colormod[1] = (unsigned char)(((c >> 2) & 7) * (32.0f / 7.0f));s.colormod[2] = (unsigned char)((c & 3) * (32.0f / 3.0f));} + if (bits & U_GLOWTRAIL) s.flags |= RENDER_GLOWTRAIL; + if (bits & U_FRAME2) s.frame = (s.frame & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); + if (bits & U_MODEL2) s.modelindex = (s.modelindex & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); + if (bits & U_VIEWMODEL) s.flags |= RENDER_VIEWMODEL; + if (bits & U_EXTERIORMODEL) s.flags |= RENDER_EXTERIORMODEL; + + // LordHavoc: to allow playback of the Nehahra movie + if (cls.protocol == PROTOCOL_NEHAHRAMOVIE && (bits & U_EXTEND1)) + { + // LordHavoc: evil format + int i = (int)MSG_ReadFloat(&cl_message); + int j = (int)(MSG_ReadFloat(&cl_message) * 255.0f); + if (i == 2) + { + i = (int)MSG_ReadFloat(&cl_message); + if (i) + s.effects |= EF_FULLBRIGHT; + } + if (j < 0) + s.alpha = 0; + else if (j == 0 || j >= 255) + s.alpha = 255; + else + s.alpha = j; + } + + ent->state_previous = ent->state_current; + ent->state_current = s; + if (ent->state_current.active == ACTIVE_NETWORK) + { + CL_MoveLerpEntityStates(ent); + cl.entities_active[ent->state_current.number] = true; + } + + if (cl_message.badread) + Host_Error("EntityFrameQuake_ReadEntity: read error"); +} + +void EntityFrameQuake_ISeeDeadEntities(void) +{ + int num, lastentity; + if (cl.lastquakeentity == 0) + return; + lastentity = cl.lastquakeentity; + cl.lastquakeentity = 0; + for (num = 0;num <= lastentity;num++) + { + if (cl.isquakeentity[num]) + { + if (cl.entities_active[num] && cl.entities[num].state_current.time == cl.mtime[0]) + { + cl.isquakeentity[num] = true; + cl.lastquakeentity = num; + } + else + { + cl.isquakeentity[num] = false; + cl.entities_active[num] = ACTIVE_NOT; + cl.entities[num].state_current = defaultstate; + cl.entities[num].state_current.number = num; + } + } + } +} + +// NOTE: this only works with DP5 protocol and upwards. For lower protocols +// (including QUAKE), no packet loss handling for CSQC is done, which makes +// CSQC basically useless. +// Always use the DP5 protocol, or a higher one, when using CSQC entities. +static void EntityFrameCSQC_LostAllFrames(client_t *client) +{ + prvm_prog_t *prog = SVVM_prog; + // mark ALL csqc entities as requiring a FULL resend! + // I know this is a bad workaround, but better than nothing. + int i, n; + prvm_edict_t *ed; + + n = client->csqcnumedicts; + for(i = 0; i < n; ++i) + { + if(client->csqcentityglobalhistory[i]) + { + ed = prog->edicts + i; + if (PRVM_serveredictfunction(ed, SendEntity)) + client->csqcentitysendflags[i] |= 0xFFFFFF; // FULL RESEND + else // if it was ever sent to that client as a CSQC entity + { + client->csqcentityscope[i] = 1; // REMOVE + client->csqcentitysendflags[i] |= 0xFFFFFF; + } + } + } +} +void EntityFrameCSQC_LostFrame(client_t *client, int framenum) +{ + // marks a frame as lost + int i, j; + qboolean valid; + int ringfirst, ringlast; + static int recoversendflags[MAX_EDICTS]; // client only + csqcentityframedb_t *d; + + if(client->csqcentityframe_lastreset < 0) + return; + if(framenum < client->csqcentityframe_lastreset) + return; // no action required, as we resent that data anyway + + // is our frame out of history? + ringfirst = client->csqcentityframehistory_next; // oldest entry + ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry + + valid = false; + + for(j = 0; j < NUM_CSQCENTITYDB_FRAMES; ++j) + { + d = &client->csqcentityframehistory[(ringfirst + j) % NUM_CSQCENTITYDB_FRAMES]; + if(d->framenum < 0) + continue; + if(d->framenum == framenum) + break; + else if(d->framenum < framenum) + valid = true; + } + if(j == NUM_CSQCENTITYDB_FRAMES) + { + if(valid) // got beaten, i.e. there is a frame < framenum + { + // a non-csqc frame got lost... great + return; + } + else + { + // a too old frame got lost... sorry, cannot handle this + Con_DPrintf("CSQC entity DB: lost a frame too early to do any handling (resending ALL)...\n"); + Con_DPrintf("Lost frame = %d\n", framenum); + Con_DPrintf("Entity DB = %d to %d\n", client->csqcentityframehistory[ringfirst].framenum, client->csqcentityframehistory[ringlast].framenum); + EntityFrameCSQC_LostAllFrames(client); + client->csqcentityframe_lastreset = -1; + } + return; + } + + // so j is the frame that got lost + // ringlast is the frame that we have to go to + ringfirst = (ringfirst + j) % NUM_CSQCENTITYDB_FRAMES; + if(ringlast < ringfirst) + ringlast += NUM_CSQCENTITYDB_FRAMES; + + memset(recoversendflags, 0, sizeof(recoversendflags)); + + for(j = ringfirst; j <= ringlast; ++j) + { + d = &client->csqcentityframehistory[j % NUM_CSQCENTITYDB_FRAMES]; + if(d->framenum < 0) + { + // deleted frame + } + else if(d->framenum < framenum) + { + // a frame in the past... should never happen + Con_Printf("CSQC entity DB encountered a frame from the past when recovering from PL...?\n"); + } + else if(d->framenum == framenum) + { + // handling the actually lost frame now + for(i = 0; i < d->num; ++i) + { + int sf = d->sendflags[i]; + int ent = d->entno[i]; + if(sf < 0) // remove + recoversendflags[ent] |= -1; // all bits, including sign + else if(sf > 0) + recoversendflags[ent] |= sf; + } + } + else + { + // handling the frames that followed it now + for(i = 0; i < d->num; ++i) + { + int sf = d->sendflags[i]; + int ent = d->entno[i]; + if(sf < 0) // remove + { + recoversendflags[ent] = 0; // no need to update, we got a more recent remove (and will fix it THEN) + break; // no flags left to remove... + } + else if(sf > 0) + recoversendflags[ent] &= ~sf; // no need to update these bits, we already got them later + } + } + } + + for(i = 0; i < client->csqcnumedicts; ++i) + { + if(recoversendflags[i] < 0) + { + // a remove got lost, then either send a remove or - if it was + // recreated later - a FULL update to make totally sure + client->csqcentityscope[i] = 1; + client->csqcentitysendflags[i] = 0xFFFFFF; + } + else + client->csqcentitysendflags[i] |= recoversendflags[i]; + } +} +static int EntityFrameCSQC_AllocFrame(client_t *client, int framenum) +{ + int ringfirst = client->csqcentityframehistory_next; // oldest entry + client->csqcentityframehistory_next += 1; + client->csqcentityframehistory_next %= NUM_CSQCENTITYDB_FRAMES; + client->csqcentityframehistory[ringfirst].framenum = framenum; + client->csqcentityframehistory[ringfirst].num = 0; + return ringfirst; +} +static void EntityFrameCSQC_DeallocFrame(client_t *client, int framenum) +{ + int ringfirst = client->csqcentityframehistory_next; // oldest entry + int ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry + if(framenum == client->csqcentityframehistory[ringlast].framenum) + { + client->csqcentityframehistory[ringlast].framenum = -1; + client->csqcentityframehistory[ringlast].num = 0; + client->csqcentityframehistory_next = ringlast; + } + else + Con_Printf("Trying to dealloc the wrong entity frame\n"); +} + +//[515]: we use only one array per-client for SendEntity feature +// TODO: add some handling for entity send priorities, to better deal with huge +// amounts of csqc networked entities +qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum) +{ + prvm_prog_t *prog = SVVM_prog; + int num, number, end, sendflags; + qboolean sectionstarted = false; + const unsigned short *n; + prvm_edict_t *ed; + client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber; + int dbframe = EntityFrameCSQC_AllocFrame(client, framenum); + csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe]; + + if(client->csqcentityframe_lastreset < 0) + client->csqcentityframe_lastreset = framenum; + + maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!) + + // make sure there is enough room to store the svc_csqcentities byte, + // the terminator (0x0000) and at least one entity update + if (msg->cursize + 32 >= maxsize) + return false; + + if (client->csqcnumedicts < prog->num_edicts) + client->csqcnumedicts = prog->num_edicts; + + number = 1; + for (num = 0, n = numbers;num < numnumbers;num++, n++) + { + end = *n; + for (;number < end;number++) + { + if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + } + ed = prog->edicts + number; + if (PRVM_serveredictfunction(ed, SendEntity)) + client->csqcentityscope[number] = 2; + else if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + number++; + } + end = client->csqcnumedicts; + for (;number < end;number++) + { + if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + } + + /* + // mark all scope entities as remove + for (number = 1;number < client->csqcnumedicts;number++) + if (client->csqcentityscope[number]) + client->csqcentityscope[number] = 1; + // keep visible entities + for (i = 0, n = numbers;i < numnumbers;i++, n++) + { + number = *n; + ed = prog->edicts + number; + if (PRVM_serveredictfunction(ed, SendEntity)) + client->csqcentityscope[number] = 2; + } + */ + + // now try to emit the entity updates + // (FIXME: prioritize by distance?) + end = client->csqcnumedicts; + for (number = 1;number < end;number++) + { + if (!client->csqcentityscope[number]) + continue; + sendflags = client->csqcentitysendflags[number]; + if (!sendflags) + continue; + if(db->num >= NUM_CSQCENTITIES_PER_FRAME) + break; + ed = prog->edicts + number; + // entity scope is either update (2) or remove (1) + if (client->csqcentityscope[number] == 1) + { + // write a remove message + // first write the message identifier if needed + if(!sectionstarted) + { + sectionstarted = 1; + MSG_WriteByte(msg, svc_csqcentities); + } + // write the remove message + { + ENTITYSIZEPROFILING_START(msg, number); + MSG_WriteShort(msg, (unsigned short)number | 0x8000); + client->csqcentityscope[number] = 0; + client->csqcentitysendflags[number] = 0xFFFFFF; // resend completely if it becomes active again + db->entno[db->num] = number; + db->sendflags[db->num] = -1; + db->num += 1; + client->csqcentityglobalhistory[number] = 1; + ENTITYSIZEPROFILING_END(msg, number); + } + if (msg->cursize + 17 >= maxsize) + break; + } + else + { + // write an update + // save the cursize value in case we overflow and have to rollback + int oldcursize = msg->cursize; + client->csqcentityscope[number] = 1; + if (PRVM_serveredictfunction(ed, SendEntity)) + { + if(!sectionstarted) + MSG_WriteByte(msg, svc_csqcentities); + { + ENTITYSIZEPROFILING_START(msg, number); + MSG_WriteShort(msg, number); + msg->allowoverflow = true; + PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber; + PRVM_G_FLOAT(OFS_PARM1) = sendflags; + PRVM_serverglobaledict(self) = number; + prog->ExecuteProgram(prog, PRVM_serveredictfunction(ed, SendEntity), "Null SendEntity\n"); + msg->allowoverflow = false; + if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize) + { + // an update has been successfully written + client->csqcentitysendflags[number] = 0; + db->entno[db->num] = number; + db->sendflags[db->num] = sendflags; + db->num += 1; + client->csqcentityglobalhistory[number] = 1; + // and take note that we have begun the svc_csqcentities + // section of the packet + sectionstarted = 1; + ENTITYSIZEPROFILING_END(msg, number); + if (msg->cursize + 17 >= maxsize) + break; + continue; + } + } + } + // self.SendEntity returned false (or does not exist) or the + // update was too big for this packet - rollback the buffer to its + // state before the writes occurred, we'll try again next frame + msg->cursize = oldcursize; + msg->overflowed = false; + } + } + if (sectionstarted) + { + // write index 0 to end the update (0 is never used by real entities) + MSG_WriteShort(msg, 0); + } + + if(db->num == 0) + // if no single ent got added, remove the frame from the DB again, to allow + // for a larger history + EntityFrameCSQC_DeallocFrame(client, framenum); + + return sectionstarted; +} + +void Protocol_UpdateClientStats(const int *stats) +{ + int i; + // update the stats array and set deltabits for any changed stats + for (i = 0;i < MAX_CL_STATS;i++) + { + if (host_client->stats[i] != stats[i]) + { + host_client->statsdeltabits[i >> 3] |= 1 << (i & 7); + host_client->stats[i] = stats[i]; + } + } +} + +// only a few stats are within the 32 stat limit of Quake, and most of them +// are sent every frame in svc_clientdata messages, so we only send the +// remaining ones here +static const int sendquakestats[] = +{ +// quake did not send these secrets/monsters stats in this way, but doing so +// allows a mod to increase STAT_TOTALMONSTERS during the game, and ensures +// that STAT_SECRETS and STAT_MONSTERS are always correct (even if a client +// didn't receive an svc_foundsecret or svc_killedmonster), which may be most +// valuable if randomly seeking around in a demo +STAT_TOTALSECRETS, // never changes during game +STAT_TOTALMONSTERS, // changes in some mods +STAT_SECRETS, // this makes svc_foundsecret unnecessary +STAT_MONSTERS, // this makes svc_killedmonster unnecessary +STAT_VIEWHEIGHT, // sent just for FTEQW clients +STAT_VIEWZOOM, // this rarely changes +-1, +}; + +void Protocol_WriteStatsReliable(void) +{ + int i, j; + if (!host_client->netconnection) + return; + // detect changes in stats and write reliable messages + // this only deals with 32 stats because the older protocols which use + // this function can only cope with 32 stats, + // they also do not support svc_updatestatubyte which was introduced in + // DP6 protocol (except for QW) + for (j = 0;sendquakestats[j] >= 0;j++) + { + i = sendquakestats[j]; + // check if this bit is set + if (host_client->statsdeltabits[i >> 3] & (1 << (i & 7))) + { + host_client->statsdeltabits[i >> 3] -= (1 << (i & 7)); + // send the stat as a byte if possible + if (sv.protocol == PROTOCOL_QUAKEWORLD) + { + if (host_client->stats[i] >= 0 && host_client->stats[i] < 256) + { + MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatestat); + MSG_WriteByte(&host_client->netconnection->message, i); + MSG_WriteByte(&host_client->netconnection->message, host_client->stats[i]); + } + else + { + MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatestatlong); + MSG_WriteByte(&host_client->netconnection->message, i); + MSG_WriteLong(&host_client->netconnection->message, host_client->stats[i]); + } + } + else + { + // this could make use of svc_updatestatubyte in DP6 and later + // protocols but those protocols do not use this function + MSG_WriteByte(&host_client->netconnection->message, svc_updatestat); + MSG_WriteByte(&host_client->netconnection->message, i); + MSG_WriteLong(&host_client->netconnection->message, host_client->stats[i]); + } + } + } +} + + +qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states) +{ + prvm_prog_t *prog = SVVM_prog; + const entity_state_t *s; + entity_state_t baseline; + int i, bits; + sizebuf_t buf; + unsigned char data[128]; + qboolean success = false; + + // prepare the buffer + memset(&buf, 0, sizeof(buf)); + buf.data = data; + buf.maxsize = sizeof(data); + + for (i = 0;i < numstates;i++) + { + ENTITYSIZEPROFILING_START(msg, states[i]->number); + s = states[i]; + if(PRVM_serveredictfunction((&prog->edicts[s->number]), SendEntity)) + continue; + + // prepare the buffer + SZ_Clear(&buf); + +// send an update + bits = 0; + if (s->number >= 256) + bits |= U_LONGENTITY; + if (s->flags & RENDER_STEP) + bits |= U_STEP; + if (s->flags & RENDER_VIEWMODEL) + bits |= U_VIEWMODEL; + if (s->flags & RENDER_GLOWTRAIL) + bits |= U_GLOWTRAIL; + if (s->flags & RENDER_EXTERIORMODEL) + bits |= U_EXTERIORMODEL; + + // LordHavoc: old stuff, but rewritten to have more exact tolerances + baseline = prog->edicts[s->number].priv.server->baseline; + if (baseline.origin[0] != s->origin[0]) + bits |= U_ORIGIN1; + if (baseline.origin[1] != s->origin[1]) + bits |= U_ORIGIN2; + if (baseline.origin[2] != s->origin[2]) + bits |= U_ORIGIN3; + if (baseline.angles[0] != s->angles[0]) + bits |= U_ANGLE1; + if (baseline.angles[1] != s->angles[1]) + bits |= U_ANGLE2; + if (baseline.angles[2] != s->angles[2]) + bits |= U_ANGLE3; + if (baseline.colormap != s->colormap) + bits |= U_COLORMAP; + if (baseline.skin != s->skin) + bits |= U_SKIN; + if (baseline.frame != s->frame) + { + bits |= U_FRAME; + if (s->frame & 0xFF00) + bits |= U_FRAME2; + } + if (baseline.effects != s->effects) + { + bits |= U_EFFECTS; + if (s->effects & 0xFF00) + bits |= U_EFFECTS2; + } + if (baseline.modelindex != s->modelindex) + { + bits |= U_MODEL; + if ((s->modelindex & 0xFF00) && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3) + bits |= U_MODEL2; + } + if (baseline.alpha != s->alpha) + bits |= U_ALPHA; + if (baseline.scale != s->scale) + bits |= U_SCALE; + if (baseline.glowsize != s->glowsize) + bits |= U_GLOWSIZE; + if (baseline.glowcolor != s->glowcolor) + bits |= U_GLOWCOLOR; + if (!VectorCompare(baseline.colormod, s->colormod)) + bits |= U_COLORMOD; + + // if extensions are disabled, clear the relevant update flags + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_NEHAHRAMOVIE) + bits &= 0x7FFF; + if (sv.protocol == PROTOCOL_NEHAHRAMOVIE) + if (s->alpha != 255 || s->effects & EF_FULLBRIGHT) + bits |= U_EXTEND1; + + // write the message + if (bits >= 16777216) + bits |= U_EXTEND2; + if (bits >= 65536) + bits |= U_EXTEND1; + if (bits >= 256) + bits |= U_MOREBITS; + bits |= U_SIGNAL; + + MSG_WriteByte (&buf, bits); + if (bits & U_MOREBITS) MSG_WriteByte(&buf, bits>>8); + if (sv.protocol != PROTOCOL_NEHAHRAMOVIE) + { + if (bits & U_EXTEND1) MSG_WriteByte(&buf, bits>>16); + if (bits & U_EXTEND2) MSG_WriteByte(&buf, bits>>24); + } + if (bits & U_LONGENTITY) MSG_WriteShort(&buf, s->number); + else MSG_WriteByte(&buf, s->number); + + if (bits & U_MODEL) + { + if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + MSG_WriteShort(&buf, s->modelindex); + else + MSG_WriteByte(&buf, s->modelindex); + } + if (bits & U_FRAME) MSG_WriteByte(&buf, s->frame); + if (bits & U_COLORMAP) MSG_WriteByte(&buf, s->colormap); + if (bits & U_SKIN) MSG_WriteByte(&buf, s->skin); + if (bits & U_EFFECTS) MSG_WriteByte(&buf, s->effects); + if (bits & U_ORIGIN1) MSG_WriteCoord(&buf, s->origin[0], sv.protocol); + if (bits & U_ANGLE1) MSG_WriteAngle(&buf, s->angles[0], sv.protocol); + if (bits & U_ORIGIN2) MSG_WriteCoord(&buf, s->origin[1], sv.protocol); + if (bits & U_ANGLE2) MSG_WriteAngle(&buf, s->angles[1], sv.protocol); + if (bits & U_ORIGIN3) MSG_WriteCoord(&buf, s->origin[2], sv.protocol); + if (bits & U_ANGLE3) MSG_WriteAngle(&buf, s->angles[2], sv.protocol); + if (bits & U_ALPHA) MSG_WriteByte(&buf, s->alpha); + if (bits & U_SCALE) MSG_WriteByte(&buf, s->scale); + if (bits & U_EFFECTS2) MSG_WriteByte(&buf, s->effects >> 8); + if (bits & U_GLOWSIZE) MSG_WriteByte(&buf, s->glowsize); + if (bits & U_GLOWCOLOR) MSG_WriteByte(&buf, s->glowcolor); + if (bits & U_COLORMOD) {int c = ((int)bound(0, s->colormod[0] * (7.0f / 32.0f), 7) << 5) | ((int)bound(0, s->colormod[1] * (7.0f / 32.0f), 7) << 2) | ((int)bound(0, s->colormod[2] * (3.0f / 32.0f), 3) << 0);MSG_WriteByte(&buf, c);} + if (bits & U_FRAME2) MSG_WriteByte(&buf, s->frame >> 8); + if (bits & U_MODEL2) MSG_WriteByte(&buf, s->modelindex >> 8); + + // the nasty protocol + if ((bits & U_EXTEND1) && sv.protocol == PROTOCOL_NEHAHRAMOVIE) + { + if (s->effects & EF_FULLBRIGHT) + { + MSG_WriteFloat(&buf, 2); // QSG protocol version + MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha + MSG_WriteFloat(&buf, 1); // fullbright + } + else + { + MSG_WriteFloat(&buf, 1); // QSG protocol version + MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha + } + } + + // if the commit is full, we're done this frame + if (msg->cursize + buf.cursize > maxsize) + { + // next frame we will continue where we left off + break; + } + // write the message to the packet + SZ_Write(msg, buf.data, buf.cursize); + success = true; + ENTITYSIZEPROFILING_END(msg, s->number); + } + return success; +} + +int EntityState_DeltaBits(const entity_state_t *o, const entity_state_t *n) +{ + unsigned int bits; + // if o is not active, delta from default + if (o->active != ACTIVE_NETWORK) + o = &defaultstate; + bits = 0; + if (fabs(n->origin[0] - o->origin[0]) > (1.0f / 256.0f)) + bits |= E_ORIGIN1; + if (fabs(n->origin[1] - o->origin[1]) > (1.0f / 256.0f)) + bits |= E_ORIGIN2; + if (fabs(n->origin[2] - o->origin[2]) > (1.0f / 256.0f)) + bits |= E_ORIGIN3; + if ((unsigned char) (n->angles[0] * (256.0f / 360.0f)) != (unsigned char) (o->angles[0] * (256.0f / 360.0f))) + bits |= E_ANGLE1; + if ((unsigned char) (n->angles[1] * (256.0f / 360.0f)) != (unsigned char) (o->angles[1] * (256.0f / 360.0f))) + bits |= E_ANGLE2; + if ((unsigned char) (n->angles[2] * (256.0f / 360.0f)) != (unsigned char) (o->angles[2] * (256.0f / 360.0f))) + bits |= E_ANGLE3; + if ((n->modelindex ^ o->modelindex) & 0x00FF) + bits |= E_MODEL1; + if ((n->modelindex ^ o->modelindex) & 0xFF00) + bits |= E_MODEL2; + if ((n->frame ^ o->frame) & 0x00FF) + bits |= E_FRAME1; + if ((n->frame ^ o->frame) & 0xFF00) + bits |= E_FRAME2; + if ((n->effects ^ o->effects) & 0x00FF) + bits |= E_EFFECTS1; + if ((n->effects ^ o->effects) & 0xFF00) + bits |= E_EFFECTS2; + if (n->colormap != o->colormap) + bits |= E_COLORMAP; + if (n->skin != o->skin) + bits |= E_SKIN; + if (n->alpha != o->alpha) + bits |= E_ALPHA; + if (n->scale != o->scale) + bits |= E_SCALE; + if (n->glowsize != o->glowsize) + bits |= E_GLOWSIZE; + if (n->glowcolor != o->glowcolor) + bits |= E_GLOWCOLOR; + if (n->flags != o->flags) + bits |= E_FLAGS; + if (n->tagindex != o->tagindex || n->tagentity != o->tagentity) + bits |= E_TAGATTACHMENT; + if (n->light[0] != o->light[0] || n->light[1] != o->light[1] || n->light[2] != o->light[2] || n->light[3] != o->light[3]) + bits |= E_LIGHT; + if (n->lightstyle != o->lightstyle) + bits |= E_LIGHTSTYLE; + if (n->lightpflags != o->lightpflags) + bits |= E_LIGHTPFLAGS; + + if (bits) + { + if (bits & 0xFF000000) + bits |= 0x00800000; + if (bits & 0x00FF0000) + bits |= 0x00008000; + if (bits & 0x0000FF00) + bits |= 0x00000080; + } + return bits; +} + +void EntityState_WriteExtendBits(sizebuf_t *msg, unsigned int bits) +{ + MSG_WriteByte(msg, bits & 0xFF); + if (bits & 0x00000080) + { + MSG_WriteByte(msg, (bits >> 8) & 0xFF); + if (bits & 0x00008000) + { + MSG_WriteByte(msg, (bits >> 16) & 0xFF); + if (bits & 0x00800000) + MSG_WriteByte(msg, (bits >> 24) & 0xFF); + } + } +} + +void EntityState_WriteFields(const entity_state_t *ent, sizebuf_t *msg, unsigned int bits) +{ + if (sv.protocol == PROTOCOL_DARKPLACES2) + { + if (bits & E_ORIGIN1) + MSG_WriteCoord16i(msg, ent->origin[0]); + if (bits & E_ORIGIN2) + MSG_WriteCoord16i(msg, ent->origin[1]); + if (bits & E_ORIGIN3) + MSG_WriteCoord16i(msg, ent->origin[2]); + } + else + { + // LordHavoc: have to write flags first, as they can modify protocol + if (bits & E_FLAGS) + MSG_WriteByte(msg, ent->flags); + if (ent->flags & RENDER_LOWPRECISION) + { + if (bits & E_ORIGIN1) + MSG_WriteCoord16i(msg, ent->origin[0]); + if (bits & E_ORIGIN2) + MSG_WriteCoord16i(msg, ent->origin[1]); + if (bits & E_ORIGIN3) + MSG_WriteCoord16i(msg, ent->origin[2]); + } + else + { + if (bits & E_ORIGIN1) + MSG_WriteCoord32f(msg, ent->origin[0]); + if (bits & E_ORIGIN2) + MSG_WriteCoord32f(msg, ent->origin[1]); + if (bits & E_ORIGIN3) + MSG_WriteCoord32f(msg, ent->origin[2]); + } + } + if ((sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4) && (ent->flags & RENDER_LOWPRECISION)) + { + if (bits & E_ANGLE1) + MSG_WriteAngle8i(msg, ent->angles[0]); + if (bits & E_ANGLE2) + MSG_WriteAngle8i(msg, ent->angles[1]); + if (bits & E_ANGLE3) + MSG_WriteAngle8i(msg, ent->angles[2]); + } + else + { + if (bits & E_ANGLE1) + MSG_WriteAngle16i(msg, ent->angles[0]); + if (bits & E_ANGLE2) + MSG_WriteAngle16i(msg, ent->angles[1]); + if (bits & E_ANGLE3) + MSG_WriteAngle16i(msg, ent->angles[2]); + } + if (bits & E_MODEL1) + MSG_WriteByte(msg, ent->modelindex & 0xFF); + if (bits & E_MODEL2) + MSG_WriteByte(msg, (ent->modelindex >> 8) & 0xFF); + if (bits & E_FRAME1) + MSG_WriteByte(msg, ent->frame & 0xFF); + if (bits & E_FRAME2) + MSG_WriteByte(msg, (ent->frame >> 8) & 0xFF); + if (bits & E_EFFECTS1) + MSG_WriteByte(msg, ent->effects & 0xFF); + if (bits & E_EFFECTS2) + MSG_WriteByte(msg, (ent->effects >> 8) & 0xFF); + if (bits & E_COLORMAP) + MSG_WriteByte(msg, ent->colormap); + if (bits & E_SKIN) + MSG_WriteByte(msg, ent->skin); + if (bits & E_ALPHA) + MSG_WriteByte(msg, ent->alpha); + if (bits & E_SCALE) + MSG_WriteByte(msg, ent->scale); + if (bits & E_GLOWSIZE) + MSG_WriteByte(msg, ent->glowsize); + if (bits & E_GLOWCOLOR) + MSG_WriteByte(msg, ent->glowcolor); + if (sv.protocol == PROTOCOL_DARKPLACES2) + if (bits & E_FLAGS) + MSG_WriteByte(msg, ent->flags); + if (bits & E_TAGATTACHMENT) + { + MSG_WriteShort(msg, ent->tagentity); + MSG_WriteByte(msg, ent->tagindex); + } + if (bits & E_LIGHT) + { + MSG_WriteShort(msg, ent->light[0]); + MSG_WriteShort(msg, ent->light[1]); + MSG_WriteShort(msg, ent->light[2]); + MSG_WriteShort(msg, ent->light[3]); + } + if (bits & E_LIGHTSTYLE) + MSG_WriteByte(msg, ent->lightstyle); + if (bits & E_LIGHTPFLAGS) + MSG_WriteByte(msg, ent->lightpflags); +} + +void EntityState_WriteUpdate(const entity_state_t *ent, sizebuf_t *msg, const entity_state_t *delta) +{ + prvm_prog_t *prog = SVVM_prog; + unsigned int bits; + ENTITYSIZEPROFILING_START(msg, ent->number); + if (ent->active == ACTIVE_NETWORK) + { + // entity is active, check for changes from the delta + if ((bits = EntityState_DeltaBits(delta, ent))) + { + // write the update number, bits, and fields + MSG_WriteShort(msg, ent->number); + EntityState_WriteExtendBits(msg, bits); + EntityState_WriteFields(ent, msg, bits); + } + } + else + { + // entity is inactive, check if the delta was active + if (delta->active == ACTIVE_NETWORK) + { + // write the remove number + MSG_WriteShort(msg, ent->number | 0x8000); + } + } + ENTITYSIZEPROFILING_END(msg, ent->number); +} + +int EntityState_ReadExtendBits(void) +{ + unsigned int bits; + bits = MSG_ReadByte(&cl_message); + if (bits & 0x00000080) + { + bits |= MSG_ReadByte(&cl_message) << 8; + if (bits & 0x00008000) + { + bits |= MSG_ReadByte(&cl_message) << 16; + if (bits & 0x00800000) + bits |= MSG_ReadByte(&cl_message) << 24; + } + } + return bits; +} + +void EntityState_ReadFields(entity_state_t *e, unsigned int bits) +{ + if (cls.protocol == PROTOCOL_DARKPLACES2) + { + if (bits & E_ORIGIN1) + e->origin[0] = MSG_ReadCoord16i(&cl_message); + if (bits & E_ORIGIN2) + e->origin[1] = MSG_ReadCoord16i(&cl_message); + if (bits & E_ORIGIN3) + e->origin[2] = MSG_ReadCoord16i(&cl_message); + } + else + { + if (bits & E_FLAGS) + e->flags = MSG_ReadByte(&cl_message); + if (e->flags & RENDER_LOWPRECISION) + { + if (bits & E_ORIGIN1) + e->origin[0] = MSG_ReadCoord16i(&cl_message); + if (bits & E_ORIGIN2) + e->origin[1] = MSG_ReadCoord16i(&cl_message); + if (bits & E_ORIGIN3) + e->origin[2] = MSG_ReadCoord16i(&cl_message); + } + else + { + if (bits & E_ORIGIN1) + e->origin[0] = MSG_ReadCoord32f(&cl_message); + if (bits & E_ORIGIN2) + e->origin[1] = MSG_ReadCoord32f(&cl_message); + if (bits & E_ORIGIN3) + e->origin[2] = MSG_ReadCoord32f(&cl_message); + } + } + if ((cls.protocol == PROTOCOL_DARKPLACES5 || cls.protocol == PROTOCOL_DARKPLACES6) && !(e->flags & RENDER_LOWPRECISION)) + { + if (bits & E_ANGLE1) + e->angles[0] = MSG_ReadAngle16i(&cl_message); + if (bits & E_ANGLE2) + e->angles[1] = MSG_ReadAngle16i(&cl_message); + if (bits & E_ANGLE3) + e->angles[2] = MSG_ReadAngle16i(&cl_message); + } + else + { + if (bits & E_ANGLE1) + e->angles[0] = MSG_ReadAngle8i(&cl_message); + if (bits & E_ANGLE2) + e->angles[1] = MSG_ReadAngle8i(&cl_message); + if (bits & E_ANGLE3) + e->angles[2] = MSG_ReadAngle8i(&cl_message); + } + if (bits & E_MODEL1) + e->modelindex = (e->modelindex & 0xFF00) | (unsigned int) MSG_ReadByte(&cl_message); + if (bits & E_MODEL2) + e->modelindex = (e->modelindex & 0x00FF) | ((unsigned int) MSG_ReadByte(&cl_message) << 8); + if (bits & E_FRAME1) + e->frame = (e->frame & 0xFF00) | (unsigned int) MSG_ReadByte(&cl_message); + if (bits & E_FRAME2) + e->frame = (e->frame & 0x00FF) | ((unsigned int) MSG_ReadByte(&cl_message) << 8); + if (bits & E_EFFECTS1) + e->effects = (e->effects & 0xFF00) | (unsigned int) MSG_ReadByte(&cl_message); + if (bits & E_EFFECTS2) + e->effects = (e->effects & 0x00FF) | ((unsigned int) MSG_ReadByte(&cl_message) << 8); + if (bits & E_COLORMAP) + e->colormap = MSG_ReadByte(&cl_message); + if (bits & E_SKIN) + e->skin = MSG_ReadByte(&cl_message); + if (bits & E_ALPHA) + e->alpha = MSG_ReadByte(&cl_message); + if (bits & E_SCALE) + e->scale = MSG_ReadByte(&cl_message); + if (bits & E_GLOWSIZE) + e->glowsize = MSG_ReadByte(&cl_message); + if (bits & E_GLOWCOLOR) + e->glowcolor = MSG_ReadByte(&cl_message); + if (cls.protocol == PROTOCOL_DARKPLACES2) + if (bits & E_FLAGS) + e->flags = MSG_ReadByte(&cl_message); + if (bits & E_TAGATTACHMENT) + { + e->tagentity = (unsigned short) MSG_ReadShort(&cl_message); + e->tagindex = MSG_ReadByte(&cl_message); + } + if (bits & E_LIGHT) + { + e->light[0] = (unsigned short) MSG_ReadShort(&cl_message); + e->light[1] = (unsigned short) MSG_ReadShort(&cl_message); + e->light[2] = (unsigned short) MSG_ReadShort(&cl_message); + e->light[3] = (unsigned short) MSG_ReadShort(&cl_message); + } + if (bits & E_LIGHTSTYLE) + e->lightstyle = MSG_ReadByte(&cl_message); + if (bits & E_LIGHTPFLAGS) + e->lightpflags = MSG_ReadByte(&cl_message); + + if (developer_networkentities.integer >= 2) + { + Con_Printf("ReadFields e%i", e->number); + + if (bits & E_ORIGIN1) + Con_Printf(" E_ORIGIN1 %f", e->origin[0]); + if (bits & E_ORIGIN2) + Con_Printf(" E_ORIGIN2 %f", e->origin[1]); + if (bits & E_ORIGIN3) + Con_Printf(" E_ORIGIN3 %f", e->origin[2]); + if (bits & E_ANGLE1) + Con_Printf(" E_ANGLE1 %f", e->angles[0]); + if (bits & E_ANGLE2) + Con_Printf(" E_ANGLE2 %f", e->angles[1]); + if (bits & E_ANGLE3) + Con_Printf(" E_ANGLE3 %f", e->angles[2]); + if (bits & (E_MODEL1 | E_MODEL2)) + Con_Printf(" E_MODEL %i", e->modelindex); + + if (bits & (E_FRAME1 | E_FRAME2)) + Con_Printf(" E_FRAME %i", e->frame); + if (bits & (E_EFFECTS1 | E_EFFECTS2)) + Con_Printf(" E_EFFECTS %i", e->effects); + if (bits & E_ALPHA) + Con_Printf(" E_ALPHA %f", e->alpha / 255.0f); + if (bits & E_SCALE) + Con_Printf(" E_SCALE %f", e->scale / 16.0f); + if (bits & E_COLORMAP) + Con_Printf(" E_COLORMAP %i", e->colormap); + if (bits & E_SKIN) + Con_Printf(" E_SKIN %i", e->skin); + + if (bits & E_GLOWSIZE) + Con_Printf(" E_GLOWSIZE %i", e->glowsize * 4); + if (bits & E_GLOWCOLOR) + Con_Printf(" E_GLOWCOLOR %i", e->glowcolor); + + if (bits & E_LIGHT) + Con_Printf(" E_LIGHT %i:%i:%i:%i", e->light[0], e->light[1], e->light[2], e->light[3]); + if (bits & E_LIGHTPFLAGS) + Con_Printf(" E_LIGHTPFLAGS %i", e->lightpflags); + + if (bits & E_TAGATTACHMENT) + Con_Printf(" E_TAGATTACHMENT e%i:%i", e->tagentity, e->tagindex); + if (bits & E_LIGHTSTYLE) + Con_Printf(" E_LIGHTSTYLE %i", e->lightstyle); + Con_Print("\n"); + } +} + +// (client and server) allocates a new empty database +entityframe_database_t *EntityFrame_AllocDatabase(mempool_t *mempool) +{ + return (entityframe_database_t *)Mem_Alloc(mempool, sizeof(entityframe_database_t)); +} + +// (client and server) frees the database +void EntityFrame_FreeDatabase(entityframe_database_t *d) +{ + Mem_Free(d); +} + +// (server) clears the database to contain no frames (thus delta compression compresses against nothing) +void EntityFrame_ClearDatabase(entityframe_database_t *d) +{ + memset(d, 0, sizeof(*d)); +} + +// (server and client) removes frames older than 'frame' from database +void EntityFrame_AckFrame(entityframe_database_t *d, int frame) +{ + int i; + d->ackframenum = frame; + for (i = 0;i < d->numframes && d->frames[i].framenum < frame;i++); + // ignore outdated frame acks (out of order packets) + if (i == 0) + return; + d->numframes -= i; + // if some queue is left, slide it down to beginning of array + if (d->numframes) + memmove(&d->frames[0], &d->frames[i], sizeof(d->frames[0]) * d->numframes); +} + +// (server) clears frame, to prepare for adding entities +void EntityFrame_Clear(entity_frame_t *f, vec3_t eye, int framenum) +{ + f->time = 0; + f->framenum = framenum; + f->numentities = 0; + if (eye == NULL) + VectorClear(f->eye); + else + VectorCopy(eye, f->eye); +} + +// (server and client) reads a frame from the database +void EntityFrame_FetchFrame(entityframe_database_t *d, int framenum, entity_frame_t *f) +{ + int i, n; + EntityFrame_Clear(f, NULL, -1); + for (i = 0;i < d->numframes && d->frames[i].framenum < framenum;i++); + if (i < d->numframes && framenum == d->frames[i].framenum) + { + f->framenum = framenum; + f->numentities = d->frames[i].endentity - d->frames[i].firstentity; + n = MAX_ENTITY_DATABASE - (d->frames[i].firstentity % MAX_ENTITY_DATABASE); + if (n > f->numentities) + n = f->numentities; + memcpy(f->entitydata, d->entitydata + d->frames[i].firstentity % MAX_ENTITY_DATABASE, sizeof(*f->entitydata) * n); + if (f->numentities > n) + memcpy(f->entitydata + n, d->entitydata, sizeof(*f->entitydata) * (f->numentities - n)); + VectorCopy(d->eye, f->eye); + } +} + +// (client) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata) +{ + int n, e; + entity_frameinfo_t *info; + + VectorCopy(eye, d->eye); + + // figure out how many entity slots are used already + if (d->numframes) + { + n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity; + if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY) + { + // ran out of room, dump database + EntityFrame_ClearDatabase(d); + } + } + + info = &d->frames[d->numframes]; + info->framenum = framenum; + e = -1000; + // make sure we check the newly added frame as well, but we haven't incremented numframes yet + for (n = 0;n <= d->numframes;n++) + { + if (e >= d->frames[n].framenum) + { + if (e == framenum) + Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n"); + else + Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n"); + return; + } + e = d->frames[n].framenum; + } + // if database still has frames after that... + if (d->numframes) + info->firstentity = d->frames[d->numframes - 1].endentity; + else + info->firstentity = 0; + info->endentity = info->firstentity + numentities; + d->numframes++; + + n = info->firstentity % MAX_ENTITY_DATABASE; + e = MAX_ENTITY_DATABASE - n; + if (e > numentities) + e = numentities; + memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e); + if (numentities > e) + memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e)); +} + +// (server) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Server(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t **entitydata) +{ + int n, e; + entity_frameinfo_t *info; + + VectorCopy(eye, d->eye); + + // figure out how many entity slots are used already + if (d->numframes) + { + n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity; + if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY) + { + // ran out of room, dump database + EntityFrame_ClearDatabase(d); + } + } + + info = &d->frames[d->numframes]; + info->framenum = framenum; + e = -1000; + // make sure we check the newly added frame as well, but we haven't incremented numframes yet + for (n = 0;n <= d->numframes;n++) + { + if (e >= d->frames[n].framenum) + { + if (e == framenum) + Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n"); + else + Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n"); + return; + } + e = d->frames[n].framenum; + } + // if database still has frames after that... + if (d->numframes) + info->firstentity = d->frames[d->numframes - 1].endentity; + else + info->firstentity = 0; + info->endentity = info->firstentity + numentities; + d->numframes++; + + n = info->firstentity % MAX_ENTITY_DATABASE; + e = MAX_ENTITY_DATABASE - n; + if (e > numentities) + e = numentities; + memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e); + if (numentities > e) + memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e)); +} + +// (server) writes a frame to network stream +qboolean EntityFrame_WriteFrame(sizebuf_t *msg, int maxsize, entityframe_database_t *d, int numstates, const entity_state_t **states, int viewentnum) +{ + prvm_prog_t *prog = SVVM_prog; + int i, onum, number; + entity_frame_t *o = &d->deltaframe; + const entity_state_t *ent, *delta; + vec3_t eye; + + d->latestframenum++; + + VectorClear(eye); + for (i = 0;i < numstates;i++) + { + ent = states[i]; + if (ent->number == viewentnum) + { + VectorSet(eye, ent->origin[0], ent->origin[1], ent->origin[2] + 22); + break; + } + } + + EntityFrame_AddFrame_Server(d, eye, d->latestframenum, numstates, states); + + EntityFrame_FetchFrame(d, d->ackframenum, o); + + MSG_WriteByte (msg, svc_entities); + MSG_WriteLong (msg, o->framenum); + MSG_WriteLong (msg, d->latestframenum); + MSG_WriteFloat (msg, eye[0]); + MSG_WriteFloat (msg, eye[1]); + MSG_WriteFloat (msg, eye[2]); + + onum = 0; + for (i = 0;i < numstates;i++) + { + ent = states[i]; + number = ent->number; + + if (PRVM_serveredictfunction((&prog->edicts[number]), SendEntity)) + continue; + for (;onum < o->numentities && o->entitydata[onum].number < number;onum++) + { + // write remove message + MSG_WriteShort(msg, o->entitydata[onum].number | 0x8000); + } + if (onum < o->numentities && (o->entitydata[onum].number == number)) + { + // delta from previous frame + delta = o->entitydata + onum; + // advance to next entity in delta frame + onum++; + } + else + { + // delta from defaults + delta = &defaultstate; + } + EntityState_WriteUpdate(ent, msg, delta); + } + for (;onum < o->numentities;onum++) + { + // write remove message + MSG_WriteShort(msg, o->entitydata[onum].number | 0x8000); + } + MSG_WriteShort(msg, 0xFFFF); + + return true; +} + +// (client) reads a frame from network stream +void EntityFrame_CL_ReadFrame(void) +{ + int i, number, removed; + entity_frame_t *f, *delta; + entity_state_t *e, *old, *oldend; + entity_t *ent; + entityframe_database_t *d; + if (!cl.entitydatabase) + cl.entitydatabase = EntityFrame_AllocDatabase(cls.levelmempool); + d = cl.entitydatabase; + f = &d->framedata; + delta = &d->deltaframe; + + EntityFrame_Clear(f, NULL, -1); + + // read the frame header info + f->time = cl.mtime[0]; + number = MSG_ReadLong(&cl_message); + f->framenum = MSG_ReadLong(&cl_message); + CL_NewFrameReceived(f->framenum); + f->eye[0] = MSG_ReadFloat(&cl_message); + f->eye[1] = MSG_ReadFloat(&cl_message); + f->eye[2] = MSG_ReadFloat(&cl_message); + EntityFrame_AckFrame(d, number); + EntityFrame_FetchFrame(d, number, delta); + old = delta->entitydata; + oldend = old + delta->numentities; + // read entities until we hit the magic 0xFFFF end tag + while ((number = (unsigned short) MSG_ReadShort(&cl_message)) != 0xFFFF && !cl_message.badread) + { + if (cl_message.badread) + Host_Error("EntityFrame_Read: read error"); + removed = number & 0x8000; + number &= 0x7FFF; + if (number >= MAX_EDICTS) + Host_Error("EntityFrame_Read: number (%i) >= MAX_EDICTS (%i)", number, MAX_EDICTS); + + // seek to entity, while copying any skipped entities (assume unchanged) + while (old < oldend && old->number < number) + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + f->entitydata[f->numentities] = *old++; + f->entitydata[f->numentities++].time = cl.mtime[0]; + } + if (removed) + { + if (old < oldend && old->number == number) + old++; + else + Con_Printf("EntityFrame_Read: REMOVE on unused entity %i\n", number); + } + else + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + + // reserve this slot + e = f->entitydata + f->numentities++; + + if (old < oldend && old->number == number) + { + // delta from old entity + *e = *old++; + } + else + { + // delta from defaults + *e = defaultstate; + } + + if (cl.num_entities <= number) + { + cl.num_entities = number + 1; + if (number >= cl.max_entities) + CL_ExpandEntities(number); + } + cl.entities_active[number] = true; + e->active = ACTIVE_NETWORK; + e->time = cl.mtime[0]; + e->number = number; + EntityState_ReadFields(e, EntityState_ReadExtendBits()); + } + } + while (old < oldend) + { + if (f->numentities >= MAX_ENTITY_DATABASE) + Host_Error("EntityFrame_Read: entity list too big"); + f->entitydata[f->numentities] = *old++; + f->entitydata[f->numentities++].time = cl.mtime[0]; + } + EntityFrame_AddFrame_Client(d, f->eye, f->framenum, f->numentities, f->entitydata); + + memset(cl.entities_active, 0, cl.num_entities * sizeof(unsigned char)); + number = 1; + for (i = 0;i < f->numentities;i++) + { + for (;number < f->entitydata[i].number && number < cl.num_entities;number++) + { + if (cl.entities_active[number]) + { + cl.entities_active[number] = false; + cl.entities[number].state_current.active = ACTIVE_NOT; + } + } + if (number >= cl.num_entities) + break; + // update the entity + ent = &cl.entities[number]; + ent->state_previous = ent->state_current; + ent->state_current = f->entitydata[i]; + CL_MoveLerpEntityStates(ent); + // the entity lives again... + cl.entities_active[number] = true; + number++; + } + for (;number < cl.num_entities;number++) + { + if (cl.entities_active[number]) + { + cl.entities_active[number] = false; + cl.entities[number].state_current.active = ACTIVE_NOT; + } + } +} + + +// (client) returns the frame number of the most recent frame recieved +int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d) +{ + if (d->numframes) + return d->frames[d->numframes - 1].framenum; + else + return -1; +} + + + + + + +entity_state_t *EntityFrame4_GetReferenceEntity(entityframe4_database_t *d, int number) +{ + if (d->maxreferenceentities <= number) + { + int oldmax = d->maxreferenceentities; + entity_state_t *oldentity = d->referenceentity; + d->maxreferenceentities = (number + 15) & ~7; + d->referenceentity = (entity_state_t *)Mem_Alloc(d->mempool, d->maxreferenceentities * sizeof(*d->referenceentity)); + if (oldentity) + { + memcpy(d->referenceentity, oldentity, oldmax * sizeof(*d->referenceentity)); + Mem_Free(oldentity); + } + // clear the newly created entities + for (;oldmax < d->maxreferenceentities;oldmax++) + { + d->referenceentity[oldmax] = defaultstate; + d->referenceentity[oldmax].number = oldmax; + } + } + return d->referenceentity + number; +} + +void EntityFrame4_AddCommitEntity(entityframe4_database_t *d, const entity_state_t *s) +{ + // resize commit's entity list if full + if (d->currentcommit->maxentities <= d->currentcommit->numentities) + { + entity_state_t *oldentity = d->currentcommit->entity; + d->currentcommit->maxentities += 8; + d->currentcommit->entity = (entity_state_t *)Mem_Alloc(d->mempool, d->currentcommit->maxentities * sizeof(*d->currentcommit->entity)); + if (oldentity) + { + memcpy(d->currentcommit->entity, oldentity, d->currentcommit->numentities * sizeof(*d->currentcommit->entity)); + Mem_Free(oldentity); + } + } + d->currentcommit->entity[d->currentcommit->numentities++] = *s; +} + +entityframe4_database_t *EntityFrame4_AllocDatabase(mempool_t *pool) +{ + entityframe4_database_t *d; + d = (entityframe4_database_t *)Mem_Alloc(pool, sizeof(*d)); + d->mempool = pool; + EntityFrame4_ResetDatabase(d); + return d; +} + +void EntityFrame4_FreeDatabase(entityframe4_database_t *d) +{ + int i; + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (d->commit[i].entity) + Mem_Free(d->commit[i].entity); + if (d->referenceentity) + Mem_Free(d->referenceentity); + Mem_Free(d); +} + +void EntityFrame4_ResetDatabase(entityframe4_database_t *d) +{ + int i; + d->referenceframenum = -1; + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + d->commit[i].numentities = 0; + for (i = 0;i < d->maxreferenceentities;i++) + d->referenceentity[i] = defaultstate; +} + +int EntityFrame4_AckFrame(entityframe4_database_t *d, int framenum, int servermode) +{ + int i, j, found; + entity_database4_commit_t *commit; + if (framenum == -1) + { + // reset reference, but leave commits alone + d->referenceframenum = -1; + for (i = 0;i < d->maxreferenceentities;i++) + d->referenceentity[i] = defaultstate; + // if this is the server, remove commits + for (i = 0, commit = d->commit;i < MAX_ENTITY_HISTORY;i++, commit++) + commit->numentities = 0; + found = true; + } + else if (d->referenceframenum == framenum) + found = true; + else + { + found = false; + for (i = 0, commit = d->commit;i < MAX_ENTITY_HISTORY;i++, commit++) + { + if (commit->numentities && commit->framenum <= framenum) + { + if (commit->framenum == framenum) + { + found = true; + d->referenceframenum = framenum; + if (developer_networkentities.integer >= 3) + { + for (j = 0;j < commit->numentities;j++) + { + entity_state_t *s = EntityFrame4_GetReferenceEntity(d, commit->entity[j].number); + if (commit->entity[j].active != s->active) + { + if (commit->entity[j].active == ACTIVE_NETWORK) + Con_Printf("commit entity %i has become active (modelindex %i)\n", commit->entity[j].number, commit->entity[j].modelindex); + else + Con_Printf("commit entity %i has become inactive (modelindex %i)\n", commit->entity[j].number, commit->entity[j].modelindex); + } + *s = commit->entity[j]; + } + } + else + for (j = 0;j < commit->numentities;j++) + *EntityFrame4_GetReferenceEntity(d, commit->entity[j].number) = commit->entity[j]; + } + commit->numentities = 0; + } + } + } + if (developer_networkentities.integer >= 1) + { + Con_Printf("ack ref:%i database updated to: ref:%i commits:", framenum, d->referenceframenum); + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (d->commit[i].numentities) + Con_Printf(" %i", d->commit[i].framenum); + Con_Print("\n"); + } + return found; +} + +void EntityFrame4_CL_ReadFrame(void) +{ + int i, n, cnumber, referenceframenum, framenum, enumber, done, stopnumber, skip = false; + entity_state_t *s; + entityframe4_database_t *d; + if (!cl.entitydatabase4) + cl.entitydatabase4 = EntityFrame4_AllocDatabase(cls.levelmempool); + d = cl.entitydatabase4; + // read the number of the frame this refers to + referenceframenum = MSG_ReadLong(&cl_message); + // read the number of this frame + framenum = MSG_ReadLong(&cl_message); + CL_NewFrameReceived(framenum); + // read the start number + enumber = (unsigned short) MSG_ReadShort(&cl_message); + if (developer_networkentities.integer >= 10) + { + Con_Printf("recv svc_entities num:%i ref:%i database: ref:%i commits:", framenum, referenceframenum, d->referenceframenum); + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (d->commit[i].numentities) + Con_Printf(" %i", d->commit[i].framenum); + Con_Print("\n"); + } + if (!EntityFrame4_AckFrame(d, referenceframenum, false)) + { + Con_Print("EntityFrame4_CL_ReadFrame: reference frame invalid (VERY BAD ERROR), this update will be skipped\n"); + skip = true; + } + d->currentcommit = NULL; + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + { + if (!d->commit[i].numentities) + { + d->currentcommit = d->commit + i; + d->currentcommit->framenum = framenum; + d->currentcommit->numentities = 0; + } + } + if (d->currentcommit == NULL) + { + Con_Printf("EntityFrame4_CL_ReadFrame: error while decoding frame %i: database full, reading but not storing this update\n", framenum); + skip = true; + } + done = false; + while (!done && !cl_message.badread) + { + // read the number of the modified entity + // (gaps will be copied unmodified) + n = (unsigned short)MSG_ReadShort(&cl_message); + if (n == 0x8000) + { + // no more entities in this update, but we still need to copy the + // rest of the reference entities (final gap) + done = true; + // read end of range number, then process normally + n = (unsigned short)MSG_ReadShort(&cl_message); + } + // high bit means it's a remove message + cnumber = n & 0x7FFF; + // if this is a live entity we may need to expand the array + if (cl.num_entities <= cnumber && !(n & 0x8000)) + { + cl.num_entities = cnumber + 1; + if (cnumber >= cl.max_entities) + CL_ExpandEntities(cnumber); + } + // add one (the changed one) if not done + stopnumber = cnumber + !done; + // process entities in range from the last one to the changed one + for (;enumber < stopnumber;enumber++) + { + if (skip || enumber >= cl.num_entities) + { + if (enumber == cnumber && (n & 0x8000) == 0) + { + entity_state_t tempstate; + EntityState_ReadFields(&tempstate, EntityState_ReadExtendBits()); + } + continue; + } + // slide the current into the previous slot + cl.entities[enumber].state_previous = cl.entities[enumber].state_current; + // copy a new current from reference database + cl.entities[enumber].state_current = *EntityFrame4_GetReferenceEntity(d, enumber); + s = &cl.entities[enumber].state_current; + // if this is the one to modify, read more data... + if (enumber == cnumber) + { + if (n & 0x8000) + { + // simply removed + if (developer_networkentities.integer >= 2) + Con_Printf("entity %i: remove\n", enumber); + *s = defaultstate; + } + else + { + // read the changes + if (developer_networkentities.integer >= 2) + Con_Printf("entity %i: update\n", enumber); + s->active = ACTIVE_NETWORK; + EntityState_ReadFields(s, EntityState_ReadExtendBits()); + } + } + else if (developer_networkentities.integer >= 4) + Con_Printf("entity %i: copy\n", enumber); + // set the cl.entities_active flag + cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); + // set the update time + s->time = cl.mtime[0]; + // fix the number (it gets wiped occasionally by copying from defaultstate) + s->number = enumber; + // check if we need to update the lerp stuff + if (s->active == ACTIVE_NETWORK) + CL_MoveLerpEntityStates(&cl.entities[enumber]); + // add this to the commit entry whether it is modified or not + if (d->currentcommit) + EntityFrame4_AddCommitEntity(d, &cl.entities[enumber].state_current); + // print extra messages if desired + if (developer_networkentities.integer >= 2 && cl.entities[enumber].state_current.active != cl.entities[enumber].state_previous.active) + { + if (cl.entities[enumber].state_current.active == ACTIVE_NETWORK) + Con_Printf("entity #%i has become active\n", enumber); + else if (cl.entities[enumber].state_previous.active) + Con_Printf("entity #%i has become inactive\n", enumber); + } + } + } + d->currentcommit = NULL; + if (skip) + EntityFrame4_ResetDatabase(d); +} + +qboolean EntityFrame4_WriteFrame(sizebuf_t *msg, int maxsize, entityframe4_database_t *d, int numstates, const entity_state_t **states) +{ + prvm_prog_t *prog = SVVM_prog; + const entity_state_t *e, *s; + entity_state_t inactiveentitystate; + int i, n, startnumber; + sizebuf_t buf; + unsigned char data[128]; + + // if there isn't enough space to accomplish anything, skip it + if (msg->cursize + 24 > maxsize) + return false; + + // prepare the buffer + memset(&buf, 0, sizeof(buf)); + buf.data = data; + buf.maxsize = sizeof(data); + + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (!d->commit[i].numentities) + break; + // if commit buffer full, just don't bother writing an update this frame + if (i == MAX_ENTITY_HISTORY) + return false; + d->currentcommit = d->commit + i; + + // this state's number gets played around with later + inactiveentitystate = defaultstate; + + d->currentcommit->numentities = 0; + d->currentcommit->framenum = ++d->latestframenumber; + MSG_WriteByte(msg, svc_entities); + MSG_WriteLong(msg, d->referenceframenum); + MSG_WriteLong(msg, d->currentcommit->framenum); + if (developer_networkentities.integer >= 10) + { + Con_Printf("send svc_entities num:%i ref:%i (database: ref:%i commits:", d->currentcommit->framenum, d->referenceframenum, d->referenceframenum); + for (i = 0;i < MAX_ENTITY_HISTORY;i++) + if (d->commit[i].numentities) + Con_Printf(" %i", d->commit[i].framenum); + Con_Print(")\n"); + } + if (d->currententitynumber >= prog->max_edicts) + startnumber = 1; + else + startnumber = bound(1, d->currententitynumber, prog->max_edicts - 1); + MSG_WriteShort(msg, startnumber); + // reset currententitynumber so if the loop does not break it we will + // start at beginning next frame (if it does break, it will set it) + d->currententitynumber = 1; + for (i = 0, n = startnumber;n < prog->max_edicts;n++) + { + if (PRVM_serveredictfunction((&prog->edicts[n]), SendEntity)) + continue; + // find the old state to delta from + e = EntityFrame4_GetReferenceEntity(d, n); + // prepare the buffer + SZ_Clear(&buf); + // entity exists, build an update (if empty there is no change) + // find the state in the list + for (;i < numstates && states[i]->number < n;i++); + // make the message + s = states[i]; + if (s->number == n) + { + // build the update + EntityState_WriteUpdate(s, &buf, e); + } + else + { + inactiveentitystate.number = n; + s = &inactiveentitystate; + if (e->active == ACTIVE_NETWORK) + { + // entity used to exist but doesn't anymore, send remove + MSG_WriteShort(&buf, n | 0x8000); + } + } + // if the commit is full, we're done this frame + if (msg->cursize + buf.cursize > maxsize - 4) + { + // next frame we will continue where we left off + break; + } + // add the entity to the commit + EntityFrame4_AddCommitEntity(d, s); + // if the message is empty, skip out now + if (buf.cursize) + { + // write the message to the packet + SZ_Write(msg, buf.data, buf.cursize); + } + } + d->currententitynumber = n; + + // remove world message (invalid, and thus a good terminator) + MSG_WriteShort(msg, 0x8000); + // write the number of the end entity + MSG_WriteShort(msg, d->currententitynumber); + // just to be sure + d->currentcommit = NULL; + + return true; +} + + + + +entityframe5_database_t *EntityFrame5_AllocDatabase(mempool_t *pool) +{ + int i; + entityframe5_database_t *d; + d = (entityframe5_database_t *)Mem_Alloc(pool, sizeof(*d)); + d->latestframenum = 0; + for (i = 0;i < d->maxedicts;i++) + d->states[i] = defaultstate; + return d; +} + +void EntityFrame5_FreeDatabase(entityframe5_database_t *d) +{ + // all the [maxedicts] memory is allocated at once, so there's only one + // thing to free + if (d->maxedicts) + Mem_Free(d->deltabits); + Mem_Free(d); +} + +static void EntityFrame5_ExpandEdicts(entityframe5_database_t *d, int newmax) +{ + if (d->maxedicts < newmax) + { + unsigned char *data; + int oldmaxedicts = d->maxedicts; + int *olddeltabits = d->deltabits; + unsigned char *oldpriorities = d->priorities; + int *oldupdateframenum = d->updateframenum; + entity_state_t *oldstates = d->states; + unsigned char *oldvisiblebits = d->visiblebits; + d->maxedicts = newmax; + data = (unsigned char *)Mem_Alloc(sv_mempool, d->maxedicts * sizeof(int) + d->maxedicts * sizeof(unsigned char) + d->maxedicts * sizeof(int) + d->maxedicts * sizeof(entity_state_t) + (d->maxedicts+7)/8 * sizeof(unsigned char)); + d->deltabits = (int *)data;data += d->maxedicts * sizeof(int); + d->priorities = (unsigned char *)data;data += d->maxedicts * sizeof(unsigned char); + d->updateframenum = (int *)data;data += d->maxedicts * sizeof(int); + d->states = (entity_state_t *)data;data += d->maxedicts * sizeof(entity_state_t); + d->visiblebits = (unsigned char *)data;data += (d->maxedicts+7)/8 * sizeof(unsigned char); + if (oldmaxedicts) + { + memcpy(d->deltabits, olddeltabits, oldmaxedicts * sizeof(int)); + memcpy(d->priorities, oldpriorities, oldmaxedicts * sizeof(unsigned char)); + memcpy(d->updateframenum, oldupdateframenum, oldmaxedicts * sizeof(int)); + memcpy(d->states, oldstates, oldmaxedicts * sizeof(entity_state_t)); + memcpy(d->visiblebits, oldvisiblebits, (oldmaxedicts+7)/8 * sizeof(unsigned char)); + // the previous buffers were a single allocation, so just one free + Mem_Free(olddeltabits); + } + } +} + +static int EntityState5_Priority(entityframe5_database_t *d, int stateindex) +{ + int limit, priority; + entity_state_t *s = NULL; // hush compiler warning by initializing this + // if it is the player, update urgently + if (stateindex == d->viewentnum) + return ENTITYFRAME5_PRIORITYLEVELS - 1; + // priority increases each frame no matter what happens + priority = d->priorities[stateindex] + 1; + // players get an extra priority boost + if (stateindex <= svs.maxclients) + priority++; + // remove dead entities very quickly because they are just 2 bytes + if (d->states[stateindex].active != ACTIVE_NETWORK) + { + priority++; + return bound(1, priority, ENTITYFRAME5_PRIORITYLEVELS - 1); + } + // certain changes are more noticable than others + if (d->deltabits[stateindex] & (E5_FULLUPDATE | E5_ATTACHMENT | E5_MODEL | E5_FLAGS | E5_COLORMAP)) + priority++; + // find the root entity this one is attached to, and judge relevance by it + for (limit = 0;limit < 256;limit++) + { + s = d->states + stateindex; + if (s->flags & RENDER_VIEWMODEL) + stateindex = d->viewentnum; + else if (s->tagentity) + stateindex = s->tagentity; + else + break; + if (d->maxedicts < stateindex) + EntityFrame5_ExpandEdicts(d, (stateindex+256)&~255); + } + if (limit >= 256) + Con_DPrintf("Protocol: Runaway loop recursing tagentity links on entity %i\n", stateindex); + // now that we have the parent entity we can make some decisions based on + // distance from the player + if (VectorDistance(d->states[d->viewentnum].netcenter, s->netcenter) < 1024.0f) + priority++; + return bound(1, priority, ENTITYFRAME5_PRIORITYLEVELS - 1); +} + +static double anim_reducetime(double t, double frameduration, double maxtime) +{ + if(t < 0) // clamp to non-negative + return 0; + if(t <= maxtime) // time can be represented normally + return t; + if(frameduration == 0) // don't like dividing by zero + return t; + if(maxtime <= 2 * frameduration) // if two frames don't fit, we better not do this + return t; + t -= frameduration * ceil((t - maxtime) / frameduration); + // now maxtime - frameduration < t <= maxtime + return t; +} + +// see VM_SV_frameduration +static double anim_frameduration(dp_model_t *model, int framenum) +{ + if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes) + return 0; + if(model->animscenes[framenum].framerate) + return model->animscenes[framenum].framecount / model->animscenes[framenum].framerate; + return 0; +} + +void EntityState5_WriteUpdate(int number, const entity_state_t *s, int changedbits, sizebuf_t *msg) +{ + prvm_prog_t *prog = SVVM_prog; + unsigned int bits = 0; + //dp_model_t *model; + ENTITYSIZEPROFILING_START(msg, s->number); + + if (s->active != ACTIVE_NETWORK) + MSG_WriteShort(msg, number | 0x8000); + else + { + if (PRVM_serveredictfunction((&prog->edicts[s->number]), SendEntity)) + return; + + bits = changedbits; + if ((bits & E5_ORIGIN) && (!(s->flags & RENDER_LOWPRECISION) || s->exteriormodelforclient || s->tagentity || s->viewmodelforclient || (s->number >= 1 && s->number <= svs.maxclients) || s->origin[0] <= -4096.0625 || s->origin[0] >= 4095.9375 || s->origin[1] <= -4096.0625 || s->origin[1] >= 4095.9375 || s->origin[2] <= -4096.0625 || s->origin[2] >= 4095.9375)) + // maybe also add: ((model = SV_GetModelByIndex(s->modelindex)) != NULL && model->name[0] == '*') + bits |= E5_ORIGIN32; + // possible values: + // negative origin: + // (int)(f * 8 - 0.5) >= -32768 + // (f * 8 - 0.5) > -32769 + // f > -4096.0625 + // positive origin: + // (int)(f * 8 + 0.5) <= 32767 + // (f * 8 + 0.5) < 32768 + // f * 8 + 0.5) < 4095.9375 + if ((bits & E5_ANGLES) && !(s->flags & RENDER_LOWPRECISION)) + bits |= E5_ANGLES16; + if ((bits & E5_MODEL) && s->modelindex >= 256) + bits |= E5_MODEL16; + if ((bits & E5_FRAME) && s->frame >= 256) + bits |= E5_FRAME16; + if (bits & E5_EFFECTS) + { + if (s->effects & 0xFFFF0000) + bits |= E5_EFFECTS32; + else if (s->effects & 0xFFFFFF00) + bits |= E5_EFFECTS16; + } + if (bits >= 256) + bits |= E5_EXTEND1; + if (bits >= 65536) + bits |= E5_EXTEND2; + if (bits >= 16777216) + bits |= E5_EXTEND3; + MSG_WriteShort(msg, number); + MSG_WriteByte(msg, bits & 0xFF); + if (bits & E5_EXTEND1) + MSG_WriteByte(msg, (bits >> 8) & 0xFF); + if (bits & E5_EXTEND2) + MSG_WriteByte(msg, (bits >> 16) & 0xFF); + if (bits & E5_EXTEND3) + MSG_WriteByte(msg, (bits >> 24) & 0xFF); + if (bits & E5_FLAGS) + MSG_WriteByte(msg, s->flags); + if (bits & E5_ORIGIN) + { + if (bits & E5_ORIGIN32) + { + MSG_WriteCoord32f(msg, s->origin[0]); + MSG_WriteCoord32f(msg, s->origin[1]); + MSG_WriteCoord32f(msg, s->origin[2]); + } + else + { + MSG_WriteCoord13i(msg, s->origin[0]); + MSG_WriteCoord13i(msg, s->origin[1]); + MSG_WriteCoord13i(msg, s->origin[2]); + } + } + if (bits & E5_ANGLES) + { + if (bits & E5_ANGLES16) + { + MSG_WriteAngle16i(msg, s->angles[0]); + MSG_WriteAngle16i(msg, s->angles[1]); + MSG_WriteAngle16i(msg, s->angles[2]); + } + else + { + MSG_WriteAngle8i(msg, s->angles[0]); + MSG_WriteAngle8i(msg, s->angles[1]); + MSG_WriteAngle8i(msg, s->angles[2]); + } + } + if (bits & E5_MODEL) + { + if (bits & E5_MODEL16) + MSG_WriteShort(msg, s->modelindex); + else + MSG_WriteByte(msg, s->modelindex); + } + if (bits & E5_FRAME) + { + if (bits & E5_FRAME16) + MSG_WriteShort(msg, s->frame); + else + MSG_WriteByte(msg, s->frame); + } + if (bits & E5_SKIN) + MSG_WriteByte(msg, s->skin); + if (bits & E5_EFFECTS) + { + if (bits & E5_EFFECTS32) + MSG_WriteLong(msg, s->effects); + else if (bits & E5_EFFECTS16) + MSG_WriteShort(msg, s->effects); + else + MSG_WriteByte(msg, s->effects); + } + if (bits & E5_ALPHA) + MSG_WriteByte(msg, s->alpha); + if (bits & E5_SCALE) + MSG_WriteByte(msg, s->scale); + if (bits & E5_COLORMAP) + MSG_WriteByte(msg, s->colormap); + if (bits & E5_ATTACHMENT) + { + MSG_WriteShort(msg, s->tagentity); + MSG_WriteByte(msg, s->tagindex); + } + if (bits & E5_LIGHT) + { + MSG_WriteShort(msg, s->light[0]); + MSG_WriteShort(msg, s->light[1]); + MSG_WriteShort(msg, s->light[2]); + MSG_WriteShort(msg, s->light[3]); + MSG_WriteByte(msg, s->lightstyle); + MSG_WriteByte(msg, s->lightpflags); + } + if (bits & E5_GLOW) + { + MSG_WriteByte(msg, s->glowsize); + MSG_WriteByte(msg, s->glowcolor); + } + if (bits & E5_COLORMOD) + { + MSG_WriteByte(msg, s->colormod[0]); + MSG_WriteByte(msg, s->colormod[1]); + MSG_WriteByte(msg, s->colormod[2]); + } + if (bits & E5_GLOWMOD) + { + MSG_WriteByte(msg, s->glowmod[0]); + MSG_WriteByte(msg, s->glowmod[1]); + MSG_WriteByte(msg, s->glowmod[2]); + } + if (bits & E5_COMPLEXANIMATION) + { + if (s->skeletonobject.model && s->skeletonobject.relativetransforms) + { + int numbones = s->skeletonobject.model->num_bones; + int bonenum; + short pose7s[7]; + MSG_WriteByte(msg, 4); + MSG_WriteShort(msg, s->modelindex); + MSG_WriteByte(msg, numbones); + for (bonenum = 0;bonenum < numbones;bonenum++) + { + Matrix4x4_ToBonePose7s(s->skeletonobject.relativetransforms + bonenum, 64, pose7s); + MSG_WriteShort(msg, pose7s[0]); + MSG_WriteShort(msg, pose7s[1]); + MSG_WriteShort(msg, pose7s[2]); + MSG_WriteShort(msg, pose7s[3]); + MSG_WriteShort(msg, pose7s[4]); + MSG_WriteShort(msg, pose7s[5]); + MSG_WriteShort(msg, pose7s[6]); + } + } + else + { + dp_model_t *model = SV_GetModelByIndex(s->modelindex); + if (s->framegroupblend[3].lerp > 0) + { + MSG_WriteByte(msg, 3); + MSG_WriteShort(msg, s->framegroupblend[0].frame); + MSG_WriteShort(msg, s->framegroupblend[1].frame); + MSG_WriteShort(msg, s->framegroupblend[2].frame); + MSG_WriteShort(msg, s->framegroupblend[3].frame); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[0].start, anim_frameduration(model, s->framegroupblend[0].frame), 65.535) * 1000.0)); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[1].start, anim_frameduration(model, s->framegroupblend[1].frame), 65.535) * 1000.0)); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[2].start, anim_frameduration(model, s->framegroupblend[2].frame), 65.535) * 1000.0)); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[3].start, anim_frameduration(model, s->framegroupblend[3].frame), 65.535) * 1000.0)); + MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[2].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[3].lerp * 255.0f); + } + else if (s->framegroupblend[2].lerp > 0) + { + MSG_WriteByte(msg, 2); + MSG_WriteShort(msg, s->framegroupblend[0].frame); + MSG_WriteShort(msg, s->framegroupblend[1].frame); + MSG_WriteShort(msg, s->framegroupblend[2].frame); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[0].start, anim_frameduration(model, s->framegroupblend[0].frame), 65.535) * 1000.0)); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[1].start, anim_frameduration(model, s->framegroupblend[1].frame), 65.535) * 1000.0)); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[2].start, anim_frameduration(model, s->framegroupblend[2].frame), 65.535) * 1000.0)); + MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[2].lerp * 255.0f); + } + else if (s->framegroupblend[1].lerp > 0) + { + MSG_WriteByte(msg, 1); + MSG_WriteShort(msg, s->framegroupblend[0].frame); + MSG_WriteShort(msg, s->framegroupblend[1].frame); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[0].start, anim_frameduration(model, s->framegroupblend[0].frame), 65.535) * 1000.0)); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[1].start, anim_frameduration(model, s->framegroupblend[1].frame), 65.535) * 1000.0)); + MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); + MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); + } + else + { + MSG_WriteByte(msg, 0); + MSG_WriteShort(msg, s->framegroupblend[0].frame); + MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[0].start, anim_frameduration(model, s->framegroupblend[0].frame), 65.535) * 1000.0)); + } + } + } + if (bits & E5_TRAILEFFECTNUM) + MSG_WriteShort(msg, s->traileffectnum); + } + + ENTITYSIZEPROFILING_END(msg, s->number); +} + +static void EntityState5_ReadUpdate(entity_state_t *s, int number) +{ + int bits; + int startoffset = cl_message.readcount; + int bytes = 0; + bits = MSG_ReadByte(&cl_message); + if (bits & E5_EXTEND1) + { + bits |= MSG_ReadByte(&cl_message) << 8; + if (bits & E5_EXTEND2) + { + bits |= MSG_ReadByte(&cl_message) << 16; + if (bits & E5_EXTEND3) + bits |= MSG_ReadByte(&cl_message) << 24; + } + } + if (bits & E5_FULLUPDATE) + { + *s = defaultstate; + s->active = ACTIVE_NETWORK; + } + if (bits & E5_FLAGS) + s->flags = MSG_ReadByte(&cl_message); + if (bits & E5_ORIGIN) + { + if (bits & E5_ORIGIN32) + { + s->origin[0] = MSG_ReadCoord32f(&cl_message); + s->origin[1] = MSG_ReadCoord32f(&cl_message); + s->origin[2] = MSG_ReadCoord32f(&cl_message); + } + else + { + s->origin[0] = MSG_ReadCoord13i(&cl_message); + s->origin[1] = MSG_ReadCoord13i(&cl_message); + s->origin[2] = MSG_ReadCoord13i(&cl_message); + } + } + if (bits & E5_ANGLES) + { + if (bits & E5_ANGLES16) + { + s->angles[0] = MSG_ReadAngle16i(&cl_message); + s->angles[1] = MSG_ReadAngle16i(&cl_message); + s->angles[2] = MSG_ReadAngle16i(&cl_message); + } + else + { + s->angles[0] = MSG_ReadAngle8i(&cl_message); + s->angles[1] = MSG_ReadAngle8i(&cl_message); + s->angles[2] = MSG_ReadAngle8i(&cl_message); + } + } + if (bits & E5_MODEL) + { + if (bits & E5_MODEL16) + s->modelindex = (unsigned short) MSG_ReadShort(&cl_message); + else + s->modelindex = MSG_ReadByte(&cl_message); + } + if (bits & E5_FRAME) + { + if (bits & E5_FRAME16) + s->frame = (unsigned short) MSG_ReadShort(&cl_message); + else + s->frame = MSG_ReadByte(&cl_message); + } + if (bits & E5_SKIN) + s->skin = MSG_ReadByte(&cl_message); + if (bits & E5_EFFECTS) + { + if (bits & E5_EFFECTS32) + s->effects = (unsigned int) MSG_ReadLong(&cl_message); + else if (bits & E5_EFFECTS16) + s->effects = (unsigned short) MSG_ReadShort(&cl_message); + else + s->effects = MSG_ReadByte(&cl_message); + } + if (bits & E5_ALPHA) + s->alpha = MSG_ReadByte(&cl_message); + if (bits & E5_SCALE) + s->scale = MSG_ReadByte(&cl_message); + if (bits & E5_COLORMAP) + s->colormap = MSG_ReadByte(&cl_message); + if (bits & E5_ATTACHMENT) + { + s->tagentity = (unsigned short) MSG_ReadShort(&cl_message); + s->tagindex = MSG_ReadByte(&cl_message); + } + if (bits & E5_LIGHT) + { + s->light[0] = (unsigned short) MSG_ReadShort(&cl_message); + s->light[1] = (unsigned short) MSG_ReadShort(&cl_message); + s->light[2] = (unsigned short) MSG_ReadShort(&cl_message); + s->light[3] = (unsigned short) MSG_ReadShort(&cl_message); + s->lightstyle = MSG_ReadByte(&cl_message); + s->lightpflags = MSG_ReadByte(&cl_message); + } + if (bits & E5_GLOW) + { + s->glowsize = MSG_ReadByte(&cl_message); + s->glowcolor = MSG_ReadByte(&cl_message); + } + if (bits & E5_COLORMOD) + { + s->colormod[0] = MSG_ReadByte(&cl_message); + s->colormod[1] = MSG_ReadByte(&cl_message); + s->colormod[2] = MSG_ReadByte(&cl_message); + } + if (bits & E5_GLOWMOD) + { + s->glowmod[0] = MSG_ReadByte(&cl_message); + s->glowmod[1] = MSG_ReadByte(&cl_message); + s->glowmod[2] = MSG_ReadByte(&cl_message); + } + if (bits & E5_COMPLEXANIMATION) + { + skeleton_t *skeleton; + const dp_model_t *model; + int modelindex; + int type; + int bonenum; + int numbones; + short pose7s[7]; + type = MSG_ReadByte(&cl_message); + switch(type) + { + case 0: + s->framegroupblend[0].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[1].frame = 0; + s->framegroupblend[2].frame = 0; + s->framegroupblend[3].frame = 0; + s->framegroupblend[0].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[1].start = 0; + s->framegroupblend[2].start = 0; + s->framegroupblend[3].start = 0; + s->framegroupblend[0].lerp = 1; + s->framegroupblend[1].lerp = 0; + s->framegroupblend[2].lerp = 0; + s->framegroupblend[3].lerp = 0; + break; + case 1: + s->framegroupblend[0].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[1].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[2].frame = 0; + s->framegroupblend[3].frame = 0; + s->framegroupblend[0].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[1].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[2].start = 0; + s->framegroupblend[3].start = 0; + s->framegroupblend[0].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + s->framegroupblend[1].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + s->framegroupblend[2].lerp = 0; + s->framegroupblend[3].lerp = 0; + break; + case 2: + s->framegroupblend[0].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[1].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[2].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[3].frame = 0; + s->framegroupblend[0].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[1].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[2].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[3].start = 0; + s->framegroupblend[0].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + s->framegroupblend[1].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + s->framegroupblend[2].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + s->framegroupblend[3].lerp = 0; + break; + case 3: + s->framegroupblend[0].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[1].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[2].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[3].frame = MSG_ReadShort(&cl_message); + s->framegroupblend[0].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[1].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[2].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[3].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); + s->framegroupblend[0].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + s->framegroupblend[1].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + s->framegroupblend[2].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + s->framegroupblend[3].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); + break; + case 4: + if (!cl.engineskeletonobjects) + cl.engineskeletonobjects = (skeleton_t *) Mem_Alloc(cls.levelmempool, sizeof(*cl.engineskeletonobjects) * MAX_EDICTS); + skeleton = &cl.engineskeletonobjects[number]; + modelindex = MSG_ReadShort(&cl_message); + model = CL_GetModelByIndex(modelindex); + numbones = MSG_ReadByte(&cl_message); + if (model && numbones != model->num_bones) + Host_Error("E5_COMPLEXANIMATION: model has different number of bones than network packet describes\n"); + if (!skeleton->relativetransforms || skeleton->model != model) + { + skeleton->model = model; + skeleton->relativetransforms = (matrix4x4_t *) Mem_Realloc(cls.levelmempool, skeleton->relativetransforms, sizeof(*skeleton->relativetransforms) * skeleton->model->num_bones); + for (bonenum = 0;bonenum < model->num_bones;bonenum++) + skeleton->relativetransforms[bonenum] = identitymatrix; + } + for (bonenum = 0;bonenum < numbones;bonenum++) + { + pose7s[0] = (short)MSG_ReadShort(&cl_message); + pose7s[1] = (short)MSG_ReadShort(&cl_message); + pose7s[2] = (short)MSG_ReadShort(&cl_message); + pose7s[3] = (short)MSG_ReadShort(&cl_message); + pose7s[4] = (short)MSG_ReadShort(&cl_message); + pose7s[5] = (short)MSG_ReadShort(&cl_message); + pose7s[6] = (short)MSG_ReadShort(&cl_message); + Matrix4x4_FromBonePose7s(skeleton->relativetransforms + bonenum, 1.0f / 64.0f, pose7s); + } + s->skeletonobject = *skeleton; + break; + default: + Host_Error("E5_COMPLEXANIMATION: Parse error - unknown type %i\n", type); + break; + } + } + if (bits & E5_TRAILEFFECTNUM) + s->traileffectnum = (unsigned short) MSG_ReadShort(&cl_message); + + + bytes = cl_message.readcount - startoffset; + if (developer_networkentities.integer >= 2) + { + Con_Printf("ReadFields e%i (%i bytes)", number, bytes); + + if (bits & E5_ORIGIN) + Con_Printf(" E5_ORIGIN %f %f %f", s->origin[0], s->origin[1], s->origin[2]); + if (bits & E5_ANGLES) + Con_Printf(" E5_ANGLES %f %f %f", s->angles[0], s->angles[1], s->angles[2]); + if (bits & E5_MODEL) + Con_Printf(" E5_MODEL %i", s->modelindex); + if (bits & E5_FRAME) + Con_Printf(" E5_FRAME %i", s->frame); + if (bits & E5_SKIN) + Con_Printf(" E5_SKIN %i", s->skin); + if (bits & E5_EFFECTS) + Con_Printf(" E5_EFFECTS %i", s->effects); + if (bits & E5_FLAGS) + { + Con_Printf(" E5_FLAGS %i (", s->flags); + if (s->flags & RENDER_STEP) + Con_Print(" STEP"); + if (s->flags & RENDER_GLOWTRAIL) + Con_Print(" GLOWTRAIL"); + if (s->flags & RENDER_VIEWMODEL) + Con_Print(" VIEWMODEL"); + if (s->flags & RENDER_EXTERIORMODEL) + Con_Print(" EXTERIORMODEL"); + if (s->flags & RENDER_LOWPRECISION) + Con_Print(" LOWPRECISION"); + if (s->flags & RENDER_COLORMAPPED) + Con_Print(" COLORMAPPED"); + if (s->flags & RENDER_SHADOW) + Con_Print(" SHADOW"); + if (s->flags & RENDER_LIGHT) + Con_Print(" LIGHT"); + if (s->flags & RENDER_NOSELFSHADOW) + Con_Print(" NOSELFSHADOW"); + Con_Print(")"); + } + if (bits & E5_ALPHA) + Con_Printf(" E5_ALPHA %f", s->alpha / 255.0f); + if (bits & E5_SCALE) + Con_Printf(" E5_SCALE %f", s->scale / 16.0f); + if (bits & E5_COLORMAP) + Con_Printf(" E5_COLORMAP %i", s->colormap); + if (bits & E5_ATTACHMENT) + Con_Printf(" E5_ATTACHMENT e%i:%i", s->tagentity, s->tagindex); + if (bits & E5_LIGHT) + Con_Printf(" E5_LIGHT %i:%i:%i:%i %i:%i", s->light[0], s->light[1], s->light[2], s->light[3], s->lightstyle, s->lightpflags); + if (bits & E5_GLOW) + Con_Printf(" E5_GLOW %i:%i", s->glowsize * 4, s->glowcolor); + if (bits & E5_COLORMOD) + Con_Printf(" E5_COLORMOD %f:%f:%f", s->colormod[0] / 32.0f, s->colormod[1] / 32.0f, s->colormod[2] / 32.0f); + if (bits & E5_GLOWMOD) + Con_Printf(" E5_GLOWMOD %f:%f:%f", s->glowmod[0] / 32.0f, s->glowmod[1] / 32.0f, s->glowmod[2] / 32.0f); + if (bits & E5_COMPLEXANIMATION) + Con_Printf(" E5_COMPLEXANIMATION"); + if (bits & E5_TRAILEFFECTNUM) + Con_Printf(" E5_TRAILEFFECTNUM %i", s->traileffectnum); + Con_Print("\n"); + } +} + +static int EntityState5_DeltaBits(const entity_state_t *o, const entity_state_t *n) +{ + unsigned int bits = 0; + if (n->active == ACTIVE_NETWORK) + { + if (o->active != ACTIVE_NETWORK) + bits |= E5_FULLUPDATE; + if (!VectorCompare(o->origin, n->origin)) + bits |= E5_ORIGIN; + if (!VectorCompare(o->angles, n->angles)) + bits |= E5_ANGLES; + if (o->modelindex != n->modelindex) + bits |= E5_MODEL; + if (o->frame != n->frame) + bits |= E5_FRAME; + if (o->skin != n->skin) + bits |= E5_SKIN; + if (o->effects != n->effects) + bits |= E5_EFFECTS; + if (o->flags != n->flags) + bits |= E5_FLAGS; + if (o->alpha != n->alpha) + bits |= E5_ALPHA; + if (o->scale != n->scale) + bits |= E5_SCALE; + if (o->colormap != n->colormap) + bits |= E5_COLORMAP; + if (o->tagentity != n->tagentity || o->tagindex != n->tagindex) + bits |= E5_ATTACHMENT; + if (o->light[0] != n->light[0] || o->light[1] != n->light[1] || o->light[2] != n->light[2] || o->light[3] != n->light[3] || o->lightstyle != n->lightstyle || o->lightpflags != n->lightpflags) + bits |= E5_LIGHT; + if (o->glowsize != n->glowsize || o->glowcolor != n->glowcolor) + bits |= E5_GLOW; + if (o->colormod[0] != n->colormod[0] || o->colormod[1] != n->colormod[1] || o->colormod[2] != n->colormod[2]) + bits |= E5_COLORMOD; + if (o->glowmod[0] != n->glowmod[0] || o->glowmod[1] != n->glowmod[1] || o->glowmod[2] != n->glowmod[2]) + bits |= E5_GLOWMOD; + if (n->flags & RENDER_COMPLEXANIMATION) + { + if ((o->skeletonobject.model && o->skeletonobject.relativetransforms) != (n->skeletonobject.model && n->skeletonobject.relativetransforms)) + { + bits |= E5_COMPLEXANIMATION; + } + else if (o->skeletonobject.model && o->skeletonobject.relativetransforms) + { + if(o->modelindex != n->modelindex) + bits |= E5_COMPLEXANIMATION; + else if(o->skeletonobject.model->num_bones != n->skeletonobject.model->num_bones) + bits |= E5_COMPLEXANIMATION; + else if(memcmp(o->skeletonobject.relativetransforms, n->skeletonobject.relativetransforms, o->skeletonobject.model->num_bones * sizeof(*o->skeletonobject.relativetransforms))) + bits |= E5_COMPLEXANIMATION; + } + else if (memcmp(o->framegroupblend, n->framegroupblend, sizeof(o->framegroupblend))) + { + bits |= E5_COMPLEXANIMATION; + } + } + if (o->traileffectnum != n->traileffectnum) + bits |= E5_TRAILEFFECTNUM; + } + else + if (o->active == ACTIVE_NETWORK) + bits |= E5_FULLUPDATE; + return bits; +} + +void EntityFrame5_CL_ReadFrame(void) +{ + int n, enumber, framenum; + entity_t *ent; + entity_state_t *s; + // read the number of this frame to echo back in next input packet + framenum = MSG_ReadLong(&cl_message); + CL_NewFrameReceived(framenum); + if (cls.protocol != PROTOCOL_QUAKE && cls.protocol != PROTOCOL_QUAKEDP && cls.protocol != PROTOCOL_NEHAHRAMOVIE && cls.protocol != PROTOCOL_DARKPLACES1 && cls.protocol != PROTOCOL_DARKPLACES2 && cls.protocol != PROTOCOL_DARKPLACES3 && cls.protocol != PROTOCOL_DARKPLACES4 && cls.protocol != PROTOCOL_DARKPLACES5 && cls.protocol != PROTOCOL_DARKPLACES6) + cls.servermovesequence = MSG_ReadLong(&cl_message); + // read entity numbers until we find a 0x8000 + // (which would be remove world entity, but is actually a terminator) + while ((n = (unsigned short)MSG_ReadShort(&cl_message)) != 0x8000 && !cl_message.badread) + { + // get the entity number + enumber = n & 0x7FFF; + // we may need to expand the array + if (cl.num_entities <= enumber) + { + cl.num_entities = enumber + 1; + if (enumber >= cl.max_entities) + CL_ExpandEntities(enumber); + } + // look up the entity + ent = cl.entities + enumber; + // slide the current into the previous slot + ent->state_previous = ent->state_current; + // read the update + s = &ent->state_current; + if (n & 0x8000) + { + // remove entity + *s = defaultstate; + } + else + { + // update entity + EntityState5_ReadUpdate(s, enumber); + } + // set the cl.entities_active flag + cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); + // set the update time + s->time = cl.mtime[0]; + // fix the number (it gets wiped occasionally by copying from defaultstate) + s->number = enumber; + // check if we need to update the lerp stuff + if (s->active == ACTIVE_NETWORK) + CL_MoveLerpEntityStates(&cl.entities[enumber]); + // print extra messages if desired + if (developer_networkentities.integer >= 2 && cl.entities[enumber].state_current.active != cl.entities[enumber].state_previous.active) + { + if (cl.entities[enumber].state_current.active == ACTIVE_NETWORK) + Con_Printf("entity #%i has become active\n", enumber); + else if (cl.entities[enumber].state_previous.active) + Con_Printf("entity #%i has become inactive\n", enumber); + } + } +} + +static int packetlog5cmp(const void *a_, const void *b_) +{ + const entityframe5_packetlog_t *a = (const entityframe5_packetlog_t *) a_; + const entityframe5_packetlog_t *b = (const entityframe5_packetlog_t *) b_; + return a->packetnumber - b->packetnumber; +} + +void EntityFrame5_LostFrame(entityframe5_database_t *d, int framenum) +{ + int i, j, l, bits; + entityframe5_changestate_t *s; + entityframe5_packetlog_t *p; + static unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; + static int deltabits[MAX_EDICTS]; + entityframe5_packetlog_t *packetlogs[ENTITYFRAME5_MAXPACKETLOGS]; + + for (i = 0, p = d->packetlog;i < ENTITYFRAME5_MAXPACKETLOGS;i++, p++) + packetlogs[i] = p; + qsort(packetlogs, sizeof(*packetlogs), ENTITYFRAME5_MAXPACKETLOGS, packetlog5cmp); + + memset(deltabits, 0, sizeof(deltabits)); + memset(statsdeltabits, 0, sizeof(statsdeltabits)); + for (i = 0; i < ENTITYFRAME5_MAXPACKETLOGS; i++) + { + p = packetlogs[i]; + + if (!p->packetnumber) + continue; + + if (p->packetnumber <= framenum) + { + for (j = 0, s = p->states;j < p->numstates;j++, s++) + deltabits[s->number] |= s->bits; + + for (l = 0;l < (MAX_CL_STATS+7)/8;l++) + statsdeltabits[l] |= p->statsdeltabits[l]; + + p->packetnumber = 0; + } + else + { + for (j = 0, s = p->states;j < p->numstates;j++, s++) + deltabits[s->number] &= ~s->bits; + for (l = 0;l < (MAX_CL_STATS+7)/8;l++) + statsdeltabits[l] &= ~p->statsdeltabits[l]; + } + } + + for(i = 0; i < d->maxedicts; ++i) + { + bits = deltabits[i] & ~d->deltabits[i]; + if(bits) + { + d->deltabits[i] |= bits; + // if it was a very important update, set priority higher + if (bits & (E5_FULLUPDATE | E5_ATTACHMENT | E5_MODEL | E5_COLORMAP)) + d->priorities[i] = max(d->priorities[i], 4); + else + d->priorities[i] = max(d->priorities[i], 1); + } + } + + for (l = 0;l < (MAX_CL_STATS+7)/8;l++) + host_client->statsdeltabits[l] |= statsdeltabits[l]; + // no need to mask out the already-set bits here, as we do not + // do that priorities stuff +} + +void EntityFrame5_AckFrame(entityframe5_database_t *d, int framenum) +{ + int i; + // scan for packets made obsolete by this ack and delete them + for (i = 0;i < ENTITYFRAME5_MAXPACKETLOGS;i++) + if (d->packetlog[i].packetnumber <= framenum) + d->packetlog[i].packetnumber = 0; +} + +qboolean EntityFrame5_WriteFrame(sizebuf_t *msg, int maxsize, entityframe5_database_t *d, int numstates, const entity_state_t **states, int viewentnum, int movesequence, qboolean need_empty) +{ + prvm_prog_t *prog = SVVM_prog; + const entity_state_t *n; + int i, num, l, framenum, packetlognumber, priority; + sizebuf_t buf; + unsigned char data[128]; + entityframe5_packetlog_t *packetlog; + + if (prog->max_edicts > d->maxedicts) + EntityFrame5_ExpandEdicts(d, prog->max_edicts); + + framenum = d->latestframenum + 1; + d->viewentnum = viewentnum; + + // if packet log is full, mark all frames as lost, this will cause + // it to send the lost data again + for (packetlognumber = 0;packetlognumber < ENTITYFRAME5_MAXPACKETLOGS;packetlognumber++) + if (d->packetlog[packetlognumber].packetnumber == 0) + break; + if (packetlognumber == ENTITYFRAME5_MAXPACKETLOGS) + { + Con_DPrintf("EntityFrame5_WriteFrame: packetlog overflow for a client, resetting\n"); + EntityFrame5_LostFrame(d, framenum); + packetlognumber = 0; + } + + // prepare the buffer + memset(&buf, 0, sizeof(buf)); + buf.data = data; + buf.maxsize = sizeof(data); + + // detect changes in states + num = 1; + for (i = 0;i < numstates;i++) + { + n = states[i]; + // mark gaps in entity numbering as removed entities + for (;num < n->number;num++) + { + // if the entity used to exist, clear it + if (CHECKPVSBIT(d->visiblebits, num)) + { + CLEARPVSBIT(d->visiblebits, num); + d->deltabits[num] = E5_FULLUPDATE; + d->priorities[num] = max(d->priorities[num], 8); // removal is cheap + d->states[num] = defaultstate; + d->states[num].number = num; + } + } + // update the entity state data + if (!CHECKPVSBIT(d->visiblebits, num)) + { + // entity just spawned in, don't let it completely hog priority + // because of being ancient on the first frame + d->updateframenum[num] = framenum; + // initial priority is a bit high to make projectiles send on the + // first frame, among other things + d->priorities[num] = max(d->priorities[num], 4); + } + SETPVSBIT(d->visiblebits, num); + d->deltabits[num] |= EntityState5_DeltaBits(d->states + num, n); + d->priorities[num] = max(d->priorities[num], 1); + d->states[num] = *n; + d->states[num].number = num; + // advance to next entity so the next iteration doesn't immediately remove it + num++; + } + // all remaining entities are dead + for (;num < d->maxedicts;num++) + { + if (CHECKPVSBIT(d->visiblebits, num)) + { + CLEARPVSBIT(d->visiblebits, num); + d->deltabits[num] = E5_FULLUPDATE; + d->priorities[num] = max(d->priorities[num], 8); // removal is cheap + d->states[num] = defaultstate; + d->states[num].number = num; + } + } + + // if there isn't at least enough room for an empty svc_entities, + // don't bother trying... + if (buf.cursize + 11 > buf.maxsize) + return false; + + // build lists of entities by priority level + memset(d->prioritychaincounts, 0, sizeof(d->prioritychaincounts)); + l = 0; + for (num = 0;num < d->maxedicts;num++) + { + if (d->priorities[num]) + { + if (d->deltabits[num]) + { + if (d->priorities[num] < (ENTITYFRAME5_PRIORITYLEVELS - 1)) + d->priorities[num] = EntityState5_Priority(d, num); + l = num; + priority = d->priorities[num]; + if (d->prioritychaincounts[priority] < ENTITYFRAME5_MAXSTATES) + d->prioritychains[priority][d->prioritychaincounts[priority]++] = num; + } + else + d->priorities[num] = 0; + } + } + + packetlog = NULL; + // write stat updates + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5) + { + for (i = 0;i < MAX_CL_STATS && msg->cursize + 6 + 11 <= maxsize;i++) + { + if (host_client->statsdeltabits[i>>3] & (1<<(i&7))) + { + host_client->statsdeltabits[i>>3] &= ~(1<<(i&7)); + // add packetlog entry now that we have something for it + if (!packetlog) + { + packetlog = d->packetlog + packetlognumber; + packetlog->packetnumber = framenum; + packetlog->numstates = 0; + memset(packetlog->statsdeltabits, 0, sizeof(packetlog->statsdeltabits)); + } + packetlog->statsdeltabits[i>>3] |= (1<<(i&7)); + if (host_client->stats[i] >= 0 && host_client->stats[i] < 256) + { + MSG_WriteByte(msg, svc_updatestatubyte); + MSG_WriteByte(msg, i); + MSG_WriteByte(msg, host_client->stats[i]); + l = 1; + } + else + { + MSG_WriteByte(msg, svc_updatestat); + MSG_WriteByte(msg, i); + MSG_WriteLong(msg, host_client->stats[i]); + l = 1; + } + } + } + } + + // only send empty svc_entities frame if needed + if(!l && !need_empty) + return false; + + // add packetlog entry now that we have something for it + if (!packetlog) + { + packetlog = d->packetlog + packetlognumber; + packetlog->packetnumber = framenum; + packetlog->numstates = 0; + memset(packetlog->statsdeltabits, 0, sizeof(packetlog->statsdeltabits)); + } + + // write state updates + if (developer_networkentities.integer >= 10) + Con_Printf("send: svc_entities %i\n", framenum); + d->latestframenum = framenum; + MSG_WriteByte(msg, svc_entities); + MSG_WriteLong(msg, framenum); + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5 && sv.protocol != PROTOCOL_DARKPLACES6) + MSG_WriteLong(msg, movesequence); + for (priority = ENTITYFRAME5_PRIORITYLEVELS - 1;priority >= 0 && packetlog->numstates < ENTITYFRAME5_MAXSTATES;priority--) + { + for (i = 0;i < d->prioritychaincounts[priority] && packetlog->numstates < ENTITYFRAME5_MAXSTATES;i++) + { + num = d->prioritychains[priority][i]; + n = d->states + num; + if (d->deltabits[num] & E5_FULLUPDATE) + d->deltabits[num] = E5_FULLUPDATE | EntityState5_DeltaBits(&defaultstate, n); + buf.cursize = 0; + EntityState5_WriteUpdate(num, n, d->deltabits[num], &buf); + // if the entity won't fit, try the next one + if (msg->cursize + buf.cursize + 2 > maxsize) + continue; + // write entity to the packet + SZ_Write(msg, buf.data, buf.cursize); + // mark age on entity for prioritization + d->updateframenum[num] = framenum; + // log entity so deltabits can be restored later if lost + packetlog->states[packetlog->numstates].number = num; + packetlog->states[packetlog->numstates].bits = d->deltabits[num]; + packetlog->numstates++; + // clear deltabits and priority so it won't be sent again + d->deltabits[num] = 0; + d->priorities[num] = 0; + } + } + MSG_WriteShort(msg, 0x8000); + + return true; +} + + +static void QW_TranslateEffects(entity_state_t *s, int qweffects) +{ + s->effects = 0; + s->internaleffects = 0; + if (qweffects & QW_EF_BRIGHTFIELD) + s->effects |= EF_BRIGHTFIELD; + if (qweffects & QW_EF_MUZZLEFLASH) + s->effects |= EF_MUZZLEFLASH; + if (qweffects & QW_EF_FLAG1) + { + // mimic FTEQW's interpretation of EF_FLAG1 as EF_NODRAW on non-player entities + if (s->number > cl.maxclients) + s->effects |= EF_NODRAW; + else + s->internaleffects |= INTEF_FLAG1QW; + } + if (qweffects & QW_EF_FLAG2) + { + // mimic FTEQW's interpretation of EF_FLAG2 as EF_ADDITIVE on non-player entities + if (s->number > cl.maxclients) + s->effects |= EF_ADDITIVE; + else + s->internaleffects |= INTEF_FLAG2QW; + } + if (qweffects & QW_EF_RED) + { + if (qweffects & QW_EF_BLUE) + s->effects |= EF_RED | EF_BLUE; + else + s->effects |= EF_RED; + } + else if (qweffects & QW_EF_BLUE) + s->effects |= EF_BLUE; + else if (qweffects & QW_EF_BRIGHTLIGHT) + s->effects |= EF_BRIGHTLIGHT; + else if (qweffects & QW_EF_DIMLIGHT) + s->effects |= EF_DIMLIGHT; +} + +void EntityStateQW_ReadPlayerUpdate(void) +{ + int slot = MSG_ReadByte(&cl_message); + int enumber = slot + 1; + int weaponframe; + int msec; + int playerflags; + int bits; + entity_state_t *s; + // look up the entity + entity_t *ent = cl.entities + enumber; + vec3_t viewangles; + vec3_t velocity; + + // slide the current state into the previous + ent->state_previous = ent->state_current; + + // read the update + s = &ent->state_current; + *s = defaultstate; + s->active = ACTIVE_NETWORK; + s->number = enumber; + s->colormap = enumber; + playerflags = MSG_ReadShort(&cl_message); + MSG_ReadVector(&cl_message, s->origin, cls.protocol); + s->frame = MSG_ReadByte(&cl_message); + + VectorClear(viewangles); + VectorClear(velocity); + + if (playerflags & QW_PF_MSEC) + { + // time difference between last update this player sent to the server, + // and last input we sent to the server (this packet is in response to + // our input, so msec is how long ago the last update of this player + // entity occurred, compared to our input being received) + msec = MSG_ReadByte(&cl_message); + } + else + msec = 0; + if (playerflags & QW_PF_COMMAND) + { + bits = MSG_ReadByte(&cl_message); + if (bits & QW_CM_ANGLE1) + viewangles[0] = MSG_ReadAngle16i(&cl_message); // cmd->angles[0] + if (bits & QW_CM_ANGLE2) + viewangles[1] = MSG_ReadAngle16i(&cl_message); // cmd->angles[1] + if (bits & QW_CM_ANGLE3) + viewangles[2] = MSG_ReadAngle16i(&cl_message); // cmd->angles[2] + if (bits & QW_CM_FORWARD) + MSG_ReadShort(&cl_message); // cmd->forwardmove + if (bits & QW_CM_SIDE) + MSG_ReadShort(&cl_message); // cmd->sidemove + if (bits & QW_CM_UP) + MSG_ReadShort(&cl_message); // cmd->upmove + if (bits & QW_CM_BUTTONS) + (void) MSG_ReadByte(&cl_message); // cmd->buttons + if (bits & QW_CM_IMPULSE) + (void) MSG_ReadByte(&cl_message); // cmd->impulse + (void) MSG_ReadByte(&cl_message); // cmd->msec + } + if (playerflags & QW_PF_VELOCITY1) + velocity[0] = MSG_ReadShort(&cl_message); + if (playerflags & QW_PF_VELOCITY2) + velocity[1] = MSG_ReadShort(&cl_message); + if (playerflags & QW_PF_VELOCITY3) + velocity[2] = MSG_ReadShort(&cl_message); + if (playerflags & QW_PF_MODEL) + s->modelindex = MSG_ReadByte(&cl_message); + else + s->modelindex = cl.qw_modelindex_player; + if (playerflags & QW_PF_SKINNUM) + s->skin = MSG_ReadByte(&cl_message); + if (playerflags & QW_PF_EFFECTS) + QW_TranslateEffects(s, MSG_ReadByte(&cl_message)); + if (playerflags & QW_PF_WEAPONFRAME) + weaponframe = MSG_ReadByte(&cl_message); + else + weaponframe = 0; + + if (enumber == cl.playerentity) + { + // if this is an update on our player, update the angles + VectorCopy(cl.viewangles, viewangles); + } + + // calculate the entity angles from the viewangles + s->angles[0] = viewangles[0] * -0.0333; + s->angles[1] = viewangles[1]; + s->angles[2] = 0; + s->angles[2] = V_CalcRoll(s->angles, velocity)*4; + + // if this is an update on our player, update interpolation state + if (enumber == cl.playerentity) + { + VectorCopy (cl.mpunchangle[0], cl.mpunchangle[1]); + VectorCopy (cl.mpunchvector[0], cl.mpunchvector[1]); + VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); + cl.mviewzoom[1] = cl.mviewzoom[0]; + + cl.idealpitch = 0; + cl.mpunchangle[0][0] = 0; + cl.mpunchangle[0][1] = 0; + cl.mpunchangle[0][2] = 0; + cl.mpunchvector[0][0] = 0; + cl.mpunchvector[0][1] = 0; + cl.mpunchvector[0][2] = 0; + cl.mvelocity[0][0] = 0; + cl.mvelocity[0][1] = 0; + cl.mvelocity[0][2] = 0; + cl.mviewzoom[0] = 1; + + VectorCopy(velocity, cl.mvelocity[0]); + cl.stats[STAT_WEAPONFRAME] = weaponframe; + if (playerflags & QW_PF_GIB) + cl.stats[STAT_VIEWHEIGHT] = 8; + else if (playerflags & QW_PF_DEAD) + cl.stats[STAT_VIEWHEIGHT] = -16; + else + cl.stats[STAT_VIEWHEIGHT] = 22; + } + + // set the cl.entities_active flag + cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); + // set the update time + s->time = cl.mtime[0] - msec * 0.001; // qw has no clock + // check if we need to update the lerp stuff + if (s->active == ACTIVE_NETWORK) + CL_MoveLerpEntityStates(&cl.entities[enumber]); +} + +static void EntityStateQW_ReadEntityUpdate(entity_state_t *s, int bits) +{ + int qweffects = 0; + s->active = ACTIVE_NETWORK; + s->number = bits & 511; + bits &= ~511; + if (bits & QW_U_MOREBITS) + bits |= MSG_ReadByte(&cl_message); + + // store the QW_U_SOLID bit here? + + if (bits & QW_U_MODEL) + s->modelindex = MSG_ReadByte(&cl_message); + if (bits & QW_U_FRAME) + s->frame = MSG_ReadByte(&cl_message); + if (bits & QW_U_COLORMAP) + s->colormap = MSG_ReadByte(&cl_message); + if (bits & QW_U_SKIN) + s->skin = MSG_ReadByte(&cl_message); + if (bits & QW_U_EFFECTS) + QW_TranslateEffects(s, qweffects = MSG_ReadByte(&cl_message)); + if (bits & QW_U_ORIGIN1) + s->origin[0] = MSG_ReadCoord13i(&cl_message); + if (bits & QW_U_ANGLE1) + s->angles[0] = MSG_ReadAngle8i(&cl_message); + if (bits & QW_U_ORIGIN2) + s->origin[1] = MSG_ReadCoord13i(&cl_message); + if (bits & QW_U_ANGLE2) + s->angles[1] = MSG_ReadAngle8i(&cl_message); + if (bits & QW_U_ORIGIN3) + s->origin[2] = MSG_ReadCoord13i(&cl_message); + if (bits & QW_U_ANGLE3) + s->angles[2] = MSG_ReadAngle8i(&cl_message); + + if (developer_networkentities.integer >= 2) + { + Con_Printf("ReadFields e%i", s->number); + if (bits & QW_U_MODEL) + Con_Printf(" U_MODEL %i", s->modelindex); + if (bits & QW_U_FRAME) + Con_Printf(" U_FRAME %i", s->frame); + if (bits & QW_U_COLORMAP) + Con_Printf(" U_COLORMAP %i", s->colormap); + if (bits & QW_U_SKIN) + Con_Printf(" U_SKIN %i", s->skin); + if (bits & QW_U_EFFECTS) + Con_Printf(" U_EFFECTS %i", qweffects); + if (bits & QW_U_ORIGIN1) + Con_Printf(" U_ORIGIN1 %f", s->origin[0]); + if (bits & QW_U_ANGLE1) + Con_Printf(" U_ANGLE1 %f", s->angles[0]); + if (bits & QW_U_ORIGIN2) + Con_Printf(" U_ORIGIN2 %f", s->origin[1]); + if (bits & QW_U_ANGLE2) + Con_Printf(" U_ANGLE2 %f", s->angles[1]); + if (bits & QW_U_ORIGIN3) + Con_Printf(" U_ORIGIN3 %f", s->origin[2]); + if (bits & QW_U_ANGLE3) + Con_Printf(" U_ANGLE3 %f", s->angles[2]); + if (bits & QW_U_SOLID) + Con_Printf(" U_SOLID"); + Con_Print("\n"); + } +} + +entityframeqw_database_t *EntityFrameQW_AllocDatabase(mempool_t *pool) +{ + entityframeqw_database_t *d; + d = (entityframeqw_database_t *)Mem_Alloc(pool, sizeof(*d)); + return d; +} + +void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d) +{ + Mem_Free(d); +} + +void EntityFrameQW_CL_ReadFrame(qboolean delta) +{ + qboolean invalid = false; + int number, oldsnapindex, newsnapindex, oldindex, newindex, oldnum, newnum; + entity_t *ent; + entityframeqw_database_t *d; + entityframeqw_snapshot_t *oldsnap, *newsnap; + + if (!cl.entitydatabaseqw) + cl.entitydatabaseqw = EntityFrameQW_AllocDatabase(cls.levelmempool); + d = cl.entitydatabaseqw; + + // there is no cls.netcon in demos, so this reading code can't access + // cls.netcon-> at all... so cls.qw_incoming_sequence and + // cls.qw_outgoing_sequence are updated every time the corresponding + // cls.netcon->qw. variables are updated + // read the number of this frame to echo back in next input packet + cl.qw_validsequence = cls.qw_incoming_sequence; + newsnapindex = cl.qw_validsequence & QW_UPDATE_MASK; + newsnap = d->snapshot + newsnapindex; + memset(newsnap, 0, sizeof(*newsnap)); + oldsnap = NULL; + if (delta) + { + number = MSG_ReadByte(&cl_message); + oldsnapindex = cl.qw_deltasequence[newsnapindex]; + if ((number & QW_UPDATE_MASK) != (oldsnapindex & QW_UPDATE_MASK)) + Con_DPrintf("WARNING: from mismatch\n"); + if (oldsnapindex != -1) + { + if (cls.qw_outgoing_sequence - oldsnapindex >= QW_UPDATE_BACKUP-1) + { + Con_DPrintf("delta update too old\n"); + newsnap->invalid = invalid = true; // too old + delta = false; + } + oldsnap = d->snapshot + (oldsnapindex & QW_UPDATE_MASK); + } + else + delta = false; + } + + // if we can't decode this frame properly, report that to the server + if (invalid) + cl.qw_validsequence = 0; + + // read entity numbers until we find a 0x0000 + // (which would be an empty update on world entity, but is actually a terminator) + newsnap->num_entities = 0; + oldindex = 0; + for (;;) + { + int word = (unsigned short)MSG_ReadShort(&cl_message); + if (cl_message.badread) + return; // just return, the main parser will print an error + newnum = word == 0 ? 512 : (word & 511); + oldnum = delta ? (oldindex >= oldsnap->num_entities ? 9999 : oldsnap->entities[oldindex].number) : 9999; + + // copy unmodified oldsnap entities + while (newnum > oldnum) // delta only + { + if (developer_networkentities.integer >= 2) + Con_Printf("copy %i\n", oldnum); + // copy one of the old entities + if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES) + Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES"); + newsnap->entities[newsnap->num_entities] = oldsnap->entities[oldindex++]; + newsnap->num_entities++; + oldnum = oldindex >= oldsnap->num_entities ? 9999 : oldsnap->entities[oldindex].number; + } + + if (word == 0) + break; + + if (developer_networkentities.integer >= 2) + { + if (word & QW_U_REMOVE) + Con_Printf("remove %i\n", newnum); + else if (newnum == oldnum) + Con_Printf("delta %i\n", newnum); + else + Con_Printf("baseline %i\n", newnum); + } + + if (word & QW_U_REMOVE) + { + if (newnum != oldnum && !delta && !invalid) + { + cl.qw_validsequence = 0; + Con_Printf("WARNING: U_REMOVE %i on full update\n", newnum); + } + } + else + { + if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES) + Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES"); + newsnap->entities[newsnap->num_entities] = (newnum == oldnum) ? oldsnap->entities[oldindex] : cl.entities[newnum].state_baseline; + EntityStateQW_ReadEntityUpdate(newsnap->entities + newsnap->num_entities, word); + newsnap->num_entities++; + } + + if (newnum == oldnum) + oldindex++; + } + + // expand cl.num_entities to include every entity we've seen this game + newnum = newsnap->num_entities ? newsnap->entities[newsnap->num_entities - 1].number : 1; + if (cl.num_entities <= newnum) + { + cl.num_entities = newnum + 1; + if (cl.max_entities < newnum + 1) + CL_ExpandEntities(newnum); + } + + // now update the non-player entities from the snapshot states + number = cl.maxclients + 1; + for (newindex = 0;;newindex++) + { + newnum = newindex >= newsnap->num_entities ? cl.num_entities : newsnap->entities[newindex].number; + // kill any missing entities + for (;number < newnum;number++) + { + if (cl.entities_active[number]) + { + cl.entities_active[number] = false; + cl.entities[number].state_current.active = ACTIVE_NOT; + } + } + if (number >= cl.num_entities) + break; + // update the entity + ent = &cl.entities[number]; + ent->state_previous = ent->state_current; + ent->state_current = newsnap->entities[newindex]; + ent->state_current.time = cl.mtime[0]; + CL_MoveLerpEntityStates(ent); + // the entity lives again... + cl.entities_active[number] = true; + number++; + } +} diff --git a/app/jni/protocol.h b/app/jni/protocol.h new file mode 100644 index 0000000..3c0c6ce --- /dev/null +++ b/app/jni/protocol.h @@ -0,0 +1,1024 @@ +/* +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. + +*/ +// protocol.h -- communications protocols + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +// protocolversion_t is defined in common.h + +protocolversion_t Protocol_EnumForName(const char *s); +const char *Protocol_NameForEnum(protocolversion_t p); +protocolversion_t Protocol_EnumForNumber(int n); +int Protocol_NumberForEnum(protocolversion_t p); +void Protocol_Names(char *buffer, size_t buffersize); + +// model effects +#define MF_ROCKET 1 // leave a trail +#define MF_GRENADE 2 // leave a trail +#define MF_GIB 4 // leave a trail +#define MF_ROTATE 8 // rotate (bonus items) +#define MF_TRACER 16 // green split trail +#define MF_ZOMGIB 32 // small blood trail +#define MF_TRACER2 64 // orange split trail + rotate +#define MF_TRACER3 128 // purple trail + +// entity effects +#define EF_BRIGHTFIELD 1 +#define EF_MUZZLEFLASH 2 +#define EF_BRIGHTLIGHT 4 +#define EF_DIMLIGHT 8 +#define EF_NODRAW 16 +#define EF_ADDITIVE 32 +#define EF_BLUE 64 +#define EF_RED 128 +#define EF_NOGUNBOB 256 // LordHavoc: when used with .viewmodelforclient this makes the entity attach to the view without gun bobbing and such effects, it also works on the player entity to disable gun bobbing of the engine-managed .viewmodel (without affecting any .viewmodelforclient entities attached to the player) +#define EF_FULLBRIGHT 512 // LordHavoc: fullbright +#define EF_FLAME 1024 // LordHavoc: on fire +#define EF_STARDUST 2048 // LordHavoc: showering sparks +#define EF_NOSHADOW 4096 // LordHavoc: does not cast a shadow +#define EF_NODEPTHTEST 8192 // LordHavoc: shows through walls +#define EF_SELECTABLE 16384 // LordHavoc: highlights when PRYDON_CLIENTCURSOR mouse is over it +#define EF_DOUBLESIDED 32768 //[515]: disable cull face for this entity +#define EF_NOSELFSHADOW 65536 // LordHavoc: does not cast a shadow on itself (or any other EF_NOSELFSHADOW entities) +#define EF_DYNAMICMODELLIGHT 131072 +#define EF_UNUSED18 262144 +#define EF_UNUSED19 524288 +#define EF_RESTARTANIM_BIT 1048576 // div0: restart animation bit (like teleport bit, but lerps between end and start of the anim, and doesn't stop player lerping) +#define EF_TELEPORT_BIT 2097152 // div0: teleport bit (toggled when teleporting, prevents lerping when the bit has changed) +#define EF_LOWPRECISION 4194304 // LordHavoc: entity is low precision (integer coordinates) to save network bandwidth (serverside only) +#define EF_NOMODELFLAGS 8388608 // indicates the model's .effects should be ignored (allows overriding them) +#define EF_ROCKET 16777216 // leave a trail +#define EF_GRENADE 33554432 // leave a trail +#define EF_GIB 67108864 // leave a trail +#define EF_ROTATE 134217728 // rotate (bonus items) +#define EF_TRACER 268435456 // green split trail +#define EF_ZOMGIB 536870912 // small blood trail +#define EF_TRACER2 1073741824 // orange split trail + rotate +#define EF_TRACER3 0x80000000 // purple trail + +// internaleffects bits (no overlap with EF_ bits): +#define INTEF_FLAG1QW 1 +#define INTEF_FLAG2QW 2 + +// flags for the pflags field of entities +#define PFLAGS_NOSHADOW 1 +#define PFLAGS_CORONA 2 +#define PFLAGS_FULLDYNAMIC 128 // must be set or the light fields are ignored + +// if the high bit of the servercmd is set, the low bits are fast update flags: +#define U_MOREBITS (1<<0) +#define U_ORIGIN1 (1<<1) +#define U_ORIGIN2 (1<<2) +#define U_ORIGIN3 (1<<3) +#define U_ANGLE2 (1<<4) +// LordHavoc: U_NOLERP was only ever used for monsters, so I renamed it U_STEP +#define U_STEP (1<<5) +#define U_FRAME (1<<6) +// just differentiates from other updates +#define U_SIGNAL (1<<7) + +#define U_ANGLE1 (1<<8) +#define U_ANGLE3 (1<<9) +#define U_MODEL (1<<10) +#define U_COLORMAP (1<<11) +#define U_SKIN (1<<12) +#define U_EFFECTS (1<<13) +#define U_LONGENTITY (1<<14) + +// LordHavoc: protocol extension +#define U_EXTEND1 (1<<15) +// LordHavoc: first extend byte +#define U_DELTA (1<<16) // no data, while this is set the entity is delta compressed (uses previous frame as a baseline, meaning only things that have changed from the previous frame are sent, except for the forced full update every half second) +#define U_ALPHA (1<<17) // 1 byte, 0.0-1.0 maps to 0-255, not sent if exactly 1, and the entity is not sent if <=0 unless it has effects (model effects are checked as well) +#define U_SCALE (1<<18) // 1 byte, scale / 16 positive, not sent if 1.0 +#define U_EFFECTS2 (1<<19) // 1 byte, this is .effects & 0xFF00 (second byte) +#define U_GLOWSIZE (1<<20) // 1 byte, encoding is float/4.0, unsigned, not sent if 0 +#define U_GLOWCOLOR (1<<21) // 1 byte, palette index, default is 254 (white), this IS used for darklight (allowing colored darklight), however the particles from a darklight are always black, not sent if default value (even if glowsize or glowtrail is set) +#define U_COLORMOD (1<<22) // 1 byte, 3 bit red, 3 bit green, 2 bit blue, this lets you tint an object artifically, so you could make a red rocket, or a blue fiend... +#define U_EXTEND2 (1<<23) // another byte to follow +// LordHavoc: second extend byte +#define U_GLOWTRAIL (1<<24) // leaves a trail of particles (of color .glowcolor, or black if it is a negative glowsize) +#define U_VIEWMODEL (1<<25) // attachs the model to the view (origin and angles become relative to it), only shown to owner, a more powerful alternative to .weaponmodel and such +#define U_FRAME2 (1<<26) // 1 byte, this is .frame & 0xFF00 (second byte) +#define U_MODEL2 (1<<27) // 1 byte, this is .modelindex & 0xFF00 (second byte) +#define U_EXTERIORMODEL (1<<28) // causes this model to not be drawn when using a first person view (third person will draw it, first person will not) +#define U_UNUSED29 (1<<29) // future expansion +#define U_UNUSED30 (1<<30) // future expansion +#define U_EXTEND3 (1<<31) // another byte to follow, future expansion + +#define SU_VIEWHEIGHT (1<<0) +#define SU_IDEALPITCH (1<<1) +#define SU_PUNCH1 (1<<2) +#define SU_PUNCH2 (1<<3) +#define SU_PUNCH3 (1<<4) +#define SU_VELOCITY1 (1<<5) +#define SU_VELOCITY2 (1<<6) +#define SU_VELOCITY3 (1<<7) +//define SU_AIMENT (1<<8) AVAILABLE BIT +#define SU_ITEMS (1<<9) +#define SU_ONGROUND (1<<10) // no data follows, the bit is it +#define SU_INWATER (1<<11) // no data follows, the bit is it +#define SU_WEAPONFRAME (1<<12) +#define SU_ARMOR (1<<13) +#define SU_WEAPON (1<<14) +#define SU_EXTEND1 (1<<15) +// first extend byte +#define SU_PUNCHVEC1 (1<<16) +#define SU_PUNCHVEC2 (1<<17) +#define SU_PUNCHVEC3 (1<<18) +#define SU_VIEWZOOM (1<<19) // byte factor (0 = 0.0 (not valid), 255 = 1.0) +#define SU_UNUSED20 (1<<20) +#define SU_UNUSED21 (1<<21) +#define SU_UNUSED22 (1<<22) +#define SU_EXTEND2 (1<<23) // another byte to follow, future expansion +// second extend byte +#define SU_UNUSED24 (1<<24) +#define SU_UNUSED25 (1<<25) +#define SU_UNUSED26 (1<<26) +#define SU_UNUSED27 (1<<27) +#define SU_UNUSED28 (1<<28) +#define SU_UNUSED29 (1<<29) +#define SU_UNUSED30 (1<<30) +#define SU_EXTEND3 (1<<31) // another byte to follow, future expansion + +// a sound with no channel is a local only sound +#define SND_VOLUME (1<<0) // a byte +#define SND_ATTENUATION (1<<1) // a byte +#define SND_LOOPING (1<<2) // a long +#define SND_LARGEENTITY (1<<3) // a short and a byte (instead of a short) +#define SND_LARGESOUND (1<<4) // a short (instead of a byte) +#define SND_SPEEDUSHORT4000 (1<<5) // ushort speed*4000 (speed is usually 1.0, a value of 0.0 is the same as 1.0) + + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 22 + + +// game types sent by serverinfo +// these determine which intermission screen plays +#define GAME_COOP 0 +#define GAME_DEATHMATCH 1 + +//================== +// note that there are some defs.qc that mirror to these numbers +// also related to svc_strings[] in cl_parse +//================== + +// +// server to client +// +#define svc_bad 0 +#define svc_nop 1 +#define svc_disconnect 2 +#define svc_updatestat 3 // [byte] [long] +#define svc_version 4 // [long] server version +#define svc_setview 5 // [short] entity number +#define svc_sound 6 // +#define svc_time 7 // [float] server time +#define svc_print 8 // [string] null terminated string +#define svc_stufftext 9 // [string] stuffed into client's console buffer + // the string should be \n terminated +#define svc_setangle 10 // [angle3] set the view angle to this absolute value + +#define svc_serverinfo 11 // [long] version + // [string] signon string + // [string]..[0]model cache + // [string]...[0]sounds cache +#define svc_lightstyle 12 // [byte] [string] +#define svc_updatename 13 // [byte] [string] +#define svc_updatefrags 14 // [byte] [short] +#define svc_clientdata 15 // +#define svc_stopsound 16 // +#define svc_updatecolors 17 // [byte] [byte] +#define svc_particle 18 // [vec3] +#define svc_damage 19 + +#define svc_spawnstatic 20 +// svc_spawnbinary 21 +#define svc_spawnbaseline 22 + +#define svc_temp_entity 23 + +#define svc_setpause 24 // [byte] on / off +#define svc_signonnum 25 // [byte] used for the signon sequence + +#define svc_centerprint 26 // [string] to put in center of the screen + +#define svc_killedmonster 27 +#define svc_foundsecret 28 + +#define svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten + +#define svc_intermission 30 // [string] music +#define svc_finale 31 // [string] music [string] text + +#define svc_cdtrack 32 // [byte] track [byte] looptrack +#define svc_sellscreen 33 + +#define svc_cutscene 34 + +#define svc_showlmp 35 // [string] slotname [string] lmpfilename [short] x [short] y +#define svc_hidelmp 36 // [string] slotname +#define svc_skybox 37 // [string] skyname + +// LordHavoc: my svc_ range, 50-69 +#define svc_downloaddata 50 // [int] start [short] size +#define svc_updatestatubyte 51 // [byte] stat [byte] value +#define svc_effect 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate +#define svc_effect2 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate +#define svc_sound2 54 // (obsolete in DP6 and later) short soundindex instead of byte +#define svc_precache 54 // [short] precacheindex [string] filename, precacheindex is + 0 for modelindex and +32768 for soundindex +#define svc_spawnbaseline2 55 // short modelindex instead of byte +#define svc_spawnstatic2 56 // short modelindex instead of byte +#define svc_entities 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata +#define svc_csqcentities 58 // [short] entnum [variable length] entitydata ... [short] 0x0000 +#define svc_spawnstaticsound2 59 // [coord3] [short] samp [byte] vol [byte] aten +#define svc_trailparticles 60 // [short] entnum [short] effectnum [vector] start [vector] end +#define svc_pointparticles 61 // [short] effectnum [vector] start [vector] velocity [short] count +#define svc_pointparticles1 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 + +// +// client to server +// +#define clc_bad 0 +#define clc_nop 1 +#define clc_disconnect 2 +#define clc_move 3 // [usercmd_t] +#define clc_stringcmd 4 // [string] message + +// LordHavoc: my clc_ range, 50-59 +#define clc_ackframe 50 // [int] framenumber +#define clc_ackdownloaddata 51 // [int] start [short] size (note: exact echo of latest values received in svc_downloaddata, packet-loss handling is in the server) +#define clc_unusedlh2 52 +#define clc_unusedlh3 53 +#define clc_unusedlh4 54 +#define clc_unusedlh5 55 +#define clc_unusedlh6 56 +#define clc_unusedlh7 57 +#define clc_unusedlh8 58 +#define clc_unusedlh9 59 + +// +// temp entity events +// +#define TE_SPIKE 0 // [vector] origin +#define TE_SUPERSPIKE 1 // [vector] origin +#define TE_GUNSHOT 2 // [vector] origin +#define TE_EXPLOSION 3 // [vector] origin +#define TE_TAREXPLOSION 4 // [vector] origin +#define TE_LIGHTNING1 5 // [entity] entity [vector] start [vector] end +#define TE_LIGHTNING2 6 // [entity] entity [vector] start [vector] end +#define TE_WIZSPIKE 7 // [vector] origin +#define TE_KNIGHTSPIKE 8 // [vector] origin +#define TE_LIGHTNING3 9 // [entity] entity [vector] start [vector] end +#define TE_LAVASPLASH 10 // [vector] origin +#define TE_TELEPORT 11 // [vector] origin +#define TE_EXPLOSION2 12 // [vector] origin [byte] startcolor [byte] colorcount + +// PGM 01/21/97 +#define TE_BEAM 13 // [entity] entity [vector] start [vector] end +// PGM 01/21/97 + +// Nehahra effects used in the movie (TE_EXPLOSION3 also got written up in a QSG tutorial, hence it's not marked NEH) +#define TE_EXPLOSION3 16 // [vector] origin [coord] red [coord] green [coord] blue +#define TE_LIGHTNING4NEH 17 // [string] model [entity] entity [vector] start [vector] end + +// LordHavoc: added some TE_ codes (block1 - 50-60) +#define TE_BLOOD 50 // [vector] origin [byte] xvel [byte] yvel [byte] zvel [byte] count +#define TE_SPARK 51 // [vector] origin [byte] xvel [byte] yvel [byte] zvel [byte] count +#define TE_BLOODSHOWER 52 // [vector] min [vector] max [coord] explosionspeed [short] count +#define TE_EXPLOSIONRGB 53 // [vector] origin [byte] red [byte] green [byte] blue +#define TE_PARTICLECUBE 54 // [vector] min [vector] max [vector] dir [short] count [byte] color [byte] gravity [coord] randomvel +#define TE_PARTICLERAIN 55 // [vector] min [vector] max [vector] dir [short] count [byte] color +#define TE_PARTICLESNOW 56 // [vector] min [vector] max [vector] dir [short] count [byte] color +#define TE_GUNSHOTQUAD 57 // [vector] origin +#define TE_SPIKEQUAD 58 // [vector] origin +#define TE_SUPERSPIKEQUAD 59 // [vector] origin +// LordHavoc: block2 - 70-80 +#define TE_EXPLOSIONQUAD 70 // [vector] origin +#define TE_UNUSED1 71 // unused +#define TE_SMALLFLASH 72 // [vector] origin +#define TE_CUSTOMFLASH 73 // [vector] origin [byte] radius / 8 - 1 [byte] lifetime / 256 - 1 [byte] red [byte] green [byte] blue +#define TE_FLAMEJET 74 // [vector] origin [vector] velocity [byte] count +#define TE_PLASMABURN 75 // [vector] origin +// LordHavoc: Tei grabbed these codes +#define TE_TEI_G3 76 // [vector] start [vector] end [vector] angles +#define TE_TEI_SMOKE 77 // [vector] origin [vector] dir [byte] count +#define TE_TEI_BIGEXPLOSION 78 // [vector] origin +#define TE_TEI_PLASMAHIT 79 // [vector} origin [vector] dir [byte] count + + +// these are bits for the 'flags' field of the entity_state_t +#define RENDER_STEP 1 +#define RENDER_GLOWTRAIL 2 +#define RENDER_VIEWMODEL 4 +#define RENDER_EXTERIORMODEL 8 +#define RENDER_LOWPRECISION 16 // send as low precision coordinates to save bandwidth +#define RENDER_COLORMAPPED 32 +#define RENDER_WORLDOBJECT 64 // do not cull this entity with r_cullentities +#define RENDER_COMPLEXANIMATION 128 + +#define RENDER_SHADOW 65536 // cast shadow +#define RENDER_LIGHT 131072 // receive light +#define RENDER_NOSELFSHADOW 262144 // render lighting on this entity before its own shadow is added to the scene +// (note: all RENDER_NOSELFSHADOW entities are grouped together and rendered in a batch before their shadows are rendered, so they can not shadow eachother either) +#define RENDER_EQUALIZE 524288 // (subflag of RENDER_LIGHT) equalize the light from the light grid hitting this ent (less invasive EF_FULLBRIGHT implementation) +#define RENDER_NODEPTHTEST 1048576 +#define RENDER_ADDITIVE 2097152 +#define RENDER_DOUBLESIDED 4194304 +#define RENDER_CUSTOMIZEDMODELLIGHT 4096 +#define RENDER_DYNAMICMODELLIGHT 8388608 // origin dependent model light + +#define MAX_FRAMEGROUPBLENDS 4 +typedef struct framegroupblend_s +{ + // animation number and blend factor + // (for most models this is the frame number) + int frame; + float lerp; + // time frame began playing (for framegroup animations) + double start; +} +framegroupblend_t; + +struct matrix4x4_s; +struct model_s; + +typedef struct skeleton_s +{ + const struct model_s *model; + struct matrix4x4_s *relativetransforms; +} +skeleton_t; + +typedef enum entity_state_active_e +{ + ACTIVE_NOT = 0, + ACTIVE_NETWORK = 1, + ACTIVE_SHARED = 2 +} +entity_state_active_t; + +// this was 96 bytes, now 168 bytes (32bit) or 176 bytes (64bit) +typedef struct entity_state_s +{ + // ! means this is not sent to client + double time; // ! time this state was built (used on client for interpolation) + float netcenter[3]; // ! for network prioritization, this is the center of the bounding box (which may differ from the origin) + float origin[3]; + float angles[3]; + int effects; + unsigned int customizeentityforclient; // ! + unsigned short number; // entity number this state is for + unsigned short modelindex; + unsigned short frame; + unsigned short tagentity; + unsigned short specialvisibilityradius; // ! larger if it has effects/light + unsigned short viewmodelforclient; // ! + unsigned short exteriormodelforclient; // ! not shown if first person viewing from this entity, shown in all other cases + unsigned short nodrawtoclient; // ! + unsigned short drawonlytoclient; // ! + unsigned short traileffectnum; + unsigned short light[4]; // color*256 (0.00 to 255.996), and radius*1 + unsigned char active; // true if a valid state + unsigned char lightstyle; + unsigned char lightpflags; + unsigned char colormap; + unsigned char skin; // also chooses cubemap for rtlights if lightpflags & LIGHTPFLAGS_FULLDYNAMIC + unsigned char alpha; + unsigned char scale; + unsigned char glowsize; + unsigned char glowcolor; + unsigned char flags; + unsigned char internaleffects; // INTEF_FLAG1QW and so on + unsigned char tagindex; + unsigned char colormod[3]; + unsigned char glowmod[3]; + // LordHavoc: very big data here :( + framegroupblend_t framegroupblend[4]; + skeleton_t skeletonobject; +} +entity_state_t; + +// baseline state values +extern entity_state_t defaultstate; +// reads a quake entity from the network stream +void EntityFrameQuake_ReadEntity(int bits); +// checks for stats changes and sets corresponding host_client->statsdeltabits +// (also updates host_client->stats array) +void Protocol_UpdateClientStats(const int *stats); +// writes reliable messages updating stats (not used by DP6 and later +// protocols which send updates in their WriteFrame function using a different +// method of reliable messaging) +void Protocol_WriteStatsReliable(void); +// writes a list of quake entities to the network stream +// (or as many will fit) +qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states); +// cleans up dead entities each frame after ReadEntity (which doesn't clear unused entities) +void EntityFrameQuake_ISeeDeadEntities(void); + +/* +PROTOCOL_DARKPLACES3 +server updates entities according to some (unmentioned) scheme. + +a frame consists of all visible entities, some of which are up to date, +often some are not up to date. + +these entities are stored in a range (firstentity/endentity) of structs in the +entitydata[] buffer. + +to make a commit the server performs these steps: +1. duplicate oldest frame in database (this is the baseline) as new frame, and + write frame numbers (oldest frame's number, new frame's number) and eye + location to network packet (eye location is obsolete and will be removed in + future revisions) +2. write an entity change to packet and modify new frame accordingly + (this repeats until packet is sufficiently full or new frame is complete) +3. write terminator (0xFFFF) to network packet + (FIXME: this terminator value conflicts with MAX_EDICTS 32768...) + +to read a commit the client performs these steps: +1. reads frame numbers from packet and duplicates baseline frame as new frame, + also reads eye location but does nothing with it (obsolete). +2. delete frames older than the baseline which was used +3. read entity changes from packet until terminator (0xFFFF) is encountered, + each change is applied to entity frame. +4. sends ack framenumber to server as part of input packet + +if server receives ack message in put packet it performs these steps: +1. remove all older frames from database. +*/ + +/* +PROTOCOL_DARKPLACES4 +a frame consists of some visible entities in a range (this is stored as start and end, note that end may be less than start if it wrapped). + +these entities are stored in a range (firstentity/endentity) of structs in the entitydata[] buffer. + +to make a commit the server performs these steps: +1. build an entity_frame_t using appropriate functions, containing (some of) the visible entities, this is passed to the Write function to send it. + +This documention is unfinished! +the Write function performs these steps: +1. check if entity frame is larger than MAX_ENTITYFRAME or is larger than available space in database, if so the baseline is defaults, otherwise it is the current baseline of the database. +2. write differences of an entity compared to selected baseline. +3. add entity to entity update in database. +4. if there are more entities to write and packet is not full, go back to step 2. +5. write terminator (0xFFFF) as entity number. +6. return. + + + + + +server updates entities in looping ranges, a frame consists of a range of visible entities (not always all visible entities), +*/ + +#define MAX_ENTITY_HISTORY 64 +#define MAX_ENTITY_DATABASE (MAX_EDICTS * 2) + +// build entity data in this, to pass to entity read/write functions +typedef struct entity_frame_s +{ + double time; + int framenum; + int numentities; + int firstentitynum; + int lastentitynum; + vec3_t eye; + entity_state_t entitydata[MAX_ENTITY_DATABASE]; +} +entity_frame_t; + +typedef struct entity_frameinfo_s +{ + double time; + int framenum; + int firstentity; // index into entitydata, modulo MAX_ENTITY_DATABASE + int endentity; // index into entitydata, firstentity + numentities +} +entity_frameinfo_t; + +typedef struct entityframe_database_s +{ + // note: these can be far out of range, modulo with MAX_ENTITY_DATABASE to get a valid range (which may wrap) + // start and end of used area, when adding a new update to database, store at endpos, and increment endpos + // when removing updates from database, nudge down frames array to only contain useful frames + // this logic should explain better: + // if (numframes >= MAX_ENTITY_HISTORY || (frames[numframes - 1].endentity - frames[0].firstentity) + entitiestoadd > MAX_ENTITY_DATABASE) + // flushdatabase(); + // note: if numframes == 0, insert at start (0 in entitydata) + // the only reason this system is used is to avoid copying memory when frames are removed + int numframes; + // server only: last sent frame + int latestframenum; + // server only: last acknowledged frame + int ackframenum; + // the current state in the database + vec3_t eye; + // table of entities in the entityhistorydata + entity_frameinfo_t frames[MAX_ENTITY_HISTORY]; + // entities + entity_state_t entitydata[MAX_ENTITY_DATABASE]; + + // structs for building new frames and reading them + entity_frame_t deltaframe; + entity_frame_t framedata; +} +entityframe_database_t; + +// LordHavoc: these are in approximately sorted order, according to cost and +// likelyhood of being used for numerous objects in a frame + +// note that the bytes are not written/read in this order, this is only the +// order of the bits to minimize overhead from extend bytes + +// enough to describe a nail, gib, shell casing, bullet hole, or rocket +#define E_ORIGIN1 (1<<0) +#define E_ORIGIN2 (1<<1) +#define E_ORIGIN3 (1<<2) +#define E_ANGLE1 (1<<3) +#define E_ANGLE2 (1<<4) +#define E_ANGLE3 (1<<5) +#define E_MODEL1 (1<<6) +#define E_EXTEND1 (1<<7) + +// enough to describe almost anything +#define E_FRAME1 (1<<8) +#define E_EFFECTS1 (1<<9) +#define E_ALPHA (1<<10) +#define E_SCALE (1<<11) +#define E_COLORMAP (1<<12) +#define E_SKIN (1<<13) +#define E_FLAGS (1<<14) +#define E_EXTEND2 (1<<15) + +// players, custom color glows, high model numbers +#define E_FRAME2 (1<<16) +#define E_MODEL2 (1<<17) +#define E_EFFECTS2 (1<<18) +#define E_GLOWSIZE (1<<19) +#define E_GLOWCOLOR (1<<20) +#define E_LIGHT (1<<21) +#define E_LIGHTPFLAGS (1<<22) +#define E_EXTEND3 (1<<23) + +#define E_SOUND1 (1<<24) +#define E_SOUNDVOL (1<<25) +#define E_SOUNDATTEN (1<<26) +#define E_TAGATTACHMENT (1<<27) +#define E_LIGHTSTYLE (1<<28) +#define E_UNUSED6 (1<<29) +#define E_UNUSED7 (1<<30) +#define E_EXTEND4 (1<<31) + +// returns difference between two states as E_ flags +int EntityState_DeltaBits(const entity_state_t *o, const entity_state_t *n); +// write E_ flags to a msg +void EntityState_WriteExtendBits(sizebuf_t *msg, unsigned int bits); +// write values for the E_ flagged fields to a msg +void EntityState_WriteFields(const entity_state_t *ent, sizebuf_t *msg, unsigned int bits); +// write entity number and E_ flags and their values, or a remove number, describing the change from delta to ent +void EntityState_WriteUpdate(const entity_state_t *ent, sizebuf_t *msg, const entity_state_t *delta); +// read E_ flags +int EntityState_ReadExtendBits(void); +// read values for E_ flagged fields and apply them to a state +void EntityState_ReadFields(entity_state_t *e, unsigned int bits); + +// (client and server) allocates a new empty database +entityframe_database_t *EntityFrame_AllocDatabase(mempool_t *mempool); +// (client and server) frees the database +void EntityFrame_FreeDatabase(entityframe_database_t *d); +// (server) clears the database to contain no frames (thus delta compression +// compresses against nothing) +void EntityFrame_ClearDatabase(entityframe_database_t *d); +// (server and client) removes frames older than 'frame' from database +void EntityFrame_AckFrame(entityframe_database_t *d, int frame); +// (server) clears frame, to prepare for adding entities +void EntityFrame_Clear(entity_frame_t *f, vec3_t eye, int framenum); +// (server and client) reads a frame from the database +void EntityFrame_FetchFrame(entityframe_database_t *d, int framenum, entity_frame_t *f); +// (client) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata); +// (server) adds a entity_frame to the database, for future reference +void EntityFrame_AddFrame_Server(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t **entitydata); +// (server) writes a frame to network stream +qboolean EntityFrame_WriteFrame(sizebuf_t *msg, int maxsize, entityframe_database_t *d, int numstates, const entity_state_t **states, int viewentnum); +// (client) reads a frame from network stream +void EntityFrame_CL_ReadFrame(void); +// (client) returns the frame number of the most recent frame recieved +int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d); + +typedef struct entity_database4_commit_s +{ + // frame number this commit represents + int framenum; + // number of entities in entity[] array + int numentities; + // maximum number of entities in entity[] array (dynamic resizing) + int maxentities; + entity_state_t *entity; +} +entity_database4_commit_t; + +typedef struct entity_database4_s +{ + // what mempool to use for allocations + mempool_t *mempool; + // reference frame + int referenceframenum; + // reference entities array is resized according to demand + int maxreferenceentities; + // array of states for entities, these are indexable by their entity number (yes there are gaps) + entity_state_t *referenceentity; + // commits waiting to be applied to the reference database when confirmed + // (commit[i]->numentities == 0 means it is empty) + entity_database4_commit_t commit[MAX_ENTITY_HISTORY]; + // (server only) the current commit being worked on + entity_database4_commit_t *currentcommit; + // (server only) if a commit won't fit entirely, continue where it left + // off next frame + int currententitynumber; + // (server only) + int latestframenumber; +} +entityframe4_database_t; + +// should-be-private functions that aren't +entity_state_t *EntityFrame4_GetReferenceEntity(entityframe4_database_t *d, int number); +void EntityFrame4_AddCommitEntity(entityframe4_database_t *d, const entity_state_t *s); + +// allocate a database +entityframe4_database_t *EntityFrame4_AllocDatabase(mempool_t *pool); +// free a database +void EntityFrame4_FreeDatabase(entityframe4_database_t *d); +// reset a database (resets compression but does not reallocate anything) +void EntityFrame4_ResetDatabase(entityframe4_database_t *d); +// updates database to account for a frame-received acknowledgment +int EntityFrame4_AckFrame(entityframe4_database_t *d, int framenum, int servermode); +// writes a frame to the network stream +qboolean EntityFrame4_WriteFrame(sizebuf_t *msg, int maxsize, entityframe4_database_t *d, int numstates, const entity_state_t **states); +// reads a frame from the network stream +void EntityFrame4_CL_ReadFrame(void); + +// reset all entity fields (typically used if status changed) +#define E5_FULLUPDATE (1<<0) +// E5_ORIGIN32=0: short[3] = s->origin[0] * 8, s->origin[1] * 8, s->origin[2] * 8 +// E5_ORIGIN32=1: float[3] = s->origin[0], s->origin[1], s->origin[2] +#define E5_ORIGIN (1<<1) +// E5_ANGLES16=0: byte[3] = s->angle[0] * 256 / 360, s->angle[1] * 256 / 360, s->angle[2] * 256 / 360 +// E5_ANGLES16=1: short[3] = s->angle[0] * 65536 / 360, s->angle[1] * 65536 / 360, s->angle[2] * 65536 / 360 +#define E5_ANGLES (1<<2) +// E5_MODEL16=0: byte = s->modelindex +// E5_MODEL16=1: short = s->modelindex +#define E5_MODEL (1<<3) +// E5_FRAME16=0: byte = s->frame +// E5_FRAME16=1: short = s->frame +#define E5_FRAME (1<<4) +// byte = s->skin +#define E5_SKIN (1<<5) +// E5_EFFECTS16=0 && E5_EFFECTS32=0: byte = s->effects +// E5_EFFECTS16=1 && E5_EFFECTS32=0: short = s->effects +// E5_EFFECTS16=0 && E5_EFFECTS32=1: int = s->effects +// E5_EFFECTS16=1 && E5_EFFECTS32=1: int = s->effects +#define E5_EFFECTS (1<<6) +// bits >= (1<<8) +#define E5_EXTEND1 (1<<7) + +// byte = s->renderflags +#define E5_FLAGS (1<<8) +// byte = bound(0, s->alpha * 255, 255) +#define E5_ALPHA (1<<9) +// byte = bound(0, s->scale * 16, 255) +#define E5_SCALE (1<<10) +// flag +#define E5_ORIGIN32 (1<<11) +// flag +#define E5_ANGLES16 (1<<12) +// flag +#define E5_MODEL16 (1<<13) +// byte = s->colormap +#define E5_COLORMAP (1<<14) +// bits >= (1<<16) +#define E5_EXTEND2 (1<<15) + +// short = s->tagentity +// byte = s->tagindex +#define E5_ATTACHMENT (1<<16) +// short[4] = s->light[0], s->light[1], s->light[2], s->light[3] +// byte = s->lightstyle +// byte = s->lightpflags +#define E5_LIGHT (1<<17) +// byte = s->glowsize +// byte = s->glowcolor +#define E5_GLOW (1<<18) +// short = s->effects +#define E5_EFFECTS16 (1<<19) +// int = s->effects +#define E5_EFFECTS32 (1<<20) +// flag +#define E5_FRAME16 (1<<21) +// byte[3] = s->colormod[0], s->colormod[1], s->colormod[2] +#define E5_COLORMOD (1<<22) +// bits >= (1<<24) +#define E5_EXTEND3 (1<<23) + +// byte[3] = s->glowmod[0], s->glowmod[1], s->glowmod[2] +#define E5_GLOWMOD (1<<24) +// byte type=0 short frames[1] short times[1] +// byte type=1 short frames[2] short times[2] byte lerps[2] +// byte type=2 short frames[3] short times[3] byte lerps[3] +// byte type=3 short frames[4] short times[4] byte lerps[4] +// byte type=4 short modelindex byte numbones {short pose7s[7]} +// see also RENDER_COMPLEXANIMATION +#define E5_COMPLEXANIMATION (1<<25) +// ushort traileffectnum +#define E5_TRAILEFFECTNUM (1<<26) +// unused +#define E5_UNUSED27 (1<<27) +// unused +#define E5_UNUSED28 (1<<28) +// unused +#define E5_UNUSED29 (1<<29) +// unused +#define E5_UNUSED30 (1<<30) +// bits2 > 0 +#define E5_EXTEND4 (1<<31) + +#define ENTITYFRAME5_MAXPACKETLOGS 64 +#define ENTITYFRAME5_MAXSTATES 1024 +#define ENTITYFRAME5_PRIORITYLEVELS 32 + +typedef struct entityframe5_changestate_s +{ + unsigned int number; + unsigned int bits; +} +entityframe5_changestate_t; + +typedef struct entityframe5_packetlog_s +{ + int packetnumber; + int numstates; + entityframe5_changestate_t states[ENTITYFRAME5_MAXSTATES]; + unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; +} +entityframe5_packetlog_t; + +typedef struct entityframe5_database_s +{ + // number of the latest message sent to client + int latestframenum; + // updated by WriteFrame for internal use + int viewentnum; + + // logs of all recently sent messages (between acked and latest) + entityframe5_packetlog_t packetlog[ENTITYFRAME5_MAXPACKETLOGS]; + + // this goes up as needed and causes all the arrays to be reallocated + int maxedicts; + + // which properties of each entity have changed since last send + int *deltabits; // [maxedicts] + // priorities of entities (updated whenever deltabits change) + // (derived from deltabits) + unsigned char *priorities; // [maxedicts] + // last frame this entity was sent on, for prioritzation + int *updateframenum; // [maxedicts] + + // database of current status of all entities + entity_state_t *states; // [maxedicts] + // which entities are currently active + // (duplicate of the active bit of every state in states[]) + // (derived from states) + unsigned char *visiblebits; // [(maxedicts+7)/8] + + // old notes + + // this is used to decide which changestates to set each frame + //int numvisiblestates; + //entity_state_t visiblestates[MAX_EDICTS]; + + // sorted changing states that need to be sent to the client + // kept sorted in lowest to highest priority order, because this allows + // the numchangestates to simply be decremented whenever an state is sent, + // rather than a memmove to remove them from the start. + //int numchangestates; + //entityframe5_changestate_t changestates[MAX_EDICTS]; + + // buffers for building priority info + int prioritychaincounts[ENTITYFRAME5_PRIORITYLEVELS]; + unsigned short prioritychains[ENTITYFRAME5_PRIORITYLEVELS][ENTITYFRAME5_MAXSTATES]; +} +entityframe5_database_t; + +entityframe5_database_t *EntityFrame5_AllocDatabase(mempool_t *pool); +void EntityFrame5_FreeDatabase(entityframe5_database_t *d); +void EntityState5_WriteUpdate(int number, const entity_state_t *s, int changedbits, sizebuf_t *msg); +int EntityState5_DeltaBitsForState(entity_state_t *o, entity_state_t *n); +void EntityFrame5_CL_ReadFrame(void); +void EntityFrame5_LostFrame(entityframe5_database_t *d, int framenum); +void EntityFrame5_AckFrame(entityframe5_database_t *d, int framenum); +qboolean EntityFrame5_WriteFrame(sizebuf_t *msg, int maxsize, entityframe5_database_t *d, int numstates, const entity_state_t **states, int viewentnum, int movesequence, qboolean need_empty); + +extern cvar_t developer_networkentities; + +// QUAKEWORLD +// server to client +#define qw_svc_bad 0 +#define qw_svc_nop 1 +#define qw_svc_disconnect 2 +#define qw_svc_updatestat 3 // [byte] [byte] +#define qw_svc_setview 5 // [short] entity number +#define qw_svc_sound 6 // +#define qw_svc_print 8 // [byte] id [string] null terminated string +#define qw_svc_stufftext 9 // [string] stuffed into client's console buffer +#define qw_svc_setangle 10 // [angle3] set the view angle to this absolute value +#define qw_svc_serverdata 11 // [long] protocol ... +#define qw_svc_lightstyle 12 // [byte] [string] +#define qw_svc_updatefrags 14 // [byte] [short] +#define qw_svc_stopsound 16 // +#define qw_svc_damage 19 +#define qw_svc_spawnstatic 20 +#define qw_svc_spawnbaseline 22 +#define qw_svc_temp_entity 23 // variable +#define qw_svc_setpause 24 // [byte] on / off +#define qw_svc_centerprint 26 // [string] to put in center of the screen +#define qw_svc_killedmonster 27 +#define qw_svc_foundsecret 28 +#define qw_svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten +#define qw_svc_intermission 30 // [vec3_t] origin [vec3_t] angle +#define qw_svc_finale 31 // [string] text +#define qw_svc_cdtrack 32 // [byte] track +#define qw_svc_sellscreen 33 +#define qw_svc_smallkick 34 // set client punchangle to 2 +#define qw_svc_bigkick 35 // set client punchangle to 4 +#define qw_svc_updateping 36 // [byte] [short] +#define qw_svc_updateentertime 37 // [byte] [float] +#define qw_svc_updatestatlong 38 // [byte] [long] +#define qw_svc_muzzleflash 39 // [short] entity +#define qw_svc_updateuserinfo 40 // [byte] slot [long] uid +#define qw_svc_download 41 // [short] size [size bytes] +#define qw_svc_playerinfo 42 // variable +#define qw_svc_nails 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 +#define qw_svc_chokecount 44 // [byte] packets choked +#define qw_svc_modellist 45 // [strings] +#define qw_svc_soundlist 46 // [strings] +#define qw_svc_packetentities 47 // [...] +#define qw_svc_deltapacketentities 48 // [...] +#define qw_svc_maxspeed 49 // maxspeed change, for prediction +#define qw_svc_entgravity 50 // gravity change, for prediction +#define qw_svc_setinfo 51 // setinfo on a client +#define qw_svc_serverinfo 52 // serverinfo +#define qw_svc_updatepl 53 // [byte] [byte] +// QUAKEWORLD +// client to server +#define qw_clc_bad 0 +#define qw_clc_nop 1 +#define qw_clc_move 3 // [[usercmd_t] +#define qw_clc_stringcmd 4 // [string] message +#define qw_clc_delta 5 // [byte] sequence number, requests delta compression of message +#define qw_clc_tmove 6 // teleport request, spectator only +#define qw_clc_upload 7 // teleport request, spectator only +// QUAKEWORLD +// playerinfo flags from server +// playerinfo always sends: playernum, flags, origin[] and framenumber +#define QW_PF_MSEC (1<<0) +#define QW_PF_COMMAND (1<<1) +#define QW_PF_VELOCITY1 (1<<2) +#define QW_PF_VELOCITY2 (1<<3) +#define QW_PF_VELOCITY3 (1<<4) +#define QW_PF_MODEL (1<<5) +#define QW_PF_SKINNUM (1<<6) +#define QW_PF_EFFECTS (1<<7) +#define QW_PF_WEAPONFRAME (1<<8) // only sent for view player +#define QW_PF_DEAD (1<<9) // don't block movement any more +#define QW_PF_GIB (1<<10) // offset the view height differently +#define QW_PF_NOGRAV (1<<11) // don't apply gravity for prediction +// QUAKEWORLD +// if the high bit of the client to server byte is set, the low bits are +// client move cmd bits +// ms and angle2 are allways sent, the others are optional +#define QW_CM_ANGLE1 (1<<0) +#define QW_CM_ANGLE3 (1<<1) +#define QW_CM_FORWARD (1<<2) +#define QW_CM_SIDE (1<<3) +#define QW_CM_UP (1<<4) +#define QW_CM_BUTTONS (1<<5) +#define QW_CM_IMPULSE (1<<6) +#define QW_CM_ANGLE2 (1<<7) +// QUAKEWORLD +// the first 16 bits of a packetentities update holds 9 bits +// of entity number and 7 bits of flags +#define QW_U_ORIGIN1 (1<<9) +#define QW_U_ORIGIN2 (1<<10) +#define QW_U_ORIGIN3 (1<<11) +#define QW_U_ANGLE2 (1<<12) +#define QW_U_FRAME (1<<13) +#define QW_U_REMOVE (1<<14) // REMOVE this entity, don't add it +#define QW_U_MOREBITS (1<<15) +// if MOREBITS is set, these additional flags are read in next +#define QW_U_ANGLE1 (1<<0) +#define QW_U_ANGLE3 (1<<1) +#define QW_U_MODEL (1<<2) +#define QW_U_COLORMAP (1<<3) +#define QW_U_SKIN (1<<4) +#define QW_U_EFFECTS (1<<5) +#define QW_U_SOLID (1<<6) // the entity should be solid for prediction +// QUAKEWORLD +// temp entity events +#define QW_TE_SPIKE 0 +#define QW_TE_SUPERSPIKE 1 +#define QW_TE_GUNSHOT 2 +#define QW_TE_EXPLOSION 3 +#define QW_TE_TAREXPLOSION 4 +#define QW_TE_LIGHTNING1 5 +#define QW_TE_LIGHTNING2 6 +#define QW_TE_WIZSPIKE 7 +#define QW_TE_KNIGHTSPIKE 8 +#define QW_TE_LIGHTNING3 9 +#define QW_TE_LAVASPLASH 10 +#define QW_TE_TELEPORT 11 +#define QW_TE_BLOOD 12 +#define QW_TE_LIGHTNINGBLOOD 13 +// QUAKEWORLD +// effect flags +#define QW_EF_BRIGHTFIELD 1 +#define QW_EF_MUZZLEFLASH 2 +#define QW_EF_BRIGHTLIGHT 4 +#define QW_EF_DIMLIGHT 8 +#define QW_EF_FLAG1 16 +#define QW_EF_FLAG2 32 +#define QW_EF_BLUE 64 +#define QW_EF_RED 128 + +#define QW_UPDATE_BACKUP 64 +#define QW_UPDATE_MASK (QW_UPDATE_BACKUP - 1) +#define QW_MAX_PACKET_ENTITIES 64 + +// note: QW stats are directly compatible with NQ +// (but FRAGS, WEAPONFRAME, and VIEWHEIGHT are unused) +// so these defines are not actually used by darkplaces, but kept for reference +#define QW_STAT_HEALTH 0 +//#define QW_STAT_FRAGS 1 +#define QW_STAT_WEAPON 2 +#define QW_STAT_AMMO 3 +#define QW_STAT_ARMOR 4 +//#define QW_STAT_WEAPONFRAME 5 +#define QW_STAT_SHELLS 6 +#define QW_STAT_NAILS 7 +#define QW_STAT_ROCKETS 8 +#define QW_STAT_CELLS 9 +#define QW_STAT_ACTIVEWEAPON 10 +#define QW_STAT_TOTALSECRETS 11 +#define QW_STAT_TOTALMONSTERS 12 +#define QW_STAT_SECRETS 13 // bumped on client side by svc_foundsecret +#define QW_STAT_MONSTERS 14 // bumped by svc_killedmonster +#define QW_STAT_ITEMS 15 +//#define QW_STAT_VIEWHEIGHT 16 + +// build entity data in this, to pass to entity read/write functions +typedef struct entityframeqw_snapshot_s +{ + double time; + qboolean invalid; + int num_entities; + entity_state_t entities[QW_MAX_PACKET_ENTITIES]; +} +entityframeqw_snapshot_t; + +typedef struct entityframeqw_database_s +{ + entityframeqw_snapshot_t snapshot[QW_UPDATE_BACKUP]; +} +entityframeqw_database_t; + +entityframeqw_database_t *EntityFrameQW_AllocDatabase(mempool_t *pool); +void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d); +void EntityStateQW_ReadPlayerUpdate(void); +void EntityFrameQW_CL_ReadFrame(qboolean delta); + +struct client_s; +void EntityFrameCSQC_LostFrame(struct client_s *client, int framenum); +qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum); + +#endif + diff --git a/app/jni/prvm_cmds.c b/app/jni/prvm_cmds.c new file mode 100644 index 0000000..c9f5527 --- /dev/null +++ b/app/jni/prvm_cmds.c @@ -0,0 +1,7334 @@ +// AK +// Basically every vm builtin cmd should be in here. +// All 3 builtin and extension lists can be found here +// cause large (I think they will) parts are from pr_cmds the same copyright like in pr_cmds +// also applies here + +#include "quakedef.h" + +#include "prvm_cmds.h" +#include "libcurl.h" +#include + +#include "cl_collision.h" +#include "clvm_cmds.h" +#include "csprogs.h" +#include "ft2.h" +#include "mdfour.h" + +extern cvar_t prvm_backtraceforwarnings; +extern dllhandle_t ode_dll; + +// LordHavoc: changed this to NOT use a return statement, so that it can be used in functions that must return a value +void VM_Warning(prvm_prog_t *prog, const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + static double recursive = -1; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Con_Print(msg); + + // TODO: either add a cvar/cmd to control the state dumping or replace some of the calls with Con_Printf [9/13/2006 Black] + if(prvm_backtraceforwarnings.integer && recursive != realtime) // NOTE: this compares to the time, just in case if PRVM_PrintState causes a Host_Error and keeps recursive set + { + recursive = realtime; + PRVM_PrintState(prog, 0); + recursive = -1; + } +} + + +//============================================================================ +// Common + +// TODO DONE: move vm_files and vm_fssearchlist to prvm_prog_t struct +// TODO: move vm_files and vm_fssearchlist back [9/13/2006 Black] +// TODO: (move vm_files and vm_fssearchlist to prvm_prog_t struct again) [2007-01-23 LordHavoc] +// TODO: will this war ever end? [2007-01-23 LordHavoc] + +void VM_CheckEmptyString(prvm_prog_t *prog, const char *s) +{ + if (ISWHITESPACE(s[0])) + prog->error_cmd("%s: Bad string", prog->name); +} + +void VM_GenerateFrameGroupBlend(prvm_prog_t *prog, framegroupblend_t *framegroupblend, const prvm_edict_t *ed) +{ + // self.frame is the interpolation target (new frame) + // self.frame1time is the animation base time for the interpolation target + // self.frame2 is the interpolation start (previous frame) + // self.frame2time is the animation base time for the interpolation start + // self.lerpfrac is the interpolation strength for self.frame2 + // self.lerpfrac3 is the interpolation strength for self.frame3 + // self.lerpfrac4 is the interpolation strength for self.frame4 + // pitch angle on a player model where the animator set up 5 sets of + // animations and the csqc simply lerps between sets) + framegroupblend[0].frame = (int) PRVM_gameedictfloat(ed, frame ); + framegroupblend[1].frame = (int) PRVM_gameedictfloat(ed, frame2 ); + framegroupblend[2].frame = (int) PRVM_gameedictfloat(ed, frame3 ); + framegroupblend[3].frame = (int) PRVM_gameedictfloat(ed, frame4 ); + framegroupblend[0].start = PRVM_gameedictfloat(ed, frame1time); + framegroupblend[1].start = PRVM_gameedictfloat(ed, frame2time); + framegroupblend[2].start = PRVM_gameedictfloat(ed, frame3time); + framegroupblend[3].start = PRVM_gameedictfloat(ed, frame4time); + framegroupblend[1].lerp = PRVM_gameedictfloat(ed, lerpfrac ); + framegroupblend[2].lerp = PRVM_gameedictfloat(ed, lerpfrac3 ); + framegroupblend[3].lerp = PRVM_gameedictfloat(ed, lerpfrac4 ); + // assume that the (missing) lerpfrac1 is whatever remains after lerpfrac2+lerpfrac3+lerpfrac4 are summed + framegroupblend[0].lerp = 1 - framegroupblend[1].lerp - framegroupblend[2].lerp - framegroupblend[3].lerp; +} + +// LordHavoc: quite tempting to break apart this function to reuse the +// duplicated code, but I suspect it is better for performance +// this way +void VM_FrameBlendFromFrameGroupBlend(frameblend_t *frameblend, const framegroupblend_t *framegroupblend, const dp_model_t *model, double curtime) +{ + int sub2, numframes, f, i, k; + int isfirstframegroup = true; + int nolerp; + double sublerp, lerp, d; + const animscene_t *scene; + const framegroupblend_t *g; + frameblend_t *blend = frameblend; + + memset(blend, 0, MAX_FRAMEBLENDS * sizeof(*blend)); + + if (!model || !model->surfmesh.isanimated) + { + blend[0].lerp = 1; + return; + } + + nolerp = (model->type == mod_sprite) ? !r_lerpsprites.integer : !r_lerpmodels.integer; + numframes = model->numframes; + for (k = 0, g = framegroupblend;k < MAX_FRAMEGROUPBLENDS;k++, g++) + { + f = g->frame; + if ((unsigned int)f >= (unsigned int)numframes) + { + if (developer_extra.integer) + Con_DPrintf("VM_FrameBlendFromFrameGroupBlend: no such frame %d in model %s\n", f, model->name); + f = 0; + } + d = lerp = g->lerp; + if (lerp <= 0) + continue; + if (nolerp) + { + if (isfirstframegroup) + { + d = lerp = 1; + isfirstframegroup = false; + } + else + continue; + } + if (model->animscenes) + { + scene = model->animscenes + f; + f = scene->firstframe; + if (scene->framecount > 1) + { + // this code path is only used on .zym models and torches + sublerp = scene->framerate * (curtime - g->start); + f = (int) floor(sublerp); + sublerp -= f; + sub2 = f + 1; + if (sublerp < (1.0 / 65536.0f)) + sublerp = 0; + if (sublerp > (65535.0f / 65536.0f)) + sublerp = 1; + if (nolerp) + sublerp = 0; + if (scene->loop) + { + f = (f % scene->framecount); + sub2 = (sub2 % scene->framecount); + } + f = bound(0, f, (scene->framecount - 1)) + scene->firstframe; + sub2 = bound(0, sub2, (scene->framecount - 1)) + scene->firstframe; + d = sublerp * lerp; + // two framelerps produced from one animation + if (d > 0) + { + for (i = 0;i < MAX_FRAMEBLENDS;i++) + { + if (blend[i].lerp <= 0 || blend[i].subframe == sub2) + { + blend[i].subframe = sub2; + blend[i].lerp += d; + break; + } + } + } + d = (1 - sublerp) * lerp; + } + } + if (d > 0) + { + for (i = 0;i < MAX_FRAMEBLENDS;i++) + { + if (blend[i].lerp <= 0 || blend[i].subframe == f) + { + blend[i].subframe = f; + blend[i].lerp += d; + break; + } + } + } + } +} + +void VM_UpdateEdictSkeleton(prvm_prog_t *prog, prvm_edict_t *ed, const dp_model_t *edmodel, const frameblend_t *frameblend) +{ + if (ed->priv.server->skeleton.model != edmodel) + { + VM_RemoveEdictSkeleton(prog, ed); + ed->priv.server->skeleton.model = edmodel; + } + if (!ed->priv.server->skeleton.model || !ed->priv.server->skeleton.model->num_bones) + { + if(ed->priv.server->skeleton.relativetransforms) + Mem_Free(ed->priv.server->skeleton.relativetransforms); + ed->priv.server->skeleton.relativetransforms = NULL; + return; + } + + { + int skeletonindex = -1; + skeleton_t *skeleton; + skeletonindex = (int)PRVM_gameedictfloat(ed, skeletonindex) - 1; + if (skeletonindex >= 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones) + { + // custom skeleton controlled by the game (FTE_CSQC_SKELETONOBJECTS) + if (!ed->priv.server->skeleton.relativetransforms) + ed->priv.server->skeleton.relativetransforms = (matrix4x4_t *)Mem_Alloc(prog->progs_mempool, ed->priv.server->skeleton.model->num_bones * sizeof(matrix4x4_t)); + memcpy(ed->priv.server->skeleton.relativetransforms, skeleton->relativetransforms, ed->priv.server->skeleton.model->num_bones * sizeof(matrix4x4_t)); + } + else + { + if(ed->priv.server->skeleton.relativetransforms) + Mem_Free(ed->priv.server->skeleton.relativetransforms); + ed->priv.server->skeleton.relativetransforms = NULL; + } + } +} + +void VM_RemoveEdictSkeleton(prvm_prog_t *prog, prvm_edict_t *ed) +{ + if (ed->priv.server->skeleton.relativetransforms) + Mem_Free(ed->priv.server->skeleton.relativetransforms); + memset(&ed->priv.server->skeleton, 0, sizeof(ed->priv.server->skeleton)); +} + + + + +//============================================================================ +//BUILT-IN FUNCTIONS + +void VM_VarString(prvm_prog_t *prog, int first, char *out, int outlength) +{ + int i; + const char *s; + char *outend; + + outend = out + outlength - 1; + for (i = first;i < prog->argc && out < outend;i++) + { + s = PRVM_G_STRING((OFS_PARM0+i*3)); + while (out < outend && *s) + *out++ = *s++; + } + *out++ = 0; +} + +/* +================= +VM_checkextension + +returns true if the extension is supported by the server + +checkextension(extensionname) +================= +*/ + +// kind of helper function +static qboolean checkextension(prvm_prog_t *prog, const char *name) +{ + int len; + const char *e, *start; + len = (int)strlen(name); + + for (e = prog->extensionstring;*e;e++) + { + while (*e == ' ') + e++; + if (!*e) + break; + start = e; + while (*e && *e != ' ') + e++; + if ((e - start) == len && !strncasecmp(start, name, len)) + { + // special sheck for ODE + if (!strncasecmp("DP_PHYSICS_ODE", name, 14)) + { +#ifdef ODE_DYNAMIC + return ode_dll ? true : false; +#else +#ifdef ODE_STATIC + return true; +#else + return false; +#endif +#endif + } + + // special sheck for d0_blind_id + if (!strcasecmp("DP_CRYPTO", name)) + return Crypto_Available(); + if (!strcasecmp("DP_QC_DIGEST_SHA256", name)) + return Crypto_Available(); + + return true; + } + } + return false; +} + +void VM_checkextension(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_checkextension); + + PRVM_G_FLOAT(OFS_RETURN) = checkextension(prog, PRVM_G_STRING(OFS_PARM0)); +} + +/* +================= +VM_error + +This is a TERMINAL error, which will kill off the entire prog. +Dumps self. + +error(value) +================= +*/ +void VM_error(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + char string[VM_STRINGTEMP_LENGTH]; + + VM_VarString(prog, 0, string, sizeof(string)); + Con_Printf("======%s ERROR in %s:\n%s\n", prog->name, PRVM_GetString(prog, prog->xfunction->s_name), string); + ed = PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)); + PRVM_ED_Print(prog, ed, NULL); + + prog->error_cmd("%s: Program error in function %s:\n%s\nTip: read above for entity information\n", prog->name, PRVM_GetString(prog, prog->xfunction->s_name), string); +} + +/* +================= +VM_objerror + +Dumps out self, then an error message. The program is aborted and self is +removed, but the level can continue. + +objerror(value) +================= +*/ +void VM_objerror(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + char string[VM_STRINGTEMP_LENGTH]; + + VM_VarString(prog, 0, string, sizeof(string)); + Con_Printf("======OBJECT ERROR======\n"); // , prog->name, PRVM_GetString(prog->xfunction->s_name), string); // or include them? FIXME + ed = PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)); + PRVM_ED_Print(prog, ed, NULL); + PRVM_ED_Free (prog, ed); + Con_Printf("%s OBJECT ERROR in %s:\n%s\nTip: read above for entity information\n", prog->name, PRVM_GetString(prog, prog->xfunction->s_name), string); +} + +/* +================= +VM_print + +print to console + +print(...[string]) +================= +*/ +void VM_print(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + + VM_VarString(prog, 0, string, sizeof(string)); + Con_Print(string); +} + +/* +================= +VM_bprint + +broadcast print to everyone on server + +bprint(...[string]) +================= +*/ +void VM_bprint(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + + if(!sv.active) + { + VM_Warning(prog, "VM_bprint: game is not server(%s) !\n", prog->name); + return; + } + + VM_VarString(prog, 0, string, sizeof(string)); + SV_BroadcastPrint(string); +} + +/* +================= +VM_sprint (menu & client but only if server.active == true) + +single print to a specific client + +sprint(float clientnum,...[string]) +================= +*/ +void VM_sprint(prvm_prog_t *prog) +{ + client_t *client; + int clientnum; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_sprint); + + //find client for this entity + clientnum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (!sv.active || clientnum < 0 || clientnum >= svs.maxclients || !svs.clients[clientnum].active) + { + VM_Warning(prog, "VM_sprint: %s: invalid client or server is not active !\n", prog->name); + return; + } + + client = svs.clients + clientnum; + if (!client->netconnection) + return; + + VM_VarString(prog, 1, string, sizeof(string)); + MSG_WriteChar(&client->netconnection->message,svc_print); + MSG_WriteString(&client->netconnection->message, string); +} + +/* +================= +VM_centerprint + +single print to the screen + +centerprint(value) +================= +*/ +void VM_centerprint(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_centerprint); + VM_VarString(prog, 0, string, sizeof(string)); + SCR_CenterPrint(string); +} + +/* +================= +VM_normalize + +vector normalize(vector) +================= +*/ +void VM_normalize(prvm_prog_t *prog) +{ + prvm_vec_t *value1; + vec3_t newvalue; + double f; + + VM_SAFEPARMCOUNT(1,VM_normalize); + + value1 = PRVM_G_VECTOR(OFS_PARM0); + + f = VectorLength2(value1); + if (f) + { + f = 1.0 / sqrt(f); + VectorScale(value1, f, newvalue); + } + else + VectorClear(newvalue); + + VectorCopy (newvalue, PRVM_G_VECTOR(OFS_RETURN)); +} + +/* +================= +VM_vlen + +scalar vlen(vector) +================= +*/ +void VM_vlen(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_vlen); + PRVM_G_FLOAT(OFS_RETURN) = VectorLength(PRVM_G_VECTOR(OFS_PARM0)); +} + +/* +================= +VM_vectoyaw + +float vectoyaw(vector) +================= +*/ +void VM_vectoyaw(prvm_prog_t *prog) +{ + prvm_vec_t *value1; + prvm_vec_t yaw; + + VM_SAFEPARMCOUNT(1,VM_vectoyaw); + + value1 = PRVM_G_VECTOR(OFS_PARM0); + + if (value1[1] == 0 && value1[0] == 0) + yaw = 0; + else + { + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + PRVM_G_FLOAT(OFS_RETURN) = yaw; +} + + +/* +================= +VM_vectoangles + +vector vectoangles(vector[, vector]) +================= +*/ +void VM_vectoangles(prvm_prog_t *prog) +{ + vec3_t result, forward, up; + VM_SAFEPARMCOUNTRANGE(1, 2,VM_vectoangles); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), forward); + if (prog->argc >= 2) + { + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), up); + AnglesFromVectors(result, forward, up, true); + } + else + AnglesFromVectors(result, forward, NULL, true); + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); +} + +/* +================= +VM_random + +Returns a number from 0<= num < 1 + +float random() +================= +*/ +void VM_random(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_random); + + PRVM_G_FLOAT(OFS_RETURN) = lhrandom(0, 1); +} + +/* +========= +VM_localsound + +localsound(string sample) +========= +*/ +void VM_localsound(prvm_prog_t *prog) +{ + const char *s; + + VM_SAFEPARMCOUNT(1,VM_localsound); + + s = PRVM_G_STRING(OFS_PARM0); + + if(!S_LocalSound (s)) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning(prog, "VM_localsound: Failed to play %s for %s !\n", s, prog->name); + return; + } + + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +================= +VM_break + +break() +================= +*/ +void VM_break(prvm_prog_t *prog) +{ + prog->error_cmd("%s: break statement", prog->name); +} + +//============================================================================ + +/* +================= +VM_localcmd + +Sends text over to the client's execution buffer + +[localcmd (string, ...) or] +cmd (string, ...) +================= +*/ +void VM_localcmd(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_localcmd); + VM_VarString(prog, 0, string, sizeof(string)); + Cbuf_AddText(string); +} + +static qboolean PRVM_Cvar_ReadOk(const char *string) +{ + cvar_t *cvar; + cvar = Cvar_FindVar(string); + return ((cvar) && ((cvar->flags & CVAR_PRIVATE) == 0)); +} + +/* +================= +VM_cvar + +float cvar (string) +================= +*/ +void VM_cvar(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar); + VM_VarString(prog, 0, string, sizeof(string)); + VM_CheckEmptyString(prog, string); + PRVM_G_FLOAT(OFS_RETURN) = PRVM_Cvar_ReadOk(string) ? Cvar_VariableValue(string) : 0; +} + +/* +================= +VM_cvar + +float cvar_type (string) +float CVAR_TYPEFLAG_EXISTS = 1; +float CVAR_TYPEFLAG_SAVED = 2; +float CVAR_TYPEFLAG_PRIVATE = 4; +float CVAR_TYPEFLAG_ENGINE = 8; +float CVAR_TYPEFLAG_HASDESCRIPTION = 16; +float CVAR_TYPEFLAG_READONLY = 32; +================= +*/ +void VM_cvar_type(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + cvar_t *cvar; + int ret; + + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar); + VM_VarString(prog, 0, string, sizeof(string)); + VM_CheckEmptyString(prog, string); + cvar = Cvar_FindVar(string); + + + if(!cvar) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; // CVAR_TYPE_NONE + } + + ret = 1; // CVAR_EXISTS + if(cvar->flags & CVAR_SAVE) + ret |= 2; // CVAR_TYPE_SAVED + if(cvar->flags & CVAR_PRIVATE) + ret |= 4; // CVAR_TYPE_PRIVATE + if(!(cvar->flags & CVAR_ALLOCATED)) + ret |= 8; // CVAR_TYPE_ENGINE + if(cvar->description != cvar_dummy_description) + ret |= 16; // CVAR_TYPE_HASDESCRIPTION + if(cvar->flags & CVAR_READONLY) + ret |= 32; // CVAR_TYPE_READONLY + + PRVM_G_FLOAT(OFS_RETURN) = ret; +} + +/* +================= +VM_cvar_string + +const string VM_cvar_string (string, ...) +================= +*/ +void VM_cvar_string(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_string); + VM_VarString(prog, 0, string, sizeof(string)); + VM_CheckEmptyString(prog, string); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, PRVM_Cvar_ReadOk(string) ? Cvar_VariableString(string) : ""); +} + + +/* +======================== +VM_cvar_defstring + +const string VM_cvar_defstring (string, ...) +======================== +*/ +void VM_cvar_defstring(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_defstring); + VM_VarString(prog, 0, string, sizeof(string)); + VM_CheckEmptyString(prog, string); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, Cvar_VariableDefString(string)); +} + +/* +======================== +VM_cvar_defstring + +const string VM_cvar_description (string, ...) +======================== +*/ +void VM_cvar_description(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_description); + VM_VarString(prog, 0, string, sizeof(string)); + VM_CheckEmptyString(prog, string); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, Cvar_VariableDescription(string)); +} +/* +================= +VM_cvar_set + +void cvar_set (string,string, ...) +================= +*/ +void VM_cvar_set(prvm_prog_t *prog) +{ + const char *name; + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(2,8,VM_cvar_set); + VM_VarString(prog, 1, string, sizeof(string)); + name = PRVM_G_STRING(OFS_PARM0); + VM_CheckEmptyString(prog, name); + Cvar_Set(name, string); +} + +/* +========= +VM_dprint + +dprint(...[string]) +========= +*/ +void VM_dprint(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_dprint); + VM_VarString(prog, 0, string, sizeof(string)); +#if 1 + Con_DPrintf("%s", string); +#else + Con_DPrintf("%s: %s", prog->name, string); +#endif +} + +/* +========= +VM_ftos + +string ftos(float) +========= +*/ + +void VM_ftos(prvm_prog_t *prog) +{ + prvm_vec_t v; + char s[128]; + + VM_SAFEPARMCOUNT(1, VM_ftos); + + v = PRVM_G_FLOAT(OFS_PARM0); + + if ((prvm_vec_t)((prvm_int_t)v) == v) + dpsnprintf(s, sizeof(s), "%.0f", v); + else + dpsnprintf(s, sizeof(s), "%f", v); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, s); +} + +/* +========= +VM_fabs + +float fabs(float) +========= +*/ + +void VM_fabs(prvm_prog_t *prog) +{ + prvm_vec_t v; + + VM_SAFEPARMCOUNT(1,VM_fabs); + + v = PRVM_G_FLOAT(OFS_PARM0); + PRVM_G_FLOAT(OFS_RETURN) = fabs(v); +} + +/* +========= +VM_vtos + +string vtos(vector) +========= +*/ + +void VM_vtos(prvm_prog_t *prog) +{ + char s[512]; + + VM_SAFEPARMCOUNT(1,VM_vtos); + + dpsnprintf (s, sizeof(s), "'%5.1f %5.1f %5.1f'", PRVM_G_VECTOR(OFS_PARM0)[0], PRVM_G_VECTOR(OFS_PARM0)[1], PRVM_G_VECTOR(OFS_PARM0)[2]); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, s); +} + +/* +========= +VM_etos + +string etos(entity) +========= +*/ + +void VM_etos(prvm_prog_t *prog) +{ + char s[128]; + + VM_SAFEPARMCOUNT(1, VM_etos); + + dpsnprintf (s, sizeof(s), "entity %i", PRVM_G_EDICTNUM(OFS_PARM0)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, s); +} + +/* +========= +VM_stof + +float stof(...[string]) +========= +*/ +void VM_stof(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_stof); + VM_VarString(prog, 0, string, sizeof(string)); + PRVM_G_FLOAT(OFS_RETURN) = atof(string); +} + +/* +======================== +VM_itof + +float itof(int ent) +======================== +*/ +void VM_itof(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_itof); + PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); +} + +/* +======================== +VM_ftoe + +entity ftoe(float num) +======================== +*/ +void VM_ftoe(prvm_prog_t *prog) +{ + prvm_int_t ent; + VM_SAFEPARMCOUNT(1, VM_ftoe); + + ent = (prvm_int_t)PRVM_G_FLOAT(OFS_PARM0); + if (ent < 0 || ent >= prog->max_edicts || PRVM_PROG_TO_EDICT(ent)->priv.required->free) + ent = 0; // return world instead of a free or invalid entity + + PRVM_G_INT(OFS_RETURN) = ent; +} + +/* +======================== +VM_etof + +float etof(entity ent) +======================== +*/ +void VM_etof(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_etof); + PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_EDICTNUM(OFS_PARM0); +} + +/* +========= +VM_strftime + +string strftime(float uselocaltime, string[, string ...]) +========= +*/ +void VM_strftime(prvm_prog_t *prog) +{ + time_t t; +#if _MSC_VER >= 1400 + struct tm tm; + int tmresult; +#else + struct tm *tm; +#endif + char fmt[VM_STRINGTEMP_LENGTH]; + char result[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(2, 8, VM_strftime); + VM_VarString(prog, 1, fmt, sizeof(fmt)); + t = time(NULL); +#if _MSC_VER >= 1400 + if (PRVM_G_FLOAT(OFS_PARM0)) + tmresult = localtime_s(&tm, &t); + else + tmresult = gmtime_s(&tm, &t); + if (!tmresult) +#else + if (PRVM_G_FLOAT(OFS_PARM0)) + tm = localtime(&t); + else + tm = gmtime(&t); + if (!tm) +#endif + { + PRVM_G_INT(OFS_RETURN) = 0; + return; + } +#if _MSC_VER >= 1400 + strftime(result, sizeof(result), fmt, &tm); +#else + strftime(result, sizeof(result), fmt, tm); +#endif + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, result); +} + +/* +========= +VM_spawn + +entity spawn() +========= +*/ + +void VM_spawn(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + VM_SAFEPARMCOUNT(0, VM_spawn); + prog->xfunction->builtinsprofile += 20; + ed = PRVM_ED_Alloc(prog); + VM_RETURN_EDICT(ed); +} + +/* +========= +VM_remove + +remove(entity e) +========= +*/ + +void VM_remove(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + prog->xfunction->builtinsprofile += 20; + + VM_SAFEPARMCOUNT(1, VM_remove); + + ed = PRVM_G_EDICT(OFS_PARM0); + if( PRVM_NUM_FOR_EDICT(ed) <= prog->reserved_edicts ) + { + if (developer.integer > 0) + VM_Warning(prog, "VM_remove: tried to remove the null entity or a reserved entity!\n" ); + } + else if( ed->priv.required->free ) + { + if (developer.integer > 0) + VM_Warning(prog, "VM_remove: tried to remove an already freed entity!\n" ); + } + else + PRVM_ED_Free (prog, ed); +} + +/* +========= +VM_find + +entity find(entity start, .string field, string match) +========= +*/ + +void VM_find(prvm_prog_t *prog) +{ + int e; + int f; + const char *s, *t; + prvm_edict_t *ed; + + VM_SAFEPARMCOUNT(3,VM_find); + + e = PRVM_G_EDICTNUM(OFS_PARM0); + f = PRVM_G_INT(OFS_PARM1); + s = PRVM_G_STRING(OFS_PARM2); + + // LordHavoc: apparently BloodMage does a find(world, weaponmodel, "") and + // expects it to find all the monsters, so we must be careful to support + // searching for "" + + for (e++ ; e < prog->num_edicts ; e++) + { + prog->xfunction->builtinsprofile++; + ed = PRVM_EDICT_NUM(e); + if (ed->priv.required->free) + continue; + t = PRVM_E_STRING(ed,f); + if (!t) + t = ""; + if (!strcmp(t,s)) + { + VM_RETURN_EDICT(ed); + return; + } + } + + VM_RETURN_EDICT(prog->edicts); +} + +/* +========= +VM_findfloat + + entity findfloat(entity start, .float field, float match) + entity findentity(entity start, .entity field, entity match) +========= +*/ +// LordHavoc: added this for searching float, int, and entity reference fields +void VM_findfloat(prvm_prog_t *prog) +{ + int e; + int f; + float s; + prvm_edict_t *ed; + + VM_SAFEPARMCOUNT(3,VM_findfloat); + + e = PRVM_G_EDICTNUM(OFS_PARM0); + f = PRVM_G_INT(OFS_PARM1); + s = PRVM_G_FLOAT(OFS_PARM2); + + for (e++ ; e < prog->num_edicts ; e++) + { + prog->xfunction->builtinsprofile++; + ed = PRVM_EDICT_NUM(e); + if (ed->priv.required->free) + continue; + if (PRVM_E_FLOAT(ed,f) == s) + { + VM_RETURN_EDICT(ed); + return; + } + } + + VM_RETURN_EDICT(prog->edicts); +} + +/* +========= +VM_findchain + +entity findchain(.string field, string match) +========= +*/ +// chained search for strings in entity fields +// entity(.string field, string match) findchain = #402; +void VM_findchain(prvm_prog_t *prog) +{ + int i; + int f; + const char *s, *t; + prvm_edict_t *ent, *chain; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2,3,VM_findchain); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if (chainfield < 0) + prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); + + chain = prog->edicts; + + f = PRVM_G_INT(OFS_PARM0); + s = PRVM_G_STRING(OFS_PARM1); + + // LordHavoc: apparently BloodMage does a find(world, weaponmodel, "") and + // expects it to find all the monsters, so we must be careful to support + // searching for "" + + ent = PRVM_NEXT_EDICT(prog->edicts); + for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + { + prog->xfunction->builtinsprofile++; + if (ent->priv.required->free) + continue; + t = PRVM_E_STRING(ent,f); + if (!t) + t = ""; + if (strcmp(t,s)) + continue; + + PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_NUM_FOR_EDICT(chain); + chain = ent; + } + + VM_RETURN_EDICT(chain); +} + +/* +========= +VM_findchainfloat + +entity findchainfloat(.string field, float match) +entity findchainentity(.string field, entity match) +========= +*/ +// LordHavoc: chained search for float, int, and entity reference fields +// entity(.string field, float match) findchainfloat = #403; +void VM_findchainfloat(prvm_prog_t *prog) +{ + int i; + int f; + float s; + prvm_edict_t *ent, *chain; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_findchainfloat); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if (chainfield < 0) + prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); + + chain = (prvm_edict_t *)prog->edicts; + + f = PRVM_G_INT(OFS_PARM0); + s = PRVM_G_FLOAT(OFS_PARM1); + + ent = PRVM_NEXT_EDICT(prog->edicts); + for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + { + prog->xfunction->builtinsprofile++; + if (ent->priv.required->free) + continue; + if (PRVM_E_FLOAT(ent,f) != s) + continue; + + PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); + chain = ent; + } + + VM_RETURN_EDICT(chain); +} + +/* +======================== +VM_findflags + +entity findflags(entity start, .float field, float match) +======================== +*/ +// LordHavoc: search for flags in float fields +void VM_findflags(prvm_prog_t *prog) +{ + prvm_int_t e; + prvm_int_t f; + prvm_int_t s; + prvm_edict_t *ed; + + VM_SAFEPARMCOUNT(3, VM_findflags); + + + e = PRVM_G_EDICTNUM(OFS_PARM0); + f = PRVM_G_INT(OFS_PARM1); + s = (prvm_int_t)PRVM_G_FLOAT(OFS_PARM2); + + for (e++ ; e < prog->num_edicts ; e++) + { + prog->xfunction->builtinsprofile++; + ed = PRVM_EDICT_NUM(e); + if (ed->priv.required->free) + continue; + if (!PRVM_E_FLOAT(ed,f)) + continue; + if ((prvm_int_t)PRVM_E_FLOAT(ed,f) & s) + { + VM_RETURN_EDICT(ed); + return; + } + } + + VM_RETURN_EDICT(prog->edicts); +} + +/* +======================== +VM_findchainflags + +entity findchainflags(.float field, float match) +======================== +*/ +// LordHavoc: chained search for flags in float fields +void VM_findchainflags(prvm_prog_t *prog) +{ + prvm_int_t i; + prvm_int_t f; + prvm_int_t s; + prvm_edict_t *ent, *chain; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_findchainflags); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if (chainfield < 0) + prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); + + chain = (prvm_edict_t *)prog->edicts; + + f = PRVM_G_INT(OFS_PARM0); + s = (prvm_int_t)PRVM_G_FLOAT(OFS_PARM1); + + ent = PRVM_NEXT_EDICT(prog->edicts); + for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + { + prog->xfunction->builtinsprofile++; + if (ent->priv.required->free) + continue; + if (!PRVM_E_FLOAT(ent,f)) + continue; + if (!((prvm_int_t)PRVM_E_FLOAT(ent,f) & s)) + continue; + + PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); + chain = ent; + } + + VM_RETURN_EDICT(chain); +} + +/* +========= +VM_precache_sound + +string precache_sound (string sample) +========= +*/ +void VM_precache_sound(prvm_prog_t *prog) +{ + const char *s; + + VM_SAFEPARMCOUNT(1, VM_precache_sound); + + s = PRVM_G_STRING(OFS_PARM0); + PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); + //VM_CheckEmptyString(prog, s); + + if(snd_initialized.integer && !S_PrecacheSound(s, true, true)) + { + VM_Warning(prog, "VM_precache_sound: Failed to load %s for %s\n", s, prog->name); + return; + } +} + +/* +================= +VM_precache_file + +returns the same string as output + +does nothing, only used by qcc to build .pak archives +================= +*/ +void VM_precache_file(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_precache_file); + // precache_file is only used to copy files with qcc, it does nothing + PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); +} + +/* +========= +VM_coredump + +coredump() +========= +*/ +void VM_coredump(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_coredump); + + Cbuf_AddText("prvm_edicts "); + Cbuf_AddText(prog->name); + Cbuf_AddText("\n"); +} + +/* +========= +VM_stackdump + +stackdump() +========= +*/ +void VM_stackdump(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_stackdump); + + PRVM_StackTrace(prog); +} + +/* +========= +VM_crash + +crash() +========= +*/ + +void VM_crash(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_crash); + + prog->error_cmd("Crash called by %s",prog->name); +} + +/* +========= +VM_traceon + +traceon() +========= +*/ +void VM_traceon(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_traceon); + + prog->trace = true; +} + +/* +========= +VM_traceoff + +traceoff() +========= +*/ +void VM_traceoff(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_traceoff); + + prog->trace = false; +} + +/* +========= +VM_eprint + +eprint(entity e) +========= +*/ +void VM_eprint(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_eprint); + + PRVM_ED_PrintNum (prog, PRVM_G_EDICTNUM(OFS_PARM0), NULL); +} + +/* +========= +VM_rint + +float rint(float) +========= +*/ +void VM_rint(prvm_prog_t *prog) +{ + prvm_vec_t f; + VM_SAFEPARMCOUNT(1,VM_rint); + + f = PRVM_G_FLOAT(OFS_PARM0); + if (f > 0) + PRVM_G_FLOAT(OFS_RETURN) = floor(f + 0.5); + else + PRVM_G_FLOAT(OFS_RETURN) = ceil(f - 0.5); +} + +/* +========= +VM_floor + +float floor(float) +========= +*/ +void VM_floor(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_floor); + + PRVM_G_FLOAT(OFS_RETURN) = floor(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_ceil + +float ceil(float) +========= +*/ +void VM_ceil(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_ceil); + + PRVM_G_FLOAT(OFS_RETURN) = ceil(PRVM_G_FLOAT(OFS_PARM0)); +} + + +/* +============= +VM_nextent + +entity nextent(entity) +============= +*/ +void VM_nextent(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *ent; + + VM_SAFEPARMCOUNT(1, VM_nextent); + + i = PRVM_G_EDICTNUM(OFS_PARM0); + while (1) + { + prog->xfunction->builtinsprofile++; + i++; + if (i == prog->num_edicts) + { + VM_RETURN_EDICT(prog->edicts); + return; + } + ent = PRVM_EDICT_NUM(i); + if (!ent->priv.required->free) + { + VM_RETURN_EDICT(ent); + return; + } + } +} + +//============================================================================= + +/* +============== +VM_changelevel +server and menu + +changelevel(string map) +============== +*/ +void VM_changelevel(prvm_prog_t *prog) +{ + char vabuf[1024]; + VM_SAFEPARMCOUNT(1, VM_changelevel); + + if(!sv.active) + { + VM_Warning(prog, "VM_changelevel: game is not server (%s)\n", prog->name); + return; + } + +// make sure we don't issue two changelevels + if (svs.changelevel_issued) + return; + svs.changelevel_issued = true; + + Cbuf_AddText(va(vabuf, sizeof(vabuf), "changelevel %s\n",PRVM_G_STRING(OFS_PARM0))); +} + +/* +========= +VM_sin + +float sin(float) +========= +*/ +void VM_sin(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_sin); + PRVM_G_FLOAT(OFS_RETURN) = sin(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_cos +float cos(float) +========= +*/ +void VM_cos(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_cos); + PRVM_G_FLOAT(OFS_RETURN) = cos(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_sqrt + +float sqrt(float) +========= +*/ +void VM_sqrt(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_sqrt); + PRVM_G_FLOAT(OFS_RETURN) = sqrt(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_asin + +float asin(float) +========= +*/ +void VM_asin(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_asin); + PRVM_G_FLOAT(OFS_RETURN) = asin(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_acos +float acos(float) +========= +*/ +void VM_acos(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_acos); + PRVM_G_FLOAT(OFS_RETURN) = acos(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_atan +float atan(float) +========= +*/ +void VM_atan(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_atan); + PRVM_G_FLOAT(OFS_RETURN) = atan(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +========= +VM_atan2 +float atan2(float,float) +========= +*/ +void VM_atan2(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2,VM_atan2); + PRVM_G_FLOAT(OFS_RETURN) = atan2(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); +} + +/* +========= +VM_tan +float tan(float) +========= +*/ +void VM_tan(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_tan); + PRVM_G_FLOAT(OFS_RETURN) = tan(PRVM_G_FLOAT(OFS_PARM0)); +} + +/* +================= +VM_randomvec + +Returns a vector of length < 1 and > 0 + +vector randomvec() +================= +*/ +void VM_randomvec(prvm_prog_t *prog) +{ + vec3_t temp; + VM_SAFEPARMCOUNT(0, VM_randomvec); + VectorRandom(temp); + VectorCopy(temp, PRVM_G_VECTOR(OFS_RETURN)); +} + +//============================================================================= + +/* +========= +VM_registercvar + +float registercvar (string name, string value[, float flags]) +========= +*/ +void VM_registercvar(prvm_prog_t *prog) +{ + const char *name, *value; + int flags; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_registercvar); + + name = PRVM_G_STRING(OFS_PARM0); + value = PRVM_G_STRING(OFS_PARM1); + flags = prog->argc >= 3 ? (int)PRVM_G_FLOAT(OFS_PARM2) : 0; + PRVM_G_FLOAT(OFS_RETURN) = 0; + + if(flags > CVAR_MAXFLAGSVAL) + return; + +// first check to see if it has already been defined + if (Cvar_FindVar (name)) + return; + +// check for overlap with a command + if (Cmd_Exists (name)) + { + VM_Warning(prog, "VM_registercvar: %s is a command\n", name); + return; + } + + Cvar_Get(name, value, flags, NULL); + + PRVM_G_FLOAT(OFS_RETURN) = 1; // success +} + + +/* +================= +VM_min + +returns the minimum of two supplied floats + +float min(float a, float b, ...[float]) +================= +*/ +void VM_min(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNTRANGE(2, 8, VM_min); + // LordHavoc: 3+ argument enhancement suggested by FrikaC + if (prog->argc >= 3) + { + int i; + float f = PRVM_G_FLOAT(OFS_PARM0); + for (i = 1;i < prog->argc;i++) + if (f > PRVM_G_FLOAT((OFS_PARM0+i*3))) + f = PRVM_G_FLOAT((OFS_PARM0+i*3)); + PRVM_G_FLOAT(OFS_RETURN) = f; + } + else + PRVM_G_FLOAT(OFS_RETURN) = min(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); +} + +/* +================= +VM_max + +returns the maximum of two supplied floats + +float max(float a, float b, ...[float]) +================= +*/ +void VM_max(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNTRANGE(2, 8, VM_max); + // LordHavoc: 3+ argument enhancement suggested by FrikaC + if (prog->argc >= 3) + { + int i; + float f = PRVM_G_FLOAT(OFS_PARM0); + for (i = 1;i < prog->argc;i++) + if (f < PRVM_G_FLOAT((OFS_PARM0+i*3))) + f = PRVM_G_FLOAT((OFS_PARM0+i*3)); + PRVM_G_FLOAT(OFS_RETURN) = f; + } + else + PRVM_G_FLOAT(OFS_RETURN) = max(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); +} + +/* +================= +VM_bound + +returns number bounded by supplied range + +float bound(float min, float value, float max) +================= +*/ +void VM_bound(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3,VM_bound); + PRVM_G_FLOAT(OFS_RETURN) = bound(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1), PRVM_G_FLOAT(OFS_PARM2)); +} + +/* +================= +VM_pow + +returns a raised to power b + +float pow(float a, float b) +================= +*/ +void VM_pow(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2,VM_pow); + PRVM_G_FLOAT(OFS_RETURN) = pow(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); +} + +void VM_log(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_log); + PRVM_G_FLOAT(OFS_RETURN) = log(PRVM_G_FLOAT(OFS_PARM0)); +} + +void VM_Files_Init(prvm_prog_t *prog) +{ + int i; + for (i = 0;i < PRVM_MAX_OPENFILES;i++) + prog->openfiles[i] = NULL; +} + +void VM_Files_CloseAll(prvm_prog_t *prog) +{ + int i; + for (i = 0;i < PRVM_MAX_OPENFILES;i++) + { + if (prog->openfiles[i]) + FS_Close(prog->openfiles[i]); + prog->openfiles[i] = NULL; + } +} + +static qfile_t *VM_GetFileHandle(prvm_prog_t *prog, int index) +{ + if (index < 0 || index >= PRVM_MAX_OPENFILES) + { + Con_Printf("VM_GetFileHandle: invalid file handle %i used in %s\n", index, prog->name); + return NULL; + } + if (prog->openfiles[index] == NULL) + { + Con_Printf("VM_GetFileHandle: no such file handle %i (or file has been closed) in %s\n", index, prog->name); + return NULL; + } + return prog->openfiles[index]; +} + +/* +========= +VM_fopen + +float fopen(string filename, float mode) +========= +*/ +// float(string filename, float mode) fopen = #110; +// opens a file inside quake/gamedir/data/ (mode is FILE_READ, FILE_APPEND, or FILE_WRITE), +// returns fhandle >= 0 if successful, or fhandle < 0 if unable to open file for any reason +void VM_fopen(prvm_prog_t *prog) +{ + int filenum, mode; + const char *modestring, *filename; + char vabuf[1024]; + + VM_SAFEPARMCOUNT(2,VM_fopen); + + for (filenum = 0;filenum < PRVM_MAX_OPENFILES;filenum++) + if (prog->openfiles[filenum] == NULL) + break; + if (filenum >= PRVM_MAX_OPENFILES) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_fopen: %s ran out of file handles (%i)\n", prog->name, PRVM_MAX_OPENFILES); + return; + } + filename = PRVM_G_STRING(OFS_PARM0); + mode = (int)PRVM_G_FLOAT(OFS_PARM1); + switch(mode) + { + case 0: // FILE_READ + modestring = "rb"; + prog->openfiles[filenum] = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "data/%s", filename), false); + if (prog->openfiles[filenum] == NULL) + prog->openfiles[filenum] = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "%s", filename), false); + break; + case 1: // FILE_APPEND + modestring = "a"; + prog->openfiles[filenum] = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "data/%s", filename), modestring, false); + break; + case 2: // FILE_WRITE + modestring = "w"; + prog->openfiles[filenum] = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "data/%s", filename), modestring, false); + break; + default: + PRVM_G_FLOAT(OFS_RETURN) = -3; + VM_Warning(prog, "VM_fopen: %s: no such mode %i (valid: 0 = read, 1 = append, 2 = write)\n", prog->name, mode); + return; + } + + if (prog->openfiles[filenum] == NULL) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + if (developer_extra.integer) + VM_Warning(prog, "VM_fopen: %s: %s mode %s failed\n", prog->name, filename, modestring); + } + else + { + PRVM_G_FLOAT(OFS_RETURN) = filenum; + if (developer_extra.integer) + Con_DPrintf("VM_fopen: %s: %s mode %s opened as #%i\n", prog->name, filename, modestring, filenum); + prog->openfiles_origin[filenum] = PRVM_AllocationOrigin(prog); + } +} + +/* +========= +VM_fclose + +fclose(float fhandle) +========= +*/ +//void(float fhandle) fclose = #111; // closes a file +void VM_fclose(prvm_prog_t *prog) +{ + int filenum; + + VM_SAFEPARMCOUNT(1,VM_fclose); + + filenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) + { + VM_Warning(prog, "VM_fclose: invalid file handle %i used in %s\n", filenum, prog->name); + return; + } + if (prog->openfiles[filenum] == NULL) + { + VM_Warning(prog, "VM_fclose: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name); + return; + } + FS_Close(prog->openfiles[filenum]); + prog->openfiles[filenum] = NULL; + if(prog->openfiles_origin[filenum]) + PRVM_Free((char *)prog->openfiles_origin[filenum]); + if (developer_extra.integer) + Con_DPrintf("VM_fclose: %s: #%i closed\n", prog->name, filenum); +} + +/* +========= +VM_fgets + +string fgets(float fhandle) +========= +*/ +//string(float fhandle) fgets = #112; // reads a line of text from the file and returns as a tempstring +void VM_fgets(prvm_prog_t *prog) +{ + int c, end; + char string[VM_STRINGTEMP_LENGTH]; + int filenum; + + VM_SAFEPARMCOUNT(1,VM_fgets); + + // set the return value regardless of any possible errors + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + + filenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) + { + VM_Warning(prog, "VM_fgets: invalid file handle %i used in %s\n", filenum, prog->name); + return; + } + if (prog->openfiles[filenum] == NULL) + { + VM_Warning(prog, "VM_fgets: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name); + return; + } + end = 0; + for (;;) + { + c = FS_Getc(prog->openfiles[filenum]); + if (c == '\r' || c == '\n' || c < 0) + break; + if (end < VM_STRINGTEMP_LENGTH - 1) + string[end++] = c; + } + string[end] = 0; + // remove \n following \r + if (c == '\r') + { + c = FS_Getc(prog->openfiles[filenum]); + if (c != '\n') + FS_UnGetc(prog->openfiles[filenum], (unsigned char)c); + } + if (developer_extra.integer) + Con_DPrintf("fgets: %s: %s\n", prog->name, string); + if (c >= 0 || end) + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); +} + +/* +========= +VM_fputs + +fputs(float fhandle, string s) +========= +*/ +//void(float fhandle, string s) fputs = #113; // writes a line of text to the end of the file +void VM_fputs(prvm_prog_t *prog) +{ + int stringlength; + char string[VM_STRINGTEMP_LENGTH]; + int filenum; + + VM_SAFEPARMCOUNT(2,VM_fputs); + + filenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) + { + VM_Warning(prog, "VM_fputs: invalid file handle %i used in %s\n", filenum, prog->name); + return; + } + if (prog->openfiles[filenum] == NULL) + { + VM_Warning(prog, "VM_fputs: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name); + return; + } + VM_VarString(prog, 1, string, sizeof(string)); + if ((stringlength = (int)strlen(string))) + FS_Write(prog->openfiles[filenum], string, stringlength); + if (developer_extra.integer) + Con_DPrintf("fputs: %s: %s\n", prog->name, string); +} + +/* +========= +VM_writetofile + + writetofile(float fhandle, entity ent) +========= +*/ +void VM_writetofile(prvm_prog_t *prog) +{ + prvm_edict_t * ent; + qfile_t *file; + + VM_SAFEPARMCOUNT(2, VM_writetofile); + + file = VM_GetFileHandle(prog, (int)PRVM_G_FLOAT(OFS_PARM0)); + if( !file ) + { + VM_Warning(prog, "VM_writetofile: invalid or closed file handle\n"); + return; + } + + ent = PRVM_G_EDICT(OFS_PARM1); + if(ent->priv.required->free) + { + VM_Warning(prog, "VM_writetofile: %s: entity %i is free !\n", prog->name, PRVM_NUM_FOR_EDICT(ent)); + return; + } + + PRVM_ED_Write (prog, file, ent); +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_numentityfields + +float() numentityfields +Return the number of entity fields - NOT offsets +========= +*/ +void VM_numentityfields(prvm_prog_t *prog) +{ + PRVM_G_FLOAT(OFS_RETURN) = prog->numfielddefs; +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_entityfieldname + +string(float fieldnum) entityfieldname +Return name of the specified field as a string, or empty if the field is invalid (warning) +========= +*/ +void VM_entityfieldname(prvm_prog_t *prog) +{ + ddef_t *d; + int i = (int)PRVM_G_FLOAT(OFS_PARM0); + + if (i < 0 || i >= prog->numfielddefs) + { + VM_Warning(prog, "VM_entityfieldname: %s: field index out of bounds\n", prog->name); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); + return; + } + + d = &prog->fielddefs[i]; + PRVM_G_INT(OFS_RETURN) = d->s_name; // presuming that s_name points to a string already +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_entityfieldtype + +float(float fieldnum) entityfieldtype +========= +*/ +void VM_entityfieldtype(prvm_prog_t *prog) +{ + ddef_t *d; + int i = (int)PRVM_G_FLOAT(OFS_PARM0); + + if (i < 0 || i >= prog->numfielddefs) + { + VM_Warning(prog, "VM_entityfieldtype: %s: field index out of bounds\n", prog->name); + PRVM_G_FLOAT(OFS_RETURN) = -1.0; + return; + } + + d = &prog->fielddefs[i]; + PRVM_G_FLOAT(OFS_RETURN) = (prvm_vec_t)d->type; +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_getentityfieldstring + +string(float fieldnum, entity ent) getentityfieldstring +========= +*/ +void VM_getentityfieldstring(prvm_prog_t *prog) +{ + // put the data into a string + ddef_t *d; + int type, j; + prvm_eval_t *val; + prvm_edict_t * ent; + int i = (int)PRVM_G_FLOAT(OFS_PARM0); + char valuebuf[MAX_INPUTLINE]; + + if (i < 0 || i >= prog->numfielddefs) + { + VM_Warning(prog, "VM_entityfielddata: %s: field index out of bounds\n", prog->name); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); + return; + } + + d = &prog->fielddefs[i]; + + // get the entity + ent = PRVM_G_EDICT(OFS_PARM1); + if(ent->priv.required->free) + { + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); + VM_Warning(prog, "VM_entityfielddata: %s: entity %i is free !\n", prog->name, PRVM_NUM_FOR_EDICT(ent)); + return; + } + val = (prvm_eval_t *)(ent->fields.fp + d->ofs); + + // if it's 0 or blank, return an empty string + type = d->type & ~DEF_SAVEGLOBAL; + for (j=0 ; jivector[j]) + break; + if (j == prvm_type_size[type]) + { + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); + return; + } + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf))); +} + +// KrimZon - DP_QC_ENTITYDATA +/* +========= +VM_putentityfieldstring + +float(float fieldnum, entity ent, string s) putentityfieldstring +========= +*/ +void VM_putentityfieldstring(prvm_prog_t *prog) +{ + ddef_t *d; + prvm_edict_t * ent; + int i = (int)PRVM_G_FLOAT(OFS_PARM0); + + if (i < 0 || i >= prog->numfielddefs) + { + VM_Warning(prog, "VM_entityfielddata: %s: field index out of bounds\n", prog->name); + PRVM_G_FLOAT(OFS_RETURN) = 0.0f; + return; + } + + d = &prog->fielddefs[i]; + + // get the entity + ent = PRVM_G_EDICT(OFS_PARM1); + if(ent->priv.required->free) + { + VM_Warning(prog, "VM_entityfielddata: %s: entity %i is free !\n", prog->name, PRVM_NUM_FOR_EDICT(ent)); + PRVM_G_FLOAT(OFS_RETURN) = 0.0f; + return; + } + + // parse the string into the value + PRVM_G_FLOAT(OFS_RETURN) = ( PRVM_ED_ParseEpair(prog, ent, d, PRVM_G_STRING(OFS_PARM2), false) ) ? 1.0f : 0.0f; +} + +/* +========= +VM_strlen + +float strlen(string s) +========= +*/ +//float(string s) strlen = #114; // returns how many characters are in a string +void VM_strlen(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_strlen); + + //PRVM_G_FLOAT(OFS_RETURN) = strlen(PRVM_G_STRING(OFS_PARM0)); + PRVM_G_FLOAT(OFS_RETURN) = u8_strlen(PRVM_G_STRING(OFS_PARM0)); +} + +// DRESK - Decolorized String +/* +========= +VM_strdecolorize + +string strdecolorize(string s) +========= +*/ +// string (string s) strdecolorize = #472; // returns the passed in string with color codes stripped +void VM_strdecolorize(prvm_prog_t *prog) +{ + char szNewString[VM_STRINGTEMP_LENGTH]; + const char *szString; + + // Prepare Strings + VM_SAFEPARMCOUNT(1,VM_strdecolorize); + szString = PRVM_G_STRING(OFS_PARM0); + COM_StringDecolorize(szString, 0, szNewString, sizeof(szNewString), TRUE); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, szNewString); +} + +// DRESK - String Length (not counting color codes) +/* +========= +VM_strlennocol + +float strlennocol(string s) +========= +*/ +// float(string s) strlennocol = #471; // returns how many characters are in a string not including color codes +// For example, ^2Dresk returns a length of 5 +void VM_strlennocol(prvm_prog_t *prog) +{ + const char *szString; + int nCnt; + + VM_SAFEPARMCOUNT(1,VM_strlennocol); + + szString = PRVM_G_STRING(OFS_PARM0); + + //nCnt = COM_StringLengthNoColors(szString, 0, NULL); + nCnt = u8_COM_StringLengthNoColors(szString, 0, NULL); + + PRVM_G_FLOAT(OFS_RETURN) = nCnt; +} + +// DRESK - String to Uppercase and Lowercase +/* +========= +VM_strtolower + +string strtolower(string s) +========= +*/ +// string (string s) strtolower = #480; // returns passed in string in lowercase form +void VM_strtolower(prvm_prog_t *prog) +{ + char szNewString[VM_STRINGTEMP_LENGTH]; + const char *szString; + + // Prepare Strings + VM_SAFEPARMCOUNT(1,VM_strtolower); + szString = PRVM_G_STRING(OFS_PARM0); + + COM_ToLowerString(szString, szNewString, sizeof(szNewString) ); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, szNewString); +} + +/* +========= +VM_strtoupper + +string strtoupper(string s) +========= +*/ +// string (string s) strtoupper = #481; // returns passed in string in uppercase form +void VM_strtoupper(prvm_prog_t *prog) +{ + char szNewString[VM_STRINGTEMP_LENGTH]; + const char *szString; + + // Prepare Strings + VM_SAFEPARMCOUNT(1,VM_strtoupper); + szString = PRVM_G_STRING(OFS_PARM0); + + COM_ToUpperString(szString, szNewString, sizeof(szNewString) ); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, szNewString); +} + +/* +========= +VM_strcat + +string strcat(string,string,...[string]) +========= +*/ +//string(string s1, string s2) strcat = #115; +// concatenates two strings (for example "abc", "def" would return "abcdef") +// and returns as a tempstring +void VM_strcat(prvm_prog_t *prog) +{ + char s[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_strcat); + + VM_VarString(prog, 0, s, sizeof(s)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, s); +} + +/* +========= +VM_substring + +string substring(string s, float start, float length) +========= +*/ +// string(string s, float start, float length) substring = #116; +// returns a section of a string as a tempstring +void VM_substring(prvm_prog_t *prog) +{ + int start, length; + int u_slength = 0, u_start; + size_t u_length; + const char *s; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT(3,VM_substring); + + /* + s = PRVM_G_STRING(OFS_PARM0); + start = (int)PRVM_G_FLOAT(OFS_PARM1); + length = (int)PRVM_G_FLOAT(OFS_PARM2); + slength = strlen(s); + + if (start < 0) // FTE_STRINGS feature + start += slength; + start = bound(0, start, slength); + + if (length < 0) // FTE_STRINGS feature + length += slength - start + 1; + maxlen = min((int)sizeof(string) - 1, slength - start); + length = bound(0, length, maxlen); + + memcpy(string, s + start, length); + string[length] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); + */ + + s = PRVM_G_STRING(OFS_PARM0); + start = (int)PRVM_G_FLOAT(OFS_PARM1); + length = (int)PRVM_G_FLOAT(OFS_PARM2); + + if (start < 0) // FTE_STRINGS feature + { + u_slength = u8_strlen(s); + start += u_slength; + start = bound(0, start, u_slength); + } + + if (length < 0) // FTE_STRINGS feature + { + if (!u_slength) // it's not calculated when it's not needed above + u_slength = u8_strlen(s); + length += u_slength - start + 1; + } + + // positive start, positive length + u_start = u8_byteofs(s, start, NULL); + if (u_start < 0) + { + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); + return; + } + u_length = u8_bytelen(s + u_start, length); + if (u_length >= sizeof(string)-1) + u_length = sizeof(string)-1; + + memcpy(string, s + u_start, u_length); + string[u_length] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); +} + +/* +========= +VM_strreplace + +string(string search, string replace, string subject) strreplace = #484; +========= +*/ +// replaces all occurrences of search with replace in the string subject, and returns the result +void VM_strreplace(prvm_prog_t *prog) +{ + int i, j, si; + const char *search, *replace, *subject; + char string[VM_STRINGTEMP_LENGTH]; + int search_len, replace_len, subject_len; + + VM_SAFEPARMCOUNT(3,VM_strreplace); + + search = PRVM_G_STRING(OFS_PARM0); + replace = PRVM_G_STRING(OFS_PARM1); + subject = PRVM_G_STRING(OFS_PARM2); + + search_len = (int)strlen(search); + replace_len = (int)strlen(replace); + subject_len = (int)strlen(subject); + + si = 0; + for (i = 0; i <= subject_len - search_len; i++) + { + for (j = 0; j < search_len; j++) // thus, i+j < subject_len + if (subject[i+j] != search[j]) + break; + if (j == search_len) + { + // NOTE: if search_len == 0, we always hit THIS case, and never the other + // found it at offset 'i' + for (j = 0; j < replace_len && si < (int)sizeof(string) - 1; j++) + string[si++] = replace[j]; + if(search_len > 0) + { + i += search_len - 1; + } + else + { + // the above would subtract 1 from i... so we + // don't do that, but instead output the next + // char + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + } + } + else + { + // in THIS case, we know search_len > 0, thus i < subject_len + // not found + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + } + } + // remaining chars (these cannot match) + for (; i < subject_len; i++) + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + string[si] = '\0'; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); +} + +/* +========= +VM_strireplace + +string(string search, string replace, string subject) strireplace = #485; +========= +*/ +// case-insensitive version of strreplace +void VM_strireplace(prvm_prog_t *prog) +{ + int i, j, si; + const char *search, *replace, *subject; + char string[VM_STRINGTEMP_LENGTH]; + int search_len, replace_len, subject_len; + + VM_SAFEPARMCOUNT(3,VM_strreplace); + + search = PRVM_G_STRING(OFS_PARM0); + replace = PRVM_G_STRING(OFS_PARM1); + subject = PRVM_G_STRING(OFS_PARM2); + + search_len = (int)strlen(search); + replace_len = (int)strlen(replace); + subject_len = (int)strlen(subject); + + si = 0; + for (i = 0; i <= subject_len - search_len; i++) + { + for (j = 0; j < search_len; j++) // thus, i+j < subject_len + if (tolower(subject[i+j]) != tolower(search[j])) + break; + if (j == search_len) + { + // NOTE: if search_len == 0, we always hit THIS case, and never the other + // found it at offset 'i' + for (j = 0; j < replace_len && si < (int)sizeof(string) - 1; j++) + string[si++] = replace[j]; + if(search_len > 0) + { + i += search_len - 1; + } + else + { + // the above would subtract 1 from i... so we + // don't do that, but instead output the next + // char + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + } + } + else + { + // in THIS case, we know search_len > 0, thus i < subject_len + // not found + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + } + } + // remaining chars (these cannot match) + for (; i < subject_len; i++) + if (si < (int)sizeof(string) - 1) + string[si++] = subject[i]; + string[si] = '\0'; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); +} + +/* +========= +VM_stov + +vector stov(string s) +========= +*/ +//vector(string s) stov = #117; // returns vector value from a string +void VM_stov(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT(1,VM_stov); + + VM_VarString(prog, 0, string, sizeof(string)); + Math_atov(string, PRVM_G_VECTOR(OFS_RETURN)); +} + +/* +========= +VM_strzone + +string strzone(string s) +========= +*/ +//string(string s, ...) strzone = #118; // makes a copy of a string into the string zone and returns it, this is often used to keep around a tempstring for longer periods of time (tempstrings are replaced often) +void VM_strzone(prvm_prog_t *prog) +{ + char *out; + char string[VM_STRINGTEMP_LENGTH]; + size_t alloclen; + + VM_SAFEPARMCOUNT(1,VM_strzone); + + VM_VarString(prog, 0, string, sizeof(string)); + alloclen = strlen(string) + 1; + PRVM_G_INT(OFS_RETURN) = PRVM_AllocString(prog, alloclen, &out); + memcpy(out, string, alloclen); +} + +/* +========= +VM_strunzone + +strunzone(string s) +========= +*/ +//void(string s) strunzone = #119; // removes a copy of a string from the string zone (you can not use that string again or it may crash!!!) +void VM_strunzone(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_strunzone); + PRVM_FreeString(prog, PRVM_G_INT(OFS_PARM0)); +} + +/* +========= +VM_command (used by client and menu) + +clientcommand(float client, string s) (for client and menu) +========= +*/ +//void(entity e, string s) clientcommand = #440; // executes a command string as if it came from the specified client +//this function originally written by KrimZon, made shorter by LordHavoc +void VM_clcommand (prvm_prog_t *prog) +{ + client_t *temp_client; + int i; + + VM_SAFEPARMCOUNT(2,VM_clcommand); + + i = (int)PRVM_G_FLOAT(OFS_PARM0); + if (!sv.active || i < 0 || i >= svs.maxclients || !svs.clients[i].active) + { + VM_Warning(prog, "VM_clientcommand: %s: invalid client/server is not active !\n", prog->name); + return; + } + + temp_client = host_client; + host_client = svs.clients + i; + Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client, true); + host_client = temp_client; +} + + +/* +========= +VM_tokenize + +float tokenize(string s) +========= +*/ +//float(string s) tokenize = #441; // takes apart a string into individal words (access them with argv), returns how many +//this function originally written by KrimZon, made shorter by LordHavoc +//20040203: rewritten by LordHavoc (no longer uses allocations) +static int num_tokens = 0; +static int tokens[VM_STRINGTEMP_LENGTH / 2]; +static int tokens_startpos[VM_STRINGTEMP_LENGTH / 2]; +static int tokens_endpos[VM_STRINGTEMP_LENGTH / 2]; +static char tokenize_string[VM_STRINGTEMP_LENGTH]; +void VM_tokenize (prvm_prog_t *prog) +{ + const char *p; + + VM_SAFEPARMCOUNT(1,VM_tokenize); + + strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); + p = tokenize_string; + + num_tokens = 0; + for(;;) + { + if (num_tokens >= (int)(sizeof(tokens)/sizeof(tokens[0]))) + break; + + // skip whitespace here to find token start pos + while(*p && ISWHITESPACE(*p)) + ++p; + + tokens_startpos[num_tokens] = p - tokenize_string; + if(!COM_ParseToken_VM_Tokenize(&p, false)) + break; + tokens_endpos[num_tokens] = p - tokenize_string; + tokens[num_tokens] = PRVM_SetTempString(prog, com_token); + ++num_tokens; + } + + PRVM_G_FLOAT(OFS_RETURN) = num_tokens; +} + +//float(string s) tokenize = #514; // takes apart a string into individal words (access them with argv), returns how many +void VM_tokenize_console (prvm_prog_t *prog) +{ + const char *p; + + VM_SAFEPARMCOUNT(1,VM_tokenize); + + strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); + p = tokenize_string; + + num_tokens = 0; + for(;;) + { + if (num_tokens >= (int)(sizeof(tokens)/sizeof(tokens[0]))) + break; + + // skip whitespace here to find token start pos + while(*p && ISWHITESPACE(*p)) + ++p; + + tokens_startpos[num_tokens] = p - tokenize_string; + if(!COM_ParseToken_Console(&p)) + break; + tokens_endpos[num_tokens] = p - tokenize_string; + tokens[num_tokens] = PRVM_SetTempString(prog, com_token); + ++num_tokens; + } + + PRVM_G_FLOAT(OFS_RETURN) = num_tokens; +} + +/* +========= +VM_tokenizebyseparator + +float tokenizebyseparator(string s, string separator1, ...) +========= +*/ +//float(string s, string separator1, ...) tokenizebyseparator = #479; // takes apart a string into individal words (access them with argv), returns how many +//this function returns the token preceding each instance of a separator (of +//which there can be multiple), and the text following the last separator +//useful for parsing certain kinds of data like IP addresses +//example: +//numnumbers = tokenizebyseparator("10.1.2.3", "."); +//returns 4 and the tokens "10" "1" "2" "3". +void VM_tokenizebyseparator (prvm_prog_t *prog) +{ + int j, k; + int numseparators; + int separatorlen[7]; + const char *separators[7]; + const char *p, *p0; + const char *token; + char tokentext[MAX_INPUTLINE]; + + VM_SAFEPARMCOUNTRANGE(2, 8,VM_tokenizebyseparator); + + strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); + p = tokenize_string; + + numseparators = 0; + for (j = 1;j < prog->argc;j++) + { + // skip any blank separator strings + const char *s = PRVM_G_STRING(OFS_PARM0+j*3); + if (!s[0]) + continue; + separators[numseparators] = s; + separatorlen[numseparators] = strlen(s); + numseparators++; + } + + num_tokens = 0; + j = 0; + + while (num_tokens < (int)(sizeof(tokens)/sizeof(tokens[0]))) + { + token = tokentext + j; + tokens_startpos[num_tokens] = p - tokenize_string; + p0 = p; + while (*p) + { + for (k = 0;k < numseparators;k++) + { + if (!strncmp(p, separators[k], separatorlen[k])) + { + p += separatorlen[k]; + break; + } + } + if (k < numseparators) + break; + if (j < (int)sizeof(tokentext)-1) + tokentext[j++] = *p; + p++; + p0 = p; + } + tokens_endpos[num_tokens] = p0 - tokenize_string; + if (j >= (int)sizeof(tokentext)) + break; + tokentext[j++] = 0; + tokens[num_tokens++] = PRVM_SetTempString(prog, token); + if (!*p) + break; + } + + PRVM_G_FLOAT(OFS_RETURN) = num_tokens; +} + +//string(float n) argv = #442; // returns a word from the tokenized string (returns nothing for an invalid index) +//this function originally written by KrimZon, made shorter by LordHavoc +void VM_argv (prvm_prog_t *prog) +{ + int token_num; + + VM_SAFEPARMCOUNT(1,VM_argv); + + token_num = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(token_num < 0) + token_num += num_tokens; + + if (token_num >= 0 && token_num < num_tokens) + PRVM_G_INT(OFS_RETURN) = tokens[token_num]; + else + PRVM_G_INT(OFS_RETURN) = OFS_NULL; +} + +//float(float n) argv_start_index = #515; // returns the start index of a token +void VM_argv_start_index (prvm_prog_t *prog) +{ + int token_num; + + VM_SAFEPARMCOUNT(1,VM_argv); + + token_num = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(token_num < 0) + token_num += num_tokens; + + if (token_num >= 0 && token_num < num_tokens) + PRVM_G_FLOAT(OFS_RETURN) = tokens_startpos[token_num]; + else + PRVM_G_FLOAT(OFS_RETURN) = -1; +} + +//float(float n) argv_end_index = #516; // returns the end index of a token +void VM_argv_end_index (prvm_prog_t *prog) +{ + int token_num; + + VM_SAFEPARMCOUNT(1,VM_argv); + + token_num = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(token_num < 0) + token_num += num_tokens; + + if (token_num >= 0 && token_num < num_tokens) + PRVM_G_FLOAT(OFS_RETURN) = tokens_endpos[token_num]; + else + PRVM_G_FLOAT(OFS_RETURN) = -1; +} + +/* +========= +VM_isserver + +float isserver() +========= +*/ +void VM_isserver(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_serverstate); + + PRVM_G_FLOAT(OFS_RETURN) = sv.active && (svs.maxclients > 1 || cls.state == ca_dedicated); +} + +/* +========= +VM_clientcount + +float clientcount() +========= +*/ +void VM_clientcount(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_clientcount); + + PRVM_G_FLOAT(OFS_RETURN) = svs.maxclients; +} + +/* +========= +VM_clientstate + +float clientstate() +========= +*/ +void VM_clientstate(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_clientstate); + + + switch( cls.state ) { + case ca_uninitialized: + case ca_dedicated: + PRVM_G_FLOAT(OFS_RETURN) = 0; + break; + case ca_disconnected: + PRVM_G_FLOAT(OFS_RETURN) = 1; + break; + case ca_connected: + PRVM_G_FLOAT(OFS_RETURN) = 2; + break; + default: + // should never be reached! + break; + } +} + +/* +========= +VM_getostype + +float getostype(prvm_prog_t *prog) +========= +*/ // not used at the moment -> not included in the common list +void VM_getostype(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_getostype); + + /* + OS_WINDOWS + OS_LINUX + OS_MAC - not supported + */ + +#ifdef WIN32 + PRVM_G_FLOAT(OFS_RETURN) = 0; +#elif defined(MACOSX) + PRVM_G_FLOAT(OFS_RETURN) = 2; +#else + PRVM_G_FLOAT(OFS_RETURN) = 1; +#endif +} + +/* +========= +VM_gettime + +float gettime(prvm_prog_t *prog) +========= +*/ +float CDAudio_GetPosition(void); +void VM_gettime(prvm_prog_t *prog) +{ + int timer_index; + + VM_SAFEPARMCOUNTRANGE(0,1,VM_gettime); + + if(prog->argc == 0) + { + PRVM_G_FLOAT(OFS_RETURN) = (prvm_vec_t) realtime; + } + else + { + timer_index = (int) PRVM_G_FLOAT(OFS_PARM0); + switch(timer_index) + { + case 0: // GETTIME_FRAMESTART + PRVM_G_FLOAT(OFS_RETURN) = realtime; + break; + case 1: // GETTIME_REALTIME + PRVM_G_FLOAT(OFS_RETURN) = Sys_DirtyTime(); + break; + case 2: // GETTIME_HIRES + PRVM_G_FLOAT(OFS_RETURN) = (Sys_DirtyTime() - host_dirtytime); + break; + case 3: // GETTIME_UPTIME + PRVM_G_FLOAT(OFS_RETURN) = realtime; + break; + case 4: // GETTIME_CDTRACK + PRVM_G_FLOAT(OFS_RETURN) = CDAudio_GetPosition(); + break; + default: + VM_Warning(prog, "VM_gettime: %s: unsupported timer specified, returning realtime\n", prog->name); + PRVM_G_FLOAT(OFS_RETURN) = realtime; + break; + } + } +} + +/* +========= +VM_getsoundtime + +float getsoundtime(prvm_prog_t *prog) +========= +*/ + +void VM_getsoundtime (prvm_prog_t *prog) +{ + int entnum, entchannel; + VM_SAFEPARMCOUNT(2,VM_getsoundtime); + + if (prog == SVVM_prog) + entnum = PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0)); + else if (prog == CLVM_prog) + entnum = MAX_EDICTS + PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0)); + else + { + VM_Warning(prog, "VM_getsoundtime: %s: not supported on this progs\n", prog->name); + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; + } + entchannel = (int)PRVM_G_FLOAT(OFS_PARM1); + entchannel = CHAN_USER2ENGINE(entchannel); + if (!IS_CHAN(entchannel)) + VM_Warning(prog, "VM_getsoundtime: %s: bad channel %i\n", prog->name, entchannel); + PRVM_G_FLOAT(OFS_RETURN) = (prvm_vec_t)S_GetEntChannelPosition(entnum, entchannel); +} + +/* +========= +VM_GetSoundLen + +string soundlength (string sample) +========= +*/ +void VM_soundlength (prvm_prog_t *prog) +{ + const char *s; + + VM_SAFEPARMCOUNT(1, VM_soundlength); + + s = PRVM_G_STRING(OFS_PARM0); + PRVM_G_FLOAT(OFS_RETURN) = S_SoundLength(s); +} + +/* +========= +VM_loadfromdata + +loadfromdata(string data) +========= +*/ +void VM_loadfromdata(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_loadentsfromfile); + + PRVM_ED_LoadFromFile(prog, PRVM_G_STRING(OFS_PARM0)); +} + +/* +======================== +VM_parseentitydata + +parseentitydata(entity ent, string data) +======================== +*/ +void VM_parseentitydata(prvm_prog_t *prog) +{ + prvm_edict_t *ent; + const char *data; + + VM_SAFEPARMCOUNT(2, VM_parseentitydata); + + // get edict and test it + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent->priv.required->free) + prog->error_cmd("VM_parseentitydata: %s: Can only set already spawned entities (entity %i is free)!", prog->name, PRVM_NUM_FOR_EDICT(ent)); + + data = PRVM_G_STRING(OFS_PARM1); + + // parse the opening brace + if (!COM_ParseToken_Simple(&data, false, false, true) || com_token[0] != '{' ) + prog->error_cmd("VM_parseentitydata: %s: Couldn't parse entity data:\n%s", prog->name, data ); + + PRVM_ED_ParseEdict (prog, data, ent); +} + +/* +========= +VM_loadfromfile + +loadfromfile(string file) +========= +*/ +void VM_loadfromfile(prvm_prog_t *prog) +{ + const char *filename; + char *data; + + VM_SAFEPARMCOUNT(1,VM_loadfromfile); + + filename = PRVM_G_STRING(OFS_PARM0); + if (FS_CheckNastyPath(filename, false)) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning(prog, "VM_loadfromfile: %s dangerous or non-portable filename \"%s\" not allowed. (contains : or \\ or begins with .. or /)\n", prog->name, filename); + return; + } + + // not conform with VM_fopen + data = (char *)FS_LoadFile(filename, tempmempool, false, NULL); + if (data == NULL) + PRVM_G_FLOAT(OFS_RETURN) = -1; + + PRVM_ED_LoadFromFile(prog, data); + + if(data) + Mem_Free(data); +} + + +/* +========= +VM_modulo + +float mod(float val, float m) +========= +*/ +void VM_modulo(prvm_prog_t *prog) +{ + prvm_int_t val, m; + VM_SAFEPARMCOUNT(2,VM_module); + + val = (prvm_int_t) PRVM_G_FLOAT(OFS_PARM0); + m = (prvm_int_t) PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_FLOAT(OFS_RETURN) = (prvm_vec_t) (val % m); +} + +static void VM_Search_Init(prvm_prog_t *prog) +{ + int i; + for (i = 0;i < PRVM_MAX_OPENSEARCHES;i++) + prog->opensearches[i] = NULL; +} + +static void VM_Search_Reset(prvm_prog_t *prog) +{ + int i; + // reset the fssearch list + for(i = 0; i < PRVM_MAX_OPENSEARCHES; i++) + { + if(prog->opensearches[i]) + FS_FreeSearch(prog->opensearches[i]); + prog->opensearches[i] = NULL; + } +} + +/* +========= +VM_search_begin + +float search_begin(string pattern, float caseinsensitive, float quiet) +========= +*/ +void VM_search_begin(prvm_prog_t *prog) +{ + int handle; + const char *pattern; + int caseinsens, quiet; + + VM_SAFEPARMCOUNT(3, VM_search_begin); + + pattern = PRVM_G_STRING(OFS_PARM0); + + VM_CheckEmptyString(prog, pattern); + + caseinsens = (int)PRVM_G_FLOAT(OFS_PARM1); + quiet = (int)PRVM_G_FLOAT(OFS_PARM2); + + for(handle = 0; handle < PRVM_MAX_OPENSEARCHES; handle++) + if(!prog->opensearches[handle]) + break; + + if(handle >= PRVM_MAX_OPENSEARCHES) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_search_begin: %s ran out of search handles (%i)\n", prog->name, PRVM_MAX_OPENSEARCHES); + return; + } + + if(!(prog->opensearches[handle] = FS_Search(pattern,caseinsens, quiet))) + PRVM_G_FLOAT(OFS_RETURN) = -1; + else + { + prog->opensearches_origin[handle] = PRVM_AllocationOrigin(prog); + PRVM_G_FLOAT(OFS_RETURN) = handle; + } +} + +/* +========= +VM_search_end + +void search_end(float handle) +========= +*/ +void VM_search_end(prvm_prog_t *prog) +{ + int handle; + VM_SAFEPARMCOUNT(1, VM_search_end); + + handle = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) + { + VM_Warning(prog, "VM_search_end: invalid handle %i used in %s\n", handle, prog->name); + return; + } + if(prog->opensearches[handle] == NULL) + { + VM_Warning(prog, "VM_search_end: no such handle %i in %s\n", handle, prog->name); + return; + } + + FS_FreeSearch(prog->opensearches[handle]); + prog->opensearches[handle] = NULL; + if(prog->opensearches_origin[handle]) + PRVM_Free((char *)prog->opensearches_origin[handle]); +} + +/* +========= +VM_search_getsize + +float search_getsize(float handle) +========= +*/ +void VM_search_getsize(prvm_prog_t *prog) +{ + int handle; + VM_SAFEPARMCOUNT(1, VM_M_search_getsize); + + handle = (int)PRVM_G_FLOAT(OFS_PARM0); + + if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) + { + VM_Warning(prog, "VM_search_getsize: invalid handle %i used in %s\n", handle, prog->name); + return; + } + if(prog->opensearches[handle] == NULL) + { + VM_Warning(prog, "VM_search_getsize: no such handle %i in %s\n", handle, prog->name); + return; + } + + PRVM_G_FLOAT(OFS_RETURN) = prog->opensearches[handle]->numfilenames; +} + +/* +========= +VM_search_getfilename + +string search_getfilename(float handle, float num) +========= +*/ +void VM_search_getfilename(prvm_prog_t *prog) +{ + int handle, filenum; + VM_SAFEPARMCOUNT(2, VM_search_getfilename); + + handle = (int)PRVM_G_FLOAT(OFS_PARM0); + filenum = (int)PRVM_G_FLOAT(OFS_PARM1); + + if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) + { + VM_Warning(prog, "VM_search_getfilename: invalid handle %i used in %s\n", handle, prog->name); + return; + } + if(prog->opensearches[handle] == NULL) + { + VM_Warning(prog, "VM_search_getfilename: no such handle %i in %s\n", handle, prog->name); + return; + } + if(filenum < 0 || filenum >= prog->opensearches[handle]->numfilenames) + { + VM_Warning(prog, "VM_search_getfilename: invalid filenum %i in %s\n", filenum, prog->name); + return; + } + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, prog->opensearches[handle]->filenames[filenum]); +} + +/* +========= +VM_chr + +string chr(float ascii) +========= +*/ +void VM_chr(prvm_prog_t *prog) +{ + /* + char tmp[2]; + VM_SAFEPARMCOUNT(1, VM_chr); + + tmp[0] = (unsigned char) PRVM_G_FLOAT(OFS_PARM0); + tmp[1] = 0; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, tmp); + */ + + char tmp[8]; + int len; + VM_SAFEPARMCOUNT(1, VM_chr); + + len = u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0), tmp, sizeof(tmp)); + tmp[len] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, tmp); +} + +//============================================================================= +// Draw builtins (client & menu) + +/* +========= +VM_iscachedpic + +float iscachedpic(string pic) +========= +*/ +void VM_iscachedpic(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_iscachedpic); + + // drawq hasnt such a function, thus always return true + PRVM_G_FLOAT(OFS_RETURN) = false; +} + +/* +========= +VM_precache_pic + +string precache_pic(string pic) +========= +*/ +#define PRECACHE_PIC_FROMWAD 1 /* FTEQW, not supported here */ +#define PRECACHE_PIC_NOTPERSISTENT 2 +//#define PRECACHE_PIC_NOCLAMP 4 +#define PRECACHE_PIC_MIPMAP 8 +void VM_precache_pic(prvm_prog_t *prog) +{ + const char *s; + int flags = 0; + + VM_SAFEPARMCOUNTRANGE(1, 2, VM_precache_pic); + + s = PRVM_G_STRING(OFS_PARM0); + PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); + VM_CheckEmptyString(prog, s); + + if(prog->argc >= 2) + { + int f = PRVM_G_FLOAT(OFS_PARM1); + if(f & PRECACHE_PIC_NOTPERSISTENT) + flags |= CACHEPICFLAG_NOTPERSISTENT; + //if(f & PRECACHE_PIC_NOCLAMP) + // flags |= CACHEPICFLAG_NOCLAMP; + if(f & PRECACHE_PIC_MIPMAP) + flags |= CACHEPICFLAG_MIPMAP; + } + + // AK Draw_CachePic is supposed to always return a valid pointer + if( Draw_CachePic_Flags(s, flags)->tex == r_texture_notexture ) + PRVM_G_INT(OFS_RETURN) = OFS_NULL; +} + +/* +========= +VM_freepic + +freepic(string s) +========= +*/ +void VM_freepic(prvm_prog_t *prog) +{ + const char *s; + + VM_SAFEPARMCOUNT(1,VM_freepic); + + s = PRVM_G_STRING(OFS_PARM0); + VM_CheckEmptyString(prog, s); + + Draw_FreePic(s); +} + +static void getdrawfontscale(prvm_prog_t *prog, float *sx, float *sy) +{ + vec3_t v; + *sx = *sy = 1; + VectorCopy(PRVM_drawglobalvector(drawfontscale), v); + if(VectorLength2(v) > 0) + { + *sx = v[0]; + *sy = v[1]; + } +} + +static dp_font_t *getdrawfont(prvm_prog_t *prog) +{ + int f = (int) PRVM_drawglobalfloat(drawfont); + if(f < 0 || f >= dp_fonts.maxsize) + return FONT_DEFAULT; + return &dp_fonts.f[f]; +} + +/* +========= +VM_drawcharacter + +float drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawcharacter(prvm_prog_t *prog) +{ + prvm_vec_t *pos,*scale,*rgb; + char character; + int flag; + float sx, sy; + VM_SAFEPARMCOUNT(6,VM_drawcharacter); + + character = (char) PRVM_G_FLOAT(OFS_PARM1); + if(character == 0) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + VM_Warning(prog, "VM_drawcharacter: %s passed null character !\n",prog->name); + return; + } + + pos = PRVM_G_VECTOR(OFS_PARM0); + scale = PRVM_G_VECTOR(OFS_PARM2); + rgb = PRVM_G_VECTOR(OFS_PARM3); + flag = (int)PRVM_G_FLOAT(OFS_PARM5); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_drawcharacter: %s: wrong DRAWFLAG %i !\n",prog->name,flag); + return; + } + + if(pos[2] || scale[2]) + VM_Warning(prog, "VM_drawcharacter: z value%c from %s discarded\n",(pos[2] && scale[2]) ? 's' : 0,((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); + + if(!scale[0] || !scale[1]) + { + PRVM_G_FLOAT(OFS_RETURN) = -3; + VM_Warning(prog, "VM_drawcharacter: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); + return; + } + + getdrawfontscale(prog, &sx, &sy); + DrawQ_String_Scale(pos[0], pos[1], &character, 1, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont(prog)); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_drawstring + +float drawstring(vector position, string text, vector scale, vector rgb, float alpha[, float flag]) +========= +*/ +void VM_drawstring(prvm_prog_t *prog) +{ + prvm_vec_t *pos,*scale,*rgb; + const char *string; + int flag = 0; + float sx, sy; + VM_SAFEPARMCOUNTRANGE(5,6,VM_drawstring); + + string = PRVM_G_STRING(OFS_PARM1); + pos = PRVM_G_VECTOR(OFS_PARM0); + scale = PRVM_G_VECTOR(OFS_PARM2); + rgb = PRVM_G_VECTOR(OFS_PARM3); + if (prog->argc >= 6) + flag = (int)PRVM_G_FLOAT(OFS_PARM5); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_drawstring: %s: wrong DRAWFLAG %i !\n",prog->name,flag); + return; + } + + if(!scale[0] || !scale[1]) + { + PRVM_G_FLOAT(OFS_RETURN) = -3; + VM_Warning(prog, "VM_drawstring: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); + return; + } + + if(pos[2] || scale[2]) + VM_Warning(prog, "VM_drawstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); + + getdrawfontscale(prog, &sx, &sy); + DrawQ_String_Scale(pos[0], pos[1], string, 0, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont(prog)); + //Font_DrawString(pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_drawcolorcodedstring + +float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) +/ +float drawcolorcodedstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawcolorcodedstring(prvm_prog_t *prog) +{ + prvm_vec_t *pos, *scale; + const char *string; + int flag; + vec3_t rgb; + float sx, sy, alpha; + + VM_SAFEPARMCOUNTRANGE(5,6,VM_drawcolorcodedstring); + + if (prog->argc == 6) // full 6 parms, like normal drawstring + { + pos = PRVM_G_VECTOR(OFS_PARM0); + string = PRVM_G_STRING(OFS_PARM1); + scale = PRVM_G_VECTOR(OFS_PARM2); + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), rgb); + alpha = PRVM_G_FLOAT(OFS_PARM4); + flag = (int)PRVM_G_FLOAT(OFS_PARM5); + } + else + { + pos = PRVM_G_VECTOR(OFS_PARM0); + string = PRVM_G_STRING(OFS_PARM1); + scale = PRVM_G_VECTOR(OFS_PARM2); + rgb[0] = 1.0; + rgb[1] = 1.0; + rgb[2] = 1.0; + alpha = PRVM_G_FLOAT(OFS_PARM3); + flag = (int)PRVM_G_FLOAT(OFS_PARM4); + } + + if(flag < DRAWFLAG_NORMAL || flag >= DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_drawcolorcodedstring: %s: wrong DRAWFLAG %i !\n",prog->name,flag); + return; + } + + if(!scale[0] || !scale[1]) + { + PRVM_G_FLOAT(OFS_RETURN) = -3; + VM_Warning(prog, "VM_drawcolorcodedstring: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); + return; + } + + if(pos[2] || scale[2]) + VM_Warning(prog, "VM_drawcolorcodedstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); + + getdrawfontscale(prog, &sx, &sy); + DrawQ_String_Scale(pos[0], pos[1], string, 0, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], alpha, flag, NULL, false, getdrawfont(prog)); + if (prog->argc == 6) // also return vector of last color + VectorCopy(DrawQ_Color, PRVM_G_VECTOR(OFS_RETURN)); + else + PRVM_G_FLOAT(OFS_RETURN) = 1; +} +/* +========= +VM_stringwidth + +float stringwidth(string text, float allowColorCodes, float size) +========= +*/ +void VM_stringwidth(prvm_prog_t *prog) +{ + const char *string; + vec2_t szv; + float mult; // sz is intended font size so we can later add freetype support, mult is font size multiplier in pixels per character cell + int colors; + float sx, sy; + size_t maxlen = 0; + VM_SAFEPARMCOUNTRANGE(2,3,VM_drawstring); + + getdrawfontscale(prog, &sx, &sy); + if(prog->argc == 3) + { + Vector2Copy(PRVM_G_VECTOR(OFS_PARM2), szv); + mult = 1; + } + else + { + // we want the width for 8x8 font size, divided by 8 + Vector2Set(szv, 8, 8); + mult = 0.125; + // to make sure snapping is turned off, ALWAYS use a nontrivial scale in this case + if(sx >= 0.9 && sx <= 1.1) + { + mult *= 2; + sx /= 2; + sy /= 2; + } + } + + string = PRVM_G_STRING(OFS_PARM0); + colors = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth_UntilWidth_TrackColors_Scale(string, &maxlen, szv[0], szv[1], sx, sy, NULL, !colors, getdrawfont(prog), 1000000000) * mult; +/* + if(prog->argc == 3) + { + mult = sz = PRVM_G_FLOAT(OFS_PARM2); + } + else + { + sz = 8; + mult = 1; + } + + string = PRVM_G_STRING(OFS_PARM0); + colors = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth(string, 0, !colors, getdrawfont()) * mult; // 1x1 characters, don't actually draw +*/ +} + +/* +========= +VM_findfont + +float findfont(string s) +========= +*/ + +static float getdrawfontnum(const char *fontname) +{ + int i; + + for(i = 0; i < dp_fonts.maxsize; ++i) + if(!strcmp(dp_fonts.f[i].title, fontname)) + return i; + return -1; +} + +void VM_findfont(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1,VM_findfont); + PRVM_G_FLOAT(OFS_RETURN) = getdrawfontnum(PRVM_G_STRING(OFS_PARM0)); +} + +/* +========= +VM_loadfont + +float loadfont(string fontname, string fontmaps, string sizes, float slot) +========= +*/ + +void VM_loadfont(prvm_prog_t *prog) +{ + const char *fontname, *filelist, *sizes, *c, *cm; + char mainfont[MAX_QPATH]; + int i, numsizes; + float sz, scale, voffset; + dp_font_t *f; + + VM_SAFEPARMCOUNTRANGE(3,6,VM_loadfont); + + fontname = PRVM_G_STRING(OFS_PARM0); + if (!fontname[0]) + fontname = "default"; + + filelist = PRVM_G_STRING(OFS_PARM1); + if (!filelist[0]) + filelist = "gfx/conchars"; + + sizes = PRVM_G_STRING(OFS_PARM2); + if (!sizes[0]) + sizes = "10"; + + // find a font + f = NULL; + if (prog->argc >= 4) + { + i = PRVM_G_FLOAT(OFS_PARM3); + if (i >= 0 && i < dp_fonts.maxsize) + { + f = &dp_fonts.f[i]; + strlcpy(f->title, fontname, sizeof(f->title)); // replace name + } + } + if (!f) + f = FindFont(fontname, true); + if (!f) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; // something go wrong + } + + memset(f->fallbacks, 0, sizeof(f->fallbacks)); + memset(f->fallback_faces, 0, sizeof(f->fallback_faces)); + + // first font is handled "normally" + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->req_face = atoi(c+1); + else + { + f->req_face = 0; + c = cm; + } + if(!c || (c - filelist) > MAX_QPATH) + strlcpy(mainfont, filelist, sizeof(mainfont)); + else + { + memcpy(mainfont, filelist, c - filelist); + mainfont[c - filelist] = 0; + } + + // handle fallbacks + for(i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + c = strchr(filelist, ','); + if(!c) + break; + filelist = c + 1; + if(!*filelist) + break; + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->fallback_faces[i] = atoi(c+1); + else + { + f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index + c = cm; + } + if(!c || (c-filelist) > MAX_QPATH) + { + strlcpy(f->fallbacks[i], filelist, sizeof(mainfont)); + } + else + { + memcpy(f->fallbacks[i], filelist, c - filelist); + f->fallbacks[i][c - filelist] = 0; + } + } + + // handle sizes + for(i = 0; i < MAX_FONT_SIZES; ++i) + f->req_sizes[i] = -1; + for (numsizes = 0,c = sizes;;) + { + if (!COM_ParseToken_VM_Tokenize(&c, 0)) + break; + sz = atof(com_token); + // detect crap size + if (sz < 0.001f || sz > 1000.0f) + { + VM_Warning(prog, "VM_loadfont: crap size %s", com_token); + continue; + } + // check overflow + if (numsizes == MAX_FONT_SIZES) + { + VM_Warning(prog, "VM_loadfont: MAX_FONT_SIZES = %i exceeded", MAX_FONT_SIZES); + break; + } + f->req_sizes[numsizes] = sz; + numsizes++; + } + + // additional scale/hoffset parms + scale = 1; + voffset = 0; + if (prog->argc >= 5) + { + scale = PRVM_G_FLOAT(OFS_PARM4); + if (scale <= 0) + scale = 1; + } + if (prog->argc >= 6) + voffset = PRVM_G_FLOAT(OFS_PARM5); + + // load + LoadFont(true, mainfont, f, scale, voffset); + + // return index of loaded font + PRVM_G_FLOAT(OFS_RETURN) = (f - dp_fonts.f); +} + +/* +========= +VM_drawpic + +float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawpic(prvm_prog_t *prog) +{ + const char *picname; + prvm_vec_t *size, *pos, *rgb; + int flag = 0; + + VM_SAFEPARMCOUNTRANGE(5,6,VM_drawpic); + + picname = PRVM_G_STRING(OFS_PARM1); + VM_CheckEmptyString(prog, picname); + + // is pic cached ? no function yet for that + if(!1) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning(prog, "VM_drawpic: %s: %s not cached !\n", prog->name, picname); + return; + } + + pos = PRVM_G_VECTOR(OFS_PARM0); + size = PRVM_G_VECTOR(OFS_PARM2); + rgb = PRVM_G_VECTOR(OFS_PARM3); + if (prog->argc >= 6) + flag = (int) PRVM_G_FLOAT(OFS_PARM5); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_drawpic: %s: wrong DRAWFLAG %i !\n",prog->name,flag); + return; + } + + if(pos[2] || size[2]) + VM_Warning(prog, "VM_drawpic: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); + + DrawQ_Pic(pos[0], pos[1], Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT), size[0], size[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} +/* +========= +VM_drawrotpic + +float drawrotpic(vector position, string pic, vector size, vector org, float angle, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawrotpic(prvm_prog_t *prog) +{ + const char *picname; + prvm_vec_t *size, *pos, *org, *rgb; + int flag; + + VM_SAFEPARMCOUNT(8,VM_drawrotpic); + + picname = PRVM_G_STRING(OFS_PARM1); + VM_CheckEmptyString(prog, picname); + + // is pic cached ? no function yet for that + if(!1) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning(prog, "VM_drawrotpic: %s: %s not cached !\n", prog->name, picname); + return; + } + + pos = PRVM_G_VECTOR(OFS_PARM0); + size = PRVM_G_VECTOR(OFS_PARM2); + org = PRVM_G_VECTOR(OFS_PARM3); + rgb = PRVM_G_VECTOR(OFS_PARM5); + flag = (int) PRVM_G_FLOAT(OFS_PARM7); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_drawrotpic: %s: wrong DRAWFLAG %i !\n",prog->name,flag); + return; + } + + if(pos[2] || size[2] || org[2]) + VM_Warning(prog, "VM_drawrotpic: z value from pos/size/org discarded\n"); + + DrawQ_RotPic(pos[0], pos[1], Draw_CachePic_Flags(picname, CACHEPICFLAG_NOTPERSISTENT), size[0], size[1], org[0], org[1], PRVM_G_FLOAT(OFS_PARM4), rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM6), flag); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} +/* +========= +VM_drawsubpic + +float drawsubpic(vector position, vector size, string pic, vector srcPos, vector srcSize, vector rgb, float alpha, float flag) + +========= +*/ +void VM_drawsubpic(prvm_prog_t *prog) +{ + const char *picname; + prvm_vec_t *size, *pos, *rgb, *srcPos, *srcSize, alpha; + int flag; + + VM_SAFEPARMCOUNT(8,VM_drawsubpic); + + picname = PRVM_G_STRING(OFS_PARM2); + VM_CheckEmptyString(prog, picname); + + // is pic cached ? no function yet for that + if(!1) + { + PRVM_G_FLOAT(OFS_RETURN) = -4; + VM_Warning(prog, "VM_drawsubpic: %s: %s not cached !\n", prog->name, picname); + return; + } + + pos = PRVM_G_VECTOR(OFS_PARM0); + size = PRVM_G_VECTOR(OFS_PARM1); + srcPos = PRVM_G_VECTOR(OFS_PARM3); + srcSize = PRVM_G_VECTOR(OFS_PARM4); + rgb = PRVM_G_VECTOR(OFS_PARM5); + alpha = PRVM_G_FLOAT(OFS_PARM6); + flag = (int) PRVM_G_FLOAT(OFS_PARM7); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_drawsubpic: %s: wrong DRAWFLAG %i !\n",prog->name,flag); + return; + } + + if(pos[2] || size[2]) + VM_Warning(prog, "VM_drawsubpic: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); + + DrawQ_SuperPic(pos[0], pos[1], Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT), + size[0], size[1], + srcPos[0], srcPos[1], rgb[0], rgb[1], rgb[2], alpha, + srcPos[0] + srcSize[0], srcPos[1], rgb[0], rgb[1], rgb[2], alpha, + srcPos[0], srcPos[1] + srcSize[1], rgb[0], rgb[1], rgb[2], alpha, + srcPos[0] + srcSize[0], srcPos[1] + srcSize[1], rgb[0], rgb[1], rgb[2], alpha, + flag); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_drawfill + +float drawfill(vector position, vector size, vector rgb, float alpha, float flag) +========= +*/ +void VM_drawfill(prvm_prog_t *prog) +{ + prvm_vec_t *size, *pos, *rgb; + int flag; + + VM_SAFEPARMCOUNT(5,VM_drawfill); + + + pos = PRVM_G_VECTOR(OFS_PARM0); + size = PRVM_G_VECTOR(OFS_PARM1); + rgb = PRVM_G_VECTOR(OFS_PARM2); + flag = (int) PRVM_G_FLOAT(OFS_PARM4); + + if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) + { + PRVM_G_FLOAT(OFS_RETURN) = -2; + VM_Warning(prog, "VM_drawfill: %s: wrong DRAWFLAG %i !\n",prog->name,flag); + return; + } + + if(pos[2] || size[2]) + VM_Warning(prog, "VM_drawfill: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); + + DrawQ_Fill(pos[0], pos[1], size[0], size[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM3), flag); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_drawsetcliparea + +drawsetcliparea(float x, float y, float width, float height) +========= +*/ +void VM_drawsetcliparea(prvm_prog_t *prog) +{ + float x,y,w,h; + VM_SAFEPARMCOUNT(4,VM_drawsetcliparea); + + x = bound(0, PRVM_G_FLOAT(OFS_PARM0), vid_conwidth.integer); + y = bound(0, PRVM_G_FLOAT(OFS_PARM1), vid_conheight.integer); + w = bound(0, PRVM_G_FLOAT(OFS_PARM2) + PRVM_G_FLOAT(OFS_PARM0) - x, (vid_conwidth.integer - x)); + h = bound(0, PRVM_G_FLOAT(OFS_PARM3) + PRVM_G_FLOAT(OFS_PARM1) - y, (vid_conheight.integer - y)); + + DrawQ_SetClipArea(x, y, w, h); +} + +/* +========= +VM_drawresetcliparea + +drawresetcliparea() +========= +*/ +void VM_drawresetcliparea(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_drawresetcliparea); + + DrawQ_ResetClipArea(); +} + +/* +========= +VM_getimagesize + +vector getimagesize(string pic) +========= +*/ +void VM_getimagesize(prvm_prog_t *prog) +{ + const char *p; + cachepic_t *pic; + + VM_SAFEPARMCOUNT(1,VM_getimagesize); + + p = PRVM_G_STRING(OFS_PARM0); + VM_CheckEmptyString(prog, p); + + pic = Draw_CachePic_Flags (p, CACHEPICFLAG_NOTPERSISTENT); + if( pic->tex == r_texture_notexture ) + { + PRVM_G_VECTOR(OFS_RETURN)[0] = 0; + PRVM_G_VECTOR(OFS_RETURN)[1] = 0; + } + else + { + PRVM_G_VECTOR(OFS_RETURN)[0] = pic->width; + PRVM_G_VECTOR(OFS_RETURN)[1] = pic->height; + } + PRVM_G_VECTOR(OFS_RETURN)[2] = 0; +} + +/* +========= +VM_keynumtostring + +string keynumtostring(float keynum) +========= +*/ +void VM_keynumtostring (prvm_prog_t *prog) +{ + char tinystr[2]; + VM_SAFEPARMCOUNT(1, VM_keynumtostring); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, Key_KeynumToString((int)PRVM_G_FLOAT(OFS_PARM0), tinystr, sizeof(tinystr))); +} + +/* +========= +VM_findkeysforcommand + +string findkeysforcommand(string command, float bindmap) + +the returned string is an altstring +========= +*/ +#define FKFC_NUMKEYS 5 +void M_FindKeysForCommand(const char *command, int *keys); +void VM_findkeysforcommand(prvm_prog_t *prog) +{ + const char *cmd; + char ret[VM_STRINGTEMP_LENGTH]; + int keys[FKFC_NUMKEYS]; + int i; + int bindmap; + char vabuf[1024]; + + VM_SAFEPARMCOUNTRANGE(1, 2, VM_findkeysforcommand); + + cmd = PRVM_G_STRING(OFS_PARM0); + if(prog->argc == 2) + bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM1), MAX_BINDMAPS-1); + else + bindmap = 0; // consistent to "bind" + + VM_CheckEmptyString(prog, cmd); + + Key_FindKeysForCommand(cmd, keys, FKFC_NUMKEYS, bindmap); + + ret[0] = 0; + for(i = 0; i < FKFC_NUMKEYS; i++) + strlcat(ret, va(vabuf, sizeof(vabuf), " \'%i\'", keys[i]), sizeof(ret)); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ret); +} + +/* +========= +VM_stringtokeynum + +float stringtokeynum(string key) +========= +*/ +void VM_stringtokeynum (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT( 1, VM_keynumtostring ); + + PRVM_G_FLOAT(OFS_RETURN) = Key_StringToKeynum(PRVM_G_STRING(OFS_PARM0)); +} + +/* +========= +VM_getkeybind + +string getkeybind(float key, float bindmap) +========= +*/ +void VM_getkeybind (prvm_prog_t *prog) +{ + int bindmap; + VM_SAFEPARMCOUNTRANGE(1, 2, VM_CL_getkeybind); + if(prog->argc == 2) + bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM1), MAX_BINDMAPS-1); + else + bindmap = 0; // consistent to "bind" + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, Key_GetBind((int)PRVM_G_FLOAT(OFS_PARM0), bindmap)); +} + +/* +========= +VM_setkeybind + +float setkeybind(float key, string cmd, float bindmap) +========= +*/ +void VM_setkeybind (prvm_prog_t *prog) +{ + int bindmap; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_setkeybind); + if(prog->argc == 3) + bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM2), MAX_BINDMAPS-1); + else + bindmap = 0; // consistent to "bind" + + PRVM_G_FLOAT(OFS_RETURN) = 0; + if(Key_SetBinding((int)PRVM_G_FLOAT(OFS_PARM0), bindmap, PRVM_G_STRING(OFS_PARM1))) + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +========= +VM_getbindmap + +vector getbindmaps() +========= +*/ +void VM_getbindmaps (prvm_prog_t *prog) +{ + int fg, bg; + VM_SAFEPARMCOUNT(0, VM_CL_getbindmap); + Key_GetBindMap(&fg, &bg); + PRVM_G_VECTOR(OFS_RETURN)[0] = fg; + PRVM_G_VECTOR(OFS_RETURN)[1] = bg; + PRVM_G_VECTOR(OFS_RETURN)[2] = 0; +} + +/* +========= +VM_setbindmap + +float setbindmaps(vector bindmap) +========= +*/ +void VM_setbindmaps (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_CL_setbindmap); + PRVM_G_FLOAT(OFS_RETURN) = 0; + if(PRVM_G_VECTOR(OFS_PARM0)[2] == 0) + if(Key_SetBindMap((int)PRVM_G_VECTOR(OFS_PARM0)[0], (int)PRVM_G_VECTOR(OFS_PARM0)[1])) + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +// CL_Video interface functions + +/* +======================== +VM_cin_open + +float cin_open(string file, string name) +======================== +*/ +void VM_cin_open(prvm_prog_t *prog) +{ + const char *file; + const char *name; + + VM_SAFEPARMCOUNT( 2, VM_cin_open ); + + file = PRVM_G_STRING( OFS_PARM0 ); + name = PRVM_G_STRING( OFS_PARM1 ); + + VM_CheckEmptyString(prog, file ); + VM_CheckEmptyString(prog, name ); + + if( CL_OpenVideo( file, name, MENUOWNER, "" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = 1; + else + PRVM_G_FLOAT( OFS_RETURN ) = 0; +} + +/* +======================== +VM_cin_close + +void cin_close(string name) +======================== +*/ +void VM_cin_close(prvm_prog_t *prog) +{ + const char *name; + + VM_SAFEPARMCOUNT( 1, VM_cin_close ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString(prog, name ); + + CL_CloseVideo( CL_GetVideoByName( name ) ); +} + +/* +======================== +VM_cin_setstate +void cin_setstate(string name, float type) +======================== +*/ +void VM_cin_setstate(prvm_prog_t *prog) +{ + const char *name; + clvideostate_t state; + clvideo_t *video; + + VM_SAFEPARMCOUNT( 2, VM_cin_netstate ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString(prog, name ); + + state = (clvideostate_t)((int)PRVM_G_FLOAT( OFS_PARM1 )); + + video = CL_GetVideoByName( name ); + if( video && state > CLVIDEO_UNUSED && state < CLVIDEO_STATECOUNT ) + CL_SetVideoState( video, state ); +} + +/* +======================== +VM_cin_getstate + +float cin_getstate(string name) +======================== +*/ +void VM_cin_getstate(prvm_prog_t *prog) +{ + const char *name; + clvideo_t *video; + + VM_SAFEPARMCOUNT( 1, VM_cin_getstate ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString(prog, name ); + + video = CL_GetVideoByName( name ); + if( video ) + PRVM_G_FLOAT( OFS_RETURN ) = (int)video->state; + else + PRVM_G_FLOAT( OFS_RETURN ) = 0; +} + +/* +======================== +VM_cin_restart + +void cin_restart(string name) +======================== +*/ +void VM_cin_restart(prvm_prog_t *prog) +{ + const char *name; + clvideo_t *video; + + VM_SAFEPARMCOUNT( 1, VM_cin_restart ); + + name = PRVM_G_STRING( OFS_PARM0 ); + VM_CheckEmptyString(prog, name ); + + video = CL_GetVideoByName( name ); + if( video ) + CL_RestartVideo( video ); +} + +/* +======================== +VM_gecko_create + +float[bool] gecko_create( string name ) +======================== +*/ +void VM_gecko_create(prvm_prog_t *prog) { + // REMOVED + PRVM_G_FLOAT( OFS_RETURN ) = 0; +} + +/* +======================== +VM_gecko_destroy + +void gecko_destroy( string name ) +======================== +*/ +void VM_gecko_destroy(prvm_prog_t *prog) { + // REMOVED +} + +/* +======================== +VM_gecko_navigate + +void gecko_navigate( string name, string URI ) +======================== +*/ +void VM_gecko_navigate(prvm_prog_t *prog) { + // REMOVED +} + +/* +======================== +VM_gecko_keyevent + +float[bool] gecko_keyevent( string name, float key, float eventtype ) +======================== +*/ +void VM_gecko_keyevent(prvm_prog_t *prog) { + // REMOVED + PRVM_G_FLOAT( OFS_RETURN ) = 0; +} + +/* +======================== +VM_gecko_movemouse + +void gecko_mousemove( string name, float x, float y ) +======================== +*/ +void VM_gecko_movemouse(prvm_prog_t *prog) { + // REMOVED +} + + +/* +======================== +VM_gecko_resize + +void gecko_resize( string name, float w, float h ) +======================== +*/ +void VM_gecko_resize(prvm_prog_t *prog) { + // REMOVED +} + + +/* +======================== +VM_gecko_get_texture_extent + +vector gecko_get_texture_extent( string name ) +======================== +*/ +void VM_gecko_get_texture_extent(prvm_prog_t *prog) { + // REMOVED + PRVM_G_VECTOR(OFS_RETURN)[0] = 0; + PRVM_G_VECTOR(OFS_RETURN)[1] = 0; +} + + + +/* +============== +VM_makevectors + +Writes new values for v_forward, v_up, and v_right based on angles +void makevectors(vector angle) +============== +*/ +void VM_makevectors (prvm_prog_t *prog) +{ + vec3_t angles, forward, right, up; + VM_SAFEPARMCOUNT(1, VM_makevectors); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), angles); + AngleVectors(angles, forward, right, up); + VectorCopy(forward, PRVM_gameglobalvector(v_forward)); + VectorCopy(right, PRVM_gameglobalvector(v_right)); + VectorCopy(up, PRVM_gameglobalvector(v_up)); +} + +/* +============== +VM_vectorvectors + +Writes new values for v_forward, v_up, and v_right based on the given forward vector +vectorvectors(vector) +============== +*/ +void VM_vectorvectors (prvm_prog_t *prog) +{ + vec3_t forward, right, up; + VM_SAFEPARMCOUNT(1, VM_vectorvectors); + VectorNormalize2(PRVM_G_VECTOR(OFS_PARM0), forward); + VectorVectors(forward, right, up); + VectorCopy(forward, PRVM_gameglobalvector(v_forward)); + VectorCopy(right, PRVM_gameglobalvector(v_right)); + VectorCopy(up, PRVM_gameglobalvector(v_up)); +} + +/* +======================== +VM_drawline + +void drawline(float width, vector pos1, vector pos2, vector rgb, float alpha, float flags) +======================== +*/ +void VM_drawline (prvm_prog_t *prog) +{ + prvm_vec_t *c1, *c2, *rgb; + float alpha, width; + unsigned char flags; + + VM_SAFEPARMCOUNT(6, VM_drawline); + width = PRVM_G_FLOAT(OFS_PARM0); + c1 = PRVM_G_VECTOR(OFS_PARM1); + c2 = PRVM_G_VECTOR(OFS_PARM2); + rgb = PRVM_G_VECTOR(OFS_PARM3); + alpha = PRVM_G_FLOAT(OFS_PARM4); + flags = (int)PRVM_G_FLOAT(OFS_PARM5); + DrawQ_Line(width, c1[0], c1[1], c2[0], c2[1], rgb[0], rgb[1], rgb[2], alpha, flags); +} + +// float(float number, float quantity) bitshift (EXT_BITSHIFT) +void VM_bitshift (prvm_prog_t *prog) +{ + prvm_int_t n1, n2; + VM_SAFEPARMCOUNT(2, VM_bitshift); + + n1 = (prvm_int_t)fabs((prvm_vec_t)((prvm_int_t)PRVM_G_FLOAT(OFS_PARM0))); + n2 = (prvm_int_t)PRVM_G_FLOAT(OFS_PARM1); + if(!n1) + PRVM_G_FLOAT(OFS_RETURN) = n1; + else + if(n2 < 0) + PRVM_G_FLOAT(OFS_RETURN) = (n1 >> -n2); + else + PRVM_G_FLOAT(OFS_RETURN) = (n1 << n2); +} + +//////////////////////////////////////// +// AltString functions +//////////////////////////////////////// + +/* +======================== +VM_altstr_count + +float altstr_count(string) +======================== +*/ +void VM_altstr_count(prvm_prog_t *prog) +{ + const char *altstr, *pos; + int count; + + VM_SAFEPARMCOUNT( 1, VM_altstr_count ); + + altstr = PRVM_G_STRING( OFS_PARM0 ); + //VM_CheckEmptyString(prog, altstr ); + + for( count = 0, pos = altstr ; *pos ; pos++ ) { + if( *pos == '\\' ) { + if( !*++pos ) { + break; + } + } else if( *pos == '\'' ) { + count++; + } + } + + PRVM_G_FLOAT( OFS_RETURN ) = (prvm_vec_t) (count / 2); +} + +/* +======================== +VM_altstr_prepare + +string altstr_prepare(string) +======================== +*/ +void VM_altstr_prepare(prvm_prog_t *prog) +{ + char *out; + const char *instr, *in; + int size; + char outstr[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT( 1, VM_altstr_prepare ); + + instr = PRVM_G_STRING( OFS_PARM0 ); + + for( out = outstr, in = instr, size = sizeof(outstr) - 1 ; size && *in ; size--, in++, out++ ) + if( *in == '\'' ) { + *out++ = '\\'; + *out = '\''; + size--; + } else + *out = *in; + *out = 0; + + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, outstr ); +} + +/* +======================== +VM_altstr_get + +string altstr_get(string, float) +======================== +*/ +void VM_altstr_get(prvm_prog_t *prog) +{ + const char *altstr, *pos; + char *out; + int count, size; + char outstr[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT( 2, VM_altstr_get ); + + altstr = PRVM_G_STRING( OFS_PARM0 ); + + count = (int)PRVM_G_FLOAT( OFS_PARM1 ); + count = count * 2 + 1; + + for( pos = altstr ; *pos && count ; pos++ ) + if( *pos == '\\' ) { + if( !*++pos ) + break; + } else if( *pos == '\'' ) + count--; + + if( !*pos ) { + PRVM_G_INT( OFS_RETURN ) = 0; + return; + } + + for( out = outstr, size = sizeof(outstr) - 1 ; size && *pos ; size--, pos++, out++ ) + if( *pos == '\\' ) { + if( !*++pos ) + break; + *out = *pos; + size--; + } else if( *pos == '\'' ) + break; + else + *out = *pos; + + *out = 0; + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, outstr ); +} + +/* +======================== +VM_altstr_set + +string altstr_set(string altstr, float num, string set) +======================== +*/ +void VM_altstr_set(prvm_prog_t *prog) +{ + int num; + const char *altstr, *str; + const char *in; + char *out; + char outstr[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT( 3, VM_altstr_set ); + + altstr = PRVM_G_STRING( OFS_PARM0 ); + + num = (int)PRVM_G_FLOAT( OFS_PARM1 ); + + str = PRVM_G_STRING( OFS_PARM2 ); + + out = outstr; + for( num = num * 2 + 1, in = altstr; *in && num; *out++ = *in++ ) + if( *in == '\\' ) { + if( !*++in ) { + break; + } + } else if( *in == '\'' ) { + num--; + } + + // copy set in + for( ; *str; *out++ = *str++ ); + // now jump over the old content + for( ; *in ; in++ ) + if( *in == '\'' || (*in == '\\' && !*++in) ) + break; + + strlcpy(out, in, outstr + sizeof(outstr) - out); + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, outstr ); +} + +/* +======================== +VM_altstr_ins +insert after num +string altstr_ins(string altstr, float num, string set) +======================== +*/ +void VM_altstr_ins(prvm_prog_t *prog) +{ + int num; + const char *set; + const char *in; + char *out; + char outstr[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT(3, VM_altstr_ins); + + in = PRVM_G_STRING( OFS_PARM0 ); + num = (int)PRVM_G_FLOAT( OFS_PARM1 ); + set = PRVM_G_STRING( OFS_PARM2 ); + + out = outstr; + for( num = num * 2 + 2 ; *in && num > 0 ; *out++ = *in++ ) + if( *in == '\\' ) { + if( !*++in ) { + break; + } + } else if( *in == '\'' ) { + num--; + } + + *out++ = '\''; + for( ; *set ; *out++ = *set++ ); + *out++ = '\''; + + strlcpy(out, in, outstr + sizeof(outstr) - out); + PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, outstr ); +} + + +//////////////////////////////////////// +// BufString functions +//////////////////////////////////////// +//[515]: string buffers support + +static size_t stringbuffers_sortlength; + +static void BufStr_Expand(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex) +{ + if (stringbuffer->max_strings <= strindex) + { + char **oldstrings = stringbuffer->strings; + stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128); + while (stringbuffer->max_strings <= strindex) + stringbuffer->max_strings *= 2; + stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0])); + if (stringbuffer->num_strings > 0) + memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0])); + if (oldstrings) + Mem_Free(oldstrings); + } +} + +static void BufStr_Shrink(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer) +{ + // reduce num_strings if there are empty string slots at the end + while (stringbuffer->num_strings > 0 && stringbuffer->strings[stringbuffer->num_strings - 1] == NULL) + stringbuffer->num_strings--; + + // if empty, free the string pointer array + if (stringbuffer->num_strings == 0) + { + stringbuffer->max_strings = 0; + if (stringbuffer->strings) + Mem_Free(stringbuffer->strings); + stringbuffer->strings = NULL; + } +} + +static int BufStr_SortStringsUP (const void *in1, const void *in2) +{ + const char *a, *b; + a = *((const char **) in1); + b = *((const char **) in2); + if(!a || !a[0]) return 1; + if(!b || !b[0]) return -1; + return strncmp(a, b, stringbuffers_sortlength); +} + +static int BufStr_SortStringsDOWN (const void *in1, const void *in2) +{ + const char *a, *b; + a = *((const char **) in1); + b = *((const char **) in2); + if(!a || !a[0]) return 1; + if(!b || !b[0]) return -1; + return strncmp(b, a, stringbuffers_sortlength); +} + +prvm_stringbuffer_t *BufStr_FindCreateReplace (prvm_prog_t *prog, int bufindex, int flags, char *format) +{ + prvm_stringbuffer_t *stringbuffer; + int i; + + if (bufindex < 0) + return NULL; + + // find buffer with wanted index + if (bufindex < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray)) + { + if ( (stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, bufindex)) ) + { + if (stringbuffer->flags & STRINGBUFFER_TEMP) + stringbuffer->flags = flags; // created but has not been used yet + return stringbuffer; + } + return NULL; + } + + // allocate new buffer with wanted index + while(1) + { + stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecord(&prog->stringbuffersarray); + stringbuffer->flags = STRINGBUFFER_TEMP; + for (i = 0;stringbuffer != Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);i++); + if (i == bufindex) + { + stringbuffer->flags = flags; // mark as used + break; + } + } + return stringbuffer; +} + +void BufStr_Set(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex, const char *str) +{ + size_t alloclen; + + if (!stringbuffer || strindex < 0) + return; + + BufStr_Expand(prog, stringbuffer, strindex); + stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1); + if (stringbuffer->strings[strindex]) + Mem_Free(stringbuffer->strings[strindex]); + stringbuffer->strings[strindex] = NULL; + + if (str) + { + // not the NULL string! + alloclen = strlen(str) + 1; + stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); + memcpy(stringbuffer->strings[strindex], str, alloclen); + } + + BufStr_Shrink(prog, stringbuffer); +} + +void BufStr_Del(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer) +{ + int i; + + if (!stringbuffer) + return; + + for (i = 0;i < stringbuffer->num_strings;i++) + if (stringbuffer->strings[i]) + Mem_Free(stringbuffer->strings[i]); + if (stringbuffer->strings) + Mem_Free(stringbuffer->strings); + if(stringbuffer->origin) + PRVM_Free((char *)stringbuffer->origin); + Mem_ExpandableArray_FreeRecord(&prog->stringbuffersarray, stringbuffer); +} + +void BufStr_Flush(prvm_prog_t *prog) +{ + prvm_stringbuffer_t *stringbuffer; + int i, numbuffers; + + numbuffers = Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); + for (i = 0; i < numbuffers; i++) + if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) ) + BufStr_Del(prog, stringbuffer); + Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64); +} + +/* +======================== +VM_buf_create +creates new buffer, and returns it's index, returns -1 if failed +float buf_create(prvm_prog_t *prog) = #460; +float newbuf(string format, float flags) = #460; +======================== +*/ + +void VM_buf_create (prvm_prog_t *prog) +{ + prvm_stringbuffer_t *stringbuffer; + int i; + + VM_SAFEPARMCOUNTRANGE(0, 2, VM_buf_create); + + // VorteX: optional parm1 (buffer format) is unfinished, to keep intact with future databuffers extension must be set to "string" + if(prog->argc >= 1 && strcmp(PRVM_G_STRING(OFS_PARM0), "string")) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; + } + stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecord(&prog->stringbuffersarray); + for (i = 0;stringbuffer != Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);i++); + stringbuffer->origin = PRVM_AllocationOrigin(prog); + // optional flags parm + if (prog->argc >= 2) + stringbuffer->flags = (int)PRVM_G_FLOAT(OFS_PARM1) & STRINGBUFFER_QCFLAGS; + PRVM_G_FLOAT(OFS_RETURN) = i; +} + + + +/* +======================== +VM_buf_del +deletes buffer and all strings in it +void buf_del(float bufhandle) = #461; +======================== +*/ +void VM_buf_del (prvm_prog_t *prog) +{ + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNT(1, VM_buf_del); + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if (stringbuffer) + BufStr_Del(prog, stringbuffer); + else + { + VM_Warning(prog, "VM_buf_del: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } +} + +/* +======================== +VM_buf_getsize +how many strings are stored in buffer +float buf_getsize(float bufhandle) = #462; +======================== +*/ +void VM_buf_getsize (prvm_prog_t *prog) +{ + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNT(1, VM_buf_getsize); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + VM_Warning(prog, "VM_buf_getsize: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + else + PRVM_G_FLOAT(OFS_RETURN) = stringbuffer->num_strings; +} + +/* +======================== +VM_buf_copy +copy all content from one buffer to another, make sure it exists +void buf_copy(float bufhandle_from, float bufhandle_to) = #463; +======================== +*/ +void VM_buf_copy (prvm_prog_t *prog) +{ + prvm_stringbuffer_t *srcstringbuffer, *dststringbuffer; + int i; + VM_SAFEPARMCOUNT(2, VM_buf_copy); + + srcstringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!srcstringbuffer) + { + VM_Warning(prog, "VM_buf_copy: invalid source buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + i = (int)PRVM_G_FLOAT(OFS_PARM1); + if(i == (int)PRVM_G_FLOAT(OFS_PARM0)) + { + VM_Warning(prog, "VM_buf_copy: source == destination (%i) in %s\n", i, prog->name); + return; + } + dststringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!dststringbuffer) + { + VM_Warning(prog, "VM_buf_copy: invalid destination buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), prog->name); + return; + } + + for (i = 0;i < dststringbuffer->num_strings;i++) + if (dststringbuffer->strings[i]) + Mem_Free(dststringbuffer->strings[i]); + if (dststringbuffer->strings) + Mem_Free(dststringbuffer->strings); + *dststringbuffer = *srcstringbuffer; + if (dststringbuffer->max_strings) + dststringbuffer->strings = (char **)Mem_Alloc(prog->progs_mempool, sizeof(dststringbuffer->strings[0]) * dststringbuffer->max_strings); + + for (i = 0;i < dststringbuffer->num_strings;i++) + { + if (srcstringbuffer->strings[i]) + { + size_t stringlen; + stringlen = strlen(srcstringbuffer->strings[i]) + 1; + dststringbuffer->strings[i] = (char *)Mem_Alloc(prog->progs_mempool, stringlen); + memcpy(dststringbuffer->strings[i], srcstringbuffer->strings[i], stringlen); + } + } +} + +/* +======================== +VM_buf_sort +sort buffer by beginnings of strings (cmplength defaults it's length) +"backward == TRUE" means that sorting goes upside-down +void buf_sort(float bufhandle, float cmplength, float backward) = #464; +======================== +*/ +void VM_buf_sort (prvm_prog_t *prog) +{ + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNT(3, VM_buf_sort); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning(prog, "VM_buf_sort: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + if(stringbuffer->num_strings <= 0) + { + VM_Warning(prog, "VM_buf_sort: tried to sort empty buffer %i in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + stringbuffers_sortlength = (int)PRVM_G_FLOAT(OFS_PARM1); + if(stringbuffers_sortlength <= 0) + stringbuffers_sortlength = 0x7FFFFFFF; + + if(!PRVM_G_FLOAT(OFS_PARM2)) + qsort(stringbuffer->strings, stringbuffer->num_strings, sizeof(char*), BufStr_SortStringsUP); + else + qsort(stringbuffer->strings, stringbuffer->num_strings, sizeof(char*), BufStr_SortStringsDOWN); + + BufStr_Shrink(prog, stringbuffer); +} + +/* +======================== +VM_buf_implode +concantenates all buffer string into one with "glue" separator and returns it as tempstring +string buf_implode(float bufhandle, string glue) = #465; +======================== +*/ +void VM_buf_implode (prvm_prog_t *prog) +{ + prvm_stringbuffer_t *stringbuffer; + char k[VM_STRINGTEMP_LENGTH]; + const char *sep; + int i; + size_t l; + VM_SAFEPARMCOUNT(2, VM_buf_implode); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + if(!stringbuffer) + { + VM_Warning(prog, "VM_buf_implode: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + if(!stringbuffer->num_strings) + return; + sep = PRVM_G_STRING(OFS_PARM1); + k[0] = 0; + for(l = i = 0;i < stringbuffer->num_strings;i++) + { + if(stringbuffer->strings[i]) + { + l += (i > 0 ? strlen(sep) : 0) + strlen(stringbuffer->strings[i]); + if (l >= sizeof(k) - 1) + break; + strlcat(k, sep, sizeof(k)); + strlcat(k, stringbuffer->strings[i], sizeof(k)); + } + } + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, k); +} + +/* +======================== +VM_bufstr_get +get a string from buffer, returns tempstring, dont str_unzone it! +string bufstr_get(float bufhandle, float string_index) = #465; +======================== +*/ +void VM_bufstr_get (prvm_prog_t *prog) +{ + prvm_stringbuffer_t *stringbuffer; + int strindex; + VM_SAFEPARMCOUNT(2, VM_bufstr_get); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning(prog, "VM_bufstr_get: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + strindex = (int)PRVM_G_FLOAT(OFS_PARM1); + if (strindex < 0) + { + // VM_Warning(prog, "VM_bufstr_get: invalid string index %i used in %s\n", strindex, prog->name); + return; + } + if (strindex < stringbuffer->num_strings && stringbuffer->strings[strindex]) + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, stringbuffer->strings[strindex]); +} + +/* +======================== +VM_bufstr_set +copies a string into selected slot of buffer +void bufstr_set(float bufhandle, float string_index, string str) = #466; +======================== +*/ +void VM_bufstr_set (prvm_prog_t *prog) +{ + int strindex; + prvm_stringbuffer_t *stringbuffer; + const char *news; + + VM_SAFEPARMCOUNT(3, VM_bufstr_set); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning(prog, "VM_bufstr_set: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + strindex = (int)PRVM_G_FLOAT(OFS_PARM1); + if(strindex < 0 || strindex >= 1000000) // huge number of strings + { + VM_Warning(prog, "VM_bufstr_set: invalid string index %i used in %s\n", strindex, prog->name); + return; + } + + news = PRVM_G_STRING(OFS_PARM2); + BufStr_Set(prog, stringbuffer, strindex, news); +} + +/* +======================== +VM_bufstr_add +adds string to buffer in first free slot and returns its index +"order == TRUE" means that string will be added after last "full" slot +float bufstr_add(float bufhandle, string str, float order) = #467; +======================== +*/ +void VM_bufstr_add (prvm_prog_t *prog) +{ + int order, strindex; + prvm_stringbuffer_t *stringbuffer; + const char *string; + size_t alloclen; + + VM_SAFEPARMCOUNT(3, VM_bufstr_add); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + PRVM_G_FLOAT(OFS_RETURN) = -1; + if(!stringbuffer) + { + VM_Warning(prog, "VM_bufstr_add: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + if(!PRVM_G_INT(OFS_PARM1)) // NULL string + { + VM_Warning(prog, "VM_bufstr_add: can not add an empty string to buffer %i in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + string = PRVM_G_STRING(OFS_PARM1); + order = (int)PRVM_G_FLOAT(OFS_PARM2); + if(order) + strindex = stringbuffer->num_strings; + else + for (strindex = 0;strindex < stringbuffer->num_strings;strindex++) + if (stringbuffer->strings[strindex] == NULL) + break; + + BufStr_Expand(prog, stringbuffer, strindex); + + stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1); + alloclen = strlen(string) + 1; + stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); + memcpy(stringbuffer->strings[strindex], string, alloclen); + + PRVM_G_FLOAT(OFS_RETURN) = strindex; +} + +/* +======================== +VM_bufstr_free +delete string from buffer +void bufstr_free(float bufhandle, float string_index) = #468; +======================== +*/ +void VM_bufstr_free (prvm_prog_t *prog) +{ + int i; + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNT(2, VM_bufstr_free); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning(prog, "VM_bufstr_free: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + i = (int)PRVM_G_FLOAT(OFS_PARM1); + if(i < 0) + { + VM_Warning(prog, "VM_bufstr_free: invalid string index %i used in %s\n", i, prog->name); + return; + } + + if (i < stringbuffer->num_strings) + { + if(stringbuffer->strings[i]) + Mem_Free(stringbuffer->strings[i]); + stringbuffer->strings[i] = NULL; + } + + BufStr_Shrink(prog, stringbuffer); +} + +/* +======================== +VM_buf_loadfile +load a file into string buffer, return 0 or 1 +float buf_loadfile(string filename, float bufhandle) = #535; +======================== +*/ +void VM_buf_loadfile(prvm_prog_t *prog) +{ + size_t alloclen; + prvm_stringbuffer_t *stringbuffer; + char string[VM_STRINGTEMP_LENGTH]; + int filenum, strindex, c, end; + const char *filename; + char vabuf[1024]; + + VM_SAFEPARMCOUNT(2, VM_buf_loadfile); + + // get file + filename = PRVM_G_STRING(OFS_PARM0); + for (filenum = 0;filenum < PRVM_MAX_OPENFILES;filenum++) + if (prog->openfiles[filenum] == NULL) + break; + prog->openfiles[filenum] = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "data/%s", filename), false); + if (prog->openfiles[filenum] == NULL) + prog->openfiles[filenum] = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "%s", filename), false); + if (prog->openfiles[filenum] == NULL) + { + if (developer_extra.integer) + VM_Warning(prog, "VM_buf_loadfile: failed to open file %s in %s\n", filename, prog->name); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + + // get string buffer + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM1)); + if(!stringbuffer) + { + VM_Warning(prog, "VM_buf_loadfile: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), prog->name); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + + // read file (append to the end of buffer) + strindex = stringbuffer->num_strings; + while(1) + { + // read line + end = 0; + for (;;) + { + c = FS_Getc(prog->openfiles[filenum]); + if (c == '\r' || c == '\n' || c < 0) + break; + if (end < VM_STRINGTEMP_LENGTH - 1) + string[end++] = c; + } + string[end] = 0; + // remove \n following \r + if (c == '\r') + { + c = FS_Getc(prog->openfiles[filenum]); + if (c != '\n') + FS_UnGetc(prog->openfiles[filenum], (unsigned char)c); + } + // add and continue + if (c >= 0 || end) + { + BufStr_Expand(prog, stringbuffer, strindex); + stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1); + alloclen = strlen(string) + 1; + stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); + memcpy(stringbuffer->strings[strindex], string, alloclen); + strindex = stringbuffer->num_strings; + } + else + break; + } + + // close file + FS_Close(prog->openfiles[filenum]); + prog->openfiles[filenum] = NULL; + if (prog->openfiles_origin[filenum]) + PRVM_Free((char *)prog->openfiles_origin[filenum]); + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +/* +======================== +VM_buf_writefile +writes stringbuffer to a file, returns 0 or 1 +float buf_writefile(float filehandle, float bufhandle, [, float startpos, float numstrings]) = #468; +======================== +*/ + +void VM_buf_writefile(prvm_prog_t *prog) +{ + int filenum, strindex, strnum, strlength; + prvm_stringbuffer_t *stringbuffer; + + VM_SAFEPARMCOUNTRANGE(2, 4, VM_buf_writefile); + + // get file + filenum = (int)PRVM_G_FLOAT(OFS_PARM0); + if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) + { + VM_Warning(prog, "VM_buf_writefile: invalid file handle %i used in %s\n", filenum, prog->name); + return; + } + if (prog->openfiles[filenum] == NULL) + { + VM_Warning(prog, "VM_buf_writefile: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name); + return; + } + + // get string buffer + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM1)); + if(!stringbuffer) + { + VM_Warning(prog, "VM_buf_writefile: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), prog->name); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + + // get start and end parms + if (prog->argc > 3) + { + strindex = (int)PRVM_G_FLOAT(OFS_PARM2); + strnum = (int)PRVM_G_FLOAT(OFS_PARM3); + } + else if (prog->argc > 2) + { + strindex = (int)PRVM_G_FLOAT(OFS_PARM2); + strnum = stringbuffer->num_strings - strindex; + } + else + { + strindex = 0; + strnum = stringbuffer->num_strings; + } + if (strindex < 0 || strindex >= stringbuffer->num_strings) + { + VM_Warning(prog, "VM_buf_writefile: wrong start string index %i used in %s\n", strindex, prog->name); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + if (strnum < 0) + { + VM_Warning(prog, "VM_buf_writefile: wrong strings count %i used in %s\n", strnum, prog->name); + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + + // write + while(strindex < stringbuffer->num_strings && strnum) + { + if (stringbuffer->strings[strindex]) + { + if ((strlength = strlen(stringbuffer->strings[strindex]))) + FS_Write(prog->openfiles[filenum], stringbuffer->strings[strindex], strlength); + FS_Write(prog->openfiles[filenum], "\n", 1); + } + strindex++; + strnum--; + } + + PRVM_G_FLOAT(OFS_RETURN) = 1; +} + +#define MATCH_AUTO 0 +#define MATCH_WHOLE 1 +#define MATCH_LEFT 2 +#define MATCH_RIGHT 3 +#define MATCH_MIDDLE 4 +#define MATCH_PATTERN 5 + +static const char *detect_match_rule(char *pattern, int *matchrule) +{ + char *ppos, *qpos; + int patternlength; + + patternlength = strlen(pattern); + ppos = strchr(pattern, '*'); + qpos = strchr(pattern, '?'); + // has ? - pattern + if (qpos) + { + *matchrule = MATCH_PATTERN; + return pattern; + } + // has * - left, mid, right or pattern + if (ppos) + { + // starts with * - may be right/mid or pattern + if ((ppos - pattern) == 0) + { + ppos = strchr(pattern+1, '*'); + // *something + if (!ppos) + { + *matchrule = MATCH_RIGHT; + return pattern+1; + } + // *something* + if ((ppos - pattern) == patternlength) + { + *matchrule = MATCH_MIDDLE; + *ppos = 0; + return pattern+1; + } + // *som*thing + *matchrule = MATCH_PATTERN; + return pattern; + } + // end with * - left + if ((ppos - pattern) == patternlength) + { + *matchrule = MATCH_LEFT; + *ppos = 0; + return pattern; + } + // som*thing + *matchrule = MATCH_PATTERN; + return pattern; + } + // have no wildcards - whole string + *matchrule = MATCH_WHOLE; + return pattern; +} + +// todo: support UTF8 +static qboolean match_rule(const char *string, int max_string, const char *pattern, int patternlength, int rule) +{ + const char *mid; + + if (rule == 1) + return !strncmp(string, pattern, max_string) ? true : false; + if (rule == 2) + return !strncmp(string, pattern, patternlength) ? true : false; + if (rule == 3) + { + mid = strstr(string, pattern); + return mid && !*(mid+patternlength); + } + if (rule == 4) + return strstr(string, pattern) ? true : false; + // pattern + return matchpattern_with_separator(string, pattern, false, "", false) ? true : false; +} + +/* +======================== +VM_bufstr_find +find an index of bufstring matching rule +float bufstr_find(float bufhandle, string match, float matchrule, float startpos, float step) = #468; +======================== +*/ + +void VM_bufstr_find(prvm_prog_t *prog) +{ + prvm_stringbuffer_t *stringbuffer; + char string[VM_STRINGTEMP_LENGTH]; + int matchrule, matchlen, i, step; + const char *match; + + VM_SAFEPARMCOUNTRANGE(3, 5, VM_bufstr_find); + + PRVM_G_FLOAT(OFS_RETURN) = -1; + + // get string buffer + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning(prog, "VM_bufstr_find: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + + // get pattern/rule + matchrule = (int)PRVM_G_FLOAT(OFS_PARM2); + if (matchrule < 0 && matchrule > 5) + { + VM_Warning(prog, "VM_bufstr_find: invalid match rule %i in %s\n", matchrule, prog->name); + return; + } + if (matchrule) + match = PRVM_G_STRING(OFS_PARM1); + else + { + strlcpy(string, PRVM_G_STRING(OFS_PARM1), sizeof(string)); + match = detect_match_rule(string, &matchrule); + } + matchlen = strlen(match); + + // find + i = (prog->argc > 3) ? (int)PRVM_G_FLOAT(OFS_PARM3) : 0; + step = (prog->argc > 4) ? (int)PRVM_G_FLOAT(OFS_PARM4) : 1; + while(i < stringbuffer->num_strings) + { + if (stringbuffer->strings[i] && match_rule(stringbuffer->strings[i], VM_STRINGTEMP_LENGTH, match, matchlen, matchrule)) + { + PRVM_G_FLOAT(OFS_RETURN) = i; + break; + } + i += step; + } +} + +/* +======================== +VM_matchpattern +float matchpattern(string s, string pattern, float matchrule, float startpos) = #468; +======================== +*/ +void VM_matchpattern(prvm_prog_t *prog) +{ + const char *s, *match; + char string[VM_STRINGTEMP_LENGTH]; + int matchrule, l; + + VM_SAFEPARMCOUNTRANGE(2, 4, VM_matchpattern); + + s = PRVM_G_STRING(OFS_PARM0); + + // get pattern/rule + matchrule = (int)PRVM_G_FLOAT(OFS_PARM2); + if (matchrule < 0 && matchrule > 5) + { + VM_Warning(prog, "VM_bufstr_find: invalid match rule %i in %s\n", matchrule, prog->name); + return; + } + if (matchrule) + match = PRVM_G_STRING(OFS_PARM1); + else + { + strlcpy(string, PRVM_G_STRING(OFS_PARM1), sizeof(string)); + match = detect_match_rule(string, &matchrule); + } + + // offset + l = strlen(match); + if (prog->argc > 3) + s += max(0, min((unsigned int)PRVM_G_FLOAT(OFS_PARM3), strlen(s)-1)); + + // match + PRVM_G_FLOAT(OFS_RETURN) = match_rule(s, VM_STRINGTEMP_LENGTH, match, l, matchrule); +} + +/* +======================== +VM_buf_cvarlist +======================== +*/ + +void VM_buf_cvarlist(prvm_prog_t *prog) +{ + cvar_t *cvar; + const char *partial, *antipartial; + size_t len, antilen; + size_t alloclen; + qboolean ispattern, antiispattern; + int n; + prvm_stringbuffer_t *stringbuffer; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_buf_cvarlist); + + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); + if(!stringbuffer) + { + VM_Warning(prog, "VM_bufstr_free: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + + partial = PRVM_G_STRING(OFS_PARM1); + if(!partial) + len = 0; + else + len = strlen(partial); + + if(prog->argc == 3) + antipartial = PRVM_G_STRING(OFS_PARM2); + else + antipartial = NULL; + if(!antipartial) + antilen = 0; + else + antilen = strlen(antipartial); + + for (n = 0;n < stringbuffer->num_strings;n++) + if (stringbuffer->strings[n]) + Mem_Free(stringbuffer->strings[n]); + if (stringbuffer->strings) + Mem_Free(stringbuffer->strings); + stringbuffer->strings = NULL; + + ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); + antiispattern = antipartial && (strchr(antipartial, '*') || strchr(antipartial, '?')); + + n = 0; + for(cvar = cvar_vars; cvar; cvar = cvar->next) + { + if(len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp(partial, cvar->name, len))) + continue; + + if(antilen && (antiispattern ? matchpattern_with_separator(cvar->name, antipartial, false, "", false) : !strncmp(antipartial, cvar->name, antilen))) + continue; + + ++n; + } + + stringbuffer->max_strings = stringbuffer->num_strings = n; + if (stringbuffer->max_strings) + stringbuffer->strings = (char **)Mem_Alloc(prog->progs_mempool, sizeof(stringbuffer->strings[0]) * stringbuffer->max_strings); + + n = 0; + for(cvar = cvar_vars; cvar; cvar = cvar->next) + { + if(len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp(partial, cvar->name, len))) + continue; + + if(antilen && (antiispattern ? matchpattern_with_separator(cvar->name, antipartial, false, "", false) : !strncmp(antipartial, cvar->name, antilen))) + continue; + + alloclen = strlen(cvar->name) + 1; + stringbuffer->strings[n] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); + memcpy(stringbuffer->strings[n], cvar->name, alloclen); + + ++n; + } +} + + + + +//============= + +/* +============== +VM_changeyaw + +This was a major timewaster in progs, so it was converted to C +============== +*/ +void VM_changeyaw (prvm_prog_t *prog) +{ + prvm_edict_t *ent; + float ideal, current, move, speed; + + // this is called (VERY HACKISHLY) by VM_SV_MoveToGoal, so it can not use any + // parameters because they are the parameters to VM_SV_MoveToGoal, not this + //VM_SAFEPARMCOUNT(0, VM_changeyaw); + + ent = PRVM_PROG_TO_EDICT(PRVM_gameglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning(prog, "changeyaw: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "changeyaw: can not modify free entity\n"); + return; + } + current = PRVM_gameedictvector(ent, angles)[1]; + current = ANGLEMOD(current); + ideal = PRVM_gameedictfloat(ent, ideal_yaw); + speed = PRVM_gameedictfloat(ent, yaw_speed); + + if (current == ideal) + return; + move = ideal - current; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + current += move; + PRVM_gameedictvector(ent, angles)[1] = ANGLEMOD(current); +} + +/* +============== +VM_changepitch +============== +*/ +void VM_changepitch (prvm_prog_t *prog) +{ + prvm_edict_t *ent; + float ideal, current, move, speed; + + VM_SAFEPARMCOUNT(1, VM_changepitch); + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning(prog, "changepitch: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "changepitch: can not modify free entity\n"); + return; + } + current = PRVM_gameedictvector(ent, angles)[0]; + current = ANGLEMOD(current); + ideal = PRVM_gameedictfloat(ent, idealpitch); + speed = PRVM_gameedictfloat(ent, pitch_speed); + + if (current == ideal) + return; + move = ideal - current; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + current += move; + PRVM_gameedictvector(ent, angles)[0] = ANGLEMOD(current); +} + + +void VM_uncolorstring (prvm_prog_t *prog) +{ + char szNewString[VM_STRINGTEMP_LENGTH]; + const char *szString; + + // Prepare Strings + VM_SAFEPARMCOUNT(1, VM_uncolorstring); + szString = PRVM_G_STRING(OFS_PARM0); + COM_StringDecolorize(szString, 0, szNewString, sizeof(szNewString), TRUE); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, szNewString); + +} + +// #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) +//strstr, without generating a new string. Use in conjunction with FRIK_FILE's substring for more similar strstr. +void VM_strstrofs (prvm_prog_t *prog) +{ + const char *instr, *match; + int firstofs; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_strstrofs); + instr = PRVM_G_STRING(OFS_PARM0); + match = PRVM_G_STRING(OFS_PARM1); + firstofs = (prog->argc > 2)?(int)PRVM_G_FLOAT(OFS_PARM2):0; + firstofs = u8_bytelen(instr, firstofs); + + if (firstofs && (firstofs < 0 || firstofs > (int)strlen(instr))) + { + PRVM_G_FLOAT(OFS_RETURN) = -1; + return; + } + + match = strstr(instr+firstofs, match); + if (!match) + PRVM_G_FLOAT(OFS_RETURN) = -1; + else + PRVM_G_FLOAT(OFS_RETURN) = u8_strnlen(instr, match-instr); +} + +//#222 string(string s, float index) str2chr (FTE_STRINGS) +void VM_str2chr (prvm_prog_t *prog) +{ + const char *s; + Uchar ch; + int index; + VM_SAFEPARMCOUNT(2, VM_str2chr); + s = PRVM_G_STRING(OFS_PARM0); + index = u8_bytelen(s, (int)PRVM_G_FLOAT(OFS_PARM1)); + + if((unsigned)index < strlen(s)) + { + if (utf8_enable.integer) + ch = u8_getchar_noendptr(s + index); + else + ch = (unsigned char)s[index]; + PRVM_G_FLOAT(OFS_RETURN) = ch; + } + else + PRVM_G_FLOAT(OFS_RETURN) = 0; +} + +//#223 string(float c, ...) chr2str (FTE_STRINGS) +void VM_chr2str (prvm_prog_t *prog) +{ + /* + char t[9]; + int i; + VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str); + for(i = 0;i < prog->argc && i < (int)sizeof(t) - 1;i++) + t[i] = (unsigned char)PRVM_G_FLOAT(OFS_PARM0+i*3); + t[i] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, t); + */ + char t[9 * 4 + 1]; + int i; + size_t len = 0; + VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str); + for(i = 0; i < prog->argc && len < sizeof(t)-1; ++i) + len += u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0+i*3), t + len, sizeof(t)-1); + t[len] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, t); +} + +static int chrconv_number(int i, int base, int conv) +{ + i -= base; + switch (conv) + { + default: + case 5: + case 6: + case 0: + break; + case 1: + base = '0'; + break; + case 2: + base = '0'+128; + break; + case 3: + base = '0'-30; + break; + case 4: + base = '0'+128-30; + break; + } + return i + base; +} +static int chrconv_punct(int i, int base, int conv) +{ + i -= base; + switch (conv) + { + default: + case 0: + break; + case 1: + base = 0; + break; + case 2: + base = 128; + break; + } + return i + base; +} + +static int chrchar_alpha(int i, int basec, int baset, int convc, int convt, int charnum) +{ + //convert case and colour seperatly... + + i -= baset + basec; + switch (convt) + { + default: + case 0: + break; + case 1: + baset = 0; + break; + case 2: + baset = 128; + break; + + case 5: + case 6: + baset = 128*((charnum&1) == (convt-5)); + break; + } + + switch (convc) + { + default: + case 0: + break; + case 1: + basec = 'a'; + break; + case 2: + basec = 'A'; + break; + } + return i + basec + baset; +} +// #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) +//bulk convert a string. change case or colouring. +void VM_strconv (prvm_prog_t *prog) +{ + int ccase, redalpha, rednum, len, i; + unsigned char resbuf[VM_STRINGTEMP_LENGTH]; + unsigned char *result = resbuf; + + VM_SAFEPARMCOUNTRANGE(3, 8, VM_strconv); + + ccase = (int) PRVM_G_FLOAT(OFS_PARM0); //0 same, 1 lower, 2 upper + redalpha = (int) PRVM_G_FLOAT(OFS_PARM1); //0 same, 1 white, 2 red, 5 alternate, 6 alternate-alternate + rednum = (int) PRVM_G_FLOAT(OFS_PARM2); //0 same, 1 white, 2 red, 3 redspecial, 4 whitespecial, 5 alternate, 6 alternate-alternate + VM_VarString(prog, 3, (char *) resbuf, sizeof(resbuf)); + len = strlen((char *) resbuf); + + for (i = 0; i < len; i++, result++) //should this be done backwards? + { + if (*result >= '0' && *result <= '9') //normal numbers... + *result = chrconv_number(*result, '0', rednum); + else if (*result >= '0'+128 && *result <= '9'+128) + *result = chrconv_number(*result, '0'+128, rednum); + else if (*result >= '0'+128-30 && *result <= '9'+128-30) + *result = chrconv_number(*result, '0'+128-30, rednum); + else if (*result >= '0'-30 && *result <= '9'-30) + *result = chrconv_number(*result, '0'-30, rednum); + + else if (*result >= 'a' && *result <= 'z') //normal numbers... + *result = chrchar_alpha(*result, 'a', 0, ccase, redalpha, i); + else if (*result >= 'A' && *result <= 'Z') //normal numbers... + *result = chrchar_alpha(*result, 'A', 0, ccase, redalpha, i); + else if (*result >= 'a'+128 && *result <= 'z'+128) //normal numbers... + *result = chrchar_alpha(*result, 'a', 128, ccase, redalpha, i); + else if (*result >= 'A'+128 && *result <= 'Z'+128) //normal numbers... + *result = chrchar_alpha(*result, 'A', 128, ccase, redalpha, i); + + else if ((*result & 127) < 16 || !redalpha) //special chars.. + *result = *result; + else if (*result < 128) + *result = chrconv_punct(*result, 0, redalpha); + else + *result = chrconv_punct(*result, 128, redalpha); + } + *result = '\0'; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, (char *) resbuf); +} + +// #225 string(float chars, string s, ...) strpad (FTE_STRINGS) +void VM_strpad (prvm_prog_t *prog) +{ + char src[VM_STRINGTEMP_LENGTH]; + char destbuf[VM_STRINGTEMP_LENGTH]; + int pad; + VM_SAFEPARMCOUNTRANGE(1, 8, VM_strpad); + pad = (int) PRVM_G_FLOAT(OFS_PARM0); + VM_VarString(prog, 1, src, sizeof(src)); + + // note: < 0 = left padding, > 0 = right padding, + // this is reverse logic of printf! + dpsnprintf(destbuf, sizeof(destbuf), "%*s", -pad, src); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, destbuf); +} + +// #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) +//uses qw style \key\value strings +void VM_infoadd (prvm_prog_t *prog) +{ + const char *info, *key; + char value[VM_STRINGTEMP_LENGTH]; + char temp[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_infoadd); + info = PRVM_G_STRING(OFS_PARM0); + key = PRVM_G_STRING(OFS_PARM1); + VM_VarString(prog, 2, value, sizeof(value)); + + strlcpy(temp, info, VM_STRINGTEMP_LENGTH); + + InfoString_SetValue(temp, VM_STRINGTEMP_LENGTH, key, value); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, temp); +} + +// #227 string(string info, string key) infoget (FTE_STRINGS) +//uses qw style \key\value strings +void VM_infoget (prvm_prog_t *prog) +{ + const char *info; + const char *key; + char value[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNT(2, VM_infoget); + info = PRVM_G_STRING(OFS_PARM0); + key = PRVM_G_STRING(OFS_PARM1); + + InfoString_GetValue(info, key, value, VM_STRINGTEMP_LENGTH); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, value); +} + +//#228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) +// also float(string s1, string s2) strcmp (FRIK_FILE) +void VM_strncmp (prvm_prog_t *prog) +{ + const char *s1, *s2; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_strncmp); + s1 = PRVM_G_STRING(OFS_PARM0); + s2 = PRVM_G_STRING(OFS_PARM1); + if (prog->argc > 2) + { + PRVM_G_FLOAT(OFS_RETURN) = strncmp(s1, s2, (size_t)PRVM_G_FLOAT(OFS_PARM2)); + } + else + { + PRVM_G_FLOAT(OFS_RETURN) = strcmp(s1, s2); + } +} + +// #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) +// #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) +void VM_strncasecmp (prvm_prog_t *prog) +{ + const char *s1, *s2; + VM_SAFEPARMCOUNTRANGE(2, 3, VM_strncasecmp); + s1 = PRVM_G_STRING(OFS_PARM0); + s2 = PRVM_G_STRING(OFS_PARM1); + if (prog->argc > 2) + { + PRVM_G_FLOAT(OFS_RETURN) = strncasecmp(s1, s2, (size_t)PRVM_G_FLOAT(OFS_PARM2)); + } + else + { + PRVM_G_FLOAT(OFS_RETURN) = strcasecmp(s1, s2); + } +} + +// #494 float(float caseinsensitive, string s, ...) crc16 +void VM_crc16(prvm_prog_t *prog) +{ + float insensitive; + char s[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNTRANGE(2, 8, VM_crc16); + insensitive = PRVM_G_FLOAT(OFS_PARM0); + VM_VarString(prog, 1, s, sizeof(s)); + PRVM_G_FLOAT(OFS_RETURN) = (unsigned short) ((insensitive ? CRC_Block_CaseInsensitive : CRC_Block) ((unsigned char *) s, strlen(s))); +} + +// #639 float(string digest, string data, ...) digest_hex +void VM_digest_hex(prvm_prog_t *prog) +{ + const char *digest; + + char out[32]; + char outhex[65]; + int outlen; + + char s[VM_STRINGTEMP_LENGTH]; + int len; + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_digest_hex); + digest = PRVM_G_STRING(OFS_PARM0); + if(!digest) + digest = ""; + VM_VarString(prog, 1, s, sizeof(s)); + len = strlen(s); + + outlen = 0; + + if(!strcmp(digest, "MD4")) + { + outlen = 16; + mdfour((unsigned char *) out, (unsigned char *) s, len); + } + else if(!strcmp(digest, "SHA256") && Crypto_Available()) + { + outlen = 32; + sha256((unsigned char *) out, (unsigned char *) s, len); + } + // no warning needed on mismatch - we return string_null to QC + + if(outlen) + { + int i; + static const char *hexmap = "0123456789abcdef"; + for(i = 0; i < outlen; ++i) + { + outhex[2*i] = hexmap[(out[i] >> 4) & 15]; + outhex[2*i+1] = hexmap[(out[i] >> 0) & 15]; + } + outhex[2*i] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, outhex); + } + else + PRVM_G_INT(OFS_RETURN) = 0; +} + +void VM_wasfreed (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_wasfreed); + PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_EDICT(OFS_PARM0)->priv.required->free; +} + +void VM_SetTraceGlobals(prvm_prog_t *prog, const trace_t *trace) +{ + PRVM_gameglobalfloat(trace_allsolid) = trace->allsolid; + PRVM_gameglobalfloat(trace_startsolid) = trace->startsolid; + PRVM_gameglobalfloat(trace_fraction) = trace->fraction; + PRVM_gameglobalfloat(trace_inwater) = trace->inwater; + PRVM_gameglobalfloat(trace_inopen) = trace->inopen; + VectorCopy(trace->endpos, PRVM_gameglobalvector(trace_endpos)); + VectorCopy(trace->plane.normal, PRVM_gameglobalvector(trace_plane_normal)); + PRVM_gameglobalfloat(trace_plane_dist) = trace->plane.dist; + PRVM_gameglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(trace->ent ? trace->ent : prog->edicts); + PRVM_gameglobalfloat(trace_dpstartcontents) = trace->startsupercontents; + PRVM_gameglobalfloat(trace_dphitcontents) = trace->hitsupercontents; + PRVM_gameglobalfloat(trace_dphitq3surfaceflags) = trace->hitq3surfaceflags; + PRVM_gameglobalstring(trace_dphittexturename) = trace->hittexture ? PRVM_SetTempString(prog, trace->hittexture->name) : 0; +} + +void VM_ClearTraceGlobals(prvm_prog_t *prog) +{ + // clean up all trace globals when leaving the VM (anti-triggerbot safeguard) + PRVM_gameglobalfloat(trace_allsolid) = 0; + PRVM_gameglobalfloat(trace_startsolid) = 0; + PRVM_gameglobalfloat(trace_fraction) = 0; + PRVM_gameglobalfloat(trace_inwater) = 0; + PRVM_gameglobalfloat(trace_inopen) = 0; + VectorClear(PRVM_gameglobalvector(trace_endpos)); + VectorClear(PRVM_gameglobalvector(trace_plane_normal)); + PRVM_gameglobalfloat(trace_plane_dist) = 0; + PRVM_gameglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_gameglobalfloat(trace_dpstartcontents) = 0; + PRVM_gameglobalfloat(trace_dphitcontents) = 0; + PRVM_gameglobalfloat(trace_dphitq3surfaceflags) = 0; + PRVM_gameglobalstring(trace_dphittexturename) = 0; +} + +//============= + +void VM_Cmd_Init(prvm_prog_t *prog) +{ + // only init the stuff for the current prog + VM_Files_Init(prog); + VM_Search_Init(prog); +} + +void VM_Cmd_Reset(prvm_prog_t *prog) +{ + CL_PurgeOwner( MENUOWNER ); + VM_Search_Reset(prog); + VM_Files_CloseAll(prog); +} + +// #510 string(string input, ...) uri_escape (DP_QC_URI_ESCAPE) +// does URI escaping on a string (replace evil stuff by %AB escapes) +void VM_uri_escape (prvm_prog_t *prog) +{ + char src[VM_STRINGTEMP_LENGTH]; + char dest[VM_STRINGTEMP_LENGTH]; + char *p, *q; + static const char *hex = "0123456789ABCDEF"; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_escape); + VM_VarString(prog, 0, src, sizeof(src)); + + for(p = src, q = dest; *p && q < dest + sizeof(dest) - 3; ++p) + { + if((*p >= 'A' && *p <= 'Z') + || (*p >= 'a' && *p <= 'z') + || (*p >= '0' && *p <= '9') + || (*p == '-') || (*p == '_') || (*p == '.') + || (*p == '!') || (*p == '~') + || (*p == '\'') || (*p == '(') || (*p == ')')) + *q++ = *p; + else + { + *q++ = '%'; + *q++ = hex[(*(unsigned char *)p >> 4) & 0xF]; + *q++ = hex[ *(unsigned char *)p & 0xF]; + } + } + *q++ = 0; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, dest); +} + +// #510 string(string input, ...) uri_unescape (DP_QC_URI_ESCAPE) +// does URI unescaping on a string (get back the evil stuff) +void VM_uri_unescape (prvm_prog_t *prog) +{ + char src[VM_STRINGTEMP_LENGTH]; + char dest[VM_STRINGTEMP_LENGTH]; + char *p, *q; + int hi, lo; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_unescape); + VM_VarString(prog, 0, src, sizeof(src)); + + for(p = src, q = dest; *p; ) // no need to check size, because unescape can't expand + { + if(*p == '%') + { + if(p[1] >= '0' && p[1] <= '9') + hi = p[1] - '0'; + else if(p[1] >= 'a' && p[1] <= 'f') + hi = p[1] - 'a' + 10; + else if(p[1] >= 'A' && p[1] <= 'F') + hi = p[1] - 'A' + 10; + else + goto nohex; + if(p[2] >= '0' && p[2] <= '9') + lo = p[2] - '0'; + else if(p[2] >= 'a' && p[2] <= 'f') + lo = p[2] - 'a' + 10; + else if(p[2] >= 'A' && p[2] <= 'F') + lo = p[2] - 'A' + 10; + else + goto nohex; + if(hi != 0 || lo != 0) // don't unescape NUL bytes + *q++ = (char) (hi * 0x10 + lo); + p += 3; + continue; + } + +nohex: + // otherwise: + *q++ = *p++; + } + *q++ = 0; + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, dest); +} + +// #502 string(string filename) whichpack (DP_QC_WHICHPACK) +// returns the name of the pack containing a file, or "" if it is not in any pack (but local or non-existant) +void VM_whichpack (prvm_prog_t *prog) +{ + const char *fn, *pack; + + VM_SAFEPARMCOUNT(1, VM_whichpack); + fn = PRVM_G_STRING(OFS_PARM0); + pack = FS_WhichPack(fn); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, pack ? pack : ""); +} + +typedef struct +{ + prvm_prog_t *prog; + double starttime; + float id; + char buffer[MAX_INPUTLINE]; + char posttype[128]; + unsigned char *postdata; // free when uri_to_prog_t is freed + size_t postlen; + char *sigdata; // free when uri_to_prog_t is freed + size_t siglen; +} +uri_to_prog_t; + +static void uri_to_string_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) +{ + prvm_prog_t *prog; + uri_to_prog_t *handle = (uri_to_prog_t *) cbdata; + + prog = handle->prog; + if(!prog->loaded) + { + // curl reply came too late... so just drop it + if(handle->postdata) + Z_Free(handle->postdata); + if(handle->sigdata) + Z_Free(handle->sigdata); + Z_Free(handle); + return; + } + + if((prog->starttime == handle->starttime) && (PRVM_allfunction(URI_Get_Callback))) + { + if(length_received >= sizeof(handle->buffer)) + length_received = sizeof(handle->buffer) - 1; + handle->buffer[length_received] = 0; + + PRVM_G_FLOAT(OFS_PARM0) = handle->id; + PRVM_G_FLOAT(OFS_PARM1) = status; + PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(prog, handle->buffer); + prog->ExecuteProgram(prog, PRVM_allfunction(URI_Get_Callback), "QC function URI_Get_Callback is missing"); + } + + if(handle->postdata) + Z_Free(handle->postdata); + if(handle->sigdata) + Z_Free(handle->sigdata); + Z_Free(handle); +} + +// uri_get() gets content from an URL and calls a callback "uri_get_callback" with it set as string; an unique ID of the transfer is returned +// returns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string +void VM_uri_get (prvm_prog_t *prog) +{ + const char *url; + float id; + qboolean ret; + uri_to_prog_t *handle; + const char *posttype = NULL; + const char *postseparator = NULL; + int poststringbuffer = -1; + int postkeyid = -1; + const char *query_string = NULL; + size_t lq; + + if(!PRVM_allfunction(URI_Get_Callback)) + prog->error_cmd("uri_get called by %s without URI_Get_Callback defined", prog->name); + + VM_SAFEPARMCOUNTRANGE(2, 6, VM_uri_get); + + url = PRVM_G_STRING(OFS_PARM0); + id = PRVM_G_FLOAT(OFS_PARM1); + if(prog->argc >= 3) + posttype = PRVM_G_STRING(OFS_PARM2); + if(prog->argc >= 4) + postseparator = PRVM_G_STRING(OFS_PARM3); + if(prog->argc >= 5) + poststringbuffer = PRVM_G_FLOAT(OFS_PARM4); + if(prog->argc >= 6) + postkeyid = PRVM_G_FLOAT(OFS_PARM5); + handle = (uri_to_prog_t *) Z_Malloc(sizeof(*handle)); // this can't be the prog's mem pool, as curl may call the callback later! + + query_string = strchr(url, '?'); + if(query_string) + ++query_string; + lq = query_string ? strlen(query_string) : 0; + + handle->prog = prog; + handle->starttime = prog->starttime; + handle->id = id; + if(postseparator && posttype && *posttype) + { + size_t l = strlen(postseparator); + if(poststringbuffer >= 0) + { + size_t ltotal; + int i; + // "implode" + prvm_stringbuffer_t *stringbuffer; + stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, poststringbuffer); + if(!stringbuffer) + { + VM_Warning(prog, "uri_get: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); + return; + } + ltotal = 0; + for(i = 0;i < stringbuffer->num_strings;i++) + { + if(i > 0) + ltotal += l; + if(stringbuffer->strings[i]) + ltotal += strlen(stringbuffer->strings[i]); + } + handle->postdata = (unsigned char *)Z_Malloc(ltotal + 1 + lq); + handle->postlen = ltotal; + ltotal = 0; + for(i = 0;i < stringbuffer->num_strings;i++) + { + if(i > 0) + { + memcpy(handle->postdata + ltotal, postseparator, l); + ltotal += l; + } + if(stringbuffer->strings[i]) + { + memcpy(handle->postdata + ltotal, stringbuffer->strings[i], strlen(stringbuffer->strings[i])); + ltotal += strlen(stringbuffer->strings[i]); + } + } + if(ltotal != handle->postlen) + prog->error_cmd("%s: string buffer content size mismatch, possible overrun", prog->name); + } + else + { + handle->postdata = (unsigned char *)Z_Malloc(l + 1 + lq); + handle->postlen = l; + memcpy(handle->postdata, postseparator, l); + } + handle->postdata[handle->postlen] = 0; + if(query_string) + memcpy(handle->postdata + handle->postlen + 1, query_string, lq); + if(postkeyid >= 0) + { + // POST: we sign postdata \0 query string + size_t ll; + handle->sigdata = (char *)Z_Malloc(8192); + strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192); + l = strlen(handle->sigdata); + handle->siglen = Crypto_SignDataDetached(handle->postdata, handle->postlen + 1 + lq, postkeyid, handle->sigdata + l, 8192 - l); + if(!handle->siglen) + { + Z_Free(handle->sigdata); + handle->sigdata = NULL; + goto out1; + } + ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1); + if(!ll) + { + Z_Free(handle->sigdata); + handle->sigdata = NULL; + goto out1; + } + handle->siglen = l + ll; + handle->sigdata[handle->siglen] = 0; + } +out1: + strlcpy(handle->posttype, posttype, sizeof(handle->posttype)); + ret = Curl_Begin_ToMemory_POST(url, handle->sigdata, 0, handle->posttype, handle->postdata, handle->postlen, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle); + } + else + { + if(postkeyid >= 0 && query_string) + { + // GET: we sign JUST the query string + size_t l, ll; + handle->sigdata = (char *)Z_Malloc(8192); + strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192); + l = strlen(handle->sigdata); + handle->siglen = Crypto_SignDataDetached(query_string, lq, postkeyid, handle->sigdata + l, 8192 - l); + if(!handle->siglen) + { + Z_Free(handle->sigdata); + handle->sigdata = NULL; + goto out2; + } + ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1); + if(!ll) + { + Z_Free(handle->sigdata); + handle->sigdata = NULL; + goto out2; + } + handle->siglen = l + ll; + handle->sigdata[handle->siglen] = 0; + } +out2: + handle->postdata = NULL; + handle->postlen = 0; + ret = Curl_Begin_ToMemory_POST(url, handle->sigdata, 0, NULL, NULL, 0, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle); + } + if(ret) + { + PRVM_G_INT(OFS_RETURN) = 1; + } + else + { + if(handle->postdata) + Z_Free(handle->postdata); + if(handle->sigdata) + Z_Free(handle->sigdata); + Z_Free(handle); + PRVM_G_INT(OFS_RETURN) = 0; + } +} + +void VM_netaddress_resolve (prvm_prog_t *prog) +{ + const char *ip; + char normalized[128]; + int port; + lhnetaddress_t addr; + + VM_SAFEPARMCOUNTRANGE(1, 2, VM_netaddress_resolve); + + ip = PRVM_G_STRING(OFS_PARM0); + port = 0; + if(prog->argc > 1) + port = (int) PRVM_G_FLOAT(OFS_PARM1); + + if(LHNETADDRESS_FromString(&addr, ip, port) && LHNETADDRESS_ToString(&addr, normalized, sizeof(normalized), prog->argc > 1)) + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, normalized); + else + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); +} + +//string(prvm_prog_t *prog) getextresponse = #624; // returns the next extResponse packet that was sent to this client +void VM_CL_getextresponse (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_argv); + + if (cl_net_extresponse_count <= 0) + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + else + { + int first; + --cl_net_extresponse_count; + first = (cl_net_extresponse_last + NET_EXTRESPONSE_MAX - cl_net_extresponse_count) % NET_EXTRESPONSE_MAX; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, cl_net_extresponse[first]); + } +} + +void VM_SV_getextresponse (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0,VM_argv); + + if (sv_net_extresponse_count <= 0) + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + else + { + int first; + --sv_net_extresponse_count; + first = (sv_net_extresponse_last + NET_EXTRESPONSE_MAX - sv_net_extresponse_count) % NET_EXTRESPONSE_MAX; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, sv_net_extresponse[first]); + } +} + +/* +========= +Common functions between menu.dat and clsprogs +========= +*/ + +//#349 float() isdemo +void VM_CL_isdemo (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_isdemo); + PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback; +} + +//#355 float() videoplaying +void VM_CL_videoplaying (prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(0, VM_CL_videoplaying); + PRVM_G_FLOAT(OFS_RETURN) = cl_videoplaying; +} + +/* +========= +VM_M_callfunction + + callfunction(...,string function_name) +Extension: pass +========= +*/ +void VM_callfunction(prvm_prog_t *prog) +{ + mfunction_t *func; + const char *s; + + VM_SAFEPARMCOUNTRANGE(1, 8, VM_callfunction); + + s = PRVM_G_STRING(OFS_PARM0+(prog->argc - 1)*3); + + VM_CheckEmptyString(prog, s); + + func = PRVM_ED_FindFunction(prog, s); + + if(!func) + prog->error_cmd("VM_callfunciton: function %s not found !", s); + else if (func->first_statement < 0) + { + // negative statements are built in functions + int builtinnumber = -func->first_statement; + prog->xfunction->builtinsprofile++; + if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber]) + prog->builtins[builtinnumber](prog); + else + prog->error_cmd("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, prog->name); + } + else if(func - prog->functions > 0) + { + prog->argc--; + prog->ExecuteProgram(prog, func - prog->functions,""); + prog->argc++; + } +} + +/* +========= +VM_isfunction + +float isfunction(string function_name) +========= +*/ +void VM_isfunction(prvm_prog_t *prog) +{ + mfunction_t *func; + const char *s; + + VM_SAFEPARMCOUNT(1, VM_isfunction); + + s = PRVM_G_STRING(OFS_PARM0); + + VM_CheckEmptyString(prog, s); + + func = PRVM_ED_FindFunction(prog, s); + + if(!func) + PRVM_G_FLOAT(OFS_RETURN) = false; + else + PRVM_G_FLOAT(OFS_RETURN) = true; +} + +/* +========= +VM_sprintf + +string sprintf(string format, ...) +========= +*/ + +void VM_sprintf(prvm_prog_t *prog) +{ + const char *s, *s0; + char outbuf[MAX_INPUTLINE]; + char *o = outbuf, *end = outbuf + sizeof(outbuf), *err; + const char *p; + int argpos = 1; + int width, precision, thisarg, flags; + char formatbuf[16]; + char *f; + int isfloat; + static prvm_int_t dummyivec[3] = {0, 0, 0}; + static prvm_vec_t dummyvec[3] = {0, 0, 0}; + char vabuf[1024]; + +#define PRINTF_ALTERNATE 1 +#define PRINTF_ZEROPAD 2 +#define PRINTF_LEFT 4 +#define PRINTF_SPACEPOSITIVE 8 +#define PRINTF_SIGNPOSITIVE 16 + + formatbuf[0] = '%'; + + s = PRVM_G_STRING(OFS_PARM0); + +#define GETARG_FLOAT(a) (((a)>=1 && (a)argc) ? (PRVM_G_FLOAT(OFS_PARM0 + 3 * (a))) : 0) +#define GETARG_VECTOR(a) (((a)>=1 && (a)argc) ? (PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec) +#define GETARG_INT(a) (((a)>=1 && (a)argc) ? (PRVM_G_INT(OFS_PARM0 + 3 * (a))) : 0) +#define GETARG_INTVECTOR(a) (((a)>=1 && (a)argc) ? ((prvm_int_t*) PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec) +#define GETARG_STRING(a) (((a)>=1 && (a)argc) ? (PRVM_G_STRING(OFS_PARM0 + 3 * (a))) : "") + + for(;;) + { + s0 = s; + switch(*s) + { + case 0: + goto finished; + case '%': + ++s; + + if(*s == '%') + goto verbatim; + + // complete directive format: + // %3$*1$.*2$ld + + width = -1; + precision = -1; + thisarg = -1; + flags = 0; + isfloat = -1; + + // is number following? + if(*s >= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err) + { + VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); + goto finished; + } + if(*err == '$') + { + thisarg = width; + width = -1; + s = err + 1; + } + else + { + if(*s == '0') + { + flags |= PRINTF_ZEROPAD; + if(width == 0) + width = -1; // it was just a flag + } + s = err; + } + } + + if(width < 0) + { + for(;;) + { + switch(*s) + { + case '#': flags |= PRINTF_ALTERNATE; break; + case '0': flags |= PRINTF_ZEROPAD; break; + case '-': flags |= PRINTF_LEFT; break; + case ' ': flags |= PRINTF_SPACEPOSITIVE; break; + case '+': flags |= PRINTF_SIGNPOSITIVE; break; + default: + goto noflags; + } + ++s; + } +noflags: + if(*s == '*') + { + ++s; + if(*s >= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err || *err != '$') + { + VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); + goto finished; + } + s = err + 1; + } + else + width = argpos++; + width = GETARG_FLOAT(width); + if(width < 0) + { + flags |= PRINTF_LEFT; + width = -width; + } + } + else if(*s >= '0' && *s <= '9') + { + width = strtol(s, &err, 10); + if(!err) + { + VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); + goto finished; + } + s = err; + if(width < 0) + { + flags |= PRINTF_LEFT; + width = -width; + } + } + // otherwise width stays -1 + } + + if(*s == '.') + { + ++s; + if(*s == '*') + { + ++s; + if(*s >= '0' && *s <= '9') + { + precision = strtol(s, &err, 10); + if(!err || *err != '$') + { + VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); + goto finished; + } + s = err + 1; + } + else + precision = argpos++; + precision = GETARG_FLOAT(precision); + } + else if(*s >= '0' && *s <= '9') + { + precision = strtol(s, &err, 10); + if(!err) + { + VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); + goto finished; + } + s = err; + } + else + { + VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); + goto finished; + } + } + + for(;;) + { + switch(*s) + { + case 'h': isfloat = 1; break; + case 'l': isfloat = 0; break; + case 'L': isfloat = 0; break; + case 'j': break; + case 'z': break; + case 't': break; + default: + goto nolength; + } + ++s; + } +nolength: + + // now s points to the final directive char and is no longer changed + if(isfloat < 0) + { + if(*s == 'i') + isfloat = 0; + else + isfloat = 1; + } + + if(thisarg < 0) + thisarg = argpos++; + + if(o < end - 1) + { + f = &formatbuf[1]; + if(*s != 's' && *s != 'c') + if(flags & PRINTF_ALTERNATE) *f++ = '#'; + if(flags & PRINTF_ZEROPAD) *f++ = '0'; + if(flags & PRINTF_LEFT) *f++ = '-'; + if(flags & PRINTF_SPACEPOSITIVE) *f++ = ' '; + if(flags & PRINTF_SIGNPOSITIVE) *f++ = '+'; + *f++ = '*'; + if(precision >= 0) + { + *f++ = '.'; + *f++ = '*'; + } + if(*s == 'd' || *s == 'i' || *s == 'o' || *s == 'u' || *s == 'x' || *s == 'X') + { + // make it use a good integer type + for(p = INT_LOSSLESS_FORMAT_SIZE; *p; ) + *f++ = *p++; + } + *f++ = *s; + *f++ = 0; + + if(width < 0) // not set + width = 0; + + switch(*s) + { + case 'd': case 'i': + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? INT_LOSSLESS_FORMAT_CONVERT_S(GETARG_FLOAT(thisarg)) : INT_LOSSLESS_FORMAT_CONVERT_S(GETARG_INT(thisarg)))); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? INT_LOSSLESS_FORMAT_CONVERT_S(GETARG_FLOAT(thisarg)) : INT_LOSSLESS_FORMAT_CONVERT_S(GETARG_INT(thisarg)))); + break; + case 'o': case 'u': case 'x': case 'X': + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? INT_LOSSLESS_FORMAT_CONVERT_U(GETARG_FLOAT(thisarg)) : INT_LOSSLESS_FORMAT_CONVERT_U(GETARG_INT(thisarg)))); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? INT_LOSSLESS_FORMAT_CONVERT_U(GETARG_FLOAT(thisarg)) : INT_LOSSLESS_FORMAT_CONVERT_U(GETARG_INT(thisarg)))); + break; + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); + break; + case 'v': case 'V': + f[-2] += 'g' - 'v'; + if(precision < 0) // not set + o += dpsnprintf(o, end - o, va(vabuf, sizeof(vabuf), "%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), + width, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) + ); + else + o += dpsnprintf(o, end - o, va(vabuf, sizeof(vabuf), "%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), + width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) + ); + break; + case 'c': + if(flags & PRINTF_ALTERNATE) + { + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); + } + else + { + unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)); + char charbuf16[16]; + const char *buf = u8_encodech(c, NULL, charbuf16); + if(!buf) + buf = ""; + if(precision < 0) // not set + precision = end - o - 1; + o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision); + } + break; + case 's': + if(flags & PRINTF_ALTERNATE) + { + if(precision < 0) // not set + o += dpsnprintf(o, end - o, formatbuf, width, GETARG_STRING(thisarg)); + else + o += dpsnprintf(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg)); + } + else + { + if(precision < 0) // not set + precision = end - o - 1; + if(flags & PRINTF_SIGNPOSITIVE) + o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision); + else + o += u8_strpad_colorcodes(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision); + } + break; + default: + VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); + goto finished; + } + } + ++s; + break; + default: +verbatim: + if(o < end - 1) + *o++ = *s++; + break; + } + } +finished: + *o = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, outbuf); +} + + +// surface querying + +static dp_model_t *getmodel(prvm_prog_t *prog, prvm_edict_t *ed) +{ + if (prog == SVVM_prog) + return SV_GetModelFromEdict(ed); + else if (prog == CLVM_prog) + return CL_GetModelFromEdict(ed); + else + return NULL; +} + +typedef struct +{ + unsigned int progid; + dp_model_t *model; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + skeleton_t *skeleton_p; + skeleton_t skeleton; + float *data_vertex3f; + float *data_svector3f; + float *data_tvector3f; + float *data_normal3f; + int max_vertices; + float *buf_vertex3f; + float *buf_svector3f; + float *buf_tvector3f; + float *buf_normal3f; +} +animatemodel_cache_t; +static animatemodel_cache_t animatemodel_cache; + +static void animatemodel(prvm_prog_t *prog, dp_model_t *model, prvm_edict_t *ed) +{ + skeleton_t *skeleton; + int skeletonindex = -1; + qboolean need = false; + if(!(model->surfmesh.isanimated && model->AnimateVertices)) + { + animatemodel_cache.data_vertex3f = model->surfmesh.data_vertex3f; + animatemodel_cache.data_svector3f = model->surfmesh.data_svector3f; + animatemodel_cache.data_tvector3f = model->surfmesh.data_tvector3f; + animatemodel_cache.data_normal3f = model->surfmesh.data_normal3f; + return; + } + if(animatemodel_cache.progid != prog->id) + memset(&animatemodel_cache, 0, sizeof(animatemodel_cache)); + need |= (animatemodel_cache.model != model); + VM_GenerateFrameGroupBlend(prog, ed->priv.server->framegroupblend, ed); + VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model, PRVM_serverglobalfloat(time)); + need |= (memcmp(&animatemodel_cache.frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend))) != 0; + skeletonindex = (int)PRVM_gameedictfloat(ed, skeletonindex) - 1; + if (!(skeletonindex >= 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones)) + skeleton = NULL; + need |= (animatemodel_cache.skeleton_p != skeleton); + if(skeleton) + need |= (memcmp(&animatemodel_cache.skeleton, skeleton, sizeof(ed->priv.server->skeleton))) != 0; + if(!need) + return; + if(model->surfmesh.num_vertices > animatemodel_cache.max_vertices) + { + animatemodel_cache.max_vertices = model->surfmesh.num_vertices * 2; + if(animatemodel_cache.buf_vertex3f) Mem_Free(animatemodel_cache.buf_vertex3f); + if(animatemodel_cache.buf_svector3f) Mem_Free(animatemodel_cache.buf_svector3f); + if(animatemodel_cache.buf_tvector3f) Mem_Free(animatemodel_cache.buf_tvector3f); + if(animatemodel_cache.buf_normal3f) Mem_Free(animatemodel_cache.buf_normal3f); + animatemodel_cache.buf_vertex3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices); + animatemodel_cache.buf_svector3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices); + animatemodel_cache.buf_tvector3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices); + animatemodel_cache.buf_normal3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices); + } + animatemodel_cache.data_vertex3f = animatemodel_cache.buf_vertex3f; + animatemodel_cache.data_svector3f = animatemodel_cache.buf_svector3f; + animatemodel_cache.data_tvector3f = animatemodel_cache.buf_tvector3f; + animatemodel_cache.data_normal3f = animatemodel_cache.buf_normal3f; + VM_UpdateEdictSkeleton(prog, ed, model, ed->priv.server->frameblend); + model->AnimateVertices(model, ed->priv.server->frameblend, &ed->priv.server->skeleton, animatemodel_cache.data_vertex3f, animatemodel_cache.data_normal3f, animatemodel_cache.data_svector3f, animatemodel_cache.data_tvector3f); + animatemodel_cache.progid = prog->id; + animatemodel_cache.model = model; + memcpy(&animatemodel_cache.frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend)); + animatemodel_cache.skeleton_p = skeleton; + if(skeleton) + memcpy(&animatemodel_cache.skeleton, skeleton, sizeof(ed->priv.server->skeleton)); +} + +static void getmatrix(prvm_prog_t *prog, prvm_edict_t *ed, matrix4x4_t *out) +{ + if (prog == SVVM_prog) + SV_GetEntityMatrix(prog, ed, out, false); + else if (prog == CLVM_prog) + CL_GetEntityMatrix(prog, ed, out, false); + else + *out = identitymatrix; +} + +static void applytransform_forward(prvm_prog_t *prog, const vec3_t in, prvm_edict_t *ed, vec3_t out) +{ + matrix4x4_t m; + getmatrix(prog, ed, &m); + Matrix4x4_Transform(&m, in, out); +} + +static void applytransform_forward_direction(prvm_prog_t *prog, const vec3_t in, prvm_edict_t *ed, vec3_t out) +{ + matrix4x4_t m; + getmatrix(prog, ed, &m); + Matrix4x4_Transform3x3(&m, in, out); +} + +static void applytransform_inverted(prvm_prog_t *prog, const vec3_t in, prvm_edict_t *ed, vec3_t out) +{ + matrix4x4_t m, n; + getmatrix(prog, ed, &m); + Matrix4x4_Invert_Full(&n, &m); + Matrix4x4_Transform3x3(&n, in, out); +} + +static void applytransform_forward_normal(prvm_prog_t *prog, const vec3_t in, prvm_edict_t *ed, vec3_t out) +{ + matrix4x4_t m; + float p[4]; + getmatrix(prog, ed, &m); + Matrix4x4_TransformPositivePlane(&m, in[0], in[1], in[2], 0, p); + VectorCopy(p, out); +} + +static void clippointtosurface(prvm_prog_t *prog, prvm_edict_t *ed, dp_model_t *model, msurface_t *surface, vec3_t p, vec3_t out) +{ + int i, j, k; + float *v[3], facenormal[3], edgenormal[3], sidenormal[3], temp[3], offsetdist, dist, bestdist; + const int *e; + animatemodel(prog, model, ed); + bestdist = 1000000000; + VectorCopy(p, out); + for (i = 0, e = (model->surfmesh.data_element3i + 3 * surface->num_firsttriangle);i < surface->num_triangles;i++, e += 3) + { + // clip original point to each triangle of the surface and find the + // triangle that is closest + v[0] = animatemodel_cache.data_vertex3f + e[0] * 3; + v[1] = animatemodel_cache.data_vertex3f + e[1] * 3; + v[2] = animatemodel_cache.data_vertex3f + e[2] * 3; + TriangleNormal(v[0], v[1], v[2], facenormal); + VectorNormalize(facenormal); + offsetdist = DotProduct(v[0], facenormal) - DotProduct(p, facenormal); + VectorMA(p, offsetdist, facenormal, temp); + for (j = 0, k = 2;j < 3;k = j, j++) + { + VectorSubtract(v[k], v[j], edgenormal); + CrossProduct(edgenormal, facenormal, sidenormal); + VectorNormalize(sidenormal); + offsetdist = DotProduct(v[k], sidenormal) - DotProduct(temp, sidenormal); + if (offsetdist < 0) + VectorMA(temp, offsetdist, sidenormal, temp); + } + dist = VectorDistance2(temp, p); + if (bestdist > dist) + { + bestdist = dist; + VectorCopy(temp, out); + } + } +} + +static msurface_t *getsurface(dp_model_t *model, int surfacenum) +{ + if (surfacenum < 0 || surfacenum >= model->nummodelsurfaces) + return NULL; + return model->data_surfaces + surfacenum + model->firstmodelsurface; +} + + +//PF_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints = #434; +void VM_getsurfacenumpoints(prvm_prog_t *prog) +{ + dp_model_t *model; + msurface_t *surface; + VM_SAFEPARMCOUNT(2, VM_getsurfacenumpoints); + // return 0 if no such surface + if (!(model = getmodel(prog, PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + + // note: this (incorrectly) assumes it is a simple polygon + PRVM_G_FLOAT(OFS_RETURN) = surface->num_vertices; +} +//PF_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint = #435; +void VM_getsurfacepoint(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + int pointnum; + vec3_t result; + VM_SAFEPARMCOUNT(3, VM_getsurfacepoint); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!(model = getmodel(prog, ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + // note: this (incorrectly) assumes it is a simple polygon + pointnum = (int)PRVM_G_FLOAT(OFS_PARM2); + if (pointnum < 0 || pointnum >= surface->num_vertices) + return; + animatemodel(prog, model, ed); + applytransform_forward(prog, &(animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); +} +//PF_getsurfacepointattribute, // #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; +// float SPA_POSITION = 0; +// float SPA_S_AXIS = 1; +// float SPA_T_AXIS = 2; +// float SPA_R_AXIS = 3; // same as SPA_NORMAL +// float SPA_TEXCOORDS0 = 4; +// float SPA_LIGHTMAP0_TEXCOORDS = 5; +// float SPA_LIGHTMAP0_COLOR = 6; +void VM_getsurfacepointattribute(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + int pointnum; + int attributetype; + vec3_t result; + + VM_SAFEPARMCOUNT(4, VM_getsurfacepoint); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!(model = getmodel(prog, ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + pointnum = (int)PRVM_G_FLOAT(OFS_PARM2); + if (pointnum < 0 || pointnum >= surface->num_vertices) + return; + attributetype = (int) PRVM_G_FLOAT(OFS_PARM3); + + animatemodel(prog, model, ed); + + switch( attributetype ) { + // float SPA_POSITION = 0; + case 0: + applytransform_forward(prog, &(animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); + break; + // float SPA_S_AXIS = 1; + case 1: + applytransform_forward_direction(prog, &(animatemodel_cache.data_svector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); + break; + // float SPA_T_AXIS = 2; + case 2: + applytransform_forward_direction(prog, &(animatemodel_cache.data_tvector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); + break; + // float SPA_R_AXIS = 3; // same as SPA_NORMAL + case 3: + applytransform_forward_direction(prog, &(animatemodel_cache.data_normal3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); + break; + // float SPA_TEXCOORDS0 = 4; + case 4: { + float *texcoord = &(model->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[pointnum * 2]; + result[0] = texcoord[0]; + result[1] = texcoord[1]; + result[2] = 0.0f; + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); + break; + } + // float SPA_LIGHTMAP0_TEXCOORDS = 5; + case 5: { + float *texcoord = &(model->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[pointnum * 2]; + result[0] = texcoord[0]; + result[1] = texcoord[1]; + result[2] = 0.0f; + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); + break; + } + // float SPA_LIGHTMAP0_COLOR = 6; + case 6: + // ignore alpha for now.. + VectorCopy( &(model->surfmesh.data_lightmapcolor4f + 4 * surface->num_firstvertex)[pointnum * 4], PRVM_G_VECTOR(OFS_RETURN)); + break; + default: + VectorSet( PRVM_G_VECTOR(OFS_RETURN), 0.0f, 0.0f, 0.0f ); + break; + } +} +//PF_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal = #436; +void VM_getsurfacenormal(prvm_prog_t *prog) +{ + dp_model_t *model; + msurface_t *surface; + vec3_t normal; + vec3_t result; + VM_SAFEPARMCOUNT(2, VM_getsurfacenormal); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + if (!(model = getmodel(prog, PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + // note: this only returns the first triangle, so it doesn't work very + // well for curved surfaces or arbitrary meshes + animatemodel(prog, model, PRVM_G_EDICT(OFS_PARM0)); + TriangleNormal((animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex), (animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex) + 3, (animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex) + 6, normal); + applytransform_forward_normal(prog, normal, PRVM_G_EDICT(OFS_PARM0), result); + VectorNormalize(result); + VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); +} +//PF_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture = #437; +void VM_getsurfacetexture(prvm_prog_t *prog) +{ + dp_model_t *model; + msurface_t *surface; + VM_SAFEPARMCOUNT(2, VM_getsurfacetexture); + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + if (!(model = getmodel(prog, PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, surface->texture->name); +} +//PF_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint = #438; +void VM_getsurfacenearpoint(prvm_prog_t *prog) +{ + int surfacenum, best; + vec3_t clipped, p; + vec_t dist, bestdist; + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + vec3_t point; + VM_SAFEPARMCOUNT(2, VM_getsurfacenearpoint); + PRVM_G_FLOAT(OFS_RETURN) = -1; + ed = PRVM_G_EDICT(OFS_PARM0); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), point); + + if (!ed || ed->priv.server->free) + return; + model = getmodel(prog, ed); + if (!model || !model->num_surfaces) + return; + + animatemodel(prog, model, ed); + + applytransform_inverted(prog, point, ed, p); + best = -1; + bestdist = 1000000000; + for (surfacenum = 0;surfacenum < model->nummodelsurfaces;surfacenum++) + { + surface = model->data_surfaces + surfacenum + model->firstmodelsurface; + // first see if the nearest point on the surface's box is closer than the previous match + clipped[0] = bound(surface->mins[0], p[0], surface->maxs[0]) - p[0]; + clipped[1] = bound(surface->mins[1], p[1], surface->maxs[1]) - p[1]; + clipped[2] = bound(surface->mins[2], p[2], surface->maxs[2]) - p[2]; + dist = VectorLength2(clipped); + if (dist < bestdist) + { + // it is, check the nearest point on the actual geometry + clippointtosurface(prog, ed, model, surface, p, clipped); + VectorSubtract(clipped, p, clipped); + dist += VectorLength2(clipped); + if (dist < bestdist) + { + // that's closer too, store it as the best match + best = surfacenum; + bestdist = dist; + } + } + } + PRVM_G_FLOAT(OFS_RETURN) = best; +} +//PF_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; +void VM_getsurfaceclippedpoint(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + vec3_t p, out, inp; + VM_SAFEPARMCOUNT(3, VM_te_getsurfaceclippedpoint); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!(model = getmodel(prog, ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + animatemodel(prog, model, ed); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), inp); + applytransform_inverted(prog, inp, ed, p); + clippointtosurface(prog, ed, model, surface, p, out); + VectorAdd(out, PRVM_serveredictvector(ed, origin), PRVM_G_VECTOR(OFS_RETURN)); +} + +//PF_getsurfacenumtriangles, // #??? float(entity e, float s) getsurfacenumtriangles = #???; +void VM_getsurfacenumtriangles(prvm_prog_t *prog) +{ + dp_model_t *model; + msurface_t *surface; + VM_SAFEPARMCOUNT(2, VM_SV_getsurfacenumtriangles); + // return 0 if no such surface + if (!(model = getmodel(prog, PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + + PRVM_G_FLOAT(OFS_RETURN) = surface->num_triangles; +} +//PF_getsurfacetriangle, // #??? vector(entity e, float s, float n) getsurfacetriangle = #???; +void VM_getsurfacetriangle(prvm_prog_t *prog) +{ + const vec3_t d = {-1, -1, -1}; + prvm_edict_t *ed; + dp_model_t *model; + msurface_t *surface; + int trinum; + VM_SAFEPARMCOUNT(3, VM_SV_getsurfacetriangle); + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!(model = getmodel(prog, ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) + return; + trinum = (int)PRVM_G_FLOAT(OFS_PARM2); + if (trinum < 0 || trinum >= surface->num_triangles) + return; + // FIXME: implement rotation/scaling + VectorMA(&(model->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[trinum * 3], surface->num_firstvertex, d, PRVM_G_VECTOR(OFS_RETURN)); +} + +// +// physics builtins +// + +#define VM_physics_ApplyCmd(ed,f) if (!ed->priv.server->ode_body) VM_physics_newstackfunction(prog, ed, f); else World_Physics_ApplyCmd(ed, f) + +static edict_odefunc_t *VM_physics_newstackfunction(prvm_prog_t *prog, prvm_edict_t *ed, edict_odefunc_t *f) +{ + edict_odefunc_t *newfunc, *func; + + newfunc = (edict_odefunc_t *)Mem_Alloc(prog->progs_mempool, sizeof(edict_odefunc_t)); + memcpy(newfunc, f, sizeof(edict_odefunc_t)); + newfunc->next = NULL; + if (!ed->priv.server->ode_func) + ed->priv.server->ode_func = newfunc; + else + { + for (func = ed->priv.server->ode_func; func->next; func = func->next); + func->next = newfunc; + } + return newfunc; +} + +// void(entity e, float physics_enabled) physics_enable = #; +void VM_physics_enable(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + edict_odefunc_t f; + + VM_SAFEPARMCOUNT(2, VM_physics_enable); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!ed) + { + if (developer.integer > 0) + VM_Warning(prog, "VM_physics_enable: null entity!\n"); + return; + } + // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer + if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) + { + VM_Warning(prog, "VM_physics_enable: entity is not MOVETYPE_PHYSICS!\n"); + return; + } + f.type = PRVM_G_FLOAT(OFS_PARM1) == 0 ? ODEFUNC_DISABLE : ODEFUNC_ENABLE; + VM_physics_ApplyCmd(ed, &f); +} + +// void(entity e, vector force, vector relative_ofs) physics_addforce = #; +void VM_physics_addforce(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + edict_odefunc_t f; + + VM_SAFEPARMCOUNT(3, VM_physics_addforce); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!ed) + { + if (developer.integer > 0) + VM_Warning(prog, "VM_physics_addforce: null entity!\n"); + return; + } + // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer + if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) + { + VM_Warning(prog, "VM_physics_addforce: entity is not MOVETYPE_PHYSICS!\n"); + return; + } + f.type = ODEFUNC_FORCE; + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f.v1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), f.v2); + VM_physics_ApplyCmd(ed, &f); +} + +// void(entity e, vector torque) physics_addtorque = #; +void VM_physics_addtorque(prvm_prog_t *prog) +{ + prvm_edict_t *ed; + edict_odefunc_t f; + + VM_SAFEPARMCOUNT(2, VM_physics_addtorque); + ed = PRVM_G_EDICT(OFS_PARM0); + if (!ed) + { + if (developer.integer > 0) + VM_Warning(prog, "VM_physics_addtorque: null entity!\n"); + return; + } + // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer + if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) + { + VM_Warning(prog, "VM_physics_addtorque: entity is not MOVETYPE_PHYSICS!\n"); + return; + } + f.type = ODEFUNC_TORQUE; + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f.v1); + VM_physics_ApplyCmd(ed, &f); +} diff --git a/app/jni/prvm_cmds.h b/app/jni/prvm_cmds.h new file mode 100644 index 0000000..e21a393 --- /dev/null +++ b/app/jni/prvm_cmds.h @@ -0,0 +1,487 @@ + +#ifndef PRVM_CMDS_H +#define PRVM_CMDS_H + +// AK +// Basically every vm builtin cmd should be in here. +// All 3 builtin and extension lists can be found here +// cause large (I think they will) parts are from pr_cmds the same copyright like in pr_cmds +// also applies here + + +/* +============================================================================ +common cmd list: +================= + + checkextension(string) + error(...[string]) + objerror(...[string) + print(...[strings]) + bprint(...[string]) + sprint(float clientnum,...[string]) + centerprint(...[string]) +vector normalize(vector) +float vlen(vector) +float vectoyaw(vector) +vector vectoangles(vector) +float random() + cmd(string) + float cvar (string) + cvar_set (string,string) + dprint(...[string]) +string ftos(float) +float fabs(float) +string vtos(vector) +string etos(entity) +float stof(...[string]) +entity spawn() + remove(entity e) +entity find(entity start, .string field, string match) + +entity findfloat(entity start, .float field, float match) +entity findentity(entity start, .entity field, entity match) + +entity findchain(.string field, string match) + +entity findchainfloat(.string field, float match) +entity findchainentity(.string field, entity match) + +string precache_file(string) +string precache_sound (string sample) + coredump() + traceon() + traceoff() + eprint(entity e) +float rint(float) +float floor(float) +float ceil(float) +entity nextent(entity) +float sin(float) +float cos(float) +float sqrt(float) +vector randomvec() +float registercvar (string name, string value, float flags) +float min(float a, float b, ...[float]) +float max(float a, float b, ...[float]) +float bound(float min, float value, float max) +float pow(float a, float b) + copyentity(entity src, entity dst) +float fopen(string filename, float mode) + fclose(float fhandle) +string fgets(float fhandle) + fputs(float fhandle, string s) +float strlen(string s) +string strcat(string,string,...[string]) +string substring(string s, float start, float length) +vector stov(string s) +string strzone(string s) + strunzone(string s) +float tokenize(string s) +string argv(float n) +float isserver() +float clientcount() +float clientstate() + clientcommand(float client, string s) (for client and menu) + changelevel(string map) + localsound(string sample) +vector getmousepos() +float gettime() + loadfromdata(string data) + loadfromfile(string file) + parseentitydata(entity ent, string data) +float mod(float val, float m) +const string cvar_string (string) +float cvar_type (string) + crash() + stackdump() + +float search_begin(string pattern, float caseinsensitive, float quiet) +void search_end(float handle) +float search_getsize(float handle) +string search_getfilename(float handle, float num) + +string chr(float ascii) + +float itof(intt ent) +entity ftoe(float num) + +-------will be removed soon---------- +float altstr_count(string) +string altstr_prepare(string) +string altstr_get(string,float) +string altstr_set(string altstr, float num, string set) +string altstr_ins(string altstr, float num, string set) +-------------------------------------- + +entity findflags(entity start, .float field, float match) +entity findchainflags(.float field, float match) + +const string VM_cvar_defstring (string) + +perhaps only : Menu : WriteMsg +=============================== + + WriteByte(float data, float dest, float desto) + WriteChar(float data, float dest, float desto) + WriteShort(float data, float dest, float desto) + WriteLong(float data, float dest, float desto) + WriteAngle(float data, float dest, float desto) + WriteCoord(float data, float dest, float desto) + WriteString(string data, float dest, float desto) + WriteEntity(entity data, float dest, float desto) + +Client & Menu : draw functions & video functions (& gecko functions) +=================================================== + +float iscachedpic(string pic) +string precache_pic(string pic) + freepic(string s) +float drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) +float drawstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) +float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) +float stringwidth(string text, float handleColors) +float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) +float drawsubpic(vector position, vector size, string pic, vector srcPos, vector srcSize, vector rgb, float alpha, float flag) +float drawfill(vector position, vector size, vector rgb, float alpha, float flag) + drawsetcliparea(float x, float y, float width, float height) + drawresetcliparea() +vector getimagesize(string pic) + +float cin_open(string file, string name) +void cin_close(string name) +void cin_setstate(string name, float type) +float cin_getstate(string name) +void cin_restart(string name) + +float[bool] gecko_create( string name ) +void gecko_destroy( string name ) +void gecko_navigate( string name, string URI ) +float[bool] gecko_keyevent( string name, float key, float eventtype ) +void gecko_mousemove( string name, float x, float y ) + +============================================================================== +menu cmd list: +=============== + + setkeydest(float dest) +float getkeydest() + setmousetarget(float target) +float getmousetarget() + + callfunction(...,string function_name) + writetofile(float fhandle, entity ent) +float isfunction(string function_name) +vector getresolution(float number) +string keynumtostring(float keynum) +string findkeysforcommand(string command) +float getserverliststat(float type) +string getserverliststring(float fld, float hostnr) + +float stringtokeynum(string key) + + resetserverlistmasks() + setserverlistmaskstring(float mask, float fld, string str) + setserverlistmasknumber(float mask, float fld, float num, float op) + resortserverlist() + setserverlistsort(float field, float descending) + refreshserverlist() +float getserverlistnumber(float fld, float hostnr) +float getserverlistindexforkey(string key) + addwantedserverlistkey(string key) +*/ + +#include "quakedef.h" +#include "progdefs.h" +#include "progsvm.h" +#include "clprogdefs.h" +#include "mprogdefs.h" + +#include "cl_video.h" + +//============================================================================ +// nice helper macros + +#ifndef VM_NOPARMCHECK +#define VM_SAFEPARMCOUNTRANGE(p1,p2,f) if(prog->argc < p1 || prog->argc > p2) prog->error_cmd(#f " wrong parameter count %i (" #p1 " to " #p2 " expected ) !", prog->argc) +#define VM_SAFEPARMCOUNT(p,f) if(prog->argc != p) prog->error_cmd(#f " wrong parameter count %i (" #p " expected ) !", prog->argc) +#else +#define VM_SAFEPARMCOUNTRANGE(p1,p2,f) +#define VM_SAFEPARMCOUNT(p,f) +#endif + +#define VM_RETURN_EDICT(e) (prog->globals.ip[OFS_RETURN] = PRVM_EDICT_TO_PROG(e)) + +#define VM_STRINGTEMP_LENGTH MAX_INPUTLINE + +// init code +void PR_Cmd_Init(void); + +// builtins and other general functions + +void VM_CheckEmptyString (prvm_prog_t *prog, const char *s); +void VM_VarString(prvm_prog_t *prog, int first, char *out, int outlength); + +void VM_checkextension (prvm_prog_t *prog); +void VM_error (prvm_prog_t *prog); +void VM_objerror (prvm_prog_t *prog); +void VM_print (prvm_prog_t *prog); +void VM_bprint (prvm_prog_t *prog); +void VM_sprint (prvm_prog_t *prog); +void VM_centerprint (prvm_prog_t *prog); +void VM_normalize (prvm_prog_t *prog); +void VM_vlen (prvm_prog_t *prog); +void VM_vectoyaw (prvm_prog_t *prog); +void VM_vectoangles (prvm_prog_t *prog); +void VM_random (prvm_prog_t *prog); +void VM_localsound(prvm_prog_t *prog); +void VM_break (prvm_prog_t *prog); +void VM_localcmd (prvm_prog_t *prog); +void VM_cvar (prvm_prog_t *prog); +void VM_cvar_string(prvm_prog_t *prog); +void VM_cvar_type (prvm_prog_t *prog); +void VM_cvar_defstring (prvm_prog_t *prog); +void VM_cvar_set (prvm_prog_t *prog); +void VM_dprint (prvm_prog_t *prog); +void VM_ftos (prvm_prog_t *prog); +void VM_fabs (prvm_prog_t *prog); +void VM_vtos (prvm_prog_t *prog); +void VM_etos (prvm_prog_t *prog); +void VM_stof(prvm_prog_t *prog); +void VM_itof(prvm_prog_t *prog); +void VM_ftoe(prvm_prog_t *prog); +void VM_strftime(prvm_prog_t *prog); +void VM_spawn (prvm_prog_t *prog); +void VM_remove (prvm_prog_t *prog); +void VM_find (prvm_prog_t *prog); +void VM_findfloat (prvm_prog_t *prog); +void VM_findchain (prvm_prog_t *prog); +void VM_findchainfloat (prvm_prog_t *prog); +void VM_findflags (prvm_prog_t *prog); +void VM_findchainflags (prvm_prog_t *prog); +void VM_precache_file (prvm_prog_t *prog); +void VM_precache_sound (prvm_prog_t *prog); +void VM_coredump (prvm_prog_t *prog); + +void VM_stackdump (prvm_prog_t *prog); +void VM_crash(prvm_prog_t *prog); // REMOVE IT +void VM_traceon (prvm_prog_t *prog); +void VM_traceoff (prvm_prog_t *prog); +void VM_eprint (prvm_prog_t *prog); +void VM_rint (prvm_prog_t *prog); +void VM_floor (prvm_prog_t *prog); +void VM_ceil (prvm_prog_t *prog); +void VM_nextent (prvm_prog_t *prog); + +void VM_changelevel (prvm_prog_t *prog); +void VM_sin (prvm_prog_t *prog); +void VM_cos (prvm_prog_t *prog); +void VM_sqrt (prvm_prog_t *prog); +void VM_randomvec (prvm_prog_t *prog); +void VM_registercvar (prvm_prog_t *prog); +void VM_min (prvm_prog_t *prog); +void VM_max (prvm_prog_t *prog); +void VM_bound (prvm_prog_t *prog); +void VM_pow (prvm_prog_t *prog); +void VM_log (prvm_prog_t *prog); +void VM_asin (prvm_prog_t *prog); +void VM_acos (prvm_prog_t *prog); +void VM_atan (prvm_prog_t *prog); +void VM_atan2 (prvm_prog_t *prog); +void VM_tan (prvm_prog_t *prog); + +void VM_Files_Init(prvm_prog_t *prog); +void VM_Files_CloseAll(prvm_prog_t *prog); + +void VM_fopen(prvm_prog_t *prog); +void VM_fclose(prvm_prog_t *prog); +void VM_fgets(prvm_prog_t *prog); +void VM_fputs(prvm_prog_t *prog); +void VM_writetofile(prvm_prog_t *prog); // only used by menu + +void VM_strlen(prvm_prog_t *prog); +void VM_strcat(prvm_prog_t *prog); +void VM_substring(prvm_prog_t *prog); +void VM_stov(prvm_prog_t *prog); +void VM_strzone(prvm_prog_t *prog); +void VM_strunzone(prvm_prog_t *prog); + +// KrimZon - DP_QC_ENTITYDATA +void VM_numentityfields(prvm_prog_t *prog); +void VM_entityfieldname(prvm_prog_t *prog); +void VM_entityfieldtype(prvm_prog_t *prog); +void VM_getentityfieldstring(prvm_prog_t *prog); +void VM_putentityfieldstring(prvm_prog_t *prog); + +// DRESK - String Length (not counting color codes) +void VM_strlennocol(prvm_prog_t *prog); +// DRESK - Decolorized String +void VM_strdecolorize(prvm_prog_t *prog); +// DRESK - String Uppercase and Lowercase Support +void VM_strtolower(prvm_prog_t *prog); +void VM_strtoupper(prvm_prog_t *prog); + +void VM_clcommand (prvm_prog_t *prog); + +void VM_tokenize (prvm_prog_t *prog); +void VM_tokenizebyseparator (prvm_prog_t *prog); +void VM_argv (prvm_prog_t *prog); + +void VM_isserver(prvm_prog_t *prog); +void VM_clientcount(prvm_prog_t *prog); +void VM_clientstate(prvm_prog_t *prog); +// not used at the moment -> not included in the common list +void VM_getostype(prvm_prog_t *prog); +void VM_getmousepos(prvm_prog_t *prog); +void VM_gettime(prvm_prog_t *prog); +void VM_getsoundtime(prvm_prog_t *prog); +void VM_soundlength(prvm_prog_t *prog); +void VM_loadfromdata(prvm_prog_t *prog); +void VM_parseentitydata(prvm_prog_t *prog); +void VM_loadfromfile(prvm_prog_t *prog); +void VM_modulo(prvm_prog_t *prog); + +void VM_search_begin(prvm_prog_t *prog); +void VM_search_end(prvm_prog_t *prog); +void VM_search_getsize(prvm_prog_t *prog); +void VM_search_getfilename(prvm_prog_t *prog); +void VM_chr(prvm_prog_t *prog); +void VM_iscachedpic(prvm_prog_t *prog); +void VM_precache_pic(prvm_prog_t *prog); +void VM_freepic(prvm_prog_t *prog); +void VM_drawcharacter(prvm_prog_t *prog); +void VM_drawstring(prvm_prog_t *prog); +void VM_drawcolorcodedstring(prvm_prog_t *prog); +void VM_stringwidth(prvm_prog_t *prog); +void VM_drawpic(prvm_prog_t *prog); +void VM_drawrotpic(prvm_prog_t *prog); +void VM_drawsubpic(prvm_prog_t *prog); +void VM_drawfill(prvm_prog_t *prog); +void VM_drawsetcliparea(prvm_prog_t *prog); +void VM_drawresetcliparea(prvm_prog_t *prog); +void VM_getimagesize(prvm_prog_t *prog); + +void VM_findfont(prvm_prog_t *prog); +void VM_loadfont(prvm_prog_t *prog); + +void VM_makevectors (prvm_prog_t *prog); +void VM_vectorvectors (prvm_prog_t *prog); + +void VM_keynumtostring (prvm_prog_t *prog); +void VM_getkeybind (prvm_prog_t *prog); +void VM_findkeysforcommand (prvm_prog_t *prog); +void VM_stringtokeynum (prvm_prog_t *prog); +void VM_setkeybind (prvm_prog_t *prog); +void VM_getbindmaps (prvm_prog_t *prog); +void VM_setbindmaps (prvm_prog_t *prog); + +void VM_cin_open(prvm_prog_t *prog); +void VM_cin_close(prvm_prog_t *prog); +void VM_cin_setstate(prvm_prog_t *prog); +void VM_cin_getstate(prvm_prog_t *prog); +void VM_cin_restart(prvm_prog_t *prog); + +void VM_gecko_create(prvm_prog_t *prog); +void VM_gecko_destroy(prvm_prog_t *prog); +void VM_gecko_navigate(prvm_prog_t *prog); +void VM_gecko_keyevent(prvm_prog_t *prog); +void VM_gecko_movemouse(prvm_prog_t *prog); +void VM_gecko_resize(prvm_prog_t *prog); +void VM_gecko_get_texture_extent(prvm_prog_t *prog); + +void VM_drawline (prvm_prog_t *prog); + +void VM_bitshift (prvm_prog_t *prog); + +void VM_altstr_count(prvm_prog_t *prog); +void VM_altstr_prepare(prvm_prog_t *prog); +void VM_altstr_get(prvm_prog_t *prog); +void VM_altstr_set(prvm_prog_t *prog); +void VM_altstr_ins(prvm_prog_t *prog); + +void VM_buf_create(prvm_prog_t *prog); +void VM_buf_del (prvm_prog_t *prog); +void VM_buf_getsize (prvm_prog_t *prog); +void VM_buf_copy (prvm_prog_t *prog); +void VM_buf_sort (prvm_prog_t *prog); +void VM_buf_implode (prvm_prog_t *prog); +void VM_bufstr_get (prvm_prog_t *prog); +void VM_bufstr_set (prvm_prog_t *prog); +void VM_bufstr_add (prvm_prog_t *prog); +void VM_bufstr_free (prvm_prog_t *prog); + +void VM_buf_loadfile(prvm_prog_t *prog); +void VM_buf_writefile(prvm_prog_t *prog); +void VM_bufstr_find(prvm_prog_t *prog); +void VM_matchpattern(prvm_prog_t *prog); + +void VM_changeyaw (prvm_prog_t *prog); +void VM_changepitch (prvm_prog_t *prog); + +void VM_uncolorstring (prvm_prog_t *prog); + +void VM_strstrofs (prvm_prog_t *prog); +void VM_str2chr (prvm_prog_t *prog); +void VM_chr2str (prvm_prog_t *prog); +void VM_strconv (prvm_prog_t *prog); +void VM_strpad (prvm_prog_t *prog); +void VM_infoadd (prvm_prog_t *prog); +void VM_infoget (prvm_prog_t *prog); +void VM_strncmp (prvm_prog_t *prog); +void VM_strncmp (prvm_prog_t *prog); +void VM_strncasecmp (prvm_prog_t *prog); +void VM_registercvar (prvm_prog_t *prog); +void VM_wasfreed (prvm_prog_t *prog); + +void VM_strreplace (prvm_prog_t *prog); +void VM_strireplace (prvm_prog_t *prog); + +void VM_crc16(prvm_prog_t *prog); +void VM_digest_hex(prvm_prog_t *prog); + +void VM_SetTraceGlobals(prvm_prog_t *prog, const trace_t *trace); +void VM_ClearTraceGlobals(prvm_prog_t *prog); + +void VM_uri_escape (prvm_prog_t *prog); +void VM_uri_unescape (prvm_prog_t *prog); +void VM_whichpack (prvm_prog_t *prog); + +void VM_etof (prvm_prog_t *prog); +void VM_uri_get (prvm_prog_t *prog); +void VM_netaddress_resolve (prvm_prog_t *prog); + +void VM_tokenize_console (prvm_prog_t *prog); +void VM_argv_start_index (prvm_prog_t *prog); +void VM_argv_end_index (prvm_prog_t *prog); + +void VM_buf_cvarlist(prvm_prog_t *prog); +void VM_cvar_description(prvm_prog_t *prog); + +void VM_CL_getextresponse (prvm_prog_t *prog); +void VM_SV_getextresponse (prvm_prog_t *prog); + +// Common functions between menu.dat and clsprogs +void VM_CL_isdemo (prvm_prog_t *prog); +void VM_CL_videoplaying (prvm_prog_t *prog); + +void VM_isfunction(prvm_prog_t *prog); +void VM_callfunction(prvm_prog_t *prog); + +void VM_sprintf(prvm_prog_t *prog); + +void VM_getsurfacenumpoints(prvm_prog_t *prog); +void VM_getsurfacepoint(prvm_prog_t *prog); +void VM_getsurfacepointattribute(prvm_prog_t *prog); +void VM_getsurfacenormal(prvm_prog_t *prog); +void VM_getsurfacetexture(prvm_prog_t *prog); +void VM_getsurfacenearpoint(prvm_prog_t *prog); +void VM_getsurfaceclippedpoint(prvm_prog_t *prog); +void VM_getsurfacenumtriangles(prvm_prog_t *prog); +void VM_getsurfacetriangle(prvm_prog_t *prog); + +// physics builtins +void VM_physics_enable(prvm_prog_t *prog); +void VM_physics_addforce(prvm_prog_t *prog); +void VM_physics_addtorque(prvm_prog_t *prog); + +#endif diff --git a/app/jni/prvm_edict.c b/app/jni/prvm_edict.c new file mode 100644 index 0000000..191197f --- /dev/null +++ b/app/jni/prvm_edict.c @@ -0,0 +1,3381 @@ +/* +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. + +*/ +// AK new vm + +#include "quakedef.h" +#include "progsvm.h" +#include "csprogs.h" + +prvm_prog_t prvm_prog_list[PRVM_PROG_MAX]; + +int prvm_type_size[8] = {1,sizeof(string_t)/4,1,3,1,1,sizeof(func_t)/4,sizeof(void *)/4}; + +prvm_eval_t prvm_badvalue; // used only for error returns + +cvar_t prvm_language = {CVAR_SAVE, "prvm_language", "", "when set, loads progs.dat.LANGUAGENAME.po for string translations; when set to dump, progs.dat.pot is written from the strings in the progs"}; +// LordHavoc: prints every opcode as it executes - warning: this is significant spew +cvar_t prvm_traceqc = {0, "prvm_traceqc", "0", "prints every QuakeC statement as it is executed (only for really thorough debugging!)"}; +// LordHavoc: counts usage of each QuakeC statement +cvar_t prvm_statementprofiling = {0, "prvm_statementprofiling", "0", "counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled)"}; +cvar_t prvm_timeprofiling = {0, "prvm_timeprofiling", "0", "counts how long each function has been executed, these counts are displayed in prvm_profile output (if enabled)"}; +cvar_t prvm_backtraceforwarnings = {0, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"}; +cvar_t prvm_leaktest = {0, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"}; +cvar_t prvm_leaktest_ignore_classnames = {0, "prvm_leaktest_ignore_classnames", "", "classnames of entities to NOT leak check because they are found by find(world, classname, ...) but are actually spawned by QC code (NOT map entities)"}; +cvar_t prvm_errordump = {0, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"}; +cvar_t prvm_breakpointdump = {0, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"}; +cvar_t prvm_reuseedicts_startuptime = {0, "prvm_reuseedicts_startuptime", "2", "allows immediate re-use of freed entity slots during start of new level (value in seconds)"}; +cvar_t prvm_reuseedicts_neverinsameframe = {0, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"}; + +static double prvm_reuseedicts_always_allow = 0; +qboolean prvm_runawaycheck = true; + +//============================================================================ +// mempool handling + +/* +=============== +PRVM_MEM_Alloc +=============== +*/ +static void PRVM_MEM_Alloc(prvm_prog_t *prog) +{ + int i; + + // reserve space for the null entity aka world + // check bound of max_edicts + prog->max_edicts = bound(1 + prog->reserved_edicts, prog->max_edicts, prog->limit_edicts); + prog->num_edicts = bound(1 + prog->reserved_edicts, prog->num_edicts, prog->max_edicts); + + // edictprivate_size has to be min as big prvm_edict_private_t + prog->edictprivate_size = max(prog->edictprivate_size,(int)sizeof(prvm_edict_private_t)); + + // alloc edicts + prog->edicts = (prvm_edict_t *)Mem_Alloc(prog->progs_mempool,prog->limit_edicts * sizeof(prvm_edict_t)); + + // alloc edict private space + prog->edictprivate = Mem_Alloc(prog->progs_mempool, prog->max_edicts * prog->edictprivate_size); + + // alloc edict fields + prog->entityfieldsarea = prog->entityfields * prog->max_edicts; + prog->edictsfields = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t)); + + // set edict pointers + for(i = 0; i < prog->max_edicts; i++) + { + prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size); + prog->edicts[i].fields.fp = prog->edictsfields + i * prog->entityfields; + } +} + +/* +=============== +PRVM_MEM_IncreaseEdicts +=============== +*/ +void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog) +{ + int i; + + if(prog->max_edicts >= prog->limit_edicts) + return; + + prog->begin_increase_edicts(prog); + + // increase edicts + prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts); + + prog->entityfieldsarea = prog->entityfields * prog->max_edicts; + prog->edictsfields = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields, prog->entityfieldsarea * sizeof(prvm_vec_t)); + prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size); + + //set e and v pointers + for(i = 0; i < prog->max_edicts; i++) + { + prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size); + prog->edicts[i].fields.fp = prog->edictsfields + i * prog->entityfields; + } + + prog->end_increase_edicts(prog); +} + +//============================================================================ +// normal prvm + +int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *field) +{ + ddef_t *d; + d = PRVM_ED_FindField(prog, field); + if (!d) + return -1; + return d->ofs; +} + +int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *global) +{ + ddef_t *d; + d = PRVM_ED_FindGlobal(prog, global); + if (!d) + return -1; + return d->ofs; +} + +func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *function) +{ + mfunction_t *f; + f = PRVM_ED_FindFunction(prog, function); + if (!f) + return 0; + return (func_t)(f - prog->functions); +} + +/* +================= +PRVM_ProgFromString +================= +*/ +prvm_prog_t *PRVM_ProgFromString(const char *str) +{ + if (!strcmp(str, "server")) + return SVVM_prog; + if (!strcmp(str, "client")) + return CLVM_prog; + if (!strcmp(str, "menu")) + return MVM_prog; + return NULL; +} + +/* +================= +PRVM_FriendlyProgFromString +================= +*/ +prvm_prog_t *PRVM_FriendlyProgFromString(const char *str) +{ + prvm_prog_t *prog = PRVM_ProgFromString(str); + if (!prog) + { + Con_Printf("%s: unknown program name\n", str); + return NULL; + } + if (!prog->loaded) + { + Con_Printf("%s: program is not loaded\n", str); + return NULL; + } + return prog; +} + +/* +================= +PRVM_ED_ClearEdict + +Sets everything to NULL +================= +*/ +void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e) +{ + memset(e->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); + e->priv.required->free = false; + + // AK: Let the init_edict function determine if something needs to be initialized + prog->init_edict(prog, e); +} + +const char *PRVM_AllocationOrigin(prvm_prog_t *prog) +{ + char *buf = NULL; + if(prog->leaktest_active) + if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame + { + buf = (char *)PRVM_Alloc(128); + PRVM_ShortStackTrace(prog, buf, 128); + } + return buf; +} + +/* +================= +PRVM_ED_CanAlloc + +Returns if this particular edict could get allocated by PRVM_ED_Alloc +================= +*/ +qboolean PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e) +{ + if(!e->priv.required->free) + return false; + if(prvm_reuseedicts_always_allow == realtime) + return true; + if(realtime <= e->priv.required->freetime + 0.1 && prvm_reuseedicts_neverinsameframe.integer) + return false; // never allow reuse in same frame (causes networking trouble) + if(e->priv.required->freetime < prog->starttime + prvm_reuseedicts_startuptime.value) + return true; + if(realtime > e->priv.required->freetime + 1) + return true; + return false; // entity slot still blocked because the entity was freed less than one second ago +} + +/* +================= +PRVM_ED_Alloc + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *e; + + // the client qc dont need maxclients + // thus it doesnt need to use svs.maxclients + // AK: changed i=svs.maxclients+1 + // AK: changed so the edict 0 wont spawn -> used as reserved/world entity + // although the menu/client has no world + for (i = prog->reserved_edicts + 1;i < prog->num_edicts;i++) + { + e = PRVM_EDICT_NUM(i); + if(PRVM_ED_CanAlloc(prog, e)) + { + PRVM_ED_ClearEdict (prog, e); + e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog); + return e; + } + } + + if (i == prog->limit_edicts) + prog->error_cmd("%s: PRVM_ED_Alloc: no free edicts", prog->name); + + prog->num_edicts++; + if (prog->num_edicts >= prog->max_edicts) + PRVM_MEM_IncreaseEdicts(prog); + + e = PRVM_EDICT_NUM(i); + PRVM_ED_ClearEdict(prog, e); + + e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog); + + return e; +} + +/* +================= +PRVM_ED_Free + +Marks the edict as free +FIXME: walk all entities and NULL out references to this entity +================= +*/ +void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed) +{ + // dont delete the null entity (world) or reserved edicts + if (ed - prog->edicts <= prog->reserved_edicts) + return; + + prog->free_edict(prog, ed); + + ed->priv.required->free = true; + ed->priv.required->freetime = realtime; + if(ed->priv.required->allocation_origin) + { + Mem_Free((char *)ed->priv.required->allocation_origin); + ed->priv.required->allocation_origin = NULL; + } +} + +//=========================================================================== + +/* +============ +PRVM_ED_GlobalAtOfs +============ +*/ +static ddef_t *PRVM_ED_GlobalAtOfs (prvm_prog_t *prog, int ofs) +{ + ddef_t *def; + int i; + + for (i = 0;i < prog->numglobaldefs;i++) + { + def = &prog->globaldefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FieldAtOfs +============ +*/ +ddef_t *PRVM_ED_FieldAtOfs (prvm_prog_t *prog, int ofs) +{ + ddef_t *def; + int i; + + for (i = 0;i < prog->numfielddefs;i++) + { + def = &prog->fielddefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FindField +============ +*/ +ddef_t *PRVM_ED_FindField (prvm_prog_t *prog, const char *name) +{ + ddef_t *def; + int i; + + for (i = 0;i < prog->numfielddefs;i++) + { + def = &prog->fielddefs[i]; + if (!strcmp(PRVM_GetString(prog, def->s_name), name)) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FindGlobal +============ +*/ +ddef_t *PRVM_ED_FindGlobal (prvm_prog_t *prog, const char *name) +{ + ddef_t *def; + int i; + + for (i = 0;i < prog->numglobaldefs;i++) + { + def = &prog->globaldefs[i]; + if (!strcmp(PRVM_GetString(prog, def->s_name), name)) + return def; + } + return NULL; +} + + +/* +============ +PRVM_ED_FindFunction +============ +*/ +mfunction_t *PRVM_ED_FindFunction (prvm_prog_t *prog, const char *name) +{ + mfunction_t *func; + int i; + + for (i = 0;i < prog->numfunctions;i++) + { + func = &prog->functions[i]; + if (!strcmp(PRVM_GetString(prog, func->s_name), name)) + return func; + } + return NULL; +} + + +/* +============ +PRVM_ValueString + +Returns a string describing *data in a type specific manner +============= +*/ +static char *PRVM_ValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength) +{ + ddef_t *def; + mfunction_t *f; + int n; + + type = (etype_t)((int) type & ~DEF_SAVEGLOBAL); + + switch (type) + { + case ev_string: + strlcpy (line, PRVM_GetString (prog, val->string), linelength); + break; + case ev_entity: + n = val->edict; + if (n < 0 || n >= prog->max_edicts) + dpsnprintf (line, linelength, "entity %i (invalid!)", n); + else + dpsnprintf (line, linelength, "entity %i", n); + break; + case ev_function: + f = prog->functions + val->function; + dpsnprintf (line, linelength, "%s()", PRVM_GetString(prog, f->s_name)); + break; + case ev_field: + def = PRVM_ED_FieldAtOfs ( prog, val->_int ); + dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name)); + break; + case ev_void: + dpsnprintf (line, linelength, "void"); + break; + case ev_float: + // LordHavoc: changed from %5.1f to %10.4f + dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float); + break; + case ev_vector: + // LordHavoc: changed from %5.1f to %10.4f + dpsnprintf (line, linelength, "'" VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]); + break; + case ev_pointer: + dpsnprintf (line, linelength, "pointer"); + break; + default: + dpsnprintf (line, linelength, "bad type %i", (int) type); + break; + } + + return line; +} + +/* +============ +PRVM_UglyValueString + +Returns a string describing *data in a type specific manner +Easier to parse than PR_ValueString +============= +*/ +char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength) +{ + int i; + const char *s; + ddef_t *def; + mfunction_t *f; + + type = (etype_t)((int)type & ~DEF_SAVEGLOBAL); + + switch (type) + { + case ev_string: + // Parse the string a bit to turn special characters + // (like newline, specifically) into escape codes, + // this fixes saving games from various mods + s = PRVM_GetString (prog, val->string); + for (i = 0;i < (int)linelength - 2 && *s;) + { + if (*s == '\n') + { + line[i++] = '\\'; + line[i++] = 'n'; + } + else if (*s == '\r') + { + line[i++] = '\\'; + line[i++] = 'r'; + } + else if (*s == '\\') + { + line[i++] = '\\'; + line[i++] = '\\'; + } + else if (*s == '"') + { + line[i++] = '\\'; + line[i++] = '"'; + } + else + line[i++] = *s; + s++; + } + line[i] = '\0'; + break; + case ev_entity: + dpsnprintf (line, linelength, "%i", val->edict); + break; + case ev_function: + f = prog->functions + val->function; + strlcpy (line, PRVM_GetString (prog, f->s_name), linelength); + break; + case ev_field: + def = PRVM_ED_FieldAtOfs ( prog, val->_int ); + dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name)); + break; + case ev_void: + dpsnprintf (line, linelength, "void"); + break; + case ev_float: + dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float); + break; + case ev_vector: + dpsnprintf (line, linelength, VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]); + break; + default: + dpsnprintf (line, linelength, "bad type %i", type); + break; + } + + return line; +} + +/* +============ +PRVM_GlobalString + +Returns a string with a description and the contents of a global, +padded to 20 field width +============ +*/ +char *PRVM_GlobalString (prvm_prog_t *prog, int ofs, char *line, size_t linelength) +{ + char *s; + //size_t i; + ddef_t *def; + prvm_eval_t *val; + char valuebuf[MAX_INPUTLINE]; + + val = (prvm_eval_t *)&prog->globals.fp[ofs]; + def = PRVM_ED_GlobalAtOfs(prog, ofs); + if (!def) + dpsnprintf (line, linelength, "GLOBAL%i", ofs); + else + { + s = PRVM_ValueString (prog, (etype_t)def->type, val, valuebuf, sizeof(valuebuf)); + dpsnprintf (line, linelength, "%s (=%s)", PRVM_GetString(prog, def->s_name), s); + } + + //i = strlen(line); + //for ( ; i<20 ; i++) + // strcat (line," "); + //strcat (line," "); + + return line; +} + +char *PRVM_GlobalStringNoContents (prvm_prog_t *prog, int ofs, char *line, size_t linelength) +{ + //size_t i; + ddef_t *def; + + def = PRVM_ED_GlobalAtOfs(prog, ofs); + if (!def) + dpsnprintf (line, linelength, "GLOBAL%i", ofs); + else + dpsnprintf (line, linelength, "%s", PRVM_GetString(prog, def->s_name)); + + //i = strlen(line); + //for ( ; i<20 ; i++) + // strcat (line," "); + //strcat (line," "); + + return line; +} + + +/* +============= +PRVM_ED_Print + +For debugging +============= +*/ +// LordHavoc: optimized this to print out much more quickly (tempstring) +// LordHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print) +void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname) +{ + size_t l; + ddef_t *d; + prvm_eval_t *val; + int i, j; + const char *name; + int type; + char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers + char valuebuf[MAX_INPUTLINE]; + + if (ed->priv.required->free) + { + Con_Printf("%s: FREE\n",prog->name); + return; + } + + tempstring[0] = 0; + dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", prog->name, PRVM_NUM_FOR_EDICT(ed)); + for (i = 1;i < prog->numfielddefs;i++) + { + d = &prog->fielddefs[i]; + name = PRVM_GetString(prog, d->s_name); + if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) + continue; // skip _x, _y, _z vars + + // Check Field Name Wildcard + if(wildcard_fieldname) + if( !matchpattern(name, wildcard_fieldname, 1) ) + // Didn't match; skip + continue; + + val = (prvm_eval_t *)(ed->fields.fp + d->ofs); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + + for (j=0 ; jivector[j]) + break; + if (j == prvm_type_size[type]) + continue; + + if (strlen(name) > sizeof(tempstring2)-4) + { + memcpy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + strlcat(tempstring, name, sizeof(tempstring)); + for (l = strlen(name);l < 14;l++) + strlcat(tempstring, " ", sizeof(tempstring)); + strlcat(tempstring, " ", sizeof(tempstring)); + + name = PRVM_ValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf)); + if (strlen(name) > sizeof(tempstring2)-4) + { + memcpy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + strlcat(tempstring, name, sizeof(tempstring)); + strlcat(tempstring, "\n", sizeof(tempstring)); + if (strlen(tempstring) >= sizeof(tempstring)/2) + { + Con_Print(tempstring); + tempstring[0] = 0; + } + } + if (tempstring[0]) + Con_Print(tempstring); +} + +/* +============= +PRVM_ED_Write + +For savegames +============= +*/ +extern cvar_t developer_entityparsing; +void PRVM_ED_Write (prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed) +{ + ddef_t *d; + prvm_eval_t *val; + int i, j; + const char *name; + int type; + char vabuf[1024]; + char valuebuf[MAX_INPUTLINE]; + + FS_Print(f, "{\n"); + + if (ed->priv.required->free) + { + FS_Print(f, "}\n"); + return; + } + + for (i = 1;i < prog->numfielddefs;i++) + { + d = &prog->fielddefs[i]; + name = PRVM_GetString(prog, d->s_name); + + if(developer_entityparsing.integer) + Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name); + + //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) + if(strlen(name) > 1 && name[strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars, and ALSO other _? vars as some mods expect them to be never saved (TODO: a gameplayfix for using the "more precise" condition above?) + + val = (prvm_eval_t *)(ed->fields.fp + d->ofs); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + for (j=0 ; jivector[j]) + break; + if (j == prvm_type_size[type]) + continue; + + FS_Printf(f,"\"%s\" ",name); + prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_Write, ent=%d, name=%s", i, name); + FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf))); + prog->statestring = NULL; + } + + FS_Print(f, "}\n"); +} + +void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname) +{ + PRVM_ED_Print(prog, PRVM_EDICT_NUM(ent), wildcard_fieldname); +} + +/* +============= +PRVM_ED_PrintEdicts_f + +For debugging, prints all the entities in the current server +============= +*/ +void PRVM_ED_PrintEdicts_f (void) +{ + prvm_prog_t *prog; + int i; + const char *wildcard_fieldname; + + if(Cmd_Argc() < 2 || Cmd_Argc() > 3) + { + Con_Print("prvm_edicts \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + if( Cmd_Argc() == 3) + wildcard_fieldname = Cmd_Argv(2); + else + wildcard_fieldname = NULL; + + Con_Printf("%s: %i entities\n", prog->name, prog->num_edicts); + for (i=0 ; inum_edicts ; i++) + PRVM_ED_PrintNum (prog, i, wildcard_fieldname); +} + +/* +============= +PRVM_ED_PrintEdict_f + +For debugging, prints a single edict +============= +*/ +static void PRVM_ED_PrintEdict_f (void) +{ + prvm_prog_t *prog; + int i; + const char *wildcard_fieldname; + + if(Cmd_Argc() < 3 || Cmd_Argc() > 4) + { + Con_Print("prvm_edict \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + i = atoi (Cmd_Argv(2)); + if (i >= prog->num_edicts) + { + Con_Print("Bad edict number\n"); + return; + } + if( Cmd_Argc() == 4) + // Optional Wildcard Provided + wildcard_fieldname = Cmd_Argv(3); + else + // Use All + wildcard_fieldname = NULL; + PRVM_ED_PrintNum (prog, i, wildcard_fieldname); +} + +/* +============= +PRVM_ED_Count + +For debugging +============= +*/ +// 2 possibilities : 1. just displaying the active edict count +// 2. making a function pointer [x] +static void PRVM_ED_Count_f (void) +{ + prvm_prog_t *prog; + + if(Cmd_Argc() != 2) + { + Con_Print("prvm_count \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + prog->count_edicts(prog); +} + +/* +============================================================================== + + ARCHIVING GLOBALS + +FIXME: need to tag constants, doesn't really work +============================================================================== +*/ + +/* +============= +PRVM_ED_WriteGlobals +============= +*/ +void PRVM_ED_WriteGlobals (prvm_prog_t *prog, qfile_t *f) +{ + ddef_t *def; + int i; + const char *name; + int type; + char vabuf[1024]; + char valuebuf[MAX_INPUTLINE]; + + FS_Print(f,"{\n"); + for (i = 0;i < prog->numglobaldefs;i++) + { + def = &prog->globaldefs[i]; + type = def->type; + if ( !(def->type & DEF_SAVEGLOBAL) ) + continue; + type &= ~DEF_SAVEGLOBAL; + + if (type != ev_string && type != ev_float && type != ev_entity) + continue; + + name = PRVM_GetString(prog, def->s_name); + + if(developer_entityparsing.integer) + Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name); + + prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_WriteGlobals, name=%s", name); + FS_Printf(f,"\"%s\" ", name); + FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)type, (prvm_eval_t *)&prog->globals.fp[def->ofs], valuebuf, sizeof(valuebuf))); + prog->statestring = NULL; + } + FS_Print(f,"}\n"); +} + +/* +============= +PRVM_ED_ParseGlobals +============= +*/ +void PRVM_ED_ParseGlobals (prvm_prog_t *prog, const char *data) +{ + char keyname[MAX_INPUTLINE]; + ddef_t *key; + + while (1) + { + // parse key + if (!COM_ParseToken_Simple(&data, false, false, true)) + prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace"); + if (com_token[0] == '}') + break; + + if (developer_entityparsing.integer) + Con_Printf("Key: \"%s\"", com_token); + + strlcpy (keyname, com_token, sizeof(keyname)); + + // parse value + if (!COM_ParseToken_Simple(&data, false, true, true)) + prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace"); + + if (developer_entityparsing.integer) + Con_Printf(" \"%s\"\n", com_token); + + if (com_token[0] == '}') + prog->error_cmd("PRVM_ED_ParseGlobals: closing brace without data"); + + key = PRVM_ED_FindGlobal (prog, keyname); + if (!key) + { + Con_DPrintf("'%s' is not a global on %s\n", keyname, prog->name); + continue; + } + + if (!PRVM_ED_ParseEpair(prog, NULL, key, com_token, true)) + prog->error_cmd("PRVM_ED_ParseGlobals: parse error"); + } +} + +//============================================================================ + + +/* +============= +PRVM_ED_ParseEval + +Can parse either fields or globals +returns false if error +============= +*/ +qboolean PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash) +{ + int i, l; + char *new_p; + ddef_t *def; + prvm_eval_t *val; + mfunction_t *func; + + if (ent) + val = (prvm_eval_t *)(ent->fields.fp + key->ofs); + else + val = (prvm_eval_t *)(prog->globals.fp + key->ofs); + switch (key->type & ~DEF_SAVEGLOBAL) + { + case ev_string: + l = (int)strlen(s) + 1; + val->string = PRVM_AllocString(prog, l, &new_p); + for (i = 0;i < l;i++) + { + if (s[i] == '\\' && s[i+1] && parsebackslash) + { + i++; + if (s[i] == 'n') + *new_p++ = '\n'; + else if (s[i] == 'r') + *new_p++ = '\r'; + else + *new_p++ = s[i]; + } + else + *new_p++ = s[i]; + } + break; + + case ev_float: + while (*s && ISWHITESPACE(*s)) + s++; + val->_float = atof(s); + break; + + case ev_vector: + for (i = 0;i < 3;i++) + { + while (*s && ISWHITESPACE(*s)) + s++; + if (!*s) + break; + val->vector[i] = atof(s); + while (!ISWHITESPACE(*s)) + s++; + if (!*s) + break; + } + break; + + case ev_entity: + while (*s && ISWHITESPACE(*s)) + s++; + i = atoi(s); + if (i >= prog->limit_edicts) + Con_Printf("PRVM_ED_ParseEpair: ev_entity reference too large (edict %u >= MAX_EDICTS %u) on %s\n", (unsigned int)i, prog->limit_edicts, prog->name); + while (i >= prog->max_edicts) + PRVM_MEM_IncreaseEdicts(prog); + // if IncreaseEdicts was called the base pointer needs to be updated + if (ent) + val = (prvm_eval_t *)(ent->fields.fp + key->ofs); + val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i)); + break; + + case ev_field: + if (*s != '.') + { + Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, prog->name); + return false; + } + def = PRVM_ED_FindField(prog, s + 1); + if (!def) + { + Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, prog->name); + return false; + } + val->_int = def->ofs; + break; + + case ev_function: + func = PRVM_ED_FindFunction(prog, s); + if (!func) + { + Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, prog->name); + return false; + } + val->function = func - prog->functions; + break; + + default: + Con_Printf("PRVM_ED_ParseEpair: Unknown key->type %i for key \"%s\" on %s\n", key->type, PRVM_GetString(prog, key->s_name), prog->name); + return false; + } + return true; +} + +/* +============= +PRVM_GameCommand_f + +Console command to send a string to QC function GameCommand of the +indicated progs + +Usage: + sv_cmd adminmsg 3 "do not teamkill" + cl_cmd someclientcommand + menu_cmd somemenucommand + +All progs can support this extension; sg calls it in server QC, cg in client +QC, mg in menu QC. +============= +*/ +static void PRVM_GameCommand(const char *whichprogs, const char *whichcmd) +{ + prvm_prog_t *prog; + if(Cmd_Argc() < 1) + { + Con_Printf("%s text...\n", whichcmd); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(whichprogs))) + return; + + if(!PRVM_allfunction(GameCommand)) + { + Con_Printf("%s program do not support GameCommand!\n", whichprogs); + } + else + { + int restorevm_tempstringsbuf_cursize; + const char *s; + + s = Cmd_Args(); + + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s ? s : ""); + prog->ExecuteProgram(prog, PRVM_allfunction(GameCommand), "QC function GameCommand is missing"); + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + } +} +static void PRVM_GameCommand_Server_f(void) +{ + PRVM_GameCommand("server", "sv_cmd"); +} +static void PRVM_GameCommand_Client_f(void) +{ + PRVM_GameCommand("client", "cl_cmd"); +} +static void PRVM_GameCommand_Menu_f(void) +{ + PRVM_GameCommand("menu", "menu_cmd"); +} + +/* +============= +PRVM_ED_EdictGet_f + +Console command to load a field of a specified edict +============= +*/ +static void PRVM_ED_EdictGet_f(void) +{ + prvm_prog_t *prog; + prvm_edict_t *ed; + ddef_t *key; + const char *s; + prvm_eval_t *v; + char valuebuf[MAX_INPUTLINE]; + + if(Cmd_Argc() != 4 && Cmd_Argc() != 5) + { + Con_Print("prvm_edictget []\n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(2))); + + if((key = PRVM_ED_FindField(prog, Cmd_Argv(3))) == 0) + { + Con_Printf("Key %s not found !\n", Cmd_Argv(3)); + goto fail; + } + + v = (prvm_eval_t *)(ed->fields.fp + key->ofs); + s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf)); + if(Cmd_Argc() == 5) + { + cvar_t *cvar = Cvar_FindVar(Cmd_Argv(4)); + if (cvar && cvar->flags & CVAR_READONLY) + { + Con_Printf("prvm_edictget: %s is read-only\n", cvar->name); + goto fail; + } + Cvar_Get(Cmd_Argv(4), s, 0, NULL); + } + else + Con_Printf("%s\n", s); + +fail: + ; +} + +static void PRVM_ED_GlobalGet_f(void) +{ + prvm_prog_t *prog; + ddef_t *key; + const char *s; + prvm_eval_t *v; + char valuebuf[MAX_INPUTLINE]; + + if(Cmd_Argc() != 3 && Cmd_Argc() != 4) + { + Con_Print("prvm_globalget []\n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + key = PRVM_ED_FindGlobal(prog, Cmd_Argv(2)); + if(!key) + { + Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); + goto fail; + } + + v = (prvm_eval_t *) &prog->globals.fp[key->ofs]; + s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf)); + if(Cmd_Argc() == 4) + { + cvar_t *cvar = Cvar_FindVar(Cmd_Argv(3)); + if (cvar && cvar->flags & CVAR_READONLY) + { + Con_Printf("prvm_globalget: %s is read-only\n", cvar->name); + goto fail; + } + Cvar_Get(Cmd_Argv(3), s, 0, NULL); + } + else + Con_Printf("%s\n", s); + +fail: + ; +} + +/* +============= +PRVM_ED_EdictSet_f + +Console command to set a field of a specified edict +============= +*/ +static void PRVM_ED_EdictSet_f(void) +{ + prvm_prog_t *prog; + prvm_edict_t *ed; + ddef_t *key; + + if(Cmd_Argc() != 5) + { + Con_Print("prvm_edictset \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(2))); + + if((key = PRVM_ED_FindField(prog, Cmd_Argv(3))) == 0) + Con_Printf("Key %s not found !\n", Cmd_Argv(3)); + else + PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(4), true); +} + +/* +==================== +PRVM_ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +Used for initial level load and for savegames. +==================== +*/ +const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_t *ent) +{ + ddef_t *key; + qboolean anglehack; + qboolean init; + char keyname[256]; + size_t n; + + init = false; + +// go through all the dictionary pairs + while (1) + { + // parse key + if (!COM_ParseToken_Simple(&data, false, false, true)) + prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace"); + if (developer_entityparsing.integer) + Con_Printf("Key: \"%s\"", com_token); + if (com_token[0] == '}') + break; + + // anglehack is to allow QuakeEd to write single scalar angles + // and allow them to be turned into vectors. (FIXME...) + if (!strcmp(com_token, "angle")) + { + strlcpy (com_token, "angles", sizeof(com_token)); + anglehack = true; + } + else + anglehack = false; + + // FIXME: change light to _light to get rid of this hack + if (!strcmp(com_token, "light")) + strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def + + strlcpy (keyname, com_token, sizeof(keyname)); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + if (!COM_ParseToken_Simple(&data, false, false, true)) + prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace"); + if (developer_entityparsing.integer) + Con_Printf(" \"%s\"\n", com_token); + + if (com_token[0] == '}') + prog->error_cmd("PRVM_ED_ParseEdict: closing brace without data"); + + init = true; + + // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp) + if (!keyname[0]) + continue; + +// keynames with a leading underscore are used for utility comments, +// and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + key = PRVM_ED_FindField (prog, keyname); + if (!key) + { + Con_DPrintf("%s: '%s' is not a field\n", prog->name, keyname); + continue; + } + + if (anglehack) + { + char temp[32]; + strlcpy (temp, com_token, sizeof(temp)); + dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp); + } + + if (!PRVM_ED_ParseEpair(prog, ent, key, com_token, strcmp(keyname, "wad") != 0)) + prog->error_cmd("PRVM_ED_ParseEdict: parse error"); + } + + if (!init) + ent->priv.required->free = true; + + return data; +} + + +/* +================ +PRVM_ED_LoadFromFile + +The entities are directly placed in the array, rather than allocated with +PRVM_ED_Alloc, because otherwise an error loading the map would have entity +number references out of order. + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. + +Used for both fresh maps and savegame loads. A fresh map would also need +to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves. +================ +*/ +void PRVM_ED_LoadFromFile (prvm_prog_t *prog, const char *data) +{ + prvm_edict_t *ent; + int parsed, inhibited, spawned, died; + const char *funcname; + mfunction_t *func; + char vabuf[1024]; + + parsed = 0; + inhibited = 0; + spawned = 0; + died = 0; + + prvm_reuseedicts_always_allow = realtime; + +// parse ents + while (1) + { +// parse the opening brace + if (!COM_ParseToken_Simple(&data, false, false, true)) + break; + if (com_token[0] != '{') + prog->error_cmd("PRVM_ED_LoadFromFile: %s: found %s when expecting {", prog->name, com_token); + + // CHANGED: this is not conform to PR_LoadFromFile + if(prog->loadintoworld) + { + prog->loadintoworld = false; + ent = PRVM_EDICT_NUM(0); + } + else + ent = PRVM_ED_Alloc(prog); + + // clear it + if (ent != prog->edicts) // hack + memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); + + data = PRVM_ED_ParseEdict (prog, data, ent); + parsed++; + + // remove the entity ? + if(!prog->load_edict(prog, ent)) + { + PRVM_ED_Free(prog, ent); + inhibited++; + continue; + } + + if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction)) + { + // self = ent + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing"); + } + + if(ent->priv.required->free) + { + inhibited++; + continue; + } + +// +// immediately call spawn function, but only if there is a self global and a classname +// + if(!ent->priv.required->free) + { + if (!PRVM_alledictstring(ent, classname)) + { + Con_Print("No classname for:\n"); + PRVM_ED_Print(prog, ent, NULL); + PRVM_ED_Free (prog, ent); + continue; + } + + // look for the spawn function + funcname = PRVM_GetString(prog, PRVM_alledictstring(ent, classname)); + func = PRVM_ED_FindFunction (prog, va(vabuf, sizeof(vabuf), "spawnfunc_%s", funcname)); + if(!func) + if(!PRVM_allglobalfloat(require_spawnfunc_prefix)) + func = PRVM_ED_FindFunction (prog, funcname); + + if (!func) + { + // check for OnEntityNoSpawnFunction + if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction)) + { + // self = ent + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing"); + } + else + { + if (developer.integer > 0) // don't confuse non-developers with errors + { + Con_Print("No spawn function for:\n"); + PRVM_ED_Print(prog, ent, NULL); + } + PRVM_ED_Free (prog, ent); + continue; // not included in "inhibited" count + } + } + else + { + // self = ent + PRVM_serverglobalfloat(time) = sv.time; + PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + prog->ExecuteProgram(prog, func - prog->functions, ""); + } + } + + if(!ent->priv.required->free) + if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction)) + { + // self = ent + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing"); + } + + spawned++; + if (ent->priv.required->free) + died++; + } + + Con_DPrintf("%s: %i new entities parsed, %i new inhibited, %i (%i new) spawned (whereas %i removed self, %i stayed)\n", prog->name, parsed, inhibited, prog->num_edicts, spawned, died, spawned - died); + + prvm_reuseedicts_always_allow = 0; +} + +static void PRVM_FindOffsets(prvm_prog_t *prog) +{ + // field and global searches use -1 for NULL + memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets)); + memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets)); + // function searches use 0 for NULL + memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets)); +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(prog, #x); +#define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(prog, #x); +#define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(prog, #x); +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +} + +// not used +/* +typedef struct dpfield_s +{ + int type; + char *string; +} +dpfield_t; + +#define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t)) + +dpfield_t dpfields[] = +{ +}; +*/ + +/* +=============== +PRVM_ResetProg +=============== +*/ + +#define PO_HASHSIZE 16384 +typedef struct po_string_s +{ + char *key, *value; + struct po_string_s *nextonhashchain; +} +po_string_t; +typedef struct po_s +{ + po_string_t *hashtable[PO_HASHSIZE]; +} +po_t; +static void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize) +{ + for(;;) + { + switch(*in) + { + case 0: + *out++ = 0; + return; + case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break; + case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break; + case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break; + case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break; + case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break; + case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break; + case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break; + default: + if(*in >= 0 && *in <= 0x1F) + { + if(outsize >= 4) + { + *out++ = '\\'; + *out++ = '0' + ((*in & 0700) >> 6); + *out++ = '0' + ((*in & 0070) >> 3); + *out++ = '0' + (*in & 0007) ; + outsize -= 4; + } + } + else + { + if(outsize >= 1) + { + *out++ = *in; + outsize -= 1; + } + } + break; + } + ++in; + } +} +static void PRVM_PO_ParseString(char *out, const char *in, size_t outsize) +{ + for(;;) + { + switch(*in) + { + case 0: + *out++ = 0; + return; + case '\\': + ++in; + switch(*in) + { + case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break; + case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break; + case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break; + case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break; + case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break; + case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break; + case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break; + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': + if(outsize > 0) + *out = *in - '0'; + ++in; + if(*in >= '0' && *in <= '7') + { + if(outsize > 0) + *out = (*out << 3) | (*in - '0'); + ++in; + } + if(*in >= '0' && *in <= '7') + { + if(outsize > 0) + *out = (*out << 3) | (*in - '0'); + ++in; + } + --in; + if(outsize > 0) + { + ++out; + --outsize; + } + break; + default: + if(outsize > 0) { *out++ = *in; --outsize; } + break; + } + break; + default: + if(outsize > 0) + { + *out++ = *in; + --outsize; + } + break; + } + ++in; + } +} +static po_t *PRVM_PO_Load(const char *filename, mempool_t *pool) +{ + po_t *po; + const char *p, *q; + int mode; + char inbuf[MAX_INPUTLINE]; + char decodedbuf[MAX_INPUTLINE]; + size_t decodedpos; + int hashindex; + po_string_t thisstr; + const char *buf = (const char *) FS_LoadFile(filename, pool, true, NULL); + + if(!buf) + return NULL; + + memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning + + po = (po_t *)Mem_Alloc(pool, sizeof(*po)); + memset(po, 0, sizeof(*po)); + + p = buf; + while(*p) + { + if(*p == '#') + { + // skip to newline + p = strchr(p, '\n'); + if(!p) + break; + ++p; + continue; + } + if(*p == '\r' || *p == '\n') + { + ++p; + continue; + } + if(!strncmp(p, "msgid \"", 7)) + { + mode = 0; + p += 6; + } + else if(!strncmp(p, "msgstr \"", 8)) + { + mode = 1; + p += 7; + } + else + { + p = strchr(p, '\n'); + if(!p) + break; + ++p; + continue; + } + decodedpos = 0; + while(*p == '"') + { + ++p; + q = strchr(p, '\n'); + if(!q) + break; + if(*(q-1) == '\r') + --q; + if(*(q-1) != '"') + break; + if((size_t)(q - p) >= (size_t) sizeof(inbuf)) + break; + strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL + PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos); + decodedpos += strlen(decodedbuf + decodedpos); + if(*q == '\r') + ++q; + if(*q == '\n') + ++q; + p = q; + } + if(mode == 0) + { + if(thisstr.key) + Mem_Free(thisstr.key); + thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1); + memcpy(thisstr.key, decodedbuf, decodedpos + 1); + } + else if(decodedpos > 0 && thisstr.key) // skip empty translation results + { + thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1); + memcpy(thisstr.value, decodedbuf, decodedpos + 1); + hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE; + thisstr.nextonhashchain = po->hashtable[hashindex]; + po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr)); + memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr)); + memset(&thisstr, 0, sizeof(thisstr)); + } + } + + Mem_Free((char *) buf); + return po; +} +static const char *PRVM_PO_Lookup(po_t *po, const char *str) +{ + int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE; + po_string_t *p = po->hashtable[hashindex]; + while(p) + { + if(!strcmp(str, p->key)) + return p->value; + p = p->nextonhashchain; + } + return NULL; +} +static void PRVM_PO_Destroy(po_t *po) +{ + int i; + for(i = 0; i < PO_HASHSIZE; ++i) + { + po_string_t *p = po->hashtable[i]; + while(p) + { + po_string_t *q = p; + p = p->nextonhashchain; + Mem_Free(q->key); + Mem_Free(q->value); + Mem_Free(q); + } + } + Mem_Free(po); +} + +void PRVM_LeakTest(prvm_prog_t *prog); +void PRVM_Prog_Reset(prvm_prog_t *prog) +{ + if (prog->loaded) + { + PRVM_LeakTest(prog); + prog->reset_cmd(prog); + Mem_FreePool(&prog->progs_mempool); + if(prog->po) + PRVM_PO_Destroy((po_t *) prog->po); + } + memset(prog,0,sizeof(prvm_prog_t)); + prog->break_statement = -1; + prog->watch_global_type = ev_void; + prog->watch_field_type = ev_void; +} + +/* +=============== +PRVM_LoadLNO +=============== +*/ +static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) { + fs_offset_t filesize; + unsigned char *lno; + unsigned int *header; + char filename[512]; + + FS_StripExtension( progname, filename, sizeof( filename ) ); + strlcat( filename, ".lno", sizeof( filename ) ); + + lno = FS_LoadFile( filename, tempmempool, false, &filesize ); + if( !lno ) { + return; + } + +/* + SafeWrite (h, &lnotype, sizeof(int)); + SafeWrite (h, &version, sizeof(int)); + SafeWrite (h, &numglobaldefs, sizeof(int)); + SafeWrite (h, &numpr_globals, sizeof(int)); + SafeWrite (h, &numfielddefs, sizeof(int)); + SafeWrite (h, &numstatements, sizeof(int)); + SafeWrite (h, statement_linenums, numstatements*sizeof(int)); +*/ + if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int)) + { + Mem_Free(lno); + return; + } + + header = (unsigned int *) lno; + if( header[ 0 ] == *(unsigned int *) "LNOF" && + LittleLong( header[ 1 ] ) == 1 && + (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs && + (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals && + (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs && + (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements ) + { + prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) ); + memcpy( prog->statement_linenums, (int *) lno + 6, prog->progs_numstatements * sizeof( int ) ); + } + Mem_Free( lno ); +} + +/* +=============== +PRVM_LoadProgs +=============== +*/ +static void PRVM_UpdateBreakpoints(prvm_prog_t *prog); +void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global) +{ + int i; + dprograms_t *dprograms; + dstatement_t *instatements; + ddef_t *infielddefs; + ddef_t *inglobaldefs; + int *inglobals; + dfunction_t *infunctions; + char *instrings; + fs_offset_t filesize; + int requiredglobalspace; + opcode_t op; + int a; + int b; + int c; + union + { + unsigned int i; + float f; + } + u; + unsigned int d; + char vabuf[1024]; + + if (prog->loaded) + prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name ); + + Host_LockSession(); // all progs can use the session cvar + Crypto_LoadKeys(); // all progs might use the keys at init time + + if (data) + { + dprograms = (dprograms_t *) data; + filesize = size; + } + else + dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize); + if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t)) + prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name); + // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file + + prog->profiletime = Sys_DirtyTime(); + prog->starttime = realtime; + + Con_DPrintf("%s programs occupy %iK.\n", prog->name, (int)(filesize/1024)); + + requiredglobalspace = 0; + for (i = 0;i < numrequiredglobals;i++) + requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1; + + prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize); + +// byte swap the header + prog->progs_version = LittleLong(dprograms->version); + prog->progs_crc = LittleLong(dprograms->crc); + if (prog->progs_version != PROG_VERSION) + prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION); + instatements = (dstatement_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements)); + prog->progs_numstatements = LittleLong(dprograms->numstatements); + inglobaldefs = (ddef_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs)); + prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs); + infielddefs = (ddef_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs)); + prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs); + infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions)); + prog->progs_numfunctions = LittleLong(dprograms->numfunctions); + instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings)); + prog->progs_numstrings = LittleLong(dprograms->numstrings); + inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals)); + prog->progs_numglobals = LittleLong(dprograms->numglobals); + prog->progs_entityfields = LittleLong(dprograms->entityfields); + + prog->numstatements = prog->progs_numstatements; + prog->numglobaldefs = prog->progs_numglobaldefs; + prog->numfielddefs = prog->progs_numfielddefs; + prog->numfunctions = prog->progs_numfunctions; + prog->numstrings = prog->progs_numstrings; + prog->numglobals = prog->progs_numglobals; + prog->entityfields = prog->progs_entityfields; + + if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize) + prog->error_cmd("%s: %s strings go past end of file", prog->name, filename); + prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings); + memcpy(prog->strings, instrings, prog->progs_numstrings); + prog->stringssize = prog->progs_numstrings; + + prog->numknownstrings = 0; + prog->maxknownstrings = 0; + prog->knownstrings = NULL; + prog->knownstrings_freeable = NULL; + + Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64); + + // we need to expand the globaldefs and fielddefs to include engine defs + prog->globaldefs = (ddef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(ddef_t)); + prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t)); + // + 2 is because of an otherwise occurring overrun in RETURN instruction + // when trying to return the last or second-last global + // (RETURN always returns a vector, there is no RETURN_F instruction) + prog->fielddefs = (ddef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(ddef_t)); + // we need to convert the statements to our memory format + prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t)); + // allocate space for profiling statement usage + prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile)); + // functions need to be converted to the memory format + prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions); + + for (i = 0;i < prog->progs_numfunctions;i++) + { + prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement); + prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start); + prog->functions[i].s_name = LittleLong(infunctions[i].s_name); + prog->functions[i].s_file = LittleLong(infunctions[i].s_file); + prog->functions[i].numparms = LittleLong(infunctions[i].numparms); + prog->functions[i].locals = LittleLong(infunctions[i].locals); + memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size)); + if(prog->functions[i].first_statement >= prog->numstatements) + prog->error_cmd("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, prog->name); + // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size + } + + // copy the globaldefs to the new globaldefs list + for (i=0 ; inumglobaldefs ; i++) + { + prog->globaldefs[i].type = LittleShort(inglobaldefs[i].type); + prog->globaldefs[i].ofs = LittleShort(inglobaldefs[i].ofs); + prog->globaldefs[i].s_name = LittleLong(inglobaldefs[i].s_name); + // TODO bounds check ofs, s_name + } + + // append the required globals + for (i = 0;i < numrequiredglobals;i++) + { + prog->globaldefs[prog->numglobaldefs].type = required_global[i].type; + prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals; + prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name); + if (prog->globaldefs[prog->numglobaldefs].type == ev_vector) + prog->numglobals += 3; + else + prog->numglobals++; + prog->numglobaldefs++; + } + + // copy the progs fields to the new fields list + for (i = 0;i < prog->numfielddefs;i++) + { + prog->fielddefs[i].type = LittleShort(infielddefs[i].type); + if (prog->fielddefs[i].type & DEF_SAVEGLOBAL) + prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name); + prog->fielddefs[i].ofs = LittleShort(infielddefs[i].ofs); + prog->fielddefs[i].s_name = LittleLong(infielddefs[i].s_name); + // TODO bounds check ofs, s_name + } + + // append the required fields + for (i = 0;i < numrequiredfields;i++) + { + prog->fielddefs[prog->numfielddefs].type = required_field[i].type; + prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields; + prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name); + if (prog->fielddefs[prog->numfielddefs].type == ev_vector) + prog->entityfields += 3; + else + prog->entityfields++; + prog->numfielddefs++; + } + + // LordHavoc: TODO: reorder globals to match engine struct + // LordHavoc: TODO: reorder fields to match engine struct +#define remapglobal(index) (index) +#define remapfield(index) (index) + + // copy globals + // FIXME: LordHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type + for (i = 0;i < prog->progs_numglobals;i++) + { + u.i = LittleLong(inglobals[i]); + // most globals are 0, we only need to deal with the ones that are not + if (u.i) + { + d = u.i & 0xFF800000; + if ((d == 0xFF800000) || (d == 0)) + { + // Looks like an integer (expand to int64) + prog->globals.ip[remapglobal(i)] = u.i; + } + else + { + // Looks like a float (expand to double) + prog->globals.fp[remapglobal(i)] = u.f; + } + } + } + + // LordHavoc: TODO: support 32bit progs statement formats + // copy, remap globals in statements, bounds check + for (i = 0;i < prog->progs_numstatements;i++) + { + op = (opcode_t)LittleShort(instatements[i].op); + a = (unsigned short)LittleShort(instatements[i].a); + b = (unsigned short)LittleShort(instatements[i].b); + c = (unsigned short)LittleShort(instatements[i].c); + switch (op) + { + case OP_IF: + case OP_IFNOT: + b = (short)b; + if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements) + prog->error_cmd("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, prog->name); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = -1; + prog->statements[i].operand[2] = -1; + prog->statements[i].jumpabsolute = i + b; + break; + case OP_GOTO: + a = (short)a; + if (a + i < 0 || a + i >= prog->progs_numstatements) + prog->error_cmd("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, prog->name); + prog->statements[i].op = op; + prog->statements[i].operand[0] = -1; + prog->statements[i].operand[1] = -1; + prog->statements[i].operand[2] = -1; + prog->statements[i].jumpabsolute = i + a; + break; + default: + Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, prog->name); + // global global global + case OP_ADD_F: + case OP_ADD_V: + case OP_SUB_F: + case OP_SUB_V: + case OP_MUL_F: + case OP_MUL_V: + case OP_MUL_FV: + case OP_MUL_VF: + case OP_DIV_F: + case OP_BITAND: + case OP_BITOR: + case OP_GE: + case OP_LE: + case OP_GT: + case OP_LT: + case OP_AND: + case OP_OR: + case OP_EQ_F: + case OP_EQ_V: + case OP_EQ_S: + case OP_EQ_E: + case OP_EQ_FNC: + case OP_NE_F: + case OP_NE_V: + case OP_NE_S: + case OP_NE_E: + case OP_NE_FNC: + case OP_ADDRESS: + case OP_LOAD_F: + case OP_LOAD_FLD: + case OP_LOAD_ENT: + case OP_LOAD_S: + case OP_LOAD_FNC: + case OP_LOAD_V: + if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals) + prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d)", i); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = remapglobal(b); + prog->statements[i].operand[2] = remapglobal(c); + prog->statements[i].jumpabsolute = -1; + break; + // global none global + case OP_NOT_F: + case OP_NOT_V: + case OP_NOT_S: + case OP_NOT_FNC: + case OP_NOT_ENT: + if (a >= prog->progs_numglobals || c >= prog->progs_numglobals) + prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = -1; + prog->statements[i].operand[2] = remapglobal(c); + prog->statements[i].jumpabsolute = -1; + break; + // 2 globals + case OP_STOREP_F: + case OP_STOREP_ENT: + case OP_STOREP_FLD: + case OP_STOREP_S: + case OP_STOREP_FNC: + case OP_STORE_F: + case OP_STORE_ENT: + case OP_STORE_FLD: + case OP_STORE_S: + case OP_STORE_FNC: + case OP_STATE: + case OP_STOREP_V: + case OP_STORE_V: + if (a >= prog->progs_numglobals || b >= prog->progs_numglobals) + prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = remapglobal(b); + prog->statements[i].operand[2] = -1; + prog->statements[i].jumpabsolute = -1; + break; + // 1 global + case OP_CALL0: + case OP_CALL1: + case OP_CALL2: + case OP_CALL3: + case OP_CALL4: + case OP_CALL5: + case OP_CALL6: + case OP_CALL7: + case OP_CALL8: + case OP_DONE: + case OP_RETURN: + if ( a >= prog->progs_numglobals) + prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name); + prog->statements[i].op = op; + prog->statements[i].operand[0] = remapglobal(a); + prog->statements[i].operand[1] = -1; + prog->statements[i].operand[2] = -1; + prog->statements[i].jumpabsolute = -1; + break; + } + } + if(prog->numstatements < 1) + { + prog->error_cmd("PRVM_LoadProgs: empty program in %s", prog->name); + } + else switch(prog->statements[prog->numstatements - 1].op) + { + case OP_RETURN: + case OP_GOTO: + case OP_DONE: + break; + default: + prog->error_cmd("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", prog->name); + break; + } + + // we're done with the file now + if(!data) + Mem_Free(dprograms); + dprograms = NULL; + + // check required functions + for(i=0 ; i < numrequiredfunc ; i++) + if(PRVM_ED_FindFunction(prog, required_func[i]) == 0) + prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename); + + PRVM_LoadLNO(prog, filename); + + PRVM_Init_Exec(prog); + + if(*prvm_language.string) + // in CSQC we really shouldn't be able to change how stuff works... sorry for now + // later idea: include a list of authorized .po file checksums with the csprogs + { + qboolean deftrans = prog == CLVM_prog; + const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string); + if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method! + { + for (i=0 ; inumglobaldefs ; i++) + { + const char *name; + name = PRVM_GetString(prog, prog->globaldefs[i].s_name); + if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) + if(name && !strncmp(name, "dotranslate_", 12)) + { + deftrans = false; + break; + } + } + } + if(!strcmp(prvm_language.string, "dump")) + { + qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false); + Con_Printf("Dumping to %s.pot\n", realfilename); + if(f) + { + for (i=0 ; inumglobaldefs ; i++) + { + const char *name; + name = PRVM_GetString(prog, prog->globaldefs[i].s_name); + if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12))) + if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) + { + prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); + const char *value = PRVM_GetString(prog, val->string); + if(*value) + { + char buf[MAX_INPUTLINE]; + PRVM_PO_UnparseString(buf, value, sizeof(buf)); + FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf); + } + } + } + FS_Close(f); + } + } + else + { + po_t *po = PRVM_PO_Load(va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string), prog->progs_mempool); + if(po) + { + for (i=0 ; inumglobaldefs ; i++) + { + const char *name; + name = PRVM_GetString(prog, prog->globaldefs[i].s_name); + if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12))) + if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) + { + prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); + const char *value = PRVM_GetString(prog, val->string); + if(*value) + { + value = PRVM_PO_Lookup(po, value); + if(value) + val->string = PRVM_SetEngineString(prog, value); + } + } + } + } + } + } + + for (i=0 ; inumglobaldefs ; i++) + { + const char *name; + name = PRVM_GetString(prog, prog->globaldefs[i].s_name); + //Con_Printf("found var %s\n", name); + if(name + && !strncmp(name, "autocvar_", 9) + && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) + ) + { + prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); + cvar_t *cvar = Cvar_FindVar(name + 9); + //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name); + if(!cvar) + { + const char *value; + char buf[64]; + Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, prog->name); + switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) + { + case ev_float: + if((float)((int)(val->_float)) == val->_float) + dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float)); + else + dpsnprintf(buf, sizeof(buf), "%.9g", val->_float); + value = buf; + break; + case ev_vector: + dpsnprintf(buf, sizeof(buf), "%.9g %.9g %.9g", val->vector[0], val->vector[1], val->vector[2]); value = buf; + break; + case ev_string: + value = PRVM_GetString(prog, val->string); + break; + default: + Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name); + goto fail; + } + cvar = Cvar_Get(name + 9, value, 0, NULL); + if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) + { + val->string = PRVM_SetEngineString(prog, cvar->string); + cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string; + } + if(!cvar) + prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name); + cvar->globaldefindex_progid[prog - prvm_prog_list] = prog->id; + cvar->globaldefindex[prog - prvm_prog_list] = i; + } + else if((cvar->flags & CVAR_PRIVATE) == 0) + { + // MUST BE SYNCED WITH cvar.c Cvar_Set + int j; + const char *s; + switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) + { + case ev_float: + val->_float = cvar->value; + break; + case ev_vector: + s = cvar->string; + VectorClear(val->vector); + for (j = 0;j < 3;j++) + { + while (*s && ISWHITESPACE(*s)) + s++; + if (!*s) + break; + val->vector[j] = atof(s); + while (!ISWHITESPACE(*s)) + s++; + if (!*s) + break; + } + break; + case ev_string: + val->string = PRVM_SetEngineString(prog, cvar->string); + cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string; + break; + default: + Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name); + goto fail; + } + cvar->globaldefindex_progid[prog - prvm_prog_list] = prog->id; + cvar->globaldefindex[prog - prvm_prog_list] = i; + } + else + Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, prog->name); + } +fail: + ; + } + + prog->loaded = TRUE; + + PRVM_UpdateBreakpoints(prog); + + // set flags & ddef_ts in prog + + prog->flag = 0; + + PRVM_FindOffsets(prog); + + prog->init_cmd(prog); + + // init mempools + PRVM_MEM_Alloc(prog); +} + + +static void PRVM_Fields_f (void) +{ + prvm_prog_t *prog; + int i, j, ednum, used, usedamount; + int *counts; + char tempstring[MAX_INPUTLINE], tempstring2[260]; + const char *name; + prvm_edict_t *ed; + ddef_t *d; + prvm_eval_t *val; + + // TODO + /* + if (!sv.active) + { + Con_Print("no progs loaded\n"); + return; + } + */ + + if(Cmd_Argc() != 2) + { + Con_Print("prvm_fields \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int)); + for (ednum = 0;ednum < prog->max_edicts;ednum++) + { + ed = PRVM_EDICT_NUM(ednum); + if (ed->priv.required->free) + continue; + for (i = 1;i < prog->numfielddefs;i++) + { + d = &prog->fielddefs[i]; + name = PRVM_GetString(prog, d->s_name); + if (name[strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars + val = (prvm_eval_t *)(ed->fields.fp + d->ofs); + // if the value is still all 0, skip the field + for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++) + { + if (val->ivector[j]) + { + counts[i]++; + break; + } + } + } + } + used = 0; + usedamount = 0; + tempstring[0] = 0; + for (i = 0;i < prog->numfielddefs;i++) + { + d = &prog->fielddefs[i]; + name = PRVM_GetString(prog, d->s_name); + if (name[strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars + switch(d->type & ~DEF_SAVEGLOBAL) + { + case ev_string: + strlcat(tempstring, "string ", sizeof(tempstring)); + break; + case ev_entity: + strlcat(tempstring, "entity ", sizeof(tempstring)); + break; + case ev_function: + strlcat(tempstring, "function ", sizeof(tempstring)); + break; + case ev_field: + strlcat(tempstring, "field ", sizeof(tempstring)); + break; + case ev_void: + strlcat(tempstring, "void ", sizeof(tempstring)); + break; + case ev_float: + strlcat(tempstring, "float ", sizeof(tempstring)); + break; + case ev_vector: + strlcat(tempstring, "vector ", sizeof(tempstring)); + break; + case ev_pointer: + strlcat(tempstring, "pointer ", sizeof(tempstring)); + break; + default: + dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL); + strlcat(tempstring, tempstring2, sizeof(tempstring)); + break; + } + if (strlen(name) > sizeof(tempstring2)-4) + { + memcpy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + strlcat(tempstring, name, sizeof(tempstring)); + for (j = (int)strlen(name);j < 25;j++) + strlcat(tempstring, " ", sizeof(tempstring)); + dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]); + strlcat(tempstring, tempstring2, sizeof(tempstring)); + strlcat(tempstring, "\n", sizeof(tempstring)); + if (strlen(tempstring) >= sizeof(tempstring)/2) + { + Con_Print(tempstring); + tempstring[0] = 0; + } + if (counts[i]) + { + used++; + usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL]; + } + } + Mem_Free(counts); + Con_Printf("%s: %i entity fields (%i in use), totalling %i bytes per edict (%i in use), %i edicts allocated, %i bytes total spent on edict fields (%i needed)\n", prog->name, prog->entityfields, used, prog->entityfields * 4, usedamount * 4, prog->max_edicts, prog->entityfields * 4 * prog->max_edicts, usedamount * 4 * prog->max_edicts); +} + +static void PRVM_Globals_f (void) +{ + prvm_prog_t *prog; + int i; + const char *wildcard; + int numculled; + numculled = 0; + // TODO + /*if (!sv.active) + { + Con_Print("no progs loaded\n"); + return; + }*/ + if(Cmd_Argc () < 2 || Cmd_Argc() > 3) + { + Con_Print("prvm_globals \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + if( Cmd_Argc() == 3) + wildcard = Cmd_Argv(2); + else + wildcard = NULL; + + Con_Printf("%s :", prog->name); + + for (i = 0;i < prog->numglobaldefs;i++) + { + if(wildcard) + if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) ) + { + numculled++; + continue; + } + Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name)); + } + Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4); +} + +/* +=============== +PRVM_Global +=============== +*/ +static void PRVM_Global_f(void) +{ + prvm_prog_t *prog; + ddef_t *global; + char valuebuf[MAX_INPUTLINE]; + if( Cmd_Argc() != 3 ) { + Con_Printf( "prvm_global \n" ); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + global = PRVM_ED_FindGlobal( prog, Cmd_Argv(2) ); + if( !global ) + Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); + else + Con_Printf( "%s: %s\n", Cmd_Argv(2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) ); +} + +/* +=============== +PRVM_GlobalSet +=============== +*/ +static void PRVM_GlobalSet_f(void) +{ + prvm_prog_t *prog; + ddef_t *global; + if( Cmd_Argc() != 4 ) { + Con_Printf( "prvm_globalset \n" ); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + global = PRVM_ED_FindGlobal( prog, Cmd_Argv(2) ); + if( !global ) + Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); + else + PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(3), true ); +} + +/* +====================== +Break- and Watchpoints +====================== +*/ +typedef struct +{ + char break_statement[256]; + char watch_global[256]; + int watch_edict; + char watch_field[256]; +} +debug_data_t; +static debug_data_t debug_data[PRVM_PROG_MAX]; + +void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text) +{ + char vabuf[1024]; + Con_Printf("PRVM_Breakpoint: %s\n", text); + PRVM_PrintState(prog, stack_index); + if (prvm_breakpointdump.integer) + Host_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name)); +} + +void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n) +{ + size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1); + if (memcmp(o, n, sz)) + { + char buf[1024]; + char valuebuf_o[128]; + char valuebuf_n[128]; + PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o)); + PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n)); + dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n); + PRVM_Breakpoint(prog, stack_index, buf); + memcpy(o, n, sz); + } +} + +static void PRVM_UpdateBreakpoints(prvm_prog_t *prog) +{ + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + if (!prog->loaded) + return; + if (debug->break_statement[0]) + { + if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9') + { + prog->break_statement = atoi(debug->break_statement); + prog->break_stack_index = 0; + } + else + { + mfunction_t *func; + func = PRVM_ED_FindFunction (prog, debug->break_statement); + if (!func) + { + Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement); + prog->break_statement = -1; + } + else + { + prog->break_statement = func->first_statement; + prog->break_stack_index = 1; + } + } + if (prog->break_statement >= -1) + Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement); + } + else + prog->break_statement = -1; + + if (debug->watch_global[0]) + { + ddef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global ); + if( !global ) + { + Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global ); + prog->watch_global_type = ev_void; + } + else + { + size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1); + prog->watch_global = global->ofs; + prog->watch_global_type = (etype_t)global->type; + memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz); + } + if (prog->watch_global_type != ev_void) + Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global); + } + else + prog->watch_global_type = ev_void; + + if (debug->watch_field[0]) + { + ddef_t *field = PRVM_ED_FindField( prog, debug->watch_field ); + if( !field ) + { + Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field ); + prog->watch_field_type = ev_void; + } + else + { + size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1); + prog->watch_edict = debug->watch_edict; + prog->watch_field = field->ofs; + prog->watch_field_type = (etype_t)field->type; + if (prog->watch_edict < prog->num_edicts) + memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz); + else + memset(&prog->watch_edictfield_value, 0, sz); + } + if (prog->watch_edict != ev_void) + Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field); + } + else + prog->watch_field_type = ev_void; +} + +static void PRVM_Breakpoint_f(void) +{ + prvm_prog_t *prog; + + if( Cmd_Argc() == 2 ) { + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + debug->break_statement[0] = 0; + } + PRVM_UpdateBreakpoints(prog); + return; + } + if( Cmd_Argc() != 3 ) { + Con_Printf( "prvm_breakpoint \n" ); + return; + } + + if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) + return; + + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + strlcpy(debug->break_statement, Cmd_Argv(2), sizeof(debug->break_statement)); + } + PRVM_UpdateBreakpoints(prog); +} + +static void PRVM_GlobalWatchpoint_f(void) +{ + prvm_prog_t *prog; + + if( Cmd_Argc() == 2 ) { + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + debug->watch_global[0] = 0; + } + PRVM_UpdateBreakpoints(prog); + return; + } + if( Cmd_Argc() != 3 ) { + Con_Printf( "prvm_globalwatchpoint \n" ); + return; + } + + if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) + return; + + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + strlcpy(debug->watch_global, Cmd_Argv(2), sizeof(debug->watch_global)); + } + PRVM_UpdateBreakpoints(prog); +} + +static void PRVM_EdictWatchpoint_f(void) +{ + prvm_prog_t *prog; + + if( Cmd_Argc() == 2 ) { + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + debug->watch_field[0] = 0; + } + PRVM_UpdateBreakpoints(prog); + return; + } + if( Cmd_Argc() != 4 ) { + Con_Printf( "prvm_edictwatchpoint \n" ); + return; + } + + if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) + return; + + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + debug->watch_edict = atoi(Cmd_Argv(2)); + strlcpy(debug->watch_field, Cmd_Argv(3), sizeof(debug->watch_field)); + } + PRVM_UpdateBreakpoints(prog); +} + +/* +=============== +PRVM_Init +=============== +*/ +void PRVM_Init (void) +{ + Cmd_AddCommand ("prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_profile", PRVM_Profile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_childprofile", PRVM_ChildProfile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu), sorted by time taken in function with child calls"); + Cmd_AddCommand ("prvm_callprofile", PRVM_CallProfile_f, "prints execution statistics about the most time consuming QuakeC calls from the engine in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_fields", PRVM_Fields_f, "prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_edictset", PRVM_ED_EdictSet_f, "changes value of a specified property of a specified entity in the selected VM (server, client, menu)"); + Cmd_AddCommand ("prvm_edictget", PRVM_ED_EdictGet_f, "retrieves the value of a specified property of a specified entity in the selected VM (server, client menu) into a cvar or to the console"); + Cmd_AddCommand ("prvm_globalget", PRVM_ED_GlobalGet_f, "retrieves the value of a specified global variable in the selected VM (server, client menu) into a cvar or to the console"); + Cmd_AddCommand ("prvm_printfunction", PRVM_PrintFunction_f, "prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, menu)"); + Cmd_AddCommand ("cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument"); + Cmd_AddCommand ("menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument"); + Cmd_AddCommand ("sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument"); + + Cmd_AddCommand ("prvm_breakpoint", PRVM_Breakpoint_f, "marks a statement or function as breakpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear breakpoint"); + Cmd_AddCommand ("prvm_globalwatchpoint", PRVM_GlobalWatchpoint_f, "marks a global as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint"); + Cmd_AddCommand ("prvm_edictwatchpoint", PRVM_EdictWatchpoint_f, "marks an entity field as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint"); + + Cvar_RegisterVariable (&prvm_language); + Cvar_RegisterVariable (&prvm_traceqc); + Cvar_RegisterVariable (&prvm_statementprofiling); + Cvar_RegisterVariable (&prvm_timeprofiling); + Cvar_RegisterVariable (&prvm_backtraceforwarnings); + Cvar_RegisterVariable (&prvm_leaktest); + Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames); + Cvar_RegisterVariable (&prvm_errordump); + Cvar_RegisterVariable (&prvm_breakpointdump); + Cvar_RegisterVariable (&prvm_reuseedicts_startuptime); + Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe); + + // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!) + prvm_runawaycheck = !COM_CheckParm("-norunaway"); + + //VM_Cmd_Init(); +} + +/* +=============== +PRVM_InitProg +=============== +*/ +void PRVM_Prog_Init(prvm_prog_t *prog) +{ + PRVM_Prog_Reset(prog); + prog->leaktest_active = prvm_leaktest.integer != 0; +} + +// LordHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons +unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline) +{ + prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline); + return 0; +} + +#define PRVM_KNOWNSTRINGBASE 0x40000000 + +const char *PRVM_GetString(prvm_prog_t *prog, int num) +{ + if (num < 0) + { + // invalid + VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num); + return ""; + } + else if (num < prog->stringssize) + { + // constant string from progs.dat + return prog->strings + num; + } + else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize) + { + // tempstring returned by engine to QC (becomes invalid after returning to engine) + num -= prog->stringssize; + if (num < prog->tempstringsbuf.cursize) + return (char *)prog->tempstringsbuf.data + num; + else + { + VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize); + return ""; + } + } + else if (num & PRVM_KNOWNSTRINGBASE) + { + // allocated string + num = num - PRVM_KNOWNSTRINGBASE; + if (num >= 0 && num < prog->numknownstrings) + { + if (!prog->knownstrings[num]) + { + VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num); + return ""; + } + return prog->knownstrings[num]; + } + else + { + VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings); + return ""; + } + } + else + { + // invalid string offset + VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize); + return ""; + } +} + +const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s) +{ + const char *old; + i = i - PRVM_KNOWNSTRINGBASE; + if(i < 0 || i >= prog->numknownstrings) + prog->error_cmd("PRVM_ChangeEngineString: s is not an engine string"); + old = prog->knownstrings[i]; + prog->knownstrings[i] = s; + return old; +} + +int PRVM_SetEngineString(prvm_prog_t *prog, const char *s) +{ + int i; + if (!s) + return 0; + if (s >= prog->strings && s <= prog->strings + prog->stringssize) + prog->error_cmd("PRVM_SetEngineString: s in prog->strings area"); + // if it's in the tempstrings area, use a reserved range + // (otherwise we'd get millions of useless string offsets cluttering the database) + if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize) + return prog->stringssize + (s - (char *)prog->tempstringsbuf.data); + // see if it's a known string address + for (i = 0;i < prog->numknownstrings;i++) + if (prog->knownstrings[i] == s) + return PRVM_KNOWNSTRINGBASE + i; + // new unknown engine string + if (developer_insane.integer) + Con_DPrintf("new engine string %p = \"%s\"\n", s, s); + for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) + if (!prog->knownstrings[i]) + break; + if (i >= prog->numknownstrings) + { + if (i >= prog->maxknownstrings) + { + const char **oldstrings = prog->knownstrings; + const unsigned char *oldstrings_freeable = prog->knownstrings_freeable; + const char **oldstrings_origin = prog->knownstrings_origin; + prog->maxknownstrings += 128; + prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); + prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); + if(prog->leaktest_active) + prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); + if (prog->numknownstrings) + { + memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *)); + memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char)); + if(prog->leaktest_active) + memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *)); + } + } + prog->numknownstrings++; + } + prog->firstfreeknownstring = i + 1; + prog->knownstrings[i] = s; + prog->knownstrings_freeable[i] = false; + if(prog->leaktest_active) + prog->knownstrings_origin[i] = NULL; + return PRVM_KNOWNSTRINGBASE + i; +} + +// temp string handling + +// all tempstrings go into this buffer consecutively, and it is reset +// whenever PRVM_ExecuteProgram returns to the engine +// (technically each PRVM_ExecuteProgram call saves the cursize value and +// restores it on return, so multiple recursive calls can share the same +// buffer) +// the buffer size is automatically grown as needed + +int PRVM_SetTempString(prvm_prog_t *prog, const char *s) +{ + int size; + char *t; + if (!s) + return 0; + size = (int)strlen(s) + 1; + if (developer_insane.integer) + Con_DPrintf("PRVM_SetTempString: cursize %i, size %i\n", prog->tempstringsbuf.cursize, size); + if (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size) + { + sizebuf_t old = prog->tempstringsbuf; + if (prog->tempstringsbuf.cursize + size >= 1<<28) + prog->error_cmd("PRVM_SetTempString: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", prog->tempstringsbuf.cursize, size); + prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536); + while (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size) + prog->tempstringsbuf.maxsize *= 2; + if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL) + { + Con_DPrintf("PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old.maxsize/1024, prog->tempstringsbuf.maxsize/1024); + prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize); + if (old.cursize) + memcpy(prog->tempstringsbuf.data, old.data, old.cursize); + if (old.data) + Mem_Free(old.data); + } + } + t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize; + memcpy(t, s, size); + prog->tempstringsbuf.cursize += size; + return PRVM_SetEngineString(prog, t); +} + +int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer) +{ + int i; + if (!bufferlength) + return 0; + for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) + if (!prog->knownstrings[i]) + break; + if (i >= prog->numknownstrings) + { + if (i >= prog->maxknownstrings) + { + const char **oldstrings = prog->knownstrings; + const unsigned char *oldstrings_freeable = prog->knownstrings_freeable; + const char **oldstrings_origin = prog->knownstrings_origin; + prog->maxknownstrings += 128; + prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); + prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); + if(prog->leaktest_active) + prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); + if (prog->numknownstrings) + { + memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *)); + memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char)); + if(prog->leaktest_active) + memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *)); + } + if (oldstrings) + Mem_Free((char **)oldstrings); + if (oldstrings_freeable) + Mem_Free((unsigned char *)oldstrings_freeable); + if (oldstrings_origin) + Mem_Free((char **)oldstrings_origin); + } + prog->numknownstrings++; + } + prog->firstfreeknownstring = i + 1; + prog->knownstrings[i] = (char *)PRVM_Alloc(bufferlength); + prog->knownstrings_freeable[i] = true; + if(prog->leaktest_active) + prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog); + if (pointer) + *pointer = (char *)(prog->knownstrings[i]); + return PRVM_KNOWNSTRINGBASE + i; +} + +void PRVM_FreeString(prvm_prog_t *prog, int num) +{ + if (num == 0) + prog->error_cmd("PRVM_FreeString: attempt to free a NULL string"); + else if (num >= 0 && num < prog->stringssize) + prog->error_cmd("PRVM_FreeString: attempt to free a constant string"); + else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings) + { + num = num - PRVM_KNOWNSTRINGBASE; + if (!prog->knownstrings[num]) + prog->error_cmd("PRVM_FreeString: attempt to free a non-existent or already freed string"); + if (!prog->knownstrings_freeable[num]) + prog->error_cmd("PRVM_FreeString: attempt to free a string owned by the engine"); + PRVM_Free((char *)prog->knownstrings[num]); + if(prog->leaktest_active) + if(prog->knownstrings_origin[num]) + PRVM_Free((char *)prog->knownstrings_origin[num]); + prog->knownstrings[num] = NULL; + prog->knownstrings_freeable[num] = false; + prog->firstfreeknownstring = min(prog->firstfreeknownstring, num); + } + else + prog->error_cmd("PRVM_FreeString: invalid string offset %i", num); +} + +static qboolean PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string) +{ + int i, j; + + for (i = 0;i < prog->numglobaldefs;i++) + { + ddef_t *d = &prog->globaldefs[i]; + if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string) + continue; + if(string == PRVM_GLOBALFIELDSTRING(d->ofs)) + return true; + } + + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if (ed->priv.required->free) + continue; + for (i=0; inumfielddefs; ++i) + { + ddef_t *d = &prog->fielddefs[i]; + if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string) + continue; + if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs)) + return true; + } + } + + return false; +} + +static qboolean PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict) +{ + char vabuf[1024]; + char vabuf2[1024]; + if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts) + return true; // world or clients + if (prog == SVVM_prog) + { + if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger? + return true; + if(PRVM_serveredictfloat(edict, modelindex)) // visible ent? + return true; + if(PRVM_serveredictfloat(edict, effects)) // particle effect? + return true; + if(PRVM_serveredictfunction(edict, think)) // has a think function? + if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run? + return true; + if(PRVM_serveredictfloat(edict, takedamage)) + return true; + if(*prvm_leaktest_ignore_classnames.string) + { + if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname))))) + return true; + } + } + else if (prog == CLVM_prog) + { + // TODO someone add more stuff here + if(PRVM_clientedictfloat(edict, entnum)) // csqc networked + return true; + if(PRVM_clientedictfloat(edict, modelindex)) // visible ent? + return true; + if(PRVM_clientedictfloat(edict, effects)) // particle effect? + return true; + if(PRVM_clientedictfunction(edict, think)) // has a think function? + if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run? + return true; + if(*prvm_leaktest_ignore_classnames.string) + { + if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname))))) + return true; + } + } + else + { + // menu prog does not have classnames + } + return false; +} + +static qboolean PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark) +{ + int i, j; + int edictnum = PRVM_NUM_FOR_EDICT(edict); + const char *targetname = NULL; + + if (prog == SVVM_prog) + targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname)); + + if(targetname) + if(!*targetname) // "" + targetname = NULL; + + if(mark == 0) + { + for (i = 0;i < prog->numglobaldefs;i++) + { + ddef_t *d = &prog->globaldefs[i]; + if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity) + continue; + if(edictnum == PRVM_GLOBALFIELDEDICT(d->ofs)) + return true; + } + } + + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if (ed->priv.required->mark < mark) + continue; + if(ed == edict) + continue; + if(targetname) + { + const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target)); + if(target) + if(!strcmp(target, targetname)) + return true; + } + for (i=0; inumfielddefs; ++i) + { + ddef_t *d = &prog->fielddefs[i]; + if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity) + continue; + if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs)) + return true; + } + } + + return false; +} + +static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog) +{ + int j; + qboolean found_new; + int stage; + + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if(ed->priv.required->free) + continue; + ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? 1 : 0; + } + + stage = 1; + do + { + found_new = false; + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if(ed->priv.required->free) + continue; + if(ed->priv.required->mark) + continue; + if(PRVM_IsEdictReferenced(prog, ed, stage)) + { + ed->priv.required->mark = stage + 1; + found_new = true; + } + } + ++stage; + } + while(found_new); + Con_DPrintf("leak check used %d stages to find all references\n", stage); +} + +void PRVM_LeakTest(prvm_prog_t *prog) +{ + int i, j; + qboolean leaked = false; + + if(!prog->leaktest_active) + return; + + // 1. Strings + for (i = 0; i < prog->numknownstrings; ++i) + { + if(prog->knownstrings[i]) + if(prog->knownstrings_freeable[i]) + if(prog->knownstrings_origin[i]) + if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i)) + { + Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]); + leaked = true; + } + } + + // 2. Edicts + PRVM_MarkReferencedEdicts(prog); + for(j = 0; j < prog->num_edicts; ++j) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(j); + if(ed->priv.required->free) + continue; + if(!ed->priv.required->mark) + if(ed->priv.required->allocation_origin) + { + Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin); + PRVM_ED_Print(prog, ed, NULL); + Con_Print("\n"); + leaked = true; + } + + ed->priv.required->mark = 0; // clear marks again when done + } + + for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i) + { + prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); + if(stringbuffer) + if(stringbuffer->origin) + { + Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin); + leaked = true; + } + } + + for(i = 0; i < PRVM_MAX_OPENFILES; ++i) + { + if(prog->openfiles[i]) + if(prog->openfiles_origin[i]) + { + Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]); + leaked = true; + } + } + + for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i) + { + if(prog->opensearches[i]) + if(prog->opensearches_origin[i]) + { + Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]); + leaked = true; + } + } + + if(!leaked) + Con_Printf("Congratulations. No leaks found.\n"); +} diff --git a/app/jni/prvm_exec.c b/app/jni/prvm_exec.c new file mode 100644 index 0000000..823c2d3 --- /dev/null +++ b/app/jni/prvm_exec.c @@ -0,0 +1,983 @@ +/* +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 "progsvm.h" + +const char *prvm_opnames[] = +{ +"^5DONE", + +"MUL_F", +"MUL_V", +"MUL_FV", +"MUL_VF", + +"DIV", + +"ADD_F", +"ADD_V", + +"SUB_F", +"SUB_V", + +"^2EQ_F", +"^2EQ_V", +"^2EQ_S", +"^2EQ_E", +"^2EQ_FNC", + +"^2NE_F", +"^2NE_V", +"^2NE_S", +"^2NE_E", +"^2NE_FNC", + +"^2LE", +"^2GE", +"^2LT", +"^2GT", + +"^6FIELD_F", +"^6FIELD_V", +"^6FIELD_S", +"^6FIELD_ENT", +"^6FIELD_FLD", +"^6FIELD_FNC", + +"^1ADDRESS", + +"STORE_F", +"STORE_V", +"STORE_S", +"STORE_ENT", +"STORE_FLD", +"STORE_FNC", + +"^1STOREP_F", +"^1STOREP_V", +"^1STOREP_S", +"^1STOREP_ENT", +"^1STOREP_FLD", +"^1STOREP_FNC", + +"^5RETURN", + +"^2NOT_F", +"^2NOT_V", +"^2NOT_S", +"^2NOT_ENT", +"^2NOT_FNC", + +"^5IF", +"^5IFNOT", + +"^3CALL0", +"^3CALL1", +"^3CALL2", +"^3CALL3", +"^3CALL4", +"^3CALL5", +"^3CALL6", +"^3CALL7", +"^3CALL8", + +"^1STATE", + +"^5GOTO", + +"^2AND", +"^2OR", + +"BITAND", +"BITOR" +}; + + + +//============================================================================= + +/* +================= +PRVM_PrintStatement +================= +*/ +extern cvar_t prvm_statementprofiling; +extern cvar_t prvm_timeprofiling; +static void PRVM_PrintStatement(prvm_prog_t *prog, mstatement_t *s) +{ + size_t i; + int opnum = (int)(s - prog->statements); + char valuebuf[MAX_INPUTLINE]; + + Con_Printf("s%i: ", opnum); + if( prog->statement_linenums ) + Con_Printf( "%s:%i: ", PRVM_GetString( prog, prog->xfunction->s_file ), prog->statement_linenums[ opnum ] ); + + if (prvm_statementprofiling.integer) + Con_Printf("%7.0f ", prog->statement_profile[s - prog->statements]); + + if ( (unsigned)s->op < sizeof(prvm_opnames)/sizeof(prvm_opnames[0])) + { + Con_Printf("%s ", prvm_opnames[s->op]); + i = strlen(prvm_opnames[s->op]); + // don't count a preceding color tag when padding the name + if (prvm_opnames[s->op][0] == STRING_COLOR_TAG) + i -= 2; + for ( ; i<10 ; i++) + Con_Print(" "); + } + if (s->operand[0] >= 0) Con_Printf( "%s", PRVM_GlobalString(prog, s->operand[0], valuebuf, sizeof(valuebuf))); + if (s->operand[1] >= 0) Con_Printf(", %s", PRVM_GlobalString(prog, s->operand[1], valuebuf, sizeof(valuebuf))); + if (s->operand[2] >= 0) Con_Printf(", %s", PRVM_GlobalString(prog, s->operand[2], valuebuf, sizeof(valuebuf))); + if (s->jumpabsolute >= 0) Con_Printf(", statement %i", s->jumpabsolute); + Con_Print("\n"); +} + +void PRVM_PrintFunctionStatements (prvm_prog_t *prog, const char *name) +{ + int i, firststatement, endstatement; + mfunction_t *func; + func = PRVM_ED_FindFunction (prog, name); + if (!func) + { + Con_Printf("%s progs: no function named %s\n", prog->name, name); + return; + } + firststatement = func->first_statement; + if (firststatement < 0) + { + Con_Printf("%s progs: function %s is builtin #%i\n", prog->name, name, -firststatement); + return; + } + + // find the end statement + endstatement = prog->numstatements; + for (i = 0;i < prog->numfunctions;i++) + if (endstatement > prog->functions[i].first_statement && firststatement < prog->functions[i].first_statement) + endstatement = prog->functions[i].first_statement; + + // now print the range of statements + Con_Printf("%s progs: disassembly of function %s (statements %i-%i, locals %i-%i):\n", prog->name, name, firststatement, endstatement, func->parm_start, func->parm_start + func->locals - 1); + prog->xfunction = func; + for (i = firststatement;i < endstatement;i++) + { + PRVM_PrintStatement(prog, prog->statements + i); + prog->statement_profile[i] = 0; + } +} + +/* +============ +PRVM_PrintFunction_f + +============ +*/ +void PRVM_PrintFunction_f (void) +{ + prvm_prog_t *prog; + if (Cmd_Argc() != 3) + { + Con_Printf("usage: prvm_printfunction \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + PRVM_PrintFunctionStatements(prog, Cmd_Argv(2)); +} + +/* +============ +PRVM_StackTrace +============ +*/ +void PRVM_StackTrace (prvm_prog_t *prog) +{ + mfunction_t *f; + int i; + + prog->stack[prog->depth].s = prog->xstatement; + prog->stack[prog->depth].f = prog->xfunction; + for (i = prog->depth;i > 0;i--) + { + f = prog->stack[i].f; + + if (!f) + Con_Print("\n"); + else + { + if (prog->statement_linenums) + Con_Printf("%12s:%i : %s : statement %i\n", PRVM_GetString(prog, f->s_file), prog->statement_linenums[prog->stack[i].s], PRVM_GetString(prog, f->s_name), prog->stack[i].s - f->first_statement); + else + Con_Printf("%12s : %s : statement %i\n", PRVM_GetString(prog, f->s_file), PRVM_GetString(prog, f->s_name), prog->stack[i].s - f->first_statement); + } + } +} + +void PRVM_ShortStackTrace(prvm_prog_t *prog, char *buf, size_t bufsize) +{ + mfunction_t *f; + int i; + char vabuf[1024]; + + if(prog) + { + dpsnprintf(buf, bufsize, "(%s) ", prog->name); + } + else + { + strlcpy(buf, "", bufsize); + return; + } + + prog->stack[prog->depth].s = prog->xstatement; + prog->stack[prog->depth].f = prog->xfunction; + for (i = prog->depth;i > 0;i--) + { + f = prog->stack[i].f; + + if(strlcat(buf, + f + ? va(vabuf, sizeof(vabuf), "%s:%s(%i) ", PRVM_GetString(prog, f->s_file), PRVM_GetString(prog, f->s_name), prog->stack[i].s - f->first_statement) + : " ", + bufsize + ) >= bufsize) + break; + } +} + + +static void PRVM_CallProfile (prvm_prog_t *prog) +{ + mfunction_t *f, *best; + int i; + double max; + double sum; + double newprofiletime; + + Con_Printf( "%s Call Profile:\n", prog->name ); + + sum = 0; + do + { + max = 0; + best = NULL; + for (i=0 ; inumfunctions ; i++) + { + f = &prog->functions[i]; + if (max < f->totaltime) + { + max = f->totaltime; + best = f; + } + } + if (best) + { + sum += best->totaltime; + Con_Printf("%9.4f %s\n", best->totaltime, PRVM_GetString(prog, best->s_name)); + best->totaltime = 0; + } + } while (best); + + newprofiletime = Sys_DirtyTime(); + Con_Printf("Total time since last profile reset: %9.4f\n", newprofiletime - prog->profiletime); + Con_Printf(" - used by QC code of this VM: %9.4f\n", sum); + + prog->profiletime = newprofiletime; +} + +void PRVM_Profile (prvm_prog_t *prog, int maxfunctions, double mintime, int sortby) +{ + mfunction_t *f, *best; + int i, num; + double max; + + if(!prvm_timeprofiling.integer) + mintime *= 10000000; // count each statement as about 0.1µs + + if(prvm_timeprofiling.integer) + Con_Printf( "%s Profile:\n[CallCount] [Time] [BuiltinTm] [Statement] [BuiltinCt] [TimeTotal] [StmtTotal] [BltnTotal] [self]\n", prog->name ); + // 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 123.45% + else + Con_Printf( "%s Profile:\n[CallCount] [Statement] [BuiltinCt] [StmtTotal] [BltnTotal] [self]\n", prog->name ); + // 12345678901 12345678901 12345678901 12345678901 12345678901 123.45% + + num = 0; + do + { + max = 0; + best = NULL; + for (i=0 ; inumfunctions ; i++) + { + f = &prog->functions[i]; + if(prvm_timeprofiling.integer) + { + if(sortby) + { + if(f->first_statement < 0) + { + if (max < f->tprofile) + { + max = f->tprofile; + best = f; + } + } + else + { + if (max < f->tprofile_total) + { + max = f->tprofile_total; + best = f; + } + } + } + else + { + if (max < f->tprofile + f->tbprofile) + { + max = f->tprofile + f->tbprofile; + best = f; + } + } + } + else + { + if(sortby) + { + if (max < f->profile_total + f->builtinsprofile_total + f->callcount) + { + max = f->profile_total + f->builtinsprofile_total + f->callcount; + best = f; + } + } + else + { + if (max < f->profile + f->builtinsprofile + f->callcount) + { + max = f->profile + f->builtinsprofile + f->callcount; + best = f; + } + } + } + } + if (best) + { + if (num < maxfunctions && max > mintime) + { + if(prvm_timeprofiling.integer) + { + if (best->first_statement < 0) + Con_Printf("%11.0f %11.6f ------------- builtin ------------- %11.6f ----------- builtin ----------- %s\n", best->callcount, best->tprofile, best->tprofile, PRVM_GetString(prog, best->s_name)); + // %11.6f 12345678901 12345678901 12345678901 %11.6f 12345678901 12345678901 123.45% + else + Con_Printf("%11.0f %11.6f %11.6f %11.0f %11.0f %11.6f %11.0f %11.0f %6.2f%% %s\n", best->callcount, best->tprofile, best->tbprofile, best->profile, best->builtinsprofile, best->tprofile_total, best->profile_total, best->builtinsprofile_total, (best->tprofile_total > 0) ? ((best->tprofile) * 100.0 / (best->tprofile_total)) : -99.99, PRVM_GetString(prog, best->s_name)); + } + else + { + if (best->first_statement < 0) + Con_Printf("%11.0f ----------------------- builtin ----------------------- %s\n", best->callcount, PRVM_GetString(prog, best->s_name)); + // 12345678901 12345678901 12345678901 12345678901 123.45% + else + Con_Printf("%11.0f %11.0f %11.0f %11.0f %11.0f %6.2f%% %s\n", best->callcount, best->profile, best->builtinsprofile, best->profile_total, best->builtinsprofile_total, (best->profile + best->builtinsprofile) * 100.0 / (best->profile_total + best->builtinsprofile_total), PRVM_GetString(prog, best->s_name)); + } + } + num++; + best->profile = 0; + best->tprofile = 0; + best->tbprofile = 0; + best->builtinsprofile = 0; + best->profile_total = 0; + best->tprofile_total = 0; + best->builtinsprofile_total = 0; + best->callcount = 0; + } + } while (best); +} + +/* +============ +PRVM_CallProfile_f + +============ +*/ +void PRVM_CallProfile_f (void) +{ + prvm_prog_t *prog; + if (Cmd_Argc() != 2) + { + Con_Print("prvm_callprofile \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + PRVM_CallProfile(prog); +} + +/* +============ +PRVM_Profile_f + +============ +*/ +void PRVM_Profile_f (void) +{ + prvm_prog_t *prog; + int howmany; + + howmany = 1<<30; + if (Cmd_Argc() == 3) + howmany = atoi(Cmd_Argv(2)); + else if (Cmd_Argc() != 2) + { + Con_Print("prvm_profile \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + PRVM_Profile(prog, howmany, 0, 0); +} + +void PRVM_ChildProfile_f (void) +{ + prvm_prog_t *prog; + int howmany; + + howmany = 1<<30; + if (Cmd_Argc() == 3) + howmany = atoi(Cmd_Argv(2)); + else if (Cmd_Argc() != 2) + { + Con_Print("prvm_childprofile \n"); + return; + } + + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + + PRVM_Profile(prog, howmany, 0, 1); +} + +void PRVM_PrintState(prvm_prog_t *prog, int stack_index) +{ + int i; + mfunction_t *func = prog->xfunction; + int st = prog->xstatement; + if (stack_index > 0 && stack_index <= prog->depth) + { + func = prog->stack[prog->depth - stack_index].f; + st = prog->stack[prog->depth - stack_index].s; + } + if (prog->statestring) + { + Con_Printf("Caller-provided information: %s\n", prog->statestring); + } + if (func) + { + for (i = -7; i <= 0;i++) + if (st + i >= func->first_statement) + PRVM_PrintStatement(prog, prog->statements + st + i); + } + PRVM_StackTrace(prog); +} + +extern cvar_t prvm_errordump; +void PRVM_Crash(prvm_prog_t *prog) +{ + char vabuf[1024]; + if (prog == NULL) + return; + if (!prog->loaded) + return; + + PRVM_serverfunction(SV_Shutdown) = 0; // don't call SV_Shutdown on crash + + if( prog->depth > 0 ) + { + Con_Printf("QuakeC crash report for %s:\n", prog->name); + PRVM_PrintState(prog, 0); + } + + if(prvm_errordump.integer) + { + // make a savegame + Host_Savegame_to(prog, va(vabuf, sizeof(vabuf), "crash-%s.dmp", prog->name)); + } + + // dump the stack so host_error can shutdown functions + prog->depth = 0; + prog->localstack_used = 0; + + // delete all tempstrings (FIXME: is this safe in VM->engine->VM recursion?) + prog->tempstringsbuf.cursize = 0; + + // reset the prog pointer + prog = NULL; +} + +/* +============================================================================ +PRVM_ExecuteProgram + +The interpretation main loop +============================================================================ +*/ + +/* +==================== +PRVM_EnterFunction + +Returns the new program statement counter +==================== +*/ +static int PRVM_EnterFunction (prvm_prog_t *prog, mfunction_t *f) +{ + int i, j, c, o; + + if (!f) + prog->error_cmd("PRVM_EnterFunction: NULL function in %s", prog->name); + + prog->stack[prog->depth].s = prog->xstatement; + prog->stack[prog->depth].f = prog->xfunction; + prog->stack[prog->depth].profile_acc = -f->profile; + prog->stack[prog->depth].tprofile_acc = -f->tprofile + -f->tbprofile; + prog->stack[prog->depth].builtinsprofile_acc = -f->builtinsprofile; + prog->depth++; + if (prog->depth >=PRVM_MAX_STACK_DEPTH) + prog->error_cmd("stack overflow"); + +// save off any locals that the new function steps on + c = f->locals; + if (prog->localstack_used + c > PRVM_LOCALSTACK_SIZE) + prog->error_cmd("PRVM_ExecuteProgram: locals stack overflow in %s", prog->name); + + for (i=0 ; i < c ; i++) + prog->localstack[prog->localstack_used+i] = prog->globals.ip[f->parm_start + i]; + prog->localstack_used += c; + +// copy parameters + o = f->parm_start; + for (i=0 ; inumparms ; i++) + { + for (j=0 ; jparm_size[i] ; j++) + { + prog->globals.ip[o] = prog->globals.ip[OFS_PARM0+i*3+j]; + o++; + } + } + + ++f->recursion; + prog->xfunction = f; + return f->first_statement - 1; // offset the s++ +} + +/* +==================== +PRVM_LeaveFunction +==================== +*/ +static int PRVM_LeaveFunction (prvm_prog_t *prog) +{ + int i, c; + mfunction_t *f; + + if (prog->depth <= 0) + prog->error_cmd("prog stack underflow in %s", prog->name); + + if (!prog->xfunction) + prog->error_cmd("PR_LeaveFunction: NULL function in %s", prog->name); +// restore locals from the stack + c = prog->xfunction->locals; + prog->localstack_used -= c; + if (prog->localstack_used < 0) + prog->error_cmd("PRVM_ExecuteProgram: locals stack underflow in %s", prog->name); + + for (i=0 ; i < c ; i++) + prog->globals.ip[prog->xfunction->parm_start + i] = prog->localstack[prog->localstack_used+i]; + +// up stack + prog->depth--; + f = prog->xfunction; + --f->recursion; + prog->xfunction = prog->stack[prog->depth].f; + prog->stack[prog->depth].profile_acc += f->profile; + prog->stack[prog->depth].tprofile_acc += f->tprofile + f->tbprofile; + prog->stack[prog->depth].builtinsprofile_acc += f->builtinsprofile; + if(prog->depth > 0) + { + prog->stack[prog->depth-1].profile_acc += prog->stack[prog->depth].profile_acc; + prog->stack[prog->depth-1].tprofile_acc += prog->stack[prog->depth].tprofile_acc; + prog->stack[prog->depth-1].builtinsprofile_acc += prog->stack[prog->depth].builtinsprofile_acc; + } + if(!f->recursion) + { + // if f is already on the call stack... + // we cannot add this profile data to it now + // or we would add it more than once + // so, let's only add to the function's profile if it is the outermost call + f->profile_total += prog->stack[prog->depth].profile_acc; + f->tprofile_total += prog->stack[prog->depth].tprofile_acc; + f->builtinsprofile_total += prog->stack[prog->depth].builtinsprofile_acc; + } + + return prog->stack[prog->depth].s; +} + +void PRVM_Init_Exec(prvm_prog_t *prog) +{ + // dump the stack + prog->depth = 0; + prog->localstack_used = 0; + // reset the string table + // nothing here yet +} + +#define OPA ((prvm_eval_t *)&prog->globals.fp[st->operand[0]]) +#define OPB ((prvm_eval_t *)&prog->globals.fp[st->operand[1]]) +#define OPC ((prvm_eval_t *)&prog->globals.fp[st->operand[2]]) +extern cvar_t prvm_traceqc; +extern cvar_t prvm_statementprofiling; +extern qboolean prvm_runawaycheck; + +#ifdef PROFILING +/* +==================== +MVM_ExecuteProgram +==================== +*/ +void MVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage) +{ + mstatement_t *st, *startst; + mfunction_t *f, *newf; + prvm_edict_t *ed; + prvm_eval_t *ptr; + int jumpcount, cachedpr_trace, exitdepth; + int restorevm_tempstringsbuf_cursize; + double calltime; + double tm, starttm; + prvm_vec_t tempfloat; + // these may become out of date when a builtin is called, and are updated accordingly + prvm_vec_t *cached_edictsfields = prog->edictsfields; + unsigned int cached_entityfields = prog->entityfields; + unsigned int cached_entityfields_3 = prog->entityfields - 3; + unsigned int cached_entityfieldsarea = prog->entityfieldsarea; + unsigned int cached_entityfieldsarea_entityfields = prog->entityfieldsarea - prog->entityfields; + unsigned int cached_entityfieldsarea_3 = prog->entityfieldsarea - 3; + unsigned int cached_entityfieldsarea_entityfields_3 = prog->entityfieldsarea - prog->entityfields - 3; + unsigned int cached_max_edicts = prog->max_edicts; + // these do not change + mstatement_t *cached_statements = prog->statements; + qboolean cached_allowworldwrites = prog->allowworldwrites; + unsigned int cached_flag = prog->flag; + + calltime = Sys_DirtyTime(); + + if (!fnum || fnum >= (unsigned int)prog->numfunctions) + { + if (PRVM_allglobaledict(self)) + PRVM_ED_Print(prog, PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); + prog->error_cmd("MVM_ExecuteProgram: %s", errormessage); + } + + f = &prog->functions[fnum]; + + // after executing this function, delete all tempstrings it created + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + + prog->trace = prvm_traceqc.integer; + + // we know we're done when pr_depth drops to this + exitdepth = prog->depth; + +// make a stack frame + st = &prog->statements[PRVM_EnterFunction(prog, f)]; + // save the starting statement pointer for profiling + // (when the function exits or jumps, the (st - startst) integer value is + // added to the function's profile counter) + startst = st; + starttm = calltime; + // instead of counting instructions, we count jumps + jumpcount = 0; + // add one to the callcount of this function because otherwise engine-called functions aren't counted + prog->xfunction->callcount++; + +chooseexecprogram: + cachedpr_trace = prog->trace; + if (prvm_statementprofiling.integer || prog->trace || prog->watch_global >= 0 || prog->watch_edict >= 0 || prog->break_statement >= 0) + { +#define PRVMSLOWINTERPRETER 1 + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } +#undef PRVMSLOWINTERPRETER + } + else + { + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } + } + +cleanup: + if (developer_insane.integer && prog->tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) + Con_DPrintf("MVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog, prog->functions[fnum].s_name), prog->tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); + // delete tempstrings created by this function + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + + tm = Sys_DirtyTime() - calltime;if (tm < 0 || tm >= 1800) tm = 0; + f->totaltime += tm; + + if (prog == SVVM_prog) + SV_FlushBroadcastMessages(); +} + +/* +==================== +CLVM_ExecuteProgram +==================== +*/ +void CLVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage) +{ + mstatement_t *st, *startst; + mfunction_t *f, *newf; + prvm_edict_t *ed; + prvm_eval_t *ptr; + int jumpcount, cachedpr_trace, exitdepth; + int restorevm_tempstringsbuf_cursize; + double calltime; + double tm, starttm; + prvm_vec_t tempfloat; + // these may become out of date when a builtin is called, and are updated accordingly + prvm_vec_t *cached_edictsfields = prog->edictsfields; + unsigned int cached_entityfields = prog->entityfields; + unsigned int cached_entityfields_3 = prog->entityfields - 3; + unsigned int cached_entityfieldsarea = prog->entityfieldsarea; + unsigned int cached_entityfieldsarea_entityfields = prog->entityfieldsarea - prog->entityfields; + unsigned int cached_entityfieldsarea_3 = prog->entityfieldsarea - 3; + unsigned int cached_entityfieldsarea_entityfields_3 = prog->entityfieldsarea - prog->entityfields - 3; + unsigned int cached_max_edicts = prog->max_edicts; + // these do not change + mstatement_t *cached_statements = prog->statements; + qboolean cached_allowworldwrites = prog->allowworldwrites; + unsigned int cached_flag = prog->flag; + + calltime = Sys_DirtyTime(); + + if (!fnum || fnum >= (unsigned int)prog->numfunctions) + { + if (PRVM_allglobaledict(self)) + PRVM_ED_Print(prog, PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); + prog->error_cmd("CLVM_ExecuteProgram: %s", errormessage); + } + + f = &prog->functions[fnum]; + + // after executing this function, delete all tempstrings it created + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + + prog->trace = prvm_traceqc.integer; + + // we know we're done when pr_depth drops to this + exitdepth = prog->depth; + +// make a stack frame + st = &prog->statements[PRVM_EnterFunction(prog, f)]; + // save the starting statement pointer for profiling + // (when the function exits or jumps, the (st - startst) integer value is + // added to the function's profile counter) + startst = st; + starttm = calltime; + // instead of counting instructions, we count jumps + jumpcount = 0; + // add one to the callcount of this function because otherwise engine-called functions aren't counted + prog->xfunction->callcount++; + +chooseexecprogram: + cachedpr_trace = prog->trace; + if (prvm_statementprofiling.integer || prog->trace || prog->watch_global >= 0 || prog->watch_edict >= 0 || prog->break_statement >= 0) + { +#define PRVMSLOWINTERPRETER 1 + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } +#undef PRVMSLOWINTERPRETER + } + else + { + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } + } + +cleanup: + if (developer_insane.integer && prog->tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) + Con_DPrintf("CLVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog, prog->functions[fnum].s_name), prog->tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); + // delete tempstrings created by this function + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + + tm = Sys_DirtyTime() - calltime;if (tm < 0 || tm >= 1800) tm = 0; + f->totaltime += tm; + + if (prog == SVVM_prog) + SV_FlushBroadcastMessages(); +} +#endif + +/* +==================== +SVVM_ExecuteProgram +==================== +*/ +#ifdef PROFILING +void SVVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage) +#else +void PRVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage) +#endif +{ + mstatement_t *st, *startst; + mfunction_t *f, *newf; + prvm_edict_t *ed; + prvm_eval_t *ptr; + int jumpcount, cachedpr_trace, exitdepth; + int restorevm_tempstringsbuf_cursize; + double calltime; + double tm, starttm; + prvm_vec_t tempfloat; + // these may become out of date when a builtin is called, and are updated accordingly + prvm_vec_t *cached_edictsfields = prog->edictsfields; + unsigned int cached_entityfields = prog->entityfields; + unsigned int cached_entityfields_3 = prog->entityfields - 3; + unsigned int cached_entityfieldsarea = prog->entityfieldsarea; + unsigned int cached_entityfieldsarea_entityfields = prog->entityfieldsarea - prog->entityfields; + unsigned int cached_entityfieldsarea_3 = prog->entityfieldsarea - 3; + unsigned int cached_entityfieldsarea_entityfields_3 = prog->entityfieldsarea - prog->entityfields - 3; + unsigned int cached_max_edicts = prog->max_edicts; + // these do not change + mstatement_t *cached_statements = prog->statements; + qboolean cached_allowworldwrites = prog->allowworldwrites; + unsigned int cached_flag = prog->flag; + + calltime = Sys_DirtyTime(); + + if (!fnum || fnum >= (unsigned int)prog->numfunctions) + { + if (PRVM_allglobaledict(self)) + PRVM_ED_Print(prog, PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); + prog->error_cmd("SVVM_ExecuteProgram: %s", errormessage); + } + + f = &prog->functions[fnum]; + + // after executing this function, delete all tempstrings it created + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + + prog->trace = prvm_traceqc.integer; + + // we know we're done when pr_depth drops to this + exitdepth = prog->depth; + +// make a stack frame + st = &prog->statements[PRVM_EnterFunction(prog, f)]; + // save the starting statement pointer for profiling + // (when the function exits or jumps, the (st - startst) integer value is + // added to the function's profile counter) + startst = st; + starttm = calltime; + // instead of counting instructions, we count jumps + jumpcount = 0; + // add one to the callcount of this function because otherwise engine-called functions aren't counted + prog->xfunction->callcount++; + +chooseexecprogram: + cachedpr_trace = prog->trace; + if (prvm_statementprofiling.integer || prog->trace || prog->watch_global >= 0 || prog->watch_edict >= 0 || prog->break_statement >= 0) + { +#define PRVMSLOWINTERPRETER 1 + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } +#undef PRVMSLOWINTERPRETER + } + else + { + if (prvm_timeprofiling.integer) + { +#define PRVMTIMEPROFILING 1 +#include "prvm_execprogram.h" +#undef PRVMTIMEPROFILING + } + else + { +#include "prvm_execprogram.h" + } + } + +cleanup: + if (developer_insane.integer && prog->tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) + Con_DPrintf("SVVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog, prog->functions[fnum].s_name), prog->tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); + // delete tempstrings created by this function + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + + tm = Sys_DirtyTime() - calltime;if (tm < 0 || tm >= 1800) tm = 0; + f->totaltime += tm; + + if (prog == SVVM_prog) + SV_FlushBroadcastMessages(); +} diff --git a/app/jni/prvm_execprogram.h b/app/jni/prvm_execprogram.h new file mode 100644 index 0000000..02b444b --- /dev/null +++ b/app/jni/prvm_execprogram.h @@ -0,0 +1,744 @@ +#ifdef PRVMTIMEPROFILING +#define PreError() \ + prog->xstatement = st - cached_statements; \ + tm = Sys_DirtyTime(); \ + prog->xfunction->profile += (st - startst); \ + prog->xfunction->tprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; +#else +#define PreError() \ + prog->xstatement = st - cached_statements; \ + prog->xfunction->profile += (st - startst); +#endif + +// This code isn't #ifdef/#define protectable, don't try. + +#if PRVMSLOWINTERPRETER + { + if (prog->watch_global_type != ev_void) + { + prvm_eval_t *f = PRVM_GLOBALFIELDVALUE(prog->watch_global); + prog->xstatement = st + 1 - cached_statements; + PRVM_Watchpoint(prog, 1, "Global watchpoint hit by engine", prog->watch_global_type, &prog->watch_global_value, f); + } + if (prog->watch_field_type != ev_void && prog->watch_edict < prog->max_edicts) + { + prvm_eval_t *f = PRVM_EDICTFIELDVALUE(prog->edicts + prog->watch_edict, prog->watch_field); + prog->xstatement = st + 1 - cached_statements; + PRVM_Watchpoint(prog, 1, "Entityfield watchpoint hit by engine", prog->watch_field_type, &prog->watch_edictfield_value, f); + } + } +#endif + + while (1) + { + st++; + +#if PRVMSLOWINTERPRETER + if (prog->trace) + PRVM_PrintStatement(prog, st); + prog->statement_profile[st - cached_statements]++; + if (prog->break_statement >= 0) + if ((st - cached_statements) == prog->break_statement) + { + prog->xstatement = st - cached_statements; + PRVM_Breakpoint(prog, prog->break_stack_index, "Breakpoint hit"); + } +#endif + + switch (st->op) + { + case OP_ADD_F: + OPC->_float = OPA->_float + OPB->_float; + break; + case OP_ADD_V: + OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; + break; + case OP_SUB_F: + OPC->_float = OPA->_float - OPB->_float; + break; + case OP_SUB_V: + OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; + break; + case OP_MUL_F: + OPC->_float = OPA->_float * OPB->_float; + break; + case OP_MUL_V: + OPC->_float = OPA->vector[0]*OPB->vector[0] + OPA->vector[1]*OPB->vector[1] + OPA->vector[2]*OPB->vector[2]; + break; + case OP_MUL_FV: + tempfloat = OPA->_float; + OPC->vector[0] = tempfloat * OPB->vector[0]; + OPC->vector[1] = tempfloat * OPB->vector[1]; + OPC->vector[2] = tempfloat * OPB->vector[2]; + break; + case OP_MUL_VF: + tempfloat = OPB->_float; + OPC->vector[0] = tempfloat * OPA->vector[0]; + OPC->vector[1] = tempfloat * OPA->vector[1]; + OPC->vector[2] = tempfloat * OPA->vector[2]; + break; + case OP_DIV_F: + if( OPB->_float != 0.0f ) + { + OPC->_float = OPA->_float / OPB->_float; + } + else + { + if (developer.integer) + { + prog->xfunction->profile += (st - startst); + startst = st; + prog->xstatement = st - cached_statements; + VM_Warning(prog, "Attempted division by zero in %s\n", prog->name ); + } + OPC->_float = 0.0f; + } + break; + case OP_BITAND: + OPC->_float = (prvm_int_t)OPA->_float & (prvm_int_t)OPB->_float; + break; + case OP_BITOR: + OPC->_float = (prvm_int_t)OPA->_float | (prvm_int_t)OPB->_float; + break; + case OP_GE: + OPC->_float = OPA->_float >= OPB->_float; + break; + case OP_LE: + OPC->_float = OPA->_float <= OPB->_float; + break; + case OP_GT: + OPC->_float = OPA->_float > OPB->_float; + break; + case OP_LT: + OPC->_float = OPA->_float < OPB->_float; + break; + case OP_AND: + OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) && FLOAT_IS_TRUE_FOR_INT(OPB->_int); // TODO change this back to float, and add AND_I to be used by fteqcc for anything not a float + break; + case OP_OR: + OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) || FLOAT_IS_TRUE_FOR_INT(OPB->_int); // TODO change this back to float, and add OR_I to be used by fteqcc for anything not a float + break; + case OP_NOT_F: + OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int); + break; + case OP_NOT_V: + OPC->_float = !OPA->vector[0] && !OPA->vector[1] && !OPA->vector[2]; + break; + case OP_NOT_S: + OPC->_float = !OPA->string || !*PRVM_GetString(prog, OPA->string); + break; + case OP_NOT_FNC: + OPC->_float = !OPA->function; + break; + case OP_NOT_ENT: + OPC->_float = (OPA->edict == 0); + break; + case OP_EQ_F: + OPC->_float = OPA->_float == OPB->_float; + break; + case OP_EQ_V: + OPC->_float = (OPA->vector[0] == OPB->vector[0]) && (OPA->vector[1] == OPB->vector[1]) && (OPA->vector[2] == OPB->vector[2]); + break; + case OP_EQ_S: + OPC->_float = !strcmp(PRVM_GetString(prog, OPA->string),PRVM_GetString(prog, OPB->string)); + break; + case OP_EQ_E: + OPC->_float = OPA->_int == OPB->_int; + break; + case OP_EQ_FNC: + OPC->_float = OPA->function == OPB->function; + break; + case OP_NE_F: + OPC->_float = OPA->_float != OPB->_float; + break; + case OP_NE_V: + OPC->_float = (OPA->vector[0] != OPB->vector[0]) || (OPA->vector[1] != OPB->vector[1]) || (OPA->vector[2] != OPB->vector[2]); + break; + case OP_NE_S: + OPC->_float = strcmp(PRVM_GetString(prog, OPA->string),PRVM_GetString(prog, OPB->string)); + break; + case OP_NE_E: + OPC->_float = OPA->_int != OPB->_int; + break; + case OP_NE_FNC: + OPC->_float = OPA->function != OPB->function; + break; + + //================== + case OP_STORE_F: + case OP_STORE_ENT: + case OP_STORE_FLD: // integers + case OP_STORE_S: + case OP_STORE_FNC: // pointers + OPB->_int = OPA->_int; + break; + case OP_STORE_V: + OPB->ivector[0] = OPA->ivector[0]; + OPB->ivector[1] = OPA->ivector[1]; + OPB->ivector[2] = OPA->ivector[2]; + break; + + case OP_STOREP_F: + case OP_STOREP_ENT: + case OP_STOREP_FLD: // integers + case OP_STOREP_S: + case OP_STOREP_FNC: // pointers + if ((prvm_uint_t)OPB->_int - cached_entityfields >= cached_entityfieldsarea_entityfields) + { + if ((prvm_uint_t)OPB->_int >= cached_entityfieldsarea) + { + PreError(); + prog->error_cmd("%s attempted to write to an out of bounds edict (%i)", prog->name, (int)OPB->_int); + goto cleanup; + } + if ((prvm_uint_t)OPB->_int < cached_entityfields && !cached_allowworldwrites) + { + prog->xstatement = st - cached_statements; + VM_Warning(prog, "assignment to world.%s (field %i) in %s\n", PRVM_GetString(prog, PRVM_ED_FieldAtOfs(prog, OPB->_int)->s_name), (int)OPB->_int, prog->name); + } + } + ptr = (prvm_eval_t *)(cached_edictsfields + OPB->_int); + ptr->_int = OPA->_int; + break; + case OP_STOREP_V: + if ((prvm_uint_t)OPB->_int - cached_entityfields > (prvm_uint_t)cached_entityfieldsarea_entityfields_3) + { + if ((prvm_uint_t)OPB->_int > cached_entityfieldsarea_3) + { + PreError(); + prog->error_cmd("%s attempted to write to an out of bounds edict (%i)", prog->name, (int)OPB->_int); + goto cleanup; + } + if ((prvm_uint_t)OPB->_int < cached_entityfields && !cached_allowworldwrites) + { + prog->xstatement = st - cached_statements; + VM_Warning(prog, "assignment to world.%s (field %i) in %s\n", PRVM_GetString(prog, PRVM_ED_FieldAtOfs(prog, OPB->_int)->s_name), (int)OPB->_int, prog->name); + } + } + ptr = (prvm_eval_t *)(cached_edictsfields + OPB->_int); + ptr->ivector[0] = OPA->ivector[0]; + ptr->ivector[1] = OPA->ivector[1]; + ptr->ivector[2] = OPA->ivector[2]; + break; + + case OP_ADDRESS: + if ((prvm_uint_t)OPA->edict >= cached_max_edicts) + { + PreError(); + prog->error_cmd("%s Progs attempted to address an out of bounds edict number", prog->name); + goto cleanup; + } + if ((prvm_uint_t)OPB->_int >= cached_entityfields) + { + PreError(); + prog->error_cmd("%s attempted to address an invalid field (%i) in an edict", prog->name, (int)OPB->_int); + goto cleanup; + } +#if 0 + if (OPA->edict == 0 && !cached_allowworldwrites) + { + PreError(); + prog->error_cmd("forbidden assignment to null/world entity in %s", prog->name); + goto cleanup; + } +#endif + OPC->_int = OPA->edict * cached_entityfields + OPB->_int; + break; + + case OP_LOAD_F: + case OP_LOAD_FLD: + case OP_LOAD_ENT: + case OP_LOAD_S: + case OP_LOAD_FNC: + if ((prvm_uint_t)OPA->edict >= cached_max_edicts) + { + PreError(); + prog->error_cmd("%s Progs attempted to read an out of bounds edict number", prog->name); + goto cleanup; + } + if ((prvm_uint_t)OPB->_int >= cached_entityfields) + { + PreError(); + prog->error_cmd("%s attempted to read an invalid field in an edict (%i)", prog->name, (int)OPB->_int); + goto cleanup; + } + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = ((prvm_eval_t *)(ed->fields.ip + OPB->_int))->_int; + break; + + case OP_LOAD_V: + if ((prvm_uint_t)OPA->edict >= cached_max_edicts) + { + PreError(); + prog->error_cmd("%s Progs attempted to read an out of bounds edict number", prog->name); + goto cleanup; + } + if ((prvm_uint_t)OPB->_int > cached_entityfields_3) + { + PreError(); + prog->error_cmd("%s attempted to read an invalid field in an edict (%i)", prog->name, (int)OPB->_int); + goto cleanup; + } + ed = PRVM_PROG_TO_EDICT(OPA->edict); + ptr = (prvm_eval_t *)(ed->fields.ip + OPB->_int); + OPC->ivector[0] = ptr->ivector[0]; + OPC->ivector[1] = ptr->ivector[1]; + OPC->ivector[2] = ptr->ivector[2]; + break; + + //================== + + case OP_IFNOT: + if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int)) + // TODO add an "int-if", and change this one to OPA->_float + // although mostly unneeded, thanks to the only float being false being 0x0 and 0x80000000 (negative zero) + // and entity, string, field values can never have that value + { + prog->xfunction->profile += (st - startst); + st = cached_statements + st->jumpabsolute - 1; // offset the st++ + startst = st; + // no bounds check needed, it is done when loading progs + if (++jumpcount == 10000000 && prvm_runawaycheck) + { + prog->xstatement = st - cached_statements; + PRVM_Profile(prog, 1<<30, 1000000, 0); + prog->error_cmd("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", prog->name, jumpcount); + } + } + break; + + case OP_IF: + if(FLOAT_IS_TRUE_FOR_INT(OPA->_int)) + // TODO add an "int-if", and change this one, as well as the FLOAT_IS_TRUE_FOR_INT usages, to OPA->_float + // although mostly unneeded, thanks to the only float being false being 0x0 and 0x80000000 (negative zero) + // and entity, string, field values can never have that value + { + prog->xfunction->profile += (st - startst); + st = cached_statements + st->jumpabsolute - 1; // offset the st++ + startst = st; + // no bounds check needed, it is done when loading progs + if (++jumpcount == 10000000 && prvm_runawaycheck) + { + prog->xstatement = st - cached_statements; + PRVM_Profile(prog, 1<<30, 0.01, 0); + prog->error_cmd("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", prog->name, jumpcount); + } + } + break; + + case OP_GOTO: + prog->xfunction->profile += (st - startst); + st = cached_statements + st->jumpabsolute - 1; // offset the st++ + startst = st; + // no bounds check needed, it is done when loading progs + if (++jumpcount == 10000000 && prvm_runawaycheck) + { + prog->xstatement = st - cached_statements; + PRVM_Profile(prog, 1<<30, 0.01, 0); + prog->error_cmd("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", prog->name, jumpcount); + } + break; + + case OP_CALL0: + case OP_CALL1: + case OP_CALL2: + case OP_CALL3: + case OP_CALL4: + case OP_CALL5: + case OP_CALL6: + case OP_CALL7: + case OP_CALL8: +#ifdef PRVMTIMEPROFILING + tm = Sys_DirtyTime(); + prog->xfunction->tprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; + starttm = tm; +#endif + prog->xfunction->profile += (st - startst); + startst = st; + prog->xstatement = st - cached_statements; + prog->argc = st->op - OP_CALL0; + if (!OPA->function) + prog->error_cmd("NULL function in %s", prog->name); + + if(!OPA->function || OPA->function < 0 || OPA->function >= prog->numfunctions) + { + PreError(); + prog->error_cmd("%s CALL outside the program", prog->name); + goto cleanup; + } + + newf = &prog->functions[OPA->function]; + newf->callcount++; + + if (newf->first_statement < 0) + { + // negative first_statement values are built in functions + int builtinnumber = -newf->first_statement; + prog->xfunction->builtinsprofile++; + if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber]) + { + prog->builtins[builtinnumber](prog); +#ifdef PRVMTIMEPROFILING + tm = Sys_DirtyTime(); + newf->tprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; + prog->xfunction->tbprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; + starttm = tm; +#endif + // builtins may cause ED_Alloc() to be called, update cached variables + cached_edictsfields = prog->edictsfields; + cached_entityfields = prog->entityfields; + cached_entityfields_3 = prog->entityfields - 3; + cached_entityfieldsarea = prog->entityfieldsarea; + cached_entityfieldsarea_entityfields = prog->entityfieldsarea - prog->entityfields; + cached_entityfieldsarea_3 = prog->entityfieldsarea - 3; + cached_entityfieldsarea_entityfields_3 = prog->entityfieldsarea - prog->entityfields - 3; + cached_max_edicts = prog->max_edicts; + // these do not change + //cached_statements = prog->statements; + //cached_allowworldwrites = prog->allowworldwrites; + //cached_flag = prog->flag; + // if prog->trace changed we need to change interpreter path + if (prog->trace != cachedpr_trace) + goto chooseexecprogram; + } + else + prog->error_cmd("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, prog->name); + } + else + st = cached_statements + PRVM_EnterFunction(prog, newf); + startst = st; + break; + + case OP_DONE: + case OP_RETURN: +#ifdef PRVMTIMEPROFILING + tm = Sys_DirtyTime(); + prog->xfunction->tprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; + starttm = tm; +#endif + prog->xfunction->profile += (st - startst); + prog->xstatement = st - cached_statements; + + prog->globals.ip[OFS_RETURN ] = prog->globals.ip[st->operand[0] ]; + prog->globals.ip[OFS_RETURN+1] = prog->globals.ip[st->operand[0]+1]; + prog->globals.ip[OFS_RETURN+2] = prog->globals.ip[st->operand[0]+2]; + + st = cached_statements + PRVM_LeaveFunction(prog); + startst = st; + if (prog->depth <= exitdepth) + goto cleanup; // all done + break; + + case OP_STATE: + if(cached_flag & PRVM_OP_STATE) + { + ed = PRVM_PROG_TO_EDICT(PRVM_gameglobaledict(self)); + PRVM_gameedictfloat(ed,nextthink) = PRVM_gameglobalfloat(time) + 0.1; + PRVM_gameedictfloat(ed,frame) = OPA->_float; + PRVM_gameedictfunction(ed,think) = OPB->function; + } + else + { + PreError(); + prog->xstatement = st - cached_statements; + prog->error_cmd("OP_STATE not supported by %s", prog->name); + } + break; + +// LordHavoc: to be enabled when Progs version 7 (or whatever it will be numbered) is finalized +/* + case OP_ADD_I: + OPC->_int = OPA->_int + OPB->_int; + break; + case OP_ADD_IF: + OPC->_int = OPA->_int + (prvm_int_t) OPB->_float; + break; + case OP_ADD_FI: + OPC->_float = OPA->_float + (prvm_vec_t) OPB->_int; + break; + case OP_SUB_I: + OPC->_int = OPA->_int - OPB->_int; + break; + case OP_SUB_IF: + OPC->_int = OPA->_int - (prvm_int_t) OPB->_float; + break; + case OP_SUB_FI: + OPC->_float = OPA->_float - (prvm_vec_t) OPB->_int; + break; + case OP_MUL_I: + OPC->_int = OPA->_int * OPB->_int; + break; + case OP_MUL_IF: + OPC->_int = OPA->_int * (prvm_int_t) OPB->_float; + break; + case OP_MUL_FI: + OPC->_float = OPA->_float * (prvm_vec_t) OPB->_int; + break; + case OP_MUL_VI: + OPC->vector[0] = (prvm_vec_t) OPB->_int * OPA->vector[0]; + OPC->vector[1] = (prvm_vec_t) OPB->_int * OPA->vector[1]; + OPC->vector[2] = (prvm_vec_t) OPB->_int * OPA->vector[2]; + break; + case OP_DIV_VF: + { + float temp = 1.0f / OPB->_float; + OPC->vector[0] = temp * OPA->vector[0]; + OPC->vector[1] = temp * OPA->vector[1]; + OPC->vector[2] = temp * OPA->vector[2]; + } + break; + case OP_DIV_I: + OPC->_int = OPA->_int / OPB->_int; + break; + case OP_DIV_IF: + OPC->_int = OPA->_int / (prvm_int_t) OPB->_float; + break; + case OP_DIV_FI: + OPC->_float = OPA->_float / (prvm_vec_t) OPB->_int; + break; + case OP_CONV_IF: + OPC->_float = OPA->_int; + break; + case OP_CONV_FI: + OPC->_int = OPA->_float; + break; + case OP_BITAND_I: + OPC->_int = OPA->_int & OPB->_int; + break; + case OP_BITOR_I: + OPC->_int = OPA->_int | OPB->_int; + break; + case OP_BITAND_IF: + OPC->_int = OPA->_int & (prvm_int_t)OPB->_float; + break; + case OP_BITOR_IF: + OPC->_int = OPA->_int | (prvm_int_t)OPB->_float; + break; + case OP_BITAND_FI: + OPC->_float = (prvm_int_t)OPA->_float & OPB->_int; + break; + case OP_BITOR_FI: + OPC->_float = (prvm_int_t)OPA->_float | OPB->_int; + break; + case OP_GE_I: + OPC->_float = OPA->_int >= OPB->_int; + break; + case OP_LE_I: + OPC->_float = OPA->_int <= OPB->_int; + break; + case OP_GT_I: + OPC->_float = OPA->_int > OPB->_int; + break; + case OP_LT_I: + OPC->_float = OPA->_int < OPB->_int; + break; + case OP_AND_I: + OPC->_float = OPA->_int && OPB->_int; + break; + case OP_OR_I: + OPC->_float = OPA->_int || OPB->_int; + break; + case OP_GE_IF: + OPC->_float = (prvm_vec_t)OPA->_int >= OPB->_float; + break; + case OP_LE_IF: + OPC->_float = (prvm_vec_t)OPA->_int <= OPB->_float; + break; + case OP_GT_IF: + OPC->_float = (prvm_vec_t)OPA->_int > OPB->_float; + break; + case OP_LT_IF: + OPC->_float = (prvm_vec_t)OPA->_int < OPB->_float; + break; + case OP_AND_IF: + OPC->_float = (prvm_vec_t)OPA->_int && OPB->_float; + break; + case OP_OR_IF: + OPC->_float = (prvm_vec_t)OPA->_int || OPB->_float; + break; + case OP_GE_FI: + OPC->_float = OPA->_float >= (prvm_vec_t)OPB->_int; + break; + case OP_LE_FI: + OPC->_float = OPA->_float <= (prvm_vec_t)OPB->_int; + break; + case OP_GT_FI: + OPC->_float = OPA->_float > (prvm_vec_t)OPB->_int; + break; + case OP_LT_FI: + OPC->_float = OPA->_float < (prvm_vec_t)OPB->_int; + break; + case OP_AND_FI: + OPC->_float = OPA->_float && (prvm_vec_t)OPB->_int; + break; + case OP_OR_FI: + OPC->_float = OPA->_float || (prvm_vec_t)OPB->_int; + break; + case OP_NOT_I: + OPC->_float = !OPA->_int; + break; + case OP_EQ_I: + OPC->_float = OPA->_int == OPB->_int; + break; + case OP_EQ_IF: + OPC->_float = (prvm_vec_t)OPA->_int == OPB->_float; + break; + case OP_EQ_FI: + OPC->_float = OPA->_float == (prvm_vec_t)OPB->_int; + break; + case OP_NE_I: + OPC->_float = OPA->_int != OPB->_int; + break; + case OP_NE_IF: + OPC->_float = (prvm_vec_t)OPA->_int != OPB->_float; + break; + case OP_NE_FI: + OPC->_float = OPA->_float != (prvm_vec_t)OPB->_int; + break; + case OP_STORE_I: + OPB->_int = OPA->_int; + break; + case OP_STOREP_I: +#if PRBOUNDSCHECK + if (OPB->_int < 0 || OPB->_int + 4 > pr_edictareasize) + { + PreError(); + prog->error_cmd("%s Progs attempted to write to an out of bounds edict", prog->name); + goto cleanup; + } +#endif + ptr = (prvm_eval_t *)(prog->edictsfields + OPB->_int); + ptr->_int = OPA->_int; + break; + case OP_LOAD_I: +#if PRBOUNDSCHECK + if (OPA->edict < 0 || OPA->edict >= prog->max_edicts) + { + PreError(); + prog->error_cmd("%s Progs attempted to read an out of bounds edict number", prog->name); + goto cleanup; + } + if (OPB->_int < 0 || OPB->_int >= progs->entityfields) + { + PreError(); + prog->error_cmd("%s Progs attempted to read an invalid field in an edict", prog->name); + goto cleanup; + } +#endif + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = ((prvm_eval_t *)((int *)ed->v + OPB->_int))->_int; + break; + + case OP_GSTOREP_I: + case OP_GSTOREP_F: + case OP_GSTOREP_ENT: + case OP_GSTOREP_FLD: // integers + case OP_GSTOREP_S: + case OP_GSTOREP_FNC: // pointers +#if PRBOUNDSCHECK + if (OPB->_int < 0 || OPB->_int >= pr_globaldefs) + { + PreError(); + prog->error_cmd("%s Progs attempted to write to an invalid indexed global", prog->name); + goto cleanup; + } +#endif + pr_iglobals[OPB->_int] = OPA->_int; + break; + case OP_GSTOREP_V: +#if PRBOUNDSCHECK + if (OPB->_int < 0 || OPB->_int + 2 >= pr_globaldefs) + { + PreError(); + prog->error_cmd("%s Progs attempted to write to an invalid indexed global", prog->name); + goto cleanup; + } +#endif + pr_iglobals[OPB->_int ] = OPA->ivector[0]; + pr_iglobals[OPB->_int+1] = OPA->ivector[1]; + pr_iglobals[OPB->_int+2] = OPA->ivector[2]; + break; + + case OP_GADDRESS: + i = OPA->_int + (prvm_int_t) OPB->_float; +#if PRBOUNDSCHECK + if (i < 0 || i >= pr_globaldefs) + { + PreError(); + prog->error_cmd("%s Progs attempted to address an out of bounds global", prog->name); + goto cleanup; + } +#endif + OPC->_int = pr_iglobals[i]; + break; + + case OP_GLOAD_I: + case OP_GLOAD_F: + case OP_GLOAD_FLD: + case OP_GLOAD_ENT: + case OP_GLOAD_S: + case OP_GLOAD_FNC: +#if PRBOUNDSCHECK + if (OPA->_int < 0 || OPA->_int >= pr_globaldefs) + { + PreError(); + prog->error_cmd("%s Progs attempted to read an invalid indexed global", prog->name); + goto cleanup; + } +#endif + OPC->_int = pr_iglobals[OPA->_int]; + break; + + case OP_GLOAD_V: +#if PRBOUNDSCHECK + if (OPA->_int < 0 || OPA->_int + 2 >= pr_globaldefs) + { + PreError(); + prog->error_cmd("%s Progs attempted to read an invalid indexed global", prog->name); + goto cleanup; + } +#endif + OPC->ivector[0] = pr_iglobals[OPA->_int ]; + OPC->ivector[1] = pr_iglobals[OPA->_int+1]; + OPC->ivector[2] = pr_iglobals[OPA->_int+2]; + break; + + case OP_BOUNDCHECK: + if (OPA->_int < 0 || OPA->_int >= st->b) + { + PreError(); + prog->error_cmd("%s Progs boundcheck failed at line number %d, value is < 0 or >= %d", prog->name, st->b, st->c); + goto cleanup; + } + break; + +*/ + + default: + PreError(); + prog->error_cmd("Bad opcode %i in %s", st->op, prog->name); + goto cleanup; + } +#if PRVMSLOWINTERPRETER + { + if (prog->watch_global_type != ev_void) + { + prvm_eval_t *f = PRVM_GLOBALFIELDVALUE(prog->watch_global); + prog->xstatement = st - cached_statements; + PRVM_Watchpoint(prog, 0, "Global watchpoint hit", prog->watch_global_type, &prog->watch_global_value, f); + } + if (prog->watch_field_type != ev_void && prog->watch_edict < prog->max_edicts) + { + prvm_eval_t *f = PRVM_EDICTFIELDVALUE(prog->edicts + prog->watch_edict, prog->watch_field); + prog->xstatement = st - cached_statements; + PRVM_Watchpoint(prog, 0, "Entityfield watchpoint hit", prog->watch_field_type, &prog->watch_edictfield_value, f); + } + } +#endif + } + +#undef PreError diff --git a/app/jni/prvm_offsets.h b/app/jni/prvm_offsets.h new file mode 100644 index 0000000..440b3e4 --- /dev/null +++ b/app/jni/prvm_offsets.h @@ -0,0 +1,850 @@ +PRVM_DECLARE_clientfieldedict(aiment) +PRVM_DECLARE_clientfieldedict(chain) +PRVM_DECLARE_clientfieldedict(enemy) +PRVM_DECLARE_clientfieldedict(groundentity) +PRVM_DECLARE_clientfieldedict(owner) +PRVM_DECLARE_clientfieldedict(tag_entity) +PRVM_DECLARE_clientfieldfloat(alpha) +PRVM_DECLARE_clientfieldfloat(bouncefactor) +PRVM_DECLARE_clientfieldfloat(bouncestop) +PRVM_DECLARE_clientfieldfloat(colormap) +PRVM_DECLARE_clientfieldfloat(dphitcontentsmask) +PRVM_DECLARE_clientfieldfloat(drawmask) +PRVM_DECLARE_clientfieldfloat(effects) +PRVM_DECLARE_clientfieldfloat(entnum) +PRVM_DECLARE_clientfieldfloat(flags) +PRVM_DECLARE_clientfieldfloat(frame) +PRVM_DECLARE_clientfieldfloat(frame1time) +PRVM_DECLARE_clientfieldfloat(frame2) +PRVM_DECLARE_clientfieldfloat(frame2time) +PRVM_DECLARE_clientfieldfloat(frame3) +PRVM_DECLARE_clientfieldfloat(frame3time) +PRVM_DECLARE_clientfieldfloat(frame4) +PRVM_DECLARE_clientfieldfloat(frame4time) +PRVM_DECLARE_clientfieldfloat(gravity) +PRVM_DECLARE_clientfieldfloat(ideal_yaw) +PRVM_DECLARE_clientfieldfloat(idealpitch) +PRVM_DECLARE_clientfieldfloat(geomtype) +PRVM_DECLARE_clientfieldfloat(jointtype) +PRVM_DECLARE_clientfieldfloat(forcetype) +PRVM_DECLARE_clientfieldfloat(lerpfrac) +PRVM_DECLARE_clientfieldfloat(lerpfrac3) +PRVM_DECLARE_clientfieldfloat(lerpfrac4) +PRVM_DECLARE_clientfieldfloat(mass) +PRVM_DECLARE_clientfieldvector(massofs) +PRVM_DECLARE_clientfieldfloat(friction) +PRVM_DECLARE_clientfieldfloat(maxcontacts) +PRVM_DECLARE_clientfieldfloat(erp) +PRVM_DECLARE_clientfieldfloat(modelindex) +PRVM_DECLARE_clientfieldfloat(movetype) +PRVM_DECLARE_clientfieldfloat(nextthink) +PRVM_DECLARE_clientfieldfloat(pitch_speed) +PRVM_DECLARE_clientfieldfloat(pmove_flags) +PRVM_DECLARE_clientfieldfloat(renderflags) +PRVM_DECLARE_clientfieldfloat(scale) +PRVM_DECLARE_clientfieldvector(modelscale_vec) +PRVM_DECLARE_clientfieldfloat(shadertime) +PRVM_DECLARE_clientfieldfloat(skeletonindex) +PRVM_DECLARE_clientfieldfloat(skin) +PRVM_DECLARE_clientfieldfloat(solid) +PRVM_DECLARE_clientfieldfloat(tag_index) +PRVM_DECLARE_clientfieldfloat(userwavefunc_param0) +PRVM_DECLARE_clientfieldfloat(userwavefunc_param1) +PRVM_DECLARE_clientfieldfloat(userwavefunc_param2) +PRVM_DECLARE_clientfieldfloat(userwavefunc_param3) +PRVM_DECLARE_clientfieldfloat(yaw_speed) +PRVM_DECLARE_clientfieldfunction(blocked) +PRVM_DECLARE_clientfieldfunction(camera_transform) +PRVM_DECLARE_clientfieldfunction(predraw) +PRVM_DECLARE_clientfieldfunction(think) +PRVM_DECLARE_clientfieldfunction(touch) +PRVM_DECLARE_clientfieldfunction(use) +PRVM_DECLARE_clientfieldstring(classname) +PRVM_DECLARE_clientfieldstring(message) +PRVM_DECLARE_clientfieldstring(model) +PRVM_DECLARE_clientfieldstring(netname) +PRVM_DECLARE_clientfieldvector(absmax) +PRVM_DECLARE_clientfieldvector(absmin) +PRVM_DECLARE_clientfieldvector(angles) +PRVM_DECLARE_clientfieldvector(avelocity) +PRVM_DECLARE_clientfieldvector(colormod) +PRVM_DECLARE_clientfieldvector(glowmod) +PRVM_DECLARE_clientfieldvector(maxs) +PRVM_DECLARE_clientfieldvector(mins) +PRVM_DECLARE_clientfieldvector(movedir) +PRVM_DECLARE_clientfieldvector(oldorigin) +PRVM_DECLARE_clientfieldvector(origin) +PRVM_DECLARE_clientfieldvector(size) +PRVM_DECLARE_clientfieldvector(velocity) +PRVM_DECLARE_clientfieldvector(modellight_ambient) +PRVM_DECLARE_clientfieldvector(modellight_diffuse) +PRVM_DECLARE_clientfieldvector(modellight_dir) +PRVM_DECLARE_clientfunction(CSQC_ConsoleCommand) +PRVM_DECLARE_clientfunction(CSQC_Ent_Remove) +PRVM_DECLARE_clientfunction(CSQC_Ent_Spawn) +PRVM_DECLARE_clientfunction(CSQC_Ent_Update) +PRVM_DECLARE_clientfunction(CSQC_Event) +PRVM_DECLARE_clientfunction(CSQC_Event_Sound) +PRVM_DECLARE_clientfunction(CSQC_Init) +PRVM_DECLARE_clientfunction(CSQC_InputEvent) +PRVM_DECLARE_clientfunction(CSQC_Parse_CenterPrint) +PRVM_DECLARE_clientfunction(CSQC_Parse_Print) +PRVM_DECLARE_clientfunction(CSQC_Parse_StuffCmd) +PRVM_DECLARE_clientfunction(CSQC_Parse_TempEntity) +PRVM_DECLARE_clientfunction(CSQC_Shutdown) +PRVM_DECLARE_clientfunction(CSQC_UpdateView) +PRVM_DECLARE_clientfunction(GameCommand) +PRVM_DECLARE_clientfunction(URI_Get_Callback) +PRVM_DECLARE_clientglobaledict(other) +PRVM_DECLARE_clientglobaledict(self) +PRVM_DECLARE_clientglobaledict(trace_ent) +PRVM_DECLARE_clientglobaledict(world) +PRVM_DECLARE_clientglobalfloat(clientcommandframe) +PRVM_DECLARE_clientglobalfloat(coop) +PRVM_DECLARE_clientglobalfloat(deathmatch) +PRVM_DECLARE_clientglobalfloat(dmg_save) +PRVM_DECLARE_clientglobalfloat(dmg_take) +PRVM_DECLARE_clientglobalfloat(drawfont) +PRVM_DECLARE_clientglobalfloat(frametime) +PRVM_DECLARE_clientglobalfloat(gettaginfo_parent) +PRVM_DECLARE_clientglobalvector(getlight_ambient) +PRVM_DECLARE_clientglobalvector(getlight_diffuse) +PRVM_DECLARE_clientglobalvector(getlight_dir) +PRVM_DECLARE_clientglobalfloat(input_buttons) +PRVM_DECLARE_clientglobalfloat(input_timelength) +PRVM_DECLARE_clientglobalfloat(intermission) +PRVM_DECLARE_clientglobalfloat(maxclients) +PRVM_DECLARE_clientglobalfloat(movevar_accelerate) +PRVM_DECLARE_clientglobalfloat(movevar_airaccelerate) +PRVM_DECLARE_clientglobalfloat(movevar_entgravity) +PRVM_DECLARE_clientglobalfloat(movevar_friction) +PRVM_DECLARE_clientglobalfloat(movevar_gravity) +PRVM_DECLARE_clientglobalfloat(movevar_maxspeed) +PRVM_DECLARE_clientglobalfloat(movevar_spectatormaxspeed) +PRVM_DECLARE_clientglobalfloat(movevar_stopspeed) +PRVM_DECLARE_clientglobalfloat(movevar_wateraccelerate) +PRVM_DECLARE_clientglobalfloat(movevar_waterfriction) +PRVM_DECLARE_clientglobalfloat(particle_airfriction) +PRVM_DECLARE_clientglobalfloat(particle_alpha) +PRVM_DECLARE_clientglobalfloat(particle_alphafade) +PRVM_DECLARE_clientglobalfloat(particle_angle) +PRVM_DECLARE_clientglobalfloat(particle_blendmode) +PRVM_DECLARE_clientglobalfloat(particle_bounce) +PRVM_DECLARE_clientglobalfloat(particle_delaycollision) +PRVM_DECLARE_clientglobalfloat(particle_delayspawn) +PRVM_DECLARE_clientglobalfloat(particle_gravity) +PRVM_DECLARE_clientglobalfloat(particle_liquidfriction) +PRVM_DECLARE_clientglobalfloat(particle_orientation) +PRVM_DECLARE_clientglobalfloat(particle_originjitter) +PRVM_DECLARE_clientglobalfloat(particle_qualityreduction) +PRVM_DECLARE_clientglobalfloat(particle_size) +PRVM_DECLARE_clientglobalfloat(particle_sizeincrease) +PRVM_DECLARE_clientglobalfloat(particle_spin) +PRVM_DECLARE_clientglobalfloat(particle_stainalpha) +PRVM_DECLARE_clientglobalfloat(particle_stainsize) +PRVM_DECLARE_clientglobalfloat(particle_staintex) +PRVM_DECLARE_clientglobalfloat(particle_stretch) +PRVM_DECLARE_clientglobalfloat(particle_tex) +PRVM_DECLARE_clientglobalfloat(particle_time) +PRVM_DECLARE_clientglobalfloat(particle_type) +PRVM_DECLARE_clientglobalfloat(particle_velocityjitter) +PRVM_DECLARE_clientglobalfloat(particles_alphamax) +PRVM_DECLARE_clientglobalfloat(particles_alphamin) +PRVM_DECLARE_clientglobalfloat(player_localentnum) +PRVM_DECLARE_clientglobalfloat(player_localnum) +PRVM_DECLARE_clientglobalfloat(require_spawnfunc_prefix) +PRVM_DECLARE_clientglobalfloat(sb_showscores) +PRVM_DECLARE_clientglobalfloat(servercommandframe) +PRVM_DECLARE_clientglobalfloat(serverdeltatime) +PRVM_DECLARE_clientglobalfloat(serverprevtime) +PRVM_DECLARE_clientglobalfloat(servertime) +PRVM_DECLARE_clientglobalfloat(time) +PRVM_DECLARE_clientglobalfloat(trace_allsolid) +PRVM_DECLARE_clientglobalfloat(trace_dphitcontents) +PRVM_DECLARE_clientglobalfloat(trace_dphitq3surfaceflags) +PRVM_DECLARE_clientglobalfloat(trace_dpstartcontents) +PRVM_DECLARE_clientglobalfloat(trace_fraction) +PRVM_DECLARE_clientglobalfloat(trace_inopen) +PRVM_DECLARE_clientglobalfloat(trace_inwater) +PRVM_DECLARE_clientglobalfloat(trace_networkentity) +PRVM_DECLARE_clientglobalfloat(trace_plane_dist) +PRVM_DECLARE_clientglobalfloat(trace_startsolid) +PRVM_DECLARE_clientglobalfloat(transparent_offset) +PRVM_DECLARE_clientglobalstring(gettaginfo_name) +PRVM_DECLARE_clientglobalstring(mapname) +PRVM_DECLARE_clientglobalstring(trace_dphittexturename) +PRVM_DECLARE_clientglobalvector(dmg_origin) +PRVM_DECLARE_clientglobalvector(drawfontscale) +PRVM_DECLARE_clientglobalvector(gettaginfo_forward) +PRVM_DECLARE_clientglobalvector(gettaginfo_offset) +PRVM_DECLARE_clientglobalvector(gettaginfo_right) +PRVM_DECLARE_clientglobalvector(gettaginfo_up) +PRVM_DECLARE_clientglobalvector(input_angles) +PRVM_DECLARE_clientglobalvector(input_movevalues) +PRVM_DECLARE_clientglobalvector(particle_color1) +PRVM_DECLARE_clientglobalvector(particle_color2) +PRVM_DECLARE_clientglobalvector(particle_staincolor1) +PRVM_DECLARE_clientglobalvector(particle_staincolor2) +PRVM_DECLARE_clientglobalvector(particles_colormax) +PRVM_DECLARE_clientglobalvector(particles_colormin) +PRVM_DECLARE_clientglobalvector(pmove_inwater) +PRVM_DECLARE_clientglobalvector(pmove_maxs) +PRVM_DECLARE_clientglobalvector(pmove_mins) +PRVM_DECLARE_clientglobalvector(pmove_onground) +PRVM_DECLARE_clientglobalfloat(pmove_waterjumptime) +PRVM_DECLARE_clientglobalfloat(pmove_jump_held) +PRVM_DECLARE_clientglobalvector(pmove_org) +PRVM_DECLARE_clientglobalvector(pmove_vel) +PRVM_DECLARE_clientglobalvector(trace_endpos) +PRVM_DECLARE_clientglobalvector(trace_plane_normal) +PRVM_DECLARE_clientglobalvector(v_forward) +PRVM_DECLARE_clientglobalvector(v_right) +PRVM_DECLARE_clientglobalvector(v_up) +PRVM_DECLARE_clientglobalvector(view_angles) +PRVM_DECLARE_clientglobalvector(view_punchangle) +PRVM_DECLARE_clientglobalvector(view_punchvector) +PRVM_DECLARE_clientglobalfloat(sound_starttime) +PRVM_DECLARE_field(SendEntity) +PRVM_DECLARE_field(SendFlags) +PRVM_DECLARE_field(Version) +PRVM_DECLARE_field(absmax) +PRVM_DECLARE_field(absmin) +PRVM_DECLARE_field(aiment) +PRVM_DECLARE_field(alpha) +PRVM_DECLARE_field(ammo_cells) +PRVM_DECLARE_field(ammo_cells1) +PRVM_DECLARE_field(ammo_lava_nails) +PRVM_DECLARE_field(ammo_multi_rockets) +PRVM_DECLARE_field(ammo_nails) +PRVM_DECLARE_field(ammo_nails1) +PRVM_DECLARE_field(ammo_plasma) +PRVM_DECLARE_field(ammo_rockets) +PRVM_DECLARE_field(ammo_rockets1) +PRVM_DECLARE_field(ammo_shells) +PRVM_DECLARE_field(ammo_shells1) +PRVM_DECLARE_field(angles) +PRVM_DECLARE_field(armortype) +PRVM_DECLARE_field(armorvalue) +PRVM_DECLARE_field(avelocity) +PRVM_DECLARE_field(blocked) +PRVM_DECLARE_field(bouncefactor) +PRVM_DECLARE_field(bouncestop) +PRVM_DECLARE_field(button0) +PRVM_DECLARE_field(button1) +PRVM_DECLARE_field(button2) +PRVM_DECLARE_field(button3) +PRVM_DECLARE_field(button4) +PRVM_DECLARE_field(button5) +PRVM_DECLARE_field(button6) +PRVM_DECLARE_field(button7) +PRVM_DECLARE_field(button8) +PRVM_DECLARE_field(button9) +PRVM_DECLARE_field(button10) +PRVM_DECLARE_field(button11) +PRVM_DECLARE_field(button12) +PRVM_DECLARE_field(button13) +PRVM_DECLARE_field(button14) +PRVM_DECLARE_field(button15) +PRVM_DECLARE_field(button16) +PRVM_DECLARE_field(buttonchat) +PRVM_DECLARE_field(buttonuse) +PRVM_DECLARE_field(camera_transform) +PRVM_DECLARE_field(chain) +PRVM_DECLARE_field(classname) +PRVM_DECLARE_field(clientcamera) +PRVM_DECLARE_field(clientcolors) +PRVM_DECLARE_field(clientstatus) +PRVM_DECLARE_field(color) +PRVM_DECLARE_field(colormap) +PRVM_DECLARE_field(colormod) +PRVM_DECLARE_field(contentstransition) +PRVM_DECLARE_field(crypto_encryptmethod) +PRVM_DECLARE_field(crypto_idfp) +PRVM_DECLARE_field(crypto_keyfp) +PRVM_DECLARE_field(crypto_mykeyfp) +PRVM_DECLARE_field(crypto_signmethod) +PRVM_DECLARE_field(currentammo) +PRVM_DECLARE_field(cursor_active) +PRVM_DECLARE_field(cursor_screen) +PRVM_DECLARE_field(cursor_trace_endpos) +PRVM_DECLARE_field(cursor_trace_ent) +PRVM_DECLARE_field(cursor_trace_start) +PRVM_DECLARE_field(customizeentityforclient) +PRVM_DECLARE_field(deadflag) +PRVM_DECLARE_field(disableclientprediction) +PRVM_DECLARE_field(discardabledemo) +PRVM_DECLARE_field(dmg_inflictor) +PRVM_DECLARE_field(dmg_save) +PRVM_DECLARE_field(dmg_take) +PRVM_DECLARE_field(dphitcontentsmask) +PRVM_DECLARE_field(drawmask) +PRVM_DECLARE_field(drawonlytoclient) +PRVM_DECLARE_field(effects) +PRVM_DECLARE_field(enemy) +PRVM_DECLARE_field(entnum) +PRVM_DECLARE_field(exteriormodeltoclient) +PRVM_DECLARE_field(fixangle) +PRVM_DECLARE_field(flags) +PRVM_DECLARE_field(frags) +PRVM_DECLARE_field(frame) +PRVM_DECLARE_field(frame1time) +PRVM_DECLARE_field(frame2) +PRVM_DECLARE_field(frame2time) +PRVM_DECLARE_field(frame3) +PRVM_DECLARE_field(frame3time) +PRVM_DECLARE_field(frame4) +PRVM_DECLARE_field(frame4time) +PRVM_DECLARE_field(fullbright) +PRVM_DECLARE_field(glow_color) +PRVM_DECLARE_field(glow_size) +PRVM_DECLARE_field(glow_trail) +PRVM_DECLARE_field(glowmod) +PRVM_DECLARE_field(goalentity) +PRVM_DECLARE_field(gravity) +PRVM_DECLARE_field(groundentity) +PRVM_DECLARE_field(health) +PRVM_DECLARE_field(ideal_yaw) +PRVM_DECLARE_field(idealpitch) +PRVM_DECLARE_field(impulse) +PRVM_DECLARE_field(items) +PRVM_DECLARE_field(items2) +PRVM_DECLARE_field(geomtype) +PRVM_DECLARE_field(jointtype) +PRVM_DECLARE_field(forcetype) +PRVM_DECLARE_field(lerpfrac) +PRVM_DECLARE_field(lerpfrac3) +PRVM_DECLARE_field(lerpfrac4) +PRVM_DECLARE_field(light_lev) +PRVM_DECLARE_field(ltime) +PRVM_DECLARE_field(mass) +PRVM_DECLARE_field(massofs) +PRVM_DECLARE_field(friction) +PRVM_DECLARE_field(maxcontacts) +PRVM_DECLARE_field(erp) +PRVM_DECLARE_field(max_health) +PRVM_DECLARE_field(maxs) +PRVM_DECLARE_field(message) +PRVM_DECLARE_field(mins) +PRVM_DECLARE_field(model) +PRVM_DECLARE_field(modelflags) +PRVM_DECLARE_field(modelindex) +PRVM_DECLARE_field(movedir) +PRVM_DECLARE_field(movement) +PRVM_DECLARE_field(movetype) +PRVM_DECLARE_field(movetypesteplandevent) +PRVM_DECLARE_field(netaddress) +PRVM_DECLARE_field(netname) +PRVM_DECLARE_field(nextthink) +PRVM_DECLARE_field(nodrawtoclient) +PRVM_DECLARE_field(noise) +PRVM_DECLARE_field(noise1) +PRVM_DECLARE_field(noise2) +PRVM_DECLARE_field(noise3) +PRVM_DECLARE_field(oldorigin) +PRVM_DECLARE_field(origin) +PRVM_DECLARE_field(owner) +PRVM_DECLARE_field(pflags) +PRVM_DECLARE_field(ping) +PRVM_DECLARE_field(ping_movementloss) +PRVM_DECLARE_field(ping_packetloss) +PRVM_DECLARE_field(pitch_speed) +PRVM_DECLARE_field(playermodel) +PRVM_DECLARE_field(playerskin) +PRVM_DECLARE_field(pmodel) +PRVM_DECLARE_field(pmove_flags) +PRVM_DECLARE_field(predraw) +PRVM_DECLARE_field(punchangle) +PRVM_DECLARE_field(punchvector) +PRVM_DECLARE_field(renderamt) +PRVM_DECLARE_field(renderflags) +PRVM_DECLARE_field(scale) +PRVM_DECLARE_field(modelscale_vec) +PRVM_DECLARE_field(sendcomplexanimation) +PRVM_DECLARE_field(shadertime) +PRVM_DECLARE_field(size) +PRVM_DECLARE_field(skeletonindex) +PRVM_DECLARE_field(skin) +PRVM_DECLARE_field(solid) +PRVM_DECLARE_field(sounds) +PRVM_DECLARE_field(spawnflags) +PRVM_DECLARE_field(style) +PRVM_DECLARE_field(tag_entity) +PRVM_DECLARE_field(tag_index) +PRVM_DECLARE_field(takedamage) +PRVM_DECLARE_field(target) +PRVM_DECLARE_field(targetname) +PRVM_DECLARE_field(team) +PRVM_DECLARE_field(teleport_time) +PRVM_DECLARE_field(think) +PRVM_DECLARE_field(touch) +PRVM_DECLARE_field(traileffectnum) +PRVM_DECLARE_field(use) +PRVM_DECLARE_field(userwavefunc_param0) +PRVM_DECLARE_field(userwavefunc_param1) +PRVM_DECLARE_field(userwavefunc_param2) +PRVM_DECLARE_field(userwavefunc_param3) +PRVM_DECLARE_field(v_angle) +PRVM_DECLARE_field(velocity) +PRVM_DECLARE_field(modellight_ambient) +PRVM_DECLARE_field(modellight_diffuse) +PRVM_DECLARE_field(modellight_dir) +PRVM_DECLARE_field(view_ofs) +PRVM_DECLARE_field(viewmodelforclient) +PRVM_DECLARE_field(viewzoom) +PRVM_DECLARE_field(waterlevel) +PRVM_DECLARE_field(watertype) +PRVM_DECLARE_field(weapon) +PRVM_DECLARE_field(weaponframe) +PRVM_DECLARE_field(weaponmodel) +PRVM_DECLARE_field(yaw_speed) +PRVM_DECLARE_function(CSQC_ConsoleCommand) +PRVM_DECLARE_function(CSQC_Ent_Remove) +PRVM_DECLARE_function(CSQC_Ent_Spawn) +PRVM_DECLARE_function(CSQC_Ent_Update) +PRVM_DECLARE_function(CSQC_Event) +PRVM_DECLARE_function(CSQC_Event_Sound) +PRVM_DECLARE_function(CSQC_Init) +PRVM_DECLARE_function(CSQC_InputEvent) +PRVM_DECLARE_function(CSQC_Parse_CenterPrint) +PRVM_DECLARE_function(CSQC_Parse_Print) +PRVM_DECLARE_function(CSQC_Parse_StuffCmd) +PRVM_DECLARE_function(CSQC_Parse_TempEntity) +PRVM_DECLARE_function(CSQC_Shutdown) +PRVM_DECLARE_function(CSQC_UpdateView) +PRVM_DECLARE_function(ClientConnect) +PRVM_DECLARE_function(ClientDisconnect) +PRVM_DECLARE_function(ClientKill) +PRVM_DECLARE_function(EndFrame) +PRVM_DECLARE_function(GameCommand) +PRVM_DECLARE_function(PlayerPostThink) +PRVM_DECLARE_function(PlayerPreThink) +PRVM_DECLARE_function(PutClientInServer) +PRVM_DECLARE_function(RestoreGame) +PRVM_DECLARE_function(SV_ChangeTeam) +PRVM_DECLARE_function(SV_OnEntityNoSpawnFunction) +PRVM_DECLARE_function(SV_OnEntityPostSpawnFunction) +PRVM_DECLARE_function(SV_OnEntityPreSpawnFunction) +PRVM_DECLARE_function(SV_ParseClientCommand) +PRVM_DECLARE_function(SV_PausedTic) +PRVM_DECLARE_function(SV_PlayerPhysics) +PRVM_DECLARE_function(SV_Shutdown) +PRVM_DECLARE_function(SetChangeParms) +PRVM_DECLARE_function(SetNewParms) +PRVM_DECLARE_function(StartFrame) +PRVM_DECLARE_function(URI_Get_Callback) +PRVM_DECLARE_function(m_draw) +PRVM_DECLARE_function(m_init) +PRVM_DECLARE_function(m_keydown) +PRVM_DECLARE_function(m_keyup) +PRVM_DECLARE_function(m_newmap) +PRVM_DECLARE_function(m_shutdown) +PRVM_DECLARE_function(m_toggle) +PRVM_DECLARE_function(main) +PRVM_DECLARE_global(SV_InitCmd) +PRVM_DECLARE_global(clientcommandframe) +PRVM_DECLARE_global(coop) +PRVM_DECLARE_global(deathmatch) +PRVM_DECLARE_global(dmg_origin) +PRVM_DECLARE_global(dmg_save) +PRVM_DECLARE_global(dmg_take) +PRVM_DECLARE_global(drawfont) +PRVM_DECLARE_global(drawfontscale) +PRVM_DECLARE_global(force_retouch) +PRVM_DECLARE_global(found_secrets) +PRVM_DECLARE_global(frametime) +PRVM_DECLARE_global(gettaginfo_forward) +PRVM_DECLARE_global(gettaginfo_name) +PRVM_DECLARE_global(gettaginfo_offset) +PRVM_DECLARE_global(gettaginfo_parent) +PRVM_DECLARE_global(gettaginfo_right) +PRVM_DECLARE_global(gettaginfo_up) +PRVM_DECLARE_global(getlight_ambient) +PRVM_DECLARE_global(getlight_diffuse) +PRVM_DECLARE_global(getlight_dir) +PRVM_DECLARE_global(input_angles) +PRVM_DECLARE_global(input_buttons) +PRVM_DECLARE_global(input_movevalues) +PRVM_DECLARE_global(input_timelength) +PRVM_DECLARE_global(intermission) +PRVM_DECLARE_global(killed_monsters) +PRVM_DECLARE_global(mapname) +PRVM_DECLARE_global(maxclients) +PRVM_DECLARE_global(movevar_accelerate) +PRVM_DECLARE_global(movevar_airaccelerate) +PRVM_DECLARE_global(movevar_entgravity) +PRVM_DECLARE_global(movevar_friction) +PRVM_DECLARE_global(movevar_gravity) +PRVM_DECLARE_global(movevar_maxspeed) +PRVM_DECLARE_global(movevar_spectatormaxspeed) +PRVM_DECLARE_global(movevar_stopspeed) +PRVM_DECLARE_global(movevar_wateraccelerate) +PRVM_DECLARE_global(movevar_waterfriction) +PRVM_DECLARE_global(msg_entity) +PRVM_DECLARE_global(other) +PRVM_DECLARE_global(parm1) +PRVM_DECLARE_global(parm2) +PRVM_DECLARE_global(parm3) +PRVM_DECLARE_global(parm4) +PRVM_DECLARE_global(parm5) +PRVM_DECLARE_global(parm6) +PRVM_DECLARE_global(parm7) +PRVM_DECLARE_global(parm8) +PRVM_DECLARE_global(parm9) +PRVM_DECLARE_global(parm10) +PRVM_DECLARE_global(parm11) +PRVM_DECLARE_global(parm12) +PRVM_DECLARE_global(parm13) +PRVM_DECLARE_global(parm14) +PRVM_DECLARE_global(parm15) +PRVM_DECLARE_global(parm16) +PRVM_DECLARE_global(particle_airfriction) +PRVM_DECLARE_global(particle_alpha) +PRVM_DECLARE_global(particle_alphafade) +PRVM_DECLARE_global(particle_angle) +PRVM_DECLARE_global(particle_blendmode) +PRVM_DECLARE_global(particle_bounce) +PRVM_DECLARE_global(particle_color1) +PRVM_DECLARE_global(particle_color2) +PRVM_DECLARE_global(particle_delaycollision) +PRVM_DECLARE_global(particle_delayspawn) +PRVM_DECLARE_global(particle_gravity) +PRVM_DECLARE_global(particle_liquidfriction) +PRVM_DECLARE_global(particle_orientation) +PRVM_DECLARE_global(particle_originjitter) +PRVM_DECLARE_global(particle_qualityreduction) +PRVM_DECLARE_global(particle_size) +PRVM_DECLARE_global(particle_sizeincrease) +PRVM_DECLARE_global(particle_spin) +PRVM_DECLARE_global(particle_stainalpha) +PRVM_DECLARE_global(particle_staincolor1) +PRVM_DECLARE_global(particle_staincolor2) +PRVM_DECLARE_global(particle_stainsize) +PRVM_DECLARE_global(particle_staintex) +PRVM_DECLARE_global(particle_stretch) +PRVM_DECLARE_global(particle_tex) +PRVM_DECLARE_global(particle_time) +PRVM_DECLARE_global(particle_type) +PRVM_DECLARE_global(particle_velocityjitter) +PRVM_DECLARE_global(particles_alphamax) +PRVM_DECLARE_global(particles_alphamin) +PRVM_DECLARE_global(particles_colormax) +PRVM_DECLARE_global(particles_colormin) +PRVM_DECLARE_global(player_localentnum) +PRVM_DECLARE_global(player_localnum) +PRVM_DECLARE_global(pmove_inwater) +PRVM_DECLARE_global(pmove_maxs) +PRVM_DECLARE_global(pmove_mins) +PRVM_DECLARE_global(pmove_onground) +PRVM_DECLARE_global(pmove_waterjumptime) +PRVM_DECLARE_global(pmove_jump_held) +PRVM_DECLARE_global(pmove_org) +PRVM_DECLARE_global(pmove_vel) +PRVM_DECLARE_global(require_spawnfunc_prefix) +PRVM_DECLARE_global(sb_showscores) +PRVM_DECLARE_global(self) +PRVM_DECLARE_global(servercommandframe) +PRVM_DECLARE_global(serverdeltatime) +PRVM_DECLARE_global(serverflags) +PRVM_DECLARE_global(serverprevtime) +PRVM_DECLARE_global(servertime) +PRVM_DECLARE_global(teamplay) +PRVM_DECLARE_global(time) +PRVM_DECLARE_global(total_monsters) +PRVM_DECLARE_global(total_secrets) +PRVM_DECLARE_global(trace_allsolid) +PRVM_DECLARE_global(trace_dphitcontents) +PRVM_DECLARE_global(trace_dphitq3surfaceflags) +PRVM_DECLARE_global(trace_dphittexturename) +PRVM_DECLARE_global(trace_dpstartcontents) +PRVM_DECLARE_global(trace_endpos) +PRVM_DECLARE_global(trace_ent) +PRVM_DECLARE_global(trace_fraction) +PRVM_DECLARE_global(trace_inopen) +PRVM_DECLARE_global(trace_inwater) +PRVM_DECLARE_global(trace_networkentity) +PRVM_DECLARE_global(trace_plane_dist) +PRVM_DECLARE_global(trace_plane_normal) +PRVM_DECLARE_global(trace_startsolid) +PRVM_DECLARE_global(transparent_offset) +PRVM_DECLARE_global(v_forward) +PRVM_DECLARE_global(v_right) +PRVM_DECLARE_global(v_up) +PRVM_DECLARE_global(view_angles) +PRVM_DECLARE_global(view_punchangle) +PRVM_DECLARE_global(view_punchvector) +PRVM_DECLARE_global(world) +PRVM_DECLARE_global(worldstatus) +PRVM_DECLARE_global(sound_starttime) +PRVM_DECLARE_menufieldstring(classname) +PRVM_DECLARE_menufunction(GameCommand) +PRVM_DECLARE_menufunction(URI_Get_Callback) +PRVM_DECLARE_menufunction(m_draw) +PRVM_DECLARE_menufunction(m_init) +PRVM_DECLARE_menufunction(m_keydown) +PRVM_DECLARE_menufunction(m_keyup) +PRVM_DECLARE_menufunction(m_newmap) +PRVM_DECLARE_menufunction(m_shutdown) +PRVM_DECLARE_menufunction(m_toggle) +PRVM_DECLARE_menuglobaledict(self) +PRVM_DECLARE_menuglobalfloat(drawfont) +PRVM_DECLARE_menuglobalfloat(require_spawnfunc_prefix) +PRVM_DECLARE_menuglobalvector(drawfontscale) +PRVM_DECLARE_serverfieldedict(aiment) +PRVM_DECLARE_serverfieldedict(chain) +PRVM_DECLARE_serverfieldedict(clientcamera) +PRVM_DECLARE_serverfieldedict(cursor_trace_ent) +PRVM_DECLARE_serverfieldedict(dmg_inflictor) +PRVM_DECLARE_serverfieldedict(drawonlytoclient) +PRVM_DECLARE_serverfieldedict(enemy) +PRVM_DECLARE_serverfieldedict(exteriormodeltoclient) +PRVM_DECLARE_serverfieldedict(goalentity) +PRVM_DECLARE_serverfieldedict(groundentity) +PRVM_DECLARE_serverfieldedict(nodrawtoclient) +PRVM_DECLARE_serverfieldedict(owner) +PRVM_DECLARE_serverfieldedict(tag_entity) +PRVM_DECLARE_serverfieldedict(viewmodelforclient) +PRVM_DECLARE_serverfieldfloat(SendFlags) +PRVM_DECLARE_serverfieldfloat(Version) +PRVM_DECLARE_serverfieldfloat(alpha) +PRVM_DECLARE_serverfieldfloat(ammo_cells) +PRVM_DECLARE_serverfieldfloat(ammo_cells1) +PRVM_DECLARE_serverfieldfloat(ammo_lava_nails) +PRVM_DECLARE_serverfieldfloat(ammo_multi_rockets) +PRVM_DECLARE_serverfieldfloat(ammo_nails) +PRVM_DECLARE_serverfieldfloat(ammo_nails1) +PRVM_DECLARE_serverfieldfloat(ammo_plasma) +PRVM_DECLARE_serverfieldfloat(ammo_rockets) +PRVM_DECLARE_serverfieldfloat(ammo_rockets1) +PRVM_DECLARE_serverfieldfloat(ammo_shells) +PRVM_DECLARE_serverfieldfloat(ammo_shells1) +PRVM_DECLARE_serverfieldfloat(armortype) +PRVM_DECLARE_serverfieldfloat(armorvalue) +PRVM_DECLARE_serverfieldfloat(bouncefactor) +PRVM_DECLARE_serverfieldfloat(bouncestop) +PRVM_DECLARE_serverfieldfloat(button0) +PRVM_DECLARE_serverfieldfloat(button1) +PRVM_DECLARE_serverfieldfloat(button2) +PRVM_DECLARE_serverfieldfloat(button3) +PRVM_DECLARE_serverfieldfloat(button4) +PRVM_DECLARE_serverfieldfloat(button5) +PRVM_DECLARE_serverfieldfloat(button6) +PRVM_DECLARE_serverfieldfloat(button7) +PRVM_DECLARE_serverfieldfloat(button8) +PRVM_DECLARE_serverfieldfloat(button9) +PRVM_DECLARE_serverfieldfloat(button10) +PRVM_DECLARE_serverfieldfloat(button11) +PRVM_DECLARE_serverfieldfloat(button12) +PRVM_DECLARE_serverfieldfloat(button13) +PRVM_DECLARE_serverfieldfloat(button14) +PRVM_DECLARE_serverfieldfloat(button15) +PRVM_DECLARE_serverfieldfloat(button16) +PRVM_DECLARE_serverfieldfloat(buttonchat) +PRVM_DECLARE_serverfieldfloat(buttonuse) +PRVM_DECLARE_serverfieldfloat(clientcolors) +PRVM_DECLARE_serverfieldfloat(colormap) +PRVM_DECLARE_serverfieldfloat(currentammo) +PRVM_DECLARE_serverfieldfloat(cursor_active) +PRVM_DECLARE_serverfieldfloat(deadflag) +PRVM_DECLARE_serverfieldfloat(disableclientprediction) +PRVM_DECLARE_serverfieldfloat(discardabledemo) +PRVM_DECLARE_serverfieldfloat(dmg_save) +PRVM_DECLARE_serverfieldfloat(dmg_take) +PRVM_DECLARE_serverfieldfloat(dphitcontentsmask) +PRVM_DECLARE_serverfieldfloat(effects) +PRVM_DECLARE_serverfieldfloat(fixangle) +PRVM_DECLARE_serverfieldfloat(flags) +PRVM_DECLARE_serverfieldfloat(frags) +PRVM_DECLARE_serverfieldfloat(frame) +PRVM_DECLARE_serverfieldfloat(frame1time) +PRVM_DECLARE_serverfieldfloat(frame2) +PRVM_DECLARE_serverfieldfloat(frame2time) +PRVM_DECLARE_serverfieldfloat(frame3) +PRVM_DECLARE_serverfieldfloat(frame3time) +PRVM_DECLARE_serverfieldfloat(frame4) +PRVM_DECLARE_serverfieldfloat(frame4time) +PRVM_DECLARE_serverfieldfloat(fullbright) +PRVM_DECLARE_serverfieldfloat(glow_color) +PRVM_DECLARE_serverfieldfloat(glow_size) +PRVM_DECLARE_serverfieldfloat(glow_trail) +PRVM_DECLARE_serverfieldfloat(gravity) +PRVM_DECLARE_serverfieldfloat(health) +PRVM_DECLARE_serverfieldfloat(ideal_yaw) +PRVM_DECLARE_serverfieldfloat(idealpitch) +PRVM_DECLARE_serverfieldfloat(impulse) +PRVM_DECLARE_serverfieldfloat(items) +PRVM_DECLARE_serverfieldfloat(items2) +PRVM_DECLARE_serverfieldfloat(geomtype) +PRVM_DECLARE_serverfieldfloat(jointtype) +PRVM_DECLARE_serverfieldfloat(forcetype) +PRVM_DECLARE_serverfieldfloat(lerpfrac) +PRVM_DECLARE_serverfieldfloat(lerpfrac3) +PRVM_DECLARE_serverfieldfloat(lerpfrac4) +PRVM_DECLARE_serverfieldfloat(light_lev) +PRVM_DECLARE_serverfieldfloat(ltime) +PRVM_DECLARE_serverfieldfloat(mass) +PRVM_DECLARE_serverfieldvector(massofs) +PRVM_DECLARE_serverfieldfloat(friction) +PRVM_DECLARE_serverfieldfloat(maxcontacts) +PRVM_DECLARE_serverfieldfloat(erp) +PRVM_DECLARE_serverfieldfloat(max_health) +PRVM_DECLARE_serverfieldfloat(modelflags) +PRVM_DECLARE_serverfieldfloat(modelindex) +PRVM_DECLARE_serverfieldfloat(movetype) +PRVM_DECLARE_serverfieldfloat(nextthink) +PRVM_DECLARE_serverfieldfloat(pflags) +PRVM_DECLARE_serverfieldfloat(ping) +PRVM_DECLARE_serverfieldfloat(ping_movementloss) +PRVM_DECLARE_serverfieldfloat(ping_packetloss) +PRVM_DECLARE_serverfieldfloat(pitch_speed) +PRVM_DECLARE_serverfieldfloat(pmodel) +PRVM_DECLARE_serverfieldfloat(renderamt) +PRVM_DECLARE_serverfieldfloat(scale) +PRVM_DECLARE_serverfieldvector(modelscale_vec) +PRVM_DECLARE_serverfieldfloat(sendcomplexanimation) +PRVM_DECLARE_serverfieldfloat(skeletonindex) +PRVM_DECLARE_serverfieldfloat(skin) +PRVM_DECLARE_serverfieldfloat(solid) +PRVM_DECLARE_serverfieldfloat(sounds) +PRVM_DECLARE_serverfieldfloat(spawnflags) +PRVM_DECLARE_serverfieldfloat(style) +PRVM_DECLARE_serverfieldfloat(tag_index) +PRVM_DECLARE_serverfieldfloat(takedamage) +PRVM_DECLARE_serverfieldfloat(team) +PRVM_DECLARE_serverfieldfloat(teleport_time) +PRVM_DECLARE_serverfieldfloat(traileffectnum) +PRVM_DECLARE_serverfieldfloat(viewzoom) +PRVM_DECLARE_serverfieldfloat(waterlevel) +PRVM_DECLARE_serverfieldfloat(watertype) +PRVM_DECLARE_serverfieldfloat(weapon) +PRVM_DECLARE_serverfieldfloat(weaponframe) +PRVM_DECLARE_serverfieldfloat(yaw_speed) +PRVM_DECLARE_serverfieldfunction(SendEntity) +PRVM_DECLARE_serverfieldfunction(blocked) +PRVM_DECLARE_serverfieldfunction(camera_transform) +PRVM_DECLARE_serverfieldfunction(contentstransition) +PRVM_DECLARE_serverfieldfunction(customizeentityforclient) +PRVM_DECLARE_serverfieldfunction(movetypesteplandevent) +PRVM_DECLARE_serverfieldfunction(think) +PRVM_DECLARE_serverfieldfunction(touch) +PRVM_DECLARE_serverfieldfunction(use) +PRVM_DECLARE_serverfieldstring(classname) +PRVM_DECLARE_serverfieldstring(clientstatus) +PRVM_DECLARE_serverfieldstring(crypto_encryptmethod) +PRVM_DECLARE_serverfieldstring(crypto_idfp) +PRVM_DECLARE_serverfieldstring(crypto_keyfp) +PRVM_DECLARE_serverfieldstring(crypto_mykeyfp) +PRVM_DECLARE_serverfieldstring(crypto_signmethod) +PRVM_DECLARE_serverfieldstring(message) +PRVM_DECLARE_serverfieldstring(model) +PRVM_DECLARE_serverfieldstring(netaddress) +PRVM_DECLARE_serverfieldstring(netname) +PRVM_DECLARE_serverfieldstring(noise) +PRVM_DECLARE_serverfieldstring(noise1) +PRVM_DECLARE_serverfieldstring(noise2) +PRVM_DECLARE_serverfieldstring(noise3) +PRVM_DECLARE_serverfieldstring(playermodel) +PRVM_DECLARE_serverfieldstring(playerskin) +PRVM_DECLARE_serverfieldstring(target) +PRVM_DECLARE_serverfieldstring(targetname) +PRVM_DECLARE_serverfieldstring(weaponmodel) +PRVM_DECLARE_serverfieldvector(absmax) +PRVM_DECLARE_serverfieldvector(absmin) +PRVM_DECLARE_serverfieldvector(angles) +PRVM_DECLARE_serverfieldvector(avelocity) +PRVM_DECLARE_serverfieldvector(color) +PRVM_DECLARE_serverfieldvector(colormod) +PRVM_DECLARE_serverfieldvector(cursor_screen) +PRVM_DECLARE_serverfieldvector(cursor_trace_endpos) +PRVM_DECLARE_serverfieldvector(cursor_trace_start) +PRVM_DECLARE_serverfieldvector(glowmod) +PRVM_DECLARE_serverfieldvector(maxs) +PRVM_DECLARE_serverfieldvector(mins) +PRVM_DECLARE_serverfieldvector(movedir) +PRVM_DECLARE_serverfieldvector(movement) +PRVM_DECLARE_serverfieldvector(oldorigin) +PRVM_DECLARE_serverfieldvector(origin) +PRVM_DECLARE_serverfieldvector(punchangle) +PRVM_DECLARE_serverfieldvector(punchvector) +PRVM_DECLARE_serverfieldvector(size) +PRVM_DECLARE_serverfieldvector(v_angle) +PRVM_DECLARE_serverfieldvector(velocity) +PRVM_DECLARE_serverfieldvector(view_ofs) +PRVM_DECLARE_serverfunction(ClientConnect) +PRVM_DECLARE_serverfunction(ClientDisconnect) +PRVM_DECLARE_serverfunction(ClientKill) +PRVM_DECLARE_serverfunction(EndFrame) +PRVM_DECLARE_serverfunction(GameCommand) +PRVM_DECLARE_serverfunction(PlayerPostThink) +PRVM_DECLARE_serverfunction(PlayerPreThink) +PRVM_DECLARE_serverfunction(PutClientInServer) +PRVM_DECLARE_serverfunction(RestoreGame) +PRVM_DECLARE_serverfunction(SV_ChangeTeam) +PRVM_DECLARE_serverfunction(SV_OnEntityNoSpawnFunction) +PRVM_DECLARE_serverfunction(SV_OnEntityPostSpawnFunction) +PRVM_DECLARE_serverfunction(SV_OnEntityPreSpawnFunction) +PRVM_DECLARE_serverfunction(SV_ParseClientCommand) +PRVM_DECLARE_serverfunction(SV_PausedTic) +PRVM_DECLARE_serverfunction(SV_PlayerPhysics) +PRVM_DECLARE_serverfunction(SV_Shutdown) +PRVM_DECLARE_serverfunction(SetChangeParms) +PRVM_DECLARE_serverfunction(SetNewParms) +PRVM_DECLARE_serverfunction(StartFrame) +PRVM_DECLARE_serverfunction(URI_Get_Callback) +PRVM_DECLARE_serverfunction(main) +PRVM_DECLARE_serverglobaledict(msg_entity) +PRVM_DECLARE_serverglobaledict(other) +PRVM_DECLARE_serverglobaledict(self) +PRVM_DECLARE_serverglobaledict(trace_ent) +PRVM_DECLARE_serverglobaledict(world) +PRVM_DECLARE_serverglobalfloat(coop) +PRVM_DECLARE_serverglobalfloat(deathmatch) +PRVM_DECLARE_serverglobalfloat(force_retouch) +PRVM_DECLARE_serverglobalfloat(found_secrets) +PRVM_DECLARE_serverglobalfloat(frametime) +PRVM_DECLARE_serverglobalfloat(gettaginfo_parent) +PRVM_DECLARE_serverglobalfloat(killed_monsters) +PRVM_DECLARE_serverglobalfloat(parm1) +PRVM_DECLARE_serverglobalfloat(parm2) +PRVM_DECLARE_serverglobalfloat(parm3) +PRVM_DECLARE_serverglobalfloat(parm4) +PRVM_DECLARE_serverglobalfloat(parm5) +PRVM_DECLARE_serverglobalfloat(parm6) +PRVM_DECLARE_serverglobalfloat(parm7) +PRVM_DECLARE_serverglobalfloat(parm8) +PRVM_DECLARE_serverglobalfloat(parm9) +PRVM_DECLARE_serverglobalfloat(parm10) +PRVM_DECLARE_serverglobalfloat(parm11) +PRVM_DECLARE_serverglobalfloat(parm12) +PRVM_DECLARE_serverglobalfloat(parm13) +PRVM_DECLARE_serverglobalfloat(parm14) +PRVM_DECLARE_serverglobalfloat(parm15) +PRVM_DECLARE_serverglobalfloat(parm16) +PRVM_DECLARE_serverglobalfloat(require_spawnfunc_prefix) +PRVM_DECLARE_serverglobalfloat(serverflags) +PRVM_DECLARE_serverglobalfloat(teamplay) +PRVM_DECLARE_serverglobalfloat(time) +PRVM_DECLARE_serverglobalfloat(total_monsters) +PRVM_DECLARE_serverglobalfloat(total_secrets) +PRVM_DECLARE_serverglobalfloat(trace_allsolid) +PRVM_DECLARE_serverglobalfloat(trace_dphitcontents) +PRVM_DECLARE_serverglobalfloat(trace_dphitq3surfaceflags) +PRVM_DECLARE_serverglobalfloat(trace_dpstartcontents) +PRVM_DECLARE_serverglobalfloat(trace_fraction) +PRVM_DECLARE_serverglobalfloat(trace_inopen) +PRVM_DECLARE_serverglobalfloat(trace_inwater) +PRVM_DECLARE_serverglobalfloat(trace_plane_dist) +PRVM_DECLARE_serverglobalfloat(trace_startsolid) +PRVM_DECLARE_serverglobalstring(SV_InitCmd) +PRVM_DECLARE_serverglobalstring(gettaginfo_name) +PRVM_DECLARE_serverglobalstring(mapname) +PRVM_DECLARE_serverglobalstring(trace_dphittexturename) +PRVM_DECLARE_serverglobalstring(worldstatus) +PRVM_DECLARE_serverglobalvector(gettaginfo_forward) +PRVM_DECLARE_serverglobalvector(gettaginfo_offset) +PRVM_DECLARE_serverglobalvector(gettaginfo_right) +PRVM_DECLARE_serverglobalvector(gettaginfo_up) +PRVM_DECLARE_serverglobalvector(trace_endpos) +PRVM_DECLARE_serverglobalvector(trace_plane_normal) +PRVM_DECLARE_serverglobalvector(v_forward) +PRVM_DECLARE_serverglobalvector(v_right) +PRVM_DECLARE_serverglobalvector(v_up) diff --git a/app/jni/qtypes.h b/app/jni/qtypes.h new file mode 100644 index 0000000..e0ba637 --- /dev/null +++ b/app/jni/qtypes.h @@ -0,0 +1,65 @@ + +#ifndef QTYPES_H +#define QTYPES_H + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum qboolean_e {false, true} qboolean; +#else +typedef bool qboolean; +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef FALSE +#define FALSE false +#define TRUE true +#endif + +// up / down +#define PITCH 0 + +// left / right +#define YAW 1 + +// fall over +#define ROLL 2 + +#if defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1400) +#define RESTRICT __restrict +#else +#define RESTRICT +#endif + +// LordHavoc: upgrade the prvm to double precision for better time values +// LordHavoc: to be enabled when bugs are worked out... +//#define PRVM_64 +#ifdef PRVM_64 +typedef double prvm_vec_t; +typedef long long prvm_int_t; +typedef unsigned long long prvm_uint_t; +#else +typedef float prvm_vec_t; +typedef int prvm_int_t; +typedef unsigned int prvm_uint_t; +#endif +typedef prvm_vec_t prvm_vec3_t[3]; + +#ifdef VEC_64 +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef vec_t vec5_t[5]; +typedef vec_t vec6_t[6]; +typedef vec_t vec7_t[7]; +typedef vec_t vec8_t[8]; + +#endif diff --git a/app/jni/quakedef.h b/app/jni/quakedef.h new file mode 100644 index 0000000..2962cb4 --- /dev/null +++ b/app/jni/quakedef.h @@ -0,0 +1,550 @@ +/* +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. + +*/ +// quakedef.h -- primary header for client + +#ifndef QUAKEDEF_H +#define QUAKEDEF_H + +#if defined(__GNUC__) && (__GNUC__ > 2) +#define DP_FUNC_PRINTF(n) __attribute__ ((format (printf, n, n+1))) +#define DP_FUNC_PURE __attribute__ ((pure)) +#define DP_FUNC_NORETURN __attribute__ ((noreturn)) +#else +#define DP_FUNC_PRINTF(n) +#define DP_FUNC_PURE +#define DP_FUNC_NORETURN +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qtypes.h" + +extern const char *buildstring; +extern char engineversion[128]; + +#define GAMENAME "id1" + +#define MAX_NUM_ARGVS 50 + +#ifdef DP_SMALLMEMORY +#define MAX_INPUTLINE 1024 +#define CON_TEXTSIZE 16384 +#define CON_MAXLINES 256 +#define HIST_TEXTSIZE 2048 +#define HIST_MAXLINES 16 +#define MAX_ALIAS_NAME 32 +#define CMDBUFSIZE 131072 +#define MAX_ARGS 80 + +#define NET_MAXMESSAGE 65536 +#define MAX_PACKETFRAGMENT 1024 +#define MAX_EDICTS 4096 +#define MAX_MODELS 1024 +#define MAX_SOUNDS 1024 +#define MAX_LIGHTSTYLES 64 +#define MAX_STYLESTRING 16 +#define MAX_SCOREBOARD 32 +#define MAX_SCOREBOARDNAME 128 +#define MAX_USERINFO_STRING 196 +#define MAX_SERVERINFO_STRING 512 +#define MAX_LOCALINFO_STRING 1 // not actually used by DP servers +#define CL_MAX_USERCMDS 32 +#define CVAR_HASHSIZE 1024 +#define M_MAX_EDICTS 4096 +#define MAX_DEMOS 8 +#define MAX_DEMONAME 16 +#define MAX_SAVEGAMES 12 +#define SAVEGAME_COMMENT_LENGTH 39 +#define MAX_CLIENTNETWORKEYES 2 +#define MAX_LEVELNETWORKEYES 0 // no portal support +#define MAX_OCCLUSION_QUERIES 256 + +#define CRYPTO_HOSTKEY_HASHSIZE 256 +#define MAX_NETWM_ICON 1026 // one 32x32 + +#define MAX_WATERPLANES 2 +#define MAX_CUBEMAPS 1024 +#define MAX_EXPLOSIONS 8 +#define MAX_DLIGHTS 16 +#define MAX_CACHED_PICS 1024 // this is 144 bytes each (or 152 on 64bit) +#define CACHEPICHASHSIZE 256 +#define MAX_PARTICLEEFFECTNAME 256 +#define MAX_PARTICLEEFFECTINFO 1024 +#define MAX_PARTICLETEXTURES 256 +#define MAXCLVIDEOS 1 +#define MAX_DYNAMIC_TEXTURE_COUNT 2 +#define MAX_MAP_LEAFS 8192 + +#define MAXTRACKS 256 +#define MAX_DYNAMIC_CHANNELS 64 +#define MAX_CHANNELS 260 +#define MODLIST_TOTALSIZE 32 +#define MAX_FAVORITESERVERS 32 +#define MAX_DECALSYSTEM_QUEUE 64 +#define PAINTBUFFER_SIZE 512 +#define MAX_BINDMAPS 8 +#define MAX_PARTICLES_INITIAL 8192 +#define MAX_PARTICLES 8192 +#define MAX_DECALS_INITIAL 1024 +#define MAX_DECALS 1024 +#define MAX_ENITIES_INITIAL 256 +#define MAX_STATICENTITIES 256 +#define MAX_EFFECTS 16 +#define MAX_BEAMS 16 +#define MAX_TEMPENTITIES 256 +#define SERVERLIST_TOTALSIZE 1024 +#define SERVERLIST_ANDMASKCOUNT 5 +#define SERVERLIST_ORMASKCOUNT 5 +#else +#define MAX_INPUTLINE 16384 ///< maximum length of console commandline, QuakeC strings, and many other text processing buffers +#define CON_TEXTSIZE 1048576 ///< max scrollback buffer characters in console +#define CON_MAXLINES 16384 ///< max scrollback buffer lines in console +#define HIST_TEXTSIZE 262144 ///< max command history buffer characters in console +#define HIST_MAXLINES 4096 ///< max command history buffer lines in console +#define MAX_ALIAS_NAME 32 +#define CMDBUFSIZE 655360 ///< maximum script size that can be loaded by the exec command (8192 in Quake) +#define MAX_ARGS 80 ///< maximum number of parameters to a console command or alias + +#define NET_MAXMESSAGE 65536 ///< max reliable packet size (sent as multiple fragments of MAX_PACKETFRAGMENT) +#define MAX_PACKETFRAGMENT 1024 ///< max length of packet fragment +#define MAX_EDICTS 32768 ///< max number of objects in game world at once (32768 protocol limit) +#define MAX_MODELS 8192 ///< max number of models loaded at once (including during level transitions) +#define MAX_SOUNDS 4096 ///< max number of sounds loaded at once +#define MAX_LIGHTSTYLES 256 ///< max flickering light styles in level (note: affects savegame format) +#define MAX_STYLESTRING 64 ///< max length of flicker pattern for light style +#define MAX_SCOREBOARD 255 ///< max number of players in game at once (255 protocol limit) +#define MAX_SCOREBOARDNAME 128 ///< max length of player name in game +#define MAX_USERINFO_STRING 1280 ///< max length of infostring for PROTOCOL_QUAKEWORLD (196 in QuakeWorld) +#define MAX_SERVERINFO_STRING 1280 ///< max length of server infostring for PROTOCOL_QUAKEWORLD (512 in QuakeWorld) +#define MAX_LOCALINFO_STRING 32768 ///< max length of server-local infostring for PROTOCOL_QUAKEWORLD (32768 in QuakeWorld) +#define CL_MAX_USERCMDS 128 ///< max number of predicted input packets in queue +#define CVAR_HASHSIZE 65536 ///< number of hash buckets for accelerating cvar name lookups +#define M_MAX_EDICTS 32768 ///< max objects in menu vm +#define MAX_DEMOS 8 ///< max demos provided to demos command +#define MAX_DEMONAME 16 ///< max demo name length for demos command +#define MAX_SAVEGAMES 12 ///< max savegames listed in savegame menu +#define SAVEGAME_COMMENT_LENGTH 39 ///< max comment length of savegame in menu +#define MAX_CLIENTNETWORKEYES 16 ///< max number of locations that can be added to pvs when culling network entities (must be at least 2 for prediction) +#define MAX_LEVELNETWORKEYES 512 ///< max number of locations that can be added to pvs when culling network entities (must be at least 2 for prediction) +#define MAX_OCCLUSION_QUERIES 4096 ///< max number of GL_ARB_occlusion_query objects that can be used in one frame + +#define CRYPTO_HOSTKEY_HASHSIZE 8192 ///< number of hash buckets for accelerating host key lookups +#define MAX_NETWM_ICON 352822 // 16x16, 22x22, 24x24, 32x32, 48x48, 64x64, 128x128, 256x256, 512x512 + +#define MAX_WATERPLANES 16 ///< max number of water planes visible (each one causes additional view renders) +#define MAX_CUBEMAPS 1024 ///< max number of cubemap textures loaded for light filters +#define MAX_EXPLOSIONS 64 ///< max number of explosion shell effects active at once (not particle related) +#define MAX_DLIGHTS 256 ///< max number of dynamic lights (rocket flashes, etc) in scene at once +#define MAX_CACHED_PICS 1024 ///< max number of 2D pics loaded at once +#define CACHEPICHASHSIZE 256 ///< number of hash buckets for accelerating 2D pic name lookups +#define MAX_PARTICLEEFFECTNAME 256 ///< maximum number of unique names of particle effects (for particleeffectnum) +#define MAX_PARTICLEEFFECTINFO 4096 ///< maximum number of unique particle effects (each name may associate with several of these) +#define MAX_PARTICLETEXTURES 256 ///< maximum number of unique particle textures in the particle font +#define MAXCLVIDEOS 65 ///< maximum number of video streams being played back at once (1 is reserved for the playvideo command) +#define MAX_DYNAMIC_TEXTURE_COUNT 64 ///< maximum number of dynamic textures (web browsers, playvideo, etc) +#define MAX_MAP_LEAFS 65536 ///< maximum number of BSP leafs in world (8192 in Quake) + +#define MAXTRACKS 256 ///< max CD track index +// 0 to NUM_AMBIENTS - 1 = water, etc +// NUM_AMBIENTS to NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS - 1 = normal entity sounds +// NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS to total_channels = static sounds +#define MAX_DYNAMIC_CHANNELS 512 +#define MAX_CHANNELS 1028 +#define MODLIST_TOTALSIZE 256 +#define MAX_FAVORITESERVERS 256 +#define MAX_DECALSYSTEM_QUEUE 1024 +#define PAINTBUFFER_SIZE 2048 +#define MAX_BINDMAPS 8 +#define MAX_PARTICLES_INITIAL 8192 ///< initial allocation for cl.particles +#define MAX_PARTICLES 1048576 ///< upper limit on cl.particles size +#define MAX_DECALS_INITIAL 8192 ///< initial allocation for cl.decals +#define MAX_DECALS 1048576 ///< upper limit on cl.decals size +#define MAX_ENITIES_INITIAL 256 ///< initial size of cl.entities +#define MAX_STATICENTITIES 1024 ///< limit on size of cl.static_entities +#define MAX_EFFECTS 256 ///< limit on size of cl.effects +#define MAX_BEAMS 256 ///< limit on size of cl.beams +#define MAX_TEMPENTITIES 4096 ///< max number of temporary models visible per frame (certain sprite effects, certain types of CSQC entities also use this) +#define SERVERLIST_TOTALSIZE 2048 ///< max servers in the server list +#define SERVERLIST_ANDMASKCOUNT 16 ///< max items in server list AND mask +#define SERVERLIST_ORMASKCOUNT 16 ///< max items in server list OR mask +#endif + + +#define CMD_TOKENIZELENGTH (MAX_INPUTLINE + MAX_ARGS) ///< maximum tokenizable commandline length (counting trailing 0) + + +#define MAX_QPATH 128 ///< max length of a quake game pathname +#ifdef PATH_MAX +#define MAX_OSPATH PATH_MAX +#elif MAX_PATH +#define MAX_OSPATH MAX_PATH +#else +#define MAX_OSPATH 1024 ///< max length of a filesystem pathname +#endif + +#define ON_EPSILON 0.1 ///< point on plane side epsilon + +#define NET_MINRATE 1000 ///< limits "rate" and "sv_maxrate" cvars + +// +// stats are integers communicated to the client by the server +// +#define MAX_CL_STATS 256 +#define STAT_HEALTH 0 +//#define STAT_FRAGS 1 +#define STAT_WEAPON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR 4 +#define STAT_WEAPONFRAME 5 +#define STAT_SHELLS 6 +#define STAT_NAILS 7 +#define STAT_ROCKETS 8 +#define STAT_CELLS 9 +#define STAT_ACTIVEWEAPON 10 +#define STAT_TOTALSECRETS 11 +#define STAT_TOTALMONSTERS 12 +#define STAT_SECRETS 13 ///< bumped on client side by svc_foundsecret +#define STAT_MONSTERS 14 ///< bumped by svc_killedmonster +#define STAT_ITEMS 15 ///< FTE, DP +#define STAT_VIEWHEIGHT 16 ///< FTE, DP +//#define STAT_TIME 17 ///< FTE +//#define STAT_VIEW2 20 ///< FTE +#define STAT_VIEWZOOM 21 ///< DP +#define STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR 220 ///< DP +#define STAT_MOVEVARS_AIRCONTROL_PENALTY 221 ///< DP +#define STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW 222 ///< DP +#define STAT_MOVEVARS_AIRSTRAFEACCEL_QW 223 ///< DP +#define STAT_MOVEVARS_AIRCONTROL_POWER 224 ///< DP +#define STAT_MOVEFLAGS 225 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL 226 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_ACCEL 227 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED 228 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL 229 ///< DP +#define STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO 230 ///< DP +#define STAT_MOVEVARS_AIRSTOPACCELERATE 231 ///< DP +#define STAT_MOVEVARS_AIRSTRAFEACCELERATE 232 ///< DP +#define STAT_MOVEVARS_MAXAIRSTRAFESPEED 233 ///< DP +#define STAT_MOVEVARS_AIRCONTROL 234 ///< DP +#define STAT_FRAGLIMIT 235 ///< DP +#define STAT_TIMELIMIT 236 ///< DP +#define STAT_MOVEVARS_WALLFRICTION 237 ///< DP +#define STAT_MOVEVARS_FRICTION 238 ///< DP +#define STAT_MOVEVARS_WATERFRICTION 239 ///< DP +#define STAT_MOVEVARS_TICRATE 240 ///< DP +#define STAT_MOVEVARS_TIMESCALE 241 ///< DP +#define STAT_MOVEVARS_GRAVITY 242 ///< DP +#define STAT_MOVEVARS_STOPSPEED 243 ///< DP +#define STAT_MOVEVARS_MAXSPEED 244 ///< DP +#define STAT_MOVEVARS_SPECTATORMAXSPEED 245 ///< DP +#define STAT_MOVEVARS_ACCELERATE 246 ///< DP +#define STAT_MOVEVARS_AIRACCELERATE 247 ///< DP +#define STAT_MOVEVARS_WATERACCELERATE 248 ///< DP +#define STAT_MOVEVARS_ENTGRAVITY 249 ///< DP +#define STAT_MOVEVARS_JUMPVELOCITY 250 ///< DP +#define STAT_MOVEVARS_EDGEFRICTION 251 ///< DP +#define STAT_MOVEVARS_MAXAIRSPEED 252 ///< DP +#define STAT_MOVEVARS_STEPHEIGHT 253 ///< DP +#define STAT_MOVEVARS_AIRACCEL_QW 254 ///< DP +#define STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION 255 ///< DP + +// moveflags values +#define MOVEFLAG_VALID 0x80000000 +#define MOVEFLAG_Q2AIRACCELERATE 0x00000001 +#define MOVEFLAG_NOGRAVITYONGROUND 0x00000002 +#define MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE 0x00000004 + +// stock defines + +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_SUPER_LIGHTNING 128 +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 +#define IT_AXE 4096 +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 +#define IT_KEY1 131072 +#define IT_KEY2 262144 +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_SIGIL1 (1<<28) +#define IT_SIGIL2 (1<<29) +#define IT_SIGIL3 (1<<30) +#define IT_SIGIL4 (1<<31) + +//=========================================== +// AK nexuiz changed and added defines + +#define NEX_IT_UZI 1 +#define NEX_IT_SHOTGUN 2 +#define NEX_IT_GRENADE_LAUNCHER 4 +#define NEX_IT_ELECTRO 8 +#define NEX_IT_CRYLINK 16 +#define NEX_IT_NEX 32 +#define NEX_IT_HAGAR 64 +#define NEX_IT_ROCKET_LAUNCHER 128 +#define NEX_IT_SHELLS 256 +#define NEX_IT_BULLETS 512 +#define NEX_IT_ROCKETS 1024 +#define NEX_IT_CELLS 2048 +#define NEX_IT_LASER 4094 +#define NEX_IT_STRENGTH 8192 +#define NEX_IT_INVINCIBLE 16384 +#define NEX_IT_SPEED 32768 +#define NEX_IT_SLOWMO 65536 + +//=========================================== +//rogue changed and added defines + +#define RIT_SHELLS 128 +#define RIT_NAILS 256 +#define RIT_ROCKETS 512 +#define RIT_CELLS 1024 +#define RIT_AXE 2048 +#define RIT_LAVA_NAILGUN 4096 +#define RIT_LAVA_SUPER_NAILGUN 8192 +#define RIT_MULTI_GRENADE 16384 +#define RIT_MULTI_ROCKET 32768 +#define RIT_PLASMA_GUN 65536 +#define RIT_ARMOR1 8388608 +#define RIT_ARMOR2 16777216 +#define RIT_ARMOR3 33554432 +#define RIT_LAVA_NAILS 67108864 +#define RIT_PLASMA_AMMO 134217728 +#define RIT_MULTI_ROCKETS 268435456 +#define RIT_SHIELD 536870912 +#define RIT_ANTIGRAV 1073741824 +#define RIT_SUPERHEALTH 2147483648 + +//MED 01/04/97 added hipnotic defines +//=========================================== +//hipnotic added defines +#define HIT_PROXIMITY_GUN_BIT 16 +#define HIT_MJOLNIR_BIT 7 +#define HIT_LASER_CANNON_BIT 23 +#define HIT_PROXIMITY_GUN (1<server packet. Demo +// playback will ignore this, but it may be useful to make DP sniff packets to +// debug protocol exploits. +#define DEMOMSG_CLIENT_TO_SERVER 0x80000000 + +// In Quake, any char in 0..32 counts as whitespace +//#define ISWHITESPACE(ch) ((unsigned char) ch <= (unsigned char) ' ') +#define ISWHITESPACE(ch) (!(ch) || (ch) == ' ' || (ch) == '\t' || (ch) == '\r' || (ch) == '\n') + +// This also includes extended characters, and ALL control chars +#define ISWHITESPACEORCONTROL(ch) ((signed char) (ch) <= (signed char) ' ') + + +#ifdef PRVM_64 +#define FLOAT_IS_TRUE_FOR_INT(x) ((x) & 0x7FFFFFFFFFFFFFFF) // also match "negative zero" doubles of value 0x8000000000000000 +#define FLOAT_LOSSLESS_FORMAT "%.17g" +#define VECTOR_LOSSLESS_FORMAT "%.17g %.17g %.17g" +#else +#define FLOAT_IS_TRUE_FOR_INT(x) ((x) & 0x7FFFFFFF) // also match "negative zero" floats of value 0x80000000 +#define FLOAT_LOSSLESS_FORMAT "%.9g" +#define VECTOR_LOSSLESS_FORMAT "%.9g %.9g %.9g" +#endif + +// originally this was _MSC_VER +// but here we want to test the system libc, which on win32 is borked, and NOT the compiler +#ifdef WIN32 +#define INT_LOSSLESS_FORMAT_SIZE "I64" +#define INT_LOSSLESS_FORMAT_CONVERT_S(x) ((__int64)(x)) +#define INT_LOSSLESS_FORMAT_CONVERT_U(x) ((unsigned __int64)(x)) +#else +#define INT_LOSSLESS_FORMAT_SIZE "j" +#define INT_LOSSLESS_FORMAT_CONVERT_S(x) ((intmax_t)(x)) +#define INT_LOSSLESS_FORMAT_CONVERT_U(x) ((uintmax_t)(x)) +#endif + +#endif + diff --git a/app/jni/r_explosion.c b/app/jni/r_explosion.c new file mode 100644 index 0000000..3c40edb --- /dev/null +++ b/app/jni/r_explosion.c @@ -0,0 +1,285 @@ +/* +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 "cl_collision.h" + +#ifdef MAX_EXPLOSIONS +#define EXPLOSIONGRID 8 +#define EXPLOSIONVERTS ((EXPLOSIONGRID+1)*(EXPLOSIONGRID+1)) +#define EXPLOSIONTRIS (EXPLOSIONGRID*EXPLOSIONGRID*2) + +static int numexplosions = 0; + +static float explosiontexcoord2f[EXPLOSIONVERTS][2]; +static unsigned short explosiontris[EXPLOSIONTRIS][3]; +static int explosionnoiseindex[EXPLOSIONVERTS]; +static vec3_t explosionpoint[EXPLOSIONVERTS]; + +typedef struct explosion_s +{ + float starttime; + float endtime; + float time; + float alpha; + float fade; + vec3_t origin; + vec3_t vert[EXPLOSIONVERTS]; + vec3_t vertvel[EXPLOSIONVERTS]; + qboolean clipping; +} +explosion_t; + +static explosion_t explosion[MAX_EXPLOSIONS]; + +static rtexture_t *explosiontexture; +//static rtexture_t *explosiontexturefog; + +static rtexturepool_t *explosiontexturepool; +#endif + +cvar_t r_explosionclip = {CVAR_SAVE, "r_explosionclip", "1", "enables collision detection for explosion shell (so that it flattens against walls and floors)"}; +#ifdef MAX_EXPLOSIONS +static cvar_t r_drawexplosions = {0, "r_drawexplosions", "1", "enables rendering of explosion shells (see also cl_particles_explosions_shell)"}; + +//extern qboolean r_loadfog; +static void r_explosion_start(void) +{ + int x, y; + static unsigned char noise1[128][128], noise2[128][128], noise3[128][128], data[128][128][4]; + explosiontexturepool = R_AllocTexturePool(); + explosiontexture = NULL; + //explosiontexturefog = NULL; + fractalnoise(&noise1[0][0], 128, 32); + fractalnoise(&noise2[0][0], 128, 4); + fractalnoise(&noise3[0][0], 128, 4); + for (y = 0;y < 128;y++) + { + for (x = 0;x < 128;x++) + { + int j, r, g, b, a; + j = (noise1[y][x] * noise2[y][x]) * 3 / 256 - 128; + r = (j * 512) / 256; + g = (j * 256) / 256; + b = (j * 128) / 256; + a = noise3[y][x] * 3 - 128; + data[y][x][2] = bound(0, r, 255); + data[y][x][1] = bound(0, g, 255); + data[y][x][0] = bound(0, b, 255); + data[y][x][3] = bound(0, a, 255); + } + } + explosiontexture = R_LoadTexture2D(explosiontexturepool, "explosiontexture", 128, 128, &data[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); +// if (r_loadfog) +// { +// for (y = 0;y < 128;y++) +// for (x = 0;x < 128;x++) +// data[y][x][0] = data[y][x][1] = data[y][x][2] = 255; +// explosiontexturefog = R_LoadTexture2D(explosiontexturepool, "explosiontexture_fog", 128, 128, &data[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_ALPHA | TEXF_FORCELINEAR, NULL); +// } + // note that explosions survive the restart +} + +static void r_explosion_shutdown(void) +{ + R_FreeTexturePool(&explosiontexturepool); +} + +static void r_explosion_newmap(void) +{ + numexplosions = 0; + memset(explosion, 0, sizeof(explosion)); +} + +static int R_ExplosionVert(int column, int row) +{ + int i; + float yaw, pitch; + // top and bottom rows are all one position... + if (row == 0 || row == EXPLOSIONGRID) + column = 0; + i = row * (EXPLOSIONGRID + 1) + column; + yaw = ((double) column / EXPLOSIONGRID) * M_PI * 2; + pitch = (((double) row / EXPLOSIONGRID) - 0.5) * M_PI; + explosionpoint[i][0] = cos(yaw) * cos(pitch); + explosionpoint[i][1] = sin(yaw) * cos(pitch); + explosionpoint[i][2] = 1 * -sin(pitch); + explosiontexcoord2f[i][0] = (float) column / (float) EXPLOSIONGRID; + explosiontexcoord2f[i][1] = (float) row / (float) EXPLOSIONGRID; + explosionnoiseindex[i] = (row % EXPLOSIONGRID) * EXPLOSIONGRID + (column % EXPLOSIONGRID); + return i; +} +#endif + +void R_Explosion_Init(void) +{ +#ifdef MAX_EXPLOSIONS + int i, x, y; + i = 0; + for (y = 0;y < EXPLOSIONGRID;y++) + { + for (x = 0;x < EXPLOSIONGRID;x++) + { + explosiontris[i][0] = R_ExplosionVert(x , y ); + explosiontris[i][1] = R_ExplosionVert(x + 1, y ); + explosiontris[i][2] = R_ExplosionVert(x , y + 1); + i++; + explosiontris[i][0] = R_ExplosionVert(x + 1, y ); + explosiontris[i][1] = R_ExplosionVert(x + 1, y + 1); + explosiontris[i][2] = R_ExplosionVert(x , y + 1); + i++; + } + } + +#endif + Cvar_RegisterVariable(&r_explosionclip); +#ifdef MAX_EXPLOSIONS + Cvar_RegisterVariable(&r_drawexplosions); + + R_RegisterModule("R_Explosions", r_explosion_start, r_explosion_shutdown, r_explosion_newmap, NULL, NULL); +#endif +} + +void R_NewExplosion(const vec3_t org) +{ +#ifdef MAX_EXPLOSIONS + int i, j; + float dist, n; + explosion_t *e; + trace_t trace; + unsigned char noise[EXPLOSIONGRID*EXPLOSIONGRID]; + fractalnoisequick(noise, EXPLOSIONGRID, 4); // adjust noise grid size according to explosion + for (i = 0, e = explosion;i < MAX_EXPLOSIONS;i++, e++) + { + if (!e->alpha) + { + numexplosions = max(numexplosions, i + 1); + e->starttime = cl.time; + e->endtime = cl.time + cl_explosions_lifetime.value; + e->time = e->starttime; + e->alpha = cl_explosions_alpha_start.value; + e->fade = (cl_explosions_alpha_start.value - cl_explosions_alpha_end.value) / cl_explosions_lifetime.value; + e->clipping = r_explosionclip.integer != 0; + VectorCopy(org, e->origin); + for (j = 0;j < EXPLOSIONVERTS;j++) + { + // calculate start origin and velocity + n = noise[explosionnoiseindex[j]] * (1.0f / 255.0f) + 0.5; + dist = n * cl_explosions_size_start.value; + VectorMA(e->origin, dist, explosionpoint[j], e->vert[j]); + dist = n * (cl_explosions_size_end.value - cl_explosions_size_start.value) / cl_explosions_lifetime.value; + VectorScale(explosionpoint[j], dist, e->vertvel[j]); + // clip start origin + if (e->clipping) + { + trace = CL_TraceLine(e->origin, e->vert[j], MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false); + VectorCopy(trace.endpos, e->vert[i]); + } + } + break; + } + } +#endif +} + +#ifdef MAX_EXPLOSIONS +static void R_DrawExplosion_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex = 0; + const int numtriangles = EXPLOSIONTRIS, numverts = EXPLOSIONVERTS; + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + GL_DepthTest(true); + GL_CullFace(r_refdef.view.cullface_back); + R_EntityMatrix(&identitymatrix); + +// R_Mesh_ResetTextureState(); + R_SetupShader_Generic(explosiontexture, NULL, GL_MODULATE, 1, false, false, false); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + const explosion_t *e = explosion + surfacelist[surfacelistindex]; + // FIXME: this can't properly handle r_refdef.view.colorscale > 1 + GL_Color(e->alpha * r_refdef.view.colorscale, e->alpha * r_refdef.view.colorscale, e->alpha * r_refdef.view.colorscale, 1); + R_Mesh_PrepareVertices_Generic_Arrays(numverts, e->vert[0], NULL, explosiontexcoord2f[0]); + R_Mesh_Draw(0, numverts, 0, numtriangles, NULL, NULL, 0, explosiontris[0], NULL, 0); + } +} + +static void R_MoveExplosion(explosion_t *e) +{ + int i; + float dot, end[3], frametime; + trace_t trace; + + frametime = cl.time - e->time; + e->time = cl.time; + e->alpha = e->alpha - (e->fade * frametime); + if (e->alpha < 0 || cl.time > e->endtime) + { + e->alpha = 0; + return; + } + for (i = 0;i < EXPLOSIONVERTS;i++) + { + if (e->vertvel[i][0] || e->vertvel[i][1] || e->vertvel[i][2]) + { + VectorMA(e->vert[i], frametime, e->vertvel[i], end); + if (e->clipping) + { + trace = CL_TraceLine(e->vert[i], end, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, false); + if (trace.fraction < 1) + { + // clip velocity against the wall + dot = -DotProduct(e->vertvel[i], trace.plane.normal); + VectorMA(e->vertvel[i], dot, trace.plane.normal, e->vertvel[i]); + } + VectorCopy(trace.endpos, e->vert[i]); + } + else + VectorCopy(end, e->vert[i]); + } + } +} +#endif + +void R_DrawExplosions(void) +{ +#ifdef MAX_EXPLOSIONS + int i; + + if (!r_drawexplosions.integer) + return; + + for (i = 0;i < numexplosions;i++) + { + if (explosion[i].alpha) + { + R_MoveExplosion(&explosion[i]); + if (explosion[i].alpha) + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, explosion[i].origin, R_DrawExplosion_TransparentCallback, NULL, i, NULL); + } + } + while (numexplosions > 0 && explosion[i-1].alpha <= 0) + numexplosions--; +#endif +} + diff --git a/app/jni/r_lerpanim.c b/app/jni/r_lerpanim.c new file mode 100644 index 0000000..e69de29 diff --git a/app/jni/r_lerpanim.h b/app/jni/r_lerpanim.h new file mode 100644 index 0000000..e69de29 diff --git a/app/jni/r_lightning.c b/app/jni/r_lightning.c new file mode 100644 index 0000000..8568292 --- /dev/null +++ b/app/jni/r_lightning.c @@ -0,0 +1,327 @@ + +#include "quakedef.h" +#include "image.h" + +cvar_t r_lightningbeam_thickness = {CVAR_SAVE, "r_lightningbeam_thickness", "4", "thickness of the lightning beam effect"}; +cvar_t r_lightningbeam_scroll = {CVAR_SAVE, "r_lightningbeam_scroll", "5", "speed of texture scrolling on the lightning beam effect"}; +cvar_t r_lightningbeam_repeatdistance = {CVAR_SAVE, "r_lightningbeam_repeatdistance", "128", "how far to stretch the texture along the lightning beam effect"}; +cvar_t r_lightningbeam_color_red = {CVAR_SAVE, "r_lightningbeam_color_red", "1", "color of the lightning beam effect"}; +cvar_t r_lightningbeam_color_green = {CVAR_SAVE, "r_lightningbeam_color_green", "1", "color of the lightning beam effect"}; +cvar_t r_lightningbeam_color_blue = {CVAR_SAVE, "r_lightningbeam_color_blue", "1", "color of the lightning beam effect"}; +cvar_t r_lightningbeam_qmbtexture = {CVAR_SAVE, "r_lightningbeam_qmbtexture", "0", "load the qmb textures/particles/lightning.pcx texture instead of generating one, can look better"}; + +skinframe_t *r_lightningbeamtexture; +skinframe_t *r_lightningbeamqmbtexture; + +int r_lightningbeamelement3i[18] = {0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11}; +unsigned short r_lightningbeamelement3s[18] = {0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11}; + +static void r_lightningbeams_start(void) +{ + r_lightningbeamtexture = NULL; + r_lightningbeamqmbtexture = NULL; +} + +static void r_lightningbeams_setupqmbtexture(void) +{ + r_lightningbeamqmbtexture = R_SkinFrame_LoadExternal("textures/particles/lightning.pcx", TEXF_ALPHA | TEXF_FORCELINEAR, false); + if (r_lightningbeamqmbtexture == NULL) + Cvar_SetValueQuick(&r_lightningbeam_qmbtexture, false); +} + +static void r_lightningbeams_setuptexture(void) +{ +#if 0 +#define BEAMWIDTH 128 +#define BEAMHEIGHT 64 +#define PATHPOINTS 8 + int i, j, px, py, nearestpathindex, imagenumber; + float particlex, particley, particlexv, particleyv, dx, dy, s, maxpathstrength; + unsigned char *pixels; + int *image; + struct lightningpathnode_s + { + float x, y, strength; + } + path[PATHPOINTS], temppath; + + image = Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * sizeof(int)); + pixels = Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * sizeof(unsigned char[4])); + + for (imagenumber = 0, maxpathstrength = 0.0339476;maxpathstrength < 0.5;imagenumber++, maxpathstrength += 0.01) + { + for (i = 0;i < PATHPOINTS;i++) + { + path[i].x = lhrandom(0, 1); + path[i].y = lhrandom(0.2, 0.8); + path[i].strength = lhrandom(0, 1); + } + for (i = 0;i < PATHPOINTS;i++) + { + for (j = i + 1;j < PATHPOINTS;j++) + { + if (path[j].x < path[i].x) + { + temppath = path[j]; + path[j] = path[i]; + path[i] = temppath; + } + } + } + particlex = path[0].x; + particley = path[0].y; + particlexv = lhrandom(0, 0.02); + particlexv = lhrandom(-0.02, 0.02); + memset(image, 0, BEAMWIDTH * BEAMHEIGHT * sizeof(int)); + for (i = 0;i < 65536;i++) + { + for (nearestpathindex = 0;nearestpathindex < PATHPOINTS;nearestpathindex++) + if (path[nearestpathindex].x > particlex) + break; + nearestpathindex %= PATHPOINTS; + dx = path[nearestpathindex].x + lhrandom(-0.01, 0.01);dx = bound(0, dx, 1) - particlex;if (dx < 0) dx += 1; + dy = path[nearestpathindex].y + lhrandom(-0.01, 0.01);dy = bound(0, dy, 1) - particley; + s = path[nearestpathindex].strength / sqrt(dx*dx+dy*dy); + particlexv = particlexv /* (1 - lhrandom(0.08, 0.12))*/ + dx * s; + particleyv = particleyv /* (1 - lhrandom(0.08, 0.12))*/ + dy * s; + particlex += particlexv * maxpathstrength;particlex -= (int) particlex; + particley += particleyv * maxpathstrength;particley = bound(0, particley, 1); + px = particlex * BEAMWIDTH; + py = particley * BEAMHEIGHT; + if (px >= 0 && py >= 0 && px < BEAMWIDTH && py < BEAMHEIGHT) + image[py*BEAMWIDTH+px] += 16; + } + + for (py = 0;py < BEAMHEIGHT;py++) + { + for (px = 0;px < BEAMWIDTH;px++) + { + pixels[(py*BEAMWIDTH+px)*4+2] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); + pixels[(py*BEAMWIDTH+px)*4+1] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); + pixels[(py*BEAMWIDTH+px)*4+0] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); + pixels[(py*BEAMWIDTH+px)*4+3] = 255; + } + } + + Image_WriteTGABGRA(va(vabuf, sizeof(vabuf), "lightningbeam%i.tga", imagenumber), BEAMWIDTH, BEAMHEIGHT, pixels); + } + + r_lightningbeamtexture = R_LoadTexture2D(r_lightningbeamtexturepool, "lightningbeam", BEAMWIDTH, BEAMHEIGHT, pixels, TEXTYPE_BGRA, TEXF_FORCELINEAR, NULL); + + Mem_Free(pixels); + Mem_Free(image); +#else +#define BEAMWIDTH 64 +#define BEAMHEIGHT 128 + float r, g, b, intensity, fx, width, center; + int x, y; + unsigned char *data, *noise1, *noise2; + + data = (unsigned char *)Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * 4); + noise1 = (unsigned char *)Mem_Alloc(tempmempool, BEAMHEIGHT * BEAMHEIGHT); + noise2 = (unsigned char *)Mem_Alloc(tempmempool, BEAMHEIGHT * BEAMHEIGHT); + fractalnoise(noise1, BEAMHEIGHT, BEAMHEIGHT / 8); + fractalnoise(noise2, BEAMHEIGHT, BEAMHEIGHT / 16); + + for (y = 0;y < BEAMHEIGHT;y++) + { + width = 0.15;//((noise1[y * BEAMHEIGHT] * (1.0f / 256.0f)) * 0.1f + 0.1f); + center = (noise1[y * BEAMHEIGHT + (BEAMHEIGHT / 2)] / 256.0f) * (1.0f - width * 2.0f) + width; + for (x = 0;x < BEAMWIDTH;x++, fx++) + { + fx = (((float) x / BEAMWIDTH) - center) / width; + intensity = 1.0f - sqrt(fx * fx); + if (intensity > 0) + intensity = pow(intensity, 2) * ((noise2[y * BEAMHEIGHT + x] * (1.0f / 256.0f)) * 0.33f + 0.66f); + intensity = bound(0, intensity, 1); + r = intensity * 1.0f; + g = intensity * 1.0f; + b = intensity * 1.0f; + data[(y * BEAMWIDTH + x) * 4 + 2] = (unsigned char)(bound(0, r, 1) * 255.0f); + data[(y * BEAMWIDTH + x) * 4 + 1] = (unsigned char)(bound(0, g, 1) * 255.0f); + data[(y * BEAMWIDTH + x) * 4 + 0] = (unsigned char)(bound(0, b, 1) * 255.0f); + data[(y * BEAMWIDTH + x) * 4 + 3] = (unsigned char)255; + } + } + + r_lightningbeamtexture = R_SkinFrame_LoadInternalBGRA("lightningbeam", TEXF_FORCELINEAR, data, BEAMWIDTH, BEAMHEIGHT, false); + Mem_Free(noise1); + Mem_Free(noise2); + Mem_Free(data); +#endif +} + +static void r_lightningbeams_shutdown(void) +{ + r_lightningbeamtexture = NULL; + r_lightningbeamqmbtexture = NULL; +} + +static void r_lightningbeams_newmap(void) +{ + if (r_lightningbeamtexture) + R_SkinFrame_MarkUsed(r_lightningbeamtexture); + if (r_lightningbeamqmbtexture) + R_SkinFrame_MarkUsed(r_lightningbeamqmbtexture); +} + +void R_LightningBeams_Init(void) +{ + Cvar_RegisterVariable(&r_lightningbeam_thickness); + Cvar_RegisterVariable(&r_lightningbeam_scroll); + Cvar_RegisterVariable(&r_lightningbeam_repeatdistance); + Cvar_RegisterVariable(&r_lightningbeam_color_red); + Cvar_RegisterVariable(&r_lightningbeam_color_green); + Cvar_RegisterVariable(&r_lightningbeam_color_blue); + Cvar_RegisterVariable(&r_lightningbeam_qmbtexture); + R_RegisterModule("R_LightningBeams", r_lightningbeams_start, r_lightningbeams_shutdown, r_lightningbeams_newmap, NULL, NULL); +} + +static void R_CalcLightningBeamPolygonVertex3f(float *v, const float *start, const float *end, const float *offset) +{ + // near right corner + VectorAdd (start, offset, (v + 0)); + // near left corner + VectorSubtract(start, offset, (v + 3)); + // far left corner + VectorSubtract(end , offset, (v + 6)); + // far right corner + VectorAdd (end , offset, (v + 9)); +} + +static void R_CalcLightningBeamPolygonTexCoord2f(float *tc, float t1, float t2) +{ + if (r_lightningbeam_qmbtexture.integer) + { + // near right corner + tc[0] = t1;tc[1] = 0; + // near left corner + tc[2] = t1;tc[3] = 1; + // far left corner + tc[4] = t2;tc[5] = 1; + // far right corner + tc[6] = t2;tc[7] = 0; + } + else + { + // near right corner + tc[0] = 0;tc[1] = t1; + // near left corner + tc[2] = 1;tc[3] = t1; + // far left corner + tc[4] = 1;tc[5] = t2; + // far right corner + tc[6] = 0;tc[7] = t2; + } +} + +float beamrepeatscale; + +static void R_DrawLightningBeam_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int surfacelistindex; + float vertex3f[12*3]; + float texcoord2f[12*2]; + + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, r_lightningbeam_color_red.value, r_lightningbeam_color_green.value, r_lightningbeam_color_blue.value, 1, 12, vertex3f, texcoord2f, NULL, NULL, NULL, NULL, 6, r_lightningbeamelement3i, r_lightningbeamelement3s, false, false); + + if (r_lightningbeam_qmbtexture.integer && r_lightningbeamqmbtexture == NULL) + r_lightningbeams_setupqmbtexture(); + if (!r_lightningbeam_qmbtexture.integer && r_lightningbeamtexture == NULL) + r_lightningbeams_setuptexture(); + + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + const beam_t *b = cl.beams + surfacelist[surfacelistindex]; + vec3_t beamdir, right, up, offset, start, end; + float length, t1, t2; + + CL_Beam_CalculatePositions(b, start, end); + + // calculate beam direction (beamdir) vector and beam length + // get difference vector + VectorSubtract(end, start, beamdir); + // find length of difference vector + length = sqrt(DotProduct(beamdir, beamdir)); + // calculate scale to make beamdir a unit vector (normalized) + t1 = 1.0f / length; + // scale beamdir so it is now normalized + VectorScale(beamdir, t1, beamdir); + + // calculate up vector such that it points toward viewer, and rotates around the beamdir + // get direction from start of beam to viewer + VectorSubtract(r_refdef.view.origin, start, up); + // remove the portion of the vector that moves along the beam + // (this leaves only a vector pointing directly away from the beam) + t1 = -DotProduct(up, beamdir); + VectorMA(up, t1, beamdir, up); + // generate right vector from forward and up, the result is unnormalized + CrossProduct(beamdir, up, right); + // now normalize the right vector and up vector + VectorNormalize(right); + VectorNormalize(up); + + // calculate T coordinate scrolling (start and end texcoord along the beam) + t1 = r_refdef.scene.time * -r_lightningbeam_scroll.value;// + beamrepeatscale * DotProduct(start, beamdir); + t1 = t1 - (int) t1; + t2 = t1 + beamrepeatscale * length; + + // the beam is 3 polygons in this configuration: + // * 2 + // * * + // 1****** + // * * + // * 3 + // they are showing different portions of the beam texture, creating an + // illusion of a beam that appears to curl around in 3D space + // (and realize that the whole polygon assembly orients itself to face + // the viewer) + + // polygon 1, verts 0-3 + VectorScale(right, r_lightningbeam_thickness.value, offset); + R_CalcLightningBeamPolygonVertex3f(vertex3f + 0, start, end, offset); + // polygon 2, verts 4-7 + VectorAdd(right, up, offset); + VectorScale(offset, r_lightningbeam_thickness.value * 0.70710681f, offset); + R_CalcLightningBeamPolygonVertex3f(vertex3f + 12, start, end, offset); + // polygon 3, verts 8-11 + VectorSubtract(right, up, offset); + VectorScale(offset, r_lightningbeam_thickness.value * 0.70710681f, offset); + R_CalcLightningBeamPolygonVertex3f(vertex3f + 24, start, end, offset); + R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 0, t1, t2); + R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 8, t1 + 0.33, t2 + 0.33); + R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 16, t1 + 0.66, t2 + 0.66); + + // draw the 3 polygons as one batch of 6 triangles using the 12 vertices + R_DrawCustomSurface(r_lightningbeam_qmbtexture.integer ? r_lightningbeamqmbtexture : r_lightningbeamtexture, &identitymatrix, MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 12, 0, 6, false, false); + } +} + +extern cvar_t cl_beams_polygons; +void R_DrawLightningBeams(void) +{ + int i; + beam_t *b; + + if (!cl_beams_polygons.integer) + return; + + beamrepeatscale = 1.0f / r_lightningbeam_repeatdistance.value; + for (i = 0, b = cl.beams;i < cl.num_beams;i++, b++) + { + if (b->model && b->lightning) + { + vec3_t org, start, end, dir; + vec_t dist; + CL_Beam_CalculatePositions(b, start, end); + // calculate the nearest point on the line (beam) for depth sorting + VectorSubtract(end, start, dir); + dist = (DotProduct(r_refdef.view.origin, dir) - DotProduct(start, dir)) / (DotProduct(end, dir) - DotProduct(start, dir)); + dist = bound(0, dist, 1); + VectorLerp(start, dist, end, org); + // now we have the nearest point on the line, so sort with it + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, org, R_DrawLightningBeam_TransparentCallback, NULL, i, NULL); + } + } +} + diff --git a/app/jni/r_modules.c b/app/jni/r_modules.c new file mode 100644 index 0000000..818637e --- /dev/null +++ b/app/jni/r_modules.c @@ -0,0 +1,134 @@ + +#include "quakedef.h" + +#define MAXRENDERMODULES 20 + +typedef struct rendermodule_s +{ + int active; // set by start, cleared by shutdown + const char *name; + void(*start)(void); + void(*shutdown)(void); + void(*newmap)(void); + void(*devicelost)(void); + void(*devicerestored)(void); +} +rendermodule_t; + +rendermodule_t rendermodule[MAXRENDERMODULES]; + +void R_Modules_Init(void) +{ + Cmd_AddCommand("r_restart", R_Modules_Restart, "restarts renderer"); +} + +void R_RegisterModule(const char *name, void(*start)(void), void(*shutdown)(void), void(*newmap)(void), void(*devicelost)(void), void(*devicerestored)(void)) +{ + int i; + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + break; + if (!strcmp(name, rendermodule[i].name)) + { + Con_Printf("R_RegisterModule: module \"%s\" registered twice\n", name); + return; + } + } + if (i >= MAXRENDERMODULES) + Sys_Error("R_RegisterModule: ran out of renderer module slots (%i)", MAXRENDERMODULES); + rendermodule[i].active = 0; + rendermodule[i].name = name; + rendermodule[i].start = start; + rendermodule[i].shutdown = shutdown; + rendermodule[i].newmap = newmap; + rendermodule[i].devicelost = devicelost; + rendermodule[i].devicerestored = devicerestored; +} + +void R_Modules_Start(void) +{ + int i; + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + continue; + if (rendermodule[i].active) + { + Con_Printf ("R_StartModules: module \"%s\" already active\n", rendermodule[i].name); + continue; + } + rendermodule[i].active = 1; + rendermodule[i].start(); + } +} + +void R_Modules_Shutdown(void) +{ + int i; + // shutdown in reverse + for (i = MAXRENDERMODULES - 1;i >= 0;i--) + { + if (rendermodule[i].name == NULL) + continue; + if (!rendermodule[i].active) + continue; + rendermodule[i].active = 0; + rendermodule[i].shutdown(); + } +} + +void R_Modules_Restart(void) +{ + Host_StartVideo(); + Con_Print("restarting renderer\n"); + R_Modules_Shutdown(); + R_Modules_Start(); +} + +void R_Modules_NewMap(void) +{ + int i; + R_SkinFrame_PrepareForPurge(); + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + continue; + if (!rendermodule[i].active) + continue; + rendermodule[i].newmap(); + } + R_SkinFrame_Purge(); +} + +void R_Modules_DeviceLost(void) +{ + int i; + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + continue; + if (!rendermodule[i].active) + continue; + if (!rendermodule[i].devicelost) + continue; + rendermodule[i].devicelost(); + } +} + + +void R_Modules_DeviceRestored(void) +{ + int i; + for (i = 0;i < MAXRENDERMODULES;i++) + { + if (rendermodule[i].name == NULL) + continue; + if (!rendermodule[i].active) + continue; + if (!rendermodule[i].devicerestored) + continue; + rendermodule[i].devicerestored(); + } +} + diff --git a/app/jni/r_modules.h b/app/jni/r_modules.h new file mode 100644 index 0000000..ad838d1 --- /dev/null +++ b/app/jni/r_modules.h @@ -0,0 +1,15 @@ + +#ifndef R_MODULES_H +#define R_MODULES_H + +void R_Modules_Init(void); +void R_RegisterModule(const char *name, void(*start)(void), void(*shutdown)(void), void(*newmap)(void), void(*devicelost)(void), void(*devicerestored)(void)); +void R_Modules_Start(void); +void R_Modules_Shutdown(void); +void R_Modules_NewMap(void); +void R_Modules_Restart(void); +void R_Modules_DeviceLost(void); +void R_Modules_DeviceRestored(void); + +#endif + diff --git a/app/jni/r_shadow.c b/app/jni/r_shadow.c new file mode 100644 index 0000000..727e7d4 --- /dev/null +++ b/app/jni/r_shadow.c @@ -0,0 +1,6919 @@ + +/* +Terminology: Stencil Shadow Volume (sometimes called Stencil Shadows) +An extrusion of the lit faces, beginning at the original geometry and ending +further from the light source than the original geometry (presumably at least +as far as the light's radius, if the light has a radius at all), capped at +both front and back to avoid any problems (extrusion from dark faces also +works but has a different set of problems) + +This is normally rendered using Carmack's Reverse technique, in which +backfaces behind zbuffer (zfail) increment the stencil, and frontfaces behind +zbuffer (zfail) decrement the stencil, the result is a stencil value of zero +where shadows did not intersect the visible geometry, suitable as a stencil +mask for rendering lighting everywhere but shadow. + +In our case to hopefully avoid the Creative Labs patent, we draw the backfaces +as decrement and the frontfaces as increment, and we redefine the DepthFunc to +GL_LESS (the patent uses GL_GEQUAL) which causes zfail when behind surfaces +and zpass when infront (the patent draws where zpass with a GL_GEQUAL test), +additionally we clear stencil to 128 to avoid the need for the unclamped +incr/decr extension (not related to patent). + +Patent warning: +This algorithm may be covered by Creative's patent (US Patent #6384822), +however that patent is quite specific about increment on backfaces and +decrement on frontfaces where zpass with GL_GEQUAL depth test, which is +opposite this implementation and partially opposite Carmack's Reverse paper +(which uses GL_LESS, but increments on backfaces and decrements on frontfaces). + + + +Terminology: Stencil Light Volume (sometimes called Light Volumes) +Similar to a Stencil Shadow Volume, but inverted; rather than containing the +areas in shadow it contains the areas in light, this can only be built +quickly for certain limited cases (such as portal visibility from a point), +but is quite useful for some effects (sunlight coming from sky polygons is +one possible example, translucent occluders is another example). + + + +Terminology: Optimized Stencil Shadow Volume +A Stencil Shadow Volume that has been processed sufficiently to ensure it has +no duplicate coverage of areas (no need to shadow an area twice), often this +greatly improves performance but is an operation too costly to use on moving +lights (however completely optimal Stencil Light Volumes can be constructed +in some ideal cases). + + + +Terminology: Per Pixel Lighting (sometimes abbreviated PPL) +Per pixel evaluation of lighting equations, at a bare minimum this involves +DOT3 shading of diffuse lighting (per pixel dotproduct of negated incidence +vector and surface normal, using a texture of the surface bumps, called a +NormalMap) if supported by hardware; in our case there is support for cards +which are incapable of DOT3, the quality is quite poor however. Additionally +it is desirable to have specular evaluation per pixel, per vertex +normalization of specular halfangle vectors causes noticable distortion but +is unavoidable on hardware without GL_ARB_fragment_program or +GL_ARB_fragment_shader. + + + +Terminology: Normalization CubeMap +A cubemap containing normalized dot3-encoded (vectors of length 1 or less +encoded as RGB colors) for any possible direction, this technique allows per +pixel calculation of incidence vector for per pixel lighting purposes, which +would not otherwise be possible per pixel without GL_ARB_fragment_program or +GL_ARB_fragment_shader. + + + +Terminology: 2D+1D Attenuation Texturing +A very crude approximation of light attenuation with distance which results +in cylindrical light shapes which fade vertically as a streak (some games +such as Doom3 allow this to be rotated to be less noticable in specific +cases), the technique is simply modulating lighting by two 2D textures (which +can be the same) on different axes of projection (XY and Z, typically), this +is the second best technique available without 3D Attenuation Texturing, +GL_ARB_fragment_program or GL_ARB_fragment_shader technology. + + + +Terminology: 2D+1D Inverse Attenuation Texturing +A clever method described in papers on the Abducted engine, this has a squared +distance texture (bright on the outside, black in the middle), which is used +twice using GL_ADD blending, the result of this is used in an inverse modulate +(GL_ONE_MINUS_DST_ALPHA, GL_ZERO) to implement the equation +lighting*=(1-((X*X+Y*Y)+(Z*Z))) which is spherical (unlike 2D+1D attenuation +texturing). + + + +Terminology: 3D Attenuation Texturing +A slightly crude approximation of light attenuation with distance, its flaws +are limited radius and resolution (performance tradeoffs). + + + +Terminology: 3D Attenuation-Normalization Texturing +A 3D Attenuation Texture merged with a Normalization CubeMap, by making the +vectors shorter the lighting becomes darker, a very effective optimization of +diffuse lighting if 3D Attenuation Textures are already used. + + + +Terminology: Light Cubemap Filtering +A technique for modeling non-uniform light distribution according to +direction, for example a lantern may use a cubemap to describe the light +emission pattern of the cage around the lantern (as well as soot buildup +discoloring the light in certain areas), often also used for softened grate +shadows and light shining through a stained glass window (done crudely by +texturing the lighting with a cubemap), another good example would be a disco +light. This technique is used heavily in many games (Doom3 does not support +this however). + + + +Terminology: Light Projection Filtering +A technique for modeling shadowing of light passing through translucent +surfaces, allowing stained glass windows and other effects to be done more +elegantly than possible with Light Cubemap Filtering by applying an occluder +texture to the lighting combined with a stencil light volume to limit the lit +area, this technique is used by Doom3 for spotlights and flashlights, among +other things, this can also be used more generally to render light passing +through multiple translucent occluders in a scene (using a light volume to +describe the area beyond the occluder, and thus mask off rendering of all +other areas). + + + +Terminology: Doom3 Lighting +A combination of Stencil Shadow Volume, Per Pixel Lighting, Normalization +CubeMap, 2D+1D Attenuation Texturing, and Light Projection Filtering, as +demonstrated by the game Doom3. +*/ + +#include "quakedef.h" +#include "r_shadow.h" +#include "cl_collision.h" +#include "portals.h" +#include "image.h" +#include "dpsoftrast.h" + +#ifdef SUPPORTD3D +#include +extern LPDIRECT3DDEVICE9 vid_d3d9dev; +#endif + +static void R_Shadow_EditLights_Init(void); + +typedef enum r_shadow_rendermode_e +{ + R_SHADOW_RENDERMODE_NONE, + R_SHADOW_RENDERMODE_ZPASS_STENCIL, + R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL, + R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE, + R_SHADOW_RENDERMODE_ZFAIL_STENCIL, + R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL, + R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE, + R_SHADOW_RENDERMODE_LIGHT_VERTEX, + R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN, + R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN, + R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN, + R_SHADOW_RENDERMODE_LIGHT_GLSL, + R_SHADOW_RENDERMODE_VISIBLEVOLUMES, + R_SHADOW_RENDERMODE_VISIBLELIGHTING, + R_SHADOW_RENDERMODE_SHADOWMAP2D +} +r_shadow_rendermode_t; + +typedef enum r_shadow_shadowmode_e +{ + R_SHADOW_SHADOWMODE_STENCIL, + R_SHADOW_SHADOWMODE_SHADOWMAP2D +} +r_shadow_shadowmode_t; + +r_shadow_rendermode_t r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; +r_shadow_rendermode_t r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_NONE; +r_shadow_rendermode_t r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_NONE; +r_shadow_rendermode_t r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_NONE; +qboolean r_shadow_usingshadowmap2d; +qboolean r_shadow_usingshadowmaportho; +int r_shadow_shadowmapside; +float r_shadow_shadowmap_texturescale[2]; +float r_shadow_shadowmap_parameters[4]; +#if 0 +int r_shadow_drawbuffer; +int r_shadow_readbuffer; +#endif +int r_shadow_cullface_front, r_shadow_cullface_back; +GLuint r_shadow_fbo2d; +r_shadow_shadowmode_t r_shadow_shadowmode; +int r_shadow_shadowmapfilterquality; +int r_shadow_shadowmapdepthbits; +int r_shadow_shadowmapmaxsize; +qboolean r_shadow_shadowmapvsdct; +qboolean r_shadow_shadowmapsampler; +qboolean r_shadow_shadowmapshadowsampler; +int r_shadow_shadowmappcf; +int r_shadow_shadowmapborder; +matrix4x4_t r_shadow_shadowmapmatrix; +int r_shadow_lightscissor[4]; +qboolean r_shadow_usingdeferredprepass; +qboolean r_shadow_shadowmapdepthtexture; +int maxshadowtriangles; +int *shadowelements; + +int maxshadowvertices; +float *shadowvertex3f; + +int maxshadowmark; +int numshadowmark; +int *shadowmark; +int *shadowmarklist; +int shadowmarkcount; + +int maxshadowsides; +int numshadowsides; +unsigned char *shadowsides; +int *shadowsideslist; + +int maxvertexupdate; +int *vertexupdate; +int *vertexremap; +int vertexupdatenum; + +int r_shadow_buffer_numleafpvsbytes; +unsigned char *r_shadow_buffer_visitingleafpvs; +unsigned char *r_shadow_buffer_leafpvs; +int *r_shadow_buffer_leaflist; + +int r_shadow_buffer_numsurfacepvsbytes; +unsigned char *r_shadow_buffer_surfacepvs; +int *r_shadow_buffer_surfacelist; +unsigned char *r_shadow_buffer_surfacesides; + +int r_shadow_buffer_numshadowtrispvsbytes; +unsigned char *r_shadow_buffer_shadowtrispvs; +int r_shadow_buffer_numlighttrispvsbytes; +unsigned char *r_shadow_buffer_lighttrispvs; + +rtexturepool_t *r_shadow_texturepool; +rtexture_t *r_shadow_attenuationgradienttexture; +rtexture_t *r_shadow_attenuation2dtexture; +rtexture_t *r_shadow_attenuation3dtexture; +skinframe_t *r_shadow_lightcorona; +rtexture_t *r_shadow_shadowmap2ddepthbuffer; +rtexture_t *r_shadow_shadowmap2ddepthtexture; +rtexture_t *r_shadow_shadowmapvsdcttexture; +int r_shadow_shadowmapsize; // changes for each light based on distance +int r_shadow_shadowmaplod; // changes for each light based on distance + +GLuint r_shadow_prepassgeometryfbo; +GLuint r_shadow_prepasslightingdiffusespecularfbo; +GLuint r_shadow_prepasslightingdiffusefbo; +int r_shadow_prepass_width; +int r_shadow_prepass_height; +rtexture_t *r_shadow_prepassgeometrydepthbuffer; +rtexture_t *r_shadow_prepassgeometrynormalmaptexture; +rtexture_t *r_shadow_prepasslightingdiffusetexture; +rtexture_t *r_shadow_prepasslightingspeculartexture; + +// keep track of the provided framebuffer info +static int r_shadow_fb_fbo; +static rtexture_t *r_shadow_fb_depthtexture; +static rtexture_t *r_shadow_fb_colortexture; + +// lights are reloaded when this changes +char r_shadow_mapname[MAX_QPATH]; + +// used only for light filters (cubemaps) +rtexturepool_t *r_shadow_filters_texturepool; + +cvar_t r_shadow_bumpscale_basetexture = {0, "r_shadow_bumpscale_basetexture", "0", "generate fake bumpmaps from diffuse textures at this bumpyness, try 4 to match tenebrae, higher values increase depth, requires r_restart to take effect"}; +cvar_t r_shadow_bumpscale_bumpmap = {0, "r_shadow_bumpscale_bumpmap", "4", "what magnitude to interpret _bump.tga textures as, higher values increase depth, requires r_restart to take effect"}; +cvar_t r_shadow_debuglight = {0, "r_shadow_debuglight", "-1", "renders only one light, for level design purposes or debugging"}; +cvar_t r_shadow_deferred = {CVAR_SAVE, "r_shadow_deferred", "0", "uses image-based lighting instead of geometry-based lighting, the method used renders a depth image and a normalmap image, renders lights into separate diffuse and specular images, and then combines this into the normal rendering, requires r_shadow_shadowmapping"}; +cvar_t r_shadow_usebihculling = {0, "r_shadow_usebihculling", "1", "use BIH (Bounding Interval Hierarchy) for culling lit surfaces instead of BSP (Binary Space Partitioning)"}; +cvar_t r_shadow_usenormalmap = {CVAR_SAVE, "r_shadow_usenormalmap", "1", "enables use of directional shading on lights"}; +cvar_t r_shadow_gloss = {CVAR_SAVE, "r_shadow_gloss", "1", "0 disables gloss (specularity) rendering, 1 uses gloss if textures are found, 2 forces a flat metallic specular effect on everything without textures (similar to tenebrae)"}; +cvar_t r_shadow_gloss2intensity = {0, "r_shadow_gloss2intensity", "0.125", "how bright the forced flat gloss should look if r_shadow_gloss is 2"}; +cvar_t r_shadow_glossintensity = {0, "r_shadow_glossintensity", "1", "how bright textured glossmaps should look if r_shadow_gloss is 1 or 2"}; +cvar_t r_shadow_glossexponent = {0, "r_shadow_glossexponent", "32", "how 'sharp' the gloss should appear (specular power)"}; +cvar_t r_shadow_gloss2exponent = {0, "r_shadow_gloss2exponent", "32", "same as r_shadow_glossexponent but for forced gloss (gloss 2) surfaces"}; +cvar_t r_shadow_glossexact = {0, "r_shadow_glossexact", "0", "use exact reflection math for gloss (slightly slower, but should look a tad better)"}; +cvar_t r_shadow_lightattenuationdividebias = {0, "r_shadow_lightattenuationdividebias", "1", "changes attenuation texture generation"}; +cvar_t r_shadow_lightattenuationlinearscale = {0, "r_shadow_lightattenuationlinearscale", "2", "changes attenuation texture generation"}; +cvar_t r_shadow_lightintensityscale = {0, "r_shadow_lightintensityscale", "1", "renders all world lights brighter or darker"}; +cvar_t r_shadow_lightradiusscale = {0, "r_shadow_lightradiusscale", "1", "renders all world lights larger or smaller"}; +cvar_t r_shadow_projectdistance = {0, "r_shadow_projectdistance", "0", "how far to cast shadows"}; +cvar_t r_shadow_frontsidecasting = {0, "r_shadow_frontsidecasting", "1", "whether to cast shadows from illuminated triangles (front side of model) or unlit triangles (back side of model)"}; +cvar_t r_shadow_realtime_dlight = {CVAR_SAVE, "r_shadow_realtime_dlight", "1", "enables rendering of dynamic lights such as explosions and rocket light"}; +cvar_t r_shadow_realtime_dlight_shadows = {CVAR_SAVE, "r_shadow_realtime_dlight_shadows", "1", "enables rendering of shadows from dynamic lights"}; +cvar_t r_shadow_realtime_dlight_svbspculling = {0, "r_shadow_realtime_dlight_svbspculling", "0", "enables svbsp optimization on dynamic lights (very slow!)"}; +cvar_t r_shadow_realtime_dlight_portalculling = {0, "r_shadow_realtime_dlight_portalculling", "0", "enables portal optimization on dynamic lights (slow!)"}; +cvar_t r_shadow_realtime_world = {CVAR_SAVE, "r_shadow_realtime_world", "0", "enables rendering of full world lighting (whether loaded from the map, or a .rtlights file, or a .ent file, or a .lights file produced by hlight)"}; +cvar_t r_shadow_realtime_world_lightmaps = {CVAR_SAVE, "r_shadow_realtime_world_lightmaps", "0", "brightness to render lightmaps when using full world lighting, try 0.5 for a tenebrae-like appearance"}; +cvar_t r_shadow_realtime_world_shadows = {CVAR_SAVE, "r_shadow_realtime_world_shadows", "1", "enables rendering of shadows from world lights"}; +cvar_t r_shadow_realtime_world_compile = {0, "r_shadow_realtime_world_compile", "1", "enables compilation of world lights for higher performance rendering"}; +cvar_t r_shadow_realtime_world_compileshadow = {0, "r_shadow_realtime_world_compileshadow", "1", "enables compilation of shadows from world lights for higher performance rendering"}; +cvar_t r_shadow_realtime_world_compilesvbsp = {0, "r_shadow_realtime_world_compilesvbsp", "1", "enables svbsp optimization during compilation (slower than compileportalculling but more exact)"}; +cvar_t r_shadow_realtime_world_compileportalculling = {0, "r_shadow_realtime_world_compileportalculling", "1", "enables portal-based culling optimization during compilation (overrides compilesvbsp)"}; +cvar_t r_shadow_scissor = {0, "r_shadow_scissor", "1", "use scissor optimization of light rendering (restricts rendering to the portion of the screen affected by the light)"}; +cvar_t r_shadow_shadowmapping = {CVAR_SAVE, "r_shadow_shadowmapping", "1", "enables use of shadowmapping (depth texture sampling) instead of stencil shadow volumes, requires gl_fbo 1"}; +cvar_t r_shadow_shadowmapping_filterquality = {CVAR_SAVE, "r_shadow_shadowmapping_filterquality", "-1", "shadowmap filter modes: -1 = auto-select, 0 = no filtering, 1 = bilinear, 2 = bilinear 2x2 blur (fast), 3 = 3x3 blur (moderate), 4 = 4x4 blur (slow)"}; +cvar_t r_shadow_shadowmapping_useshadowsampler = {CVAR_SAVE, "r_shadow_shadowmapping_useshadowsampler", "1", "whether to use sampler2DShadow if available"}; +cvar_t r_shadow_shadowmapping_depthbits = {CVAR_SAVE, "r_shadow_shadowmapping_depthbits", "24", "requested minimum shadowmap texture depth bits"}; +cvar_t r_shadow_shadowmapping_vsdct = {CVAR_SAVE, "r_shadow_shadowmapping_vsdct", "1", "enables use of virtual shadow depth cube texture"}; +cvar_t r_shadow_shadowmapping_minsize = {CVAR_SAVE, "r_shadow_shadowmapping_minsize", "32", "shadowmap size limit"}; +cvar_t r_shadow_shadowmapping_maxsize = {CVAR_SAVE, "r_shadow_shadowmapping_maxsize", "512", "shadowmap size limit"}; +cvar_t r_shadow_shadowmapping_precision = {CVAR_SAVE, "r_shadow_shadowmapping_precision", "1", "makes shadowmaps have a maximum resolution of this number of pixels per light source radius unit such that, for example, at precision 0.5 a light with radius 200 will have a maximum resolution of 100 pixels"}; +//cvar_t r_shadow_shadowmapping_lod_bias = {CVAR_SAVE, "r_shadow_shadowmapping_lod_bias", "16", "shadowmap size bias"}; +//cvar_t r_shadow_shadowmapping_lod_scale = {CVAR_SAVE, "r_shadow_shadowmapping_lod_scale", "128", "shadowmap size scaling parameter"}; +cvar_t r_shadow_shadowmapping_bordersize = {CVAR_SAVE, "r_shadow_shadowmapping_bordersize", "4", "shadowmap size bias for filtering"}; +cvar_t r_shadow_shadowmapping_nearclip = {CVAR_SAVE, "r_shadow_shadowmapping_nearclip", "1", "shadowmap nearclip in world units"}; +cvar_t r_shadow_shadowmapping_bias = {CVAR_SAVE, "r_shadow_shadowmapping_bias", "0.03", "shadowmap bias parameter (this is multiplied by nearclip * 1024 / lodsize)"}; +cvar_t r_shadow_shadowmapping_polygonfactor = {CVAR_SAVE, "r_shadow_shadowmapping_polygonfactor", "2", "slope-dependent shadowmapping bias"}; +cvar_t r_shadow_shadowmapping_polygonoffset = {CVAR_SAVE, "r_shadow_shadowmapping_polygonoffset", "0", "constant shadowmapping bias"}; +cvar_t r_shadow_sortsurfaces = {0, "r_shadow_sortsurfaces", "1", "improve performance by sorting illuminated surfaces by texture"}; +cvar_t r_shadow_polygonfactor = {0, "r_shadow_polygonfactor", "0", "how much to enlarge shadow volume polygons when rendering (should be 0!)"}; +cvar_t r_shadow_polygonoffset = {0, "r_shadow_polygonoffset", "1", "how much to push shadow volumes into the distance when rendering, to reduce chances of zfighting artifacts (should not be less than 0)"}; +cvar_t r_shadow_texture3d = {0, "r_shadow_texture3d", "1", "use 3D voxel textures for spherical attenuation rather than cylindrical (does not affect OpenGL 2.0 render path)"}; +cvar_t r_shadow_bouncegrid = {CVAR_SAVE, "r_shadow_bouncegrid", "0", "perform particle tracing for indirect lighting (Global Illumination / radiosity) using a 3D texture covering the scene, only active on levels with realtime lights active (r_shadow_realtime_world is usually required for these)"}; +cvar_t r_shadow_bouncegrid_bounceanglediffuse = {CVAR_SAVE, "r_shadow_bouncegrid_bounceanglediffuse", "0", "use random bounce direction rather than true reflection, makes some corner areas dark"}; +cvar_t r_shadow_bouncegrid_directionalshading = {CVAR_SAVE, "r_shadow_bouncegrid_directionalshading", "0", "use diffuse shading rather than ambient, 3D texture becomes 8x as many pixels to hold the additional data"}; +cvar_t r_shadow_bouncegrid_dlightparticlemultiplier = {CVAR_SAVE, "r_shadow_bouncegrid_dlightparticlemultiplier", "0", "if set to a high value like 16 this can make dlights look great, but 0 is recommended for performance reasons"}; +cvar_t r_shadow_bouncegrid_hitmodels = {CVAR_SAVE, "r_shadow_bouncegrid_hitmodels", "0", "enables hitting character model geometry (SLOW)"}; +cvar_t r_shadow_bouncegrid_includedirectlighting = {CVAR_SAVE, "r_shadow_bouncegrid_includedirectlighting", "0", "allows direct lighting to be recorded, not just indirect (gives an effect somewhat like r_shadow_realtime_world_lightmaps)"}; +cvar_t r_shadow_bouncegrid_intensity = {CVAR_SAVE, "r_shadow_bouncegrid_intensity", "4", "overall brightness of bouncegrid texture"}; +cvar_t r_shadow_bouncegrid_lightradiusscale = {CVAR_SAVE, "r_shadow_bouncegrid_lightradiusscale", "4", "particles stop at this fraction of light radius (can be more than 1)"}; +cvar_t r_shadow_bouncegrid_maxbounce = {CVAR_SAVE, "r_shadow_bouncegrid_maxbounce", "2", "maximum number of bounces for a particle (minimum is 0)"}; +cvar_t r_shadow_bouncegrid_particlebounceintensity = {CVAR_SAVE, "r_shadow_bouncegrid_particlebounceintensity", "1", "amount of energy carried over after each bounce, this is a multiplier of texture color and the result is clamped to 1 or less, to prevent adding energy on each bounce"}; +cvar_t r_shadow_bouncegrid_particleintensity = {CVAR_SAVE, "r_shadow_bouncegrid_particleintensity", "1", "brightness of particles contributing to bouncegrid texture"}; +cvar_t r_shadow_bouncegrid_photons = {CVAR_SAVE, "r_shadow_bouncegrid_photons", "2000", "total photons to shoot per update, divided proportionately between lights"}; +cvar_t r_shadow_bouncegrid_spacing = {CVAR_SAVE, "r_shadow_bouncegrid_spacing", "64", "unit size of bouncegrid pixel"}; +cvar_t r_shadow_bouncegrid_stablerandom = {CVAR_SAVE, "r_shadow_bouncegrid_stablerandom", "1", "make particle distribution consistent from frame to frame"}; +cvar_t r_shadow_bouncegrid_static = {CVAR_SAVE, "r_shadow_bouncegrid_static", "1", "use static radiosity solution (high quality) rather than dynamic (splotchy)"}; +cvar_t r_shadow_bouncegrid_static_directionalshading = {CVAR_SAVE, "r_shadow_bouncegrid_static_directionalshading", "1", "whether to use directionalshading when in static mode"}; +cvar_t r_shadow_bouncegrid_static_lightradiusscale = {CVAR_SAVE, "r_shadow_bouncegrid_static_lightradiusscale", "10", "particles stop at this fraction of light radius (can be more than 1) when in static mode"}; +cvar_t r_shadow_bouncegrid_static_maxbounce = {CVAR_SAVE, "r_shadow_bouncegrid_static_maxbounce", "5", "maximum number of bounces for a particle (minimum is 0) in static mode"}; +cvar_t r_shadow_bouncegrid_static_photons = {CVAR_SAVE, "r_shadow_bouncegrid_static_photons", "25000", "photons value to use when in static mode"}; +cvar_t r_shadow_bouncegrid_updateinterval = {CVAR_SAVE, "r_shadow_bouncegrid_updateinterval", "0", "update bouncegrid texture once per this many seconds, useful values are 0, 0.05, or 1000000"}; +cvar_t r_shadow_bouncegrid_x = {CVAR_SAVE, "r_shadow_bouncegrid_x", "64", "maximum texture size of bouncegrid on X axis"}; +cvar_t r_shadow_bouncegrid_y = {CVAR_SAVE, "r_shadow_bouncegrid_y", "64", "maximum texture size of bouncegrid on Y axis"}; +cvar_t r_shadow_bouncegrid_z = {CVAR_SAVE, "r_shadow_bouncegrid_z", "32", "maximum texture size of bouncegrid on Z axis"}; +cvar_t r_coronas = {CVAR_SAVE, "r_coronas", "0", "brightness of corona flare effects around certain lights, 0 disables corona effects"}; +cvar_t r_coronas_occlusionsizescale = {CVAR_SAVE, "r_coronas_occlusionsizescale", "0.1", "size of light source for corona occlusion checksum the proportion of hidden pixels controls corona intensity"}; +cvar_t r_coronas_occlusionquery = {CVAR_SAVE, "r_coronas_occlusionquery", "0", "use GL_ARB_occlusion_query extension if supported (fades coronas according to visibility) - bad performance (synchronous rendering) - worse on multi-gpu!"}; +cvar_t gl_flashblend = {CVAR_SAVE, "gl_flashblend", "0", "render bright coronas for dynamic lights instead of actual lighting, fast but ugly"}; +cvar_t gl_ext_separatestencil = {0, "gl_ext_separatestencil", "1", "make use of OpenGL 2.0 glStencilOpSeparate or GL_ATI_separate_stencil extension"}; +cvar_t gl_ext_stenciltwoside = {0, "gl_ext_stenciltwoside", "1", "make use of GL_EXT_stenciltwoside extension (NVIDIA only)"}; +cvar_t r_editlights = {0, "r_editlights", "0", "enables .rtlights file editing mode"}; +cvar_t r_editlights_cursordistance = {0, "r_editlights_cursordistance", "1024", "maximum distance of cursor from eye"}; +cvar_t r_editlights_cursorpushback = {0, "r_editlights_cursorpushback", "0", "how far to pull the cursor back toward the eye"}; +cvar_t r_editlights_cursorpushoff = {0, "r_editlights_cursorpushoff", "4", "how far to push the cursor off the impacted surface"}; +cvar_t r_editlights_cursorgrid = {0, "r_editlights_cursorgrid", "4", "snaps cursor to this grid size"}; +cvar_t r_editlights_quakelightsizescale = {CVAR_SAVE, "r_editlights_quakelightsizescale", "1", "changes size of light entities loaded from a map"}; +cvar_t r_editlights_drawproperties = {0, "r_editlights_drawproperties", "1", "draw properties of currently selected light"}; +cvar_t r_editlights_current_origin = {0, "r_editlights_current_origin", "0 0 0", "origin of selected light"}; +cvar_t r_editlights_current_angles = {0, "r_editlights_current_angles", "0 0 0", "angles of selected light"}; +cvar_t r_editlights_current_color = {0, "r_editlights_current_color", "1 1 1", "color of selected light"}; +cvar_t r_editlights_current_radius = {0, "r_editlights_current_radius", "0", "radius of selected light"}; +cvar_t r_editlights_current_corona = {0, "r_editlights_current_corona", "0", "corona intensity of selected light"}; +cvar_t r_editlights_current_coronasize = {0, "r_editlights_current_coronasize", "0", "corona size of selected light"}; +cvar_t r_editlights_current_style = {0, "r_editlights_current_style", "0", "style of selected light"}; +cvar_t r_editlights_current_shadows = {0, "r_editlights_current_shadows", "0", "shadows flag of selected light"}; +cvar_t r_editlights_current_cubemap = {0, "r_editlights_current_cubemap", "0", "cubemap of selected light"}; +cvar_t r_editlights_current_ambient = {0, "r_editlights_current_ambient", "0", "ambient intensity of selected light"}; +cvar_t r_editlights_current_diffuse = {0, "r_editlights_current_diffuse", "1", "diffuse intensity of selected light"}; +cvar_t r_editlights_current_specular = {0, "r_editlights_current_specular", "1", "specular intensity of selected light"}; +cvar_t r_editlights_current_normalmode = {0, "r_editlights_current_normalmode", "0", "normalmode flag of selected light"}; +cvar_t r_editlights_current_realtimemode = {0, "r_editlights_current_realtimemode", "0", "realtimemode flag of selected light"}; + + +typedef struct r_shadow_bouncegrid_settings_s +{ + qboolean staticmode; + qboolean bounceanglediffuse; + qboolean directionalshading; + qboolean includedirectlighting; + float dlightparticlemultiplier; + qboolean hitmodels; + float lightradiusscale; + int maxbounce; + float particlebounceintensity; + float particleintensity; + int photons; + float spacing[3]; + int stablerandom; +} +r_shadow_bouncegrid_settings_t; + +r_shadow_bouncegrid_settings_t r_shadow_bouncegridsettings; +rtexture_t *r_shadow_bouncegridtexture; +matrix4x4_t r_shadow_bouncegridmatrix; +vec_t r_shadow_bouncegridintensity; +qboolean r_shadow_bouncegriddirectional; +static double r_shadow_bouncegridtime; +static int r_shadow_bouncegridresolution[3]; +static int r_shadow_bouncegridnumpixels; +static unsigned char *r_shadow_bouncegridpixels; +static float *r_shadow_bouncegridhighpixels; + +// note the table actually includes one more value, just to avoid the need to clamp the distance index due to minor math error +#define ATTENTABLESIZE 256 +// 1D gradient, 2D circle and 3D sphere attenuation textures +#define ATTEN1DSIZE 32 +#define ATTEN2DSIZE 64 +#define ATTEN3DSIZE 32 + +static float r_shadow_attendividebias; // r_shadow_lightattenuationdividebias +static float r_shadow_attenlinearscale; // r_shadow_lightattenuationlinearscale +static float r_shadow_attentable[ATTENTABLESIZE+1]; + +rtlight_t *r_shadow_compilingrtlight; +static memexpandablearray_t r_shadow_worldlightsarray; +dlight_t *r_shadow_selectedlight; +dlight_t r_shadow_bufferlight; +vec3_t r_editlights_cursorlocation; +qboolean r_editlights_lockcursor; + +extern int con_vislines; + +void R_Shadow_UncompileWorldLights(void); +void R_Shadow_ClearWorldLights(void); +void R_Shadow_SaveWorldLights(void); +void R_Shadow_LoadWorldLights(void); +void R_Shadow_LoadLightsFile(void); +void R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(void); +void R_Shadow_EditLights_Reload_f(void); +void R_Shadow_ValidateCvars(void); +static void R_Shadow_MakeTextures(void); + +#define EDLIGHTSPRSIZE 8 +skinframe_t *r_editlights_sprcursor; +skinframe_t *r_editlights_sprlight; +skinframe_t *r_editlights_sprnoshadowlight; +skinframe_t *r_editlights_sprcubemaplight; +skinframe_t *r_editlights_sprcubemapnoshadowlight; +skinframe_t *r_editlights_sprselection; + +static void R_Shadow_SetShadowMode(void) +{ + r_shadow_shadowmapmaxsize = bound(1, r_shadow_shadowmapping_maxsize.integer, (int)vid.maxtexturesize_2d / 4); + r_shadow_shadowmapvsdct = r_shadow_shadowmapping_vsdct.integer != 0 && vid.renderpath == RENDERPATH_GL20; + r_shadow_shadowmapfilterquality = r_shadow_shadowmapping_filterquality.integer; + r_shadow_shadowmapshadowsampler = r_shadow_shadowmapping_useshadowsampler.integer != 0; + r_shadow_shadowmapdepthbits = r_shadow_shadowmapping_depthbits.integer; + r_shadow_shadowmapborder = bound(0, r_shadow_shadowmapping_bordersize.integer, 16); + r_shadow_shadowmaplod = -1; + r_shadow_shadowmapsize = 0; + r_shadow_shadowmapsampler = false; + r_shadow_shadowmappcf = 0; + r_shadow_shadowmapdepthtexture = r_fb.usedepthtextures; + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_STENCIL; + if ((r_shadow_shadowmapping.integer || r_shadow_deferred.integer) && vid.support.ext_framebuffer_object) + { + switch(vid.renderpath) + { + case RENDERPATH_GL20: + if(r_shadow_shadowmapfilterquality < 0) + { + if (!r_fb.usedepthtextures) + r_shadow_shadowmappcf = 1; + else if((strstr(gl_vendor, "NVIDIA") || strstr(gl_renderer, "Radeon HD")) && vid.support.arb_shadow && r_shadow_shadowmapshadowsampler) + { + r_shadow_shadowmapsampler = true; + r_shadow_shadowmappcf = 1; + } + else if(vid.support.amd_texture_texture4 || vid.support.arb_texture_gather) + r_shadow_shadowmappcf = 1; + else if((strstr(gl_vendor, "ATI") || strstr(gl_vendor, "Advanced Micro Devices")) && !strstr(gl_renderer, "Mesa") && !strstr(gl_version, "Mesa")) + r_shadow_shadowmappcf = 1; + else + r_shadow_shadowmapsampler = vid.support.arb_shadow && r_shadow_shadowmapshadowsampler; + } + else + { + r_shadow_shadowmapsampler = vid.support.arb_shadow && r_shadow_shadowmapshadowsampler; + switch (r_shadow_shadowmapfilterquality) + { + case 1: + break; + case 2: + r_shadow_shadowmappcf = 1; + break; + case 3: + r_shadow_shadowmappcf = 1; + break; + case 4: + r_shadow_shadowmappcf = 2; + break; + } + } + if (!r_fb.usedepthtextures) + r_shadow_shadowmapsampler = false; + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_SHADOWMAP2D; + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + r_shadow_shadowmapsampler = false; + r_shadow_shadowmappcf = 1; + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_SHADOWMAP2D; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + } + } + + if(R_CompileShader_CheckStaticParms()) + R_GLSL_Restart_f(); +} + +qboolean R_Shadow_ShadowMappingEnabled(void) +{ + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + return true; + default: + return false; + } +} + +static void R_Shadow_FreeShadowMaps(void) +{ + R_Shadow_SetShadowMode(); + + R_Mesh_DestroyFramebufferObject(r_shadow_fbo2d); + + r_shadow_fbo2d = 0; + + if (r_shadow_shadowmap2ddepthtexture) + R_FreeTexture(r_shadow_shadowmap2ddepthtexture); + r_shadow_shadowmap2ddepthtexture = NULL; + + if (r_shadow_shadowmap2ddepthbuffer) + R_FreeTexture(r_shadow_shadowmap2ddepthbuffer); + r_shadow_shadowmap2ddepthbuffer = NULL; + + if (r_shadow_shadowmapvsdcttexture) + R_FreeTexture(r_shadow_shadowmapvsdcttexture); + r_shadow_shadowmapvsdcttexture = NULL; +} + +static void r_shadow_start(void) +{ + // allocate vertex processing arrays + r_shadow_bouncegridpixels = NULL; + r_shadow_bouncegridhighpixels = NULL; + r_shadow_bouncegridnumpixels = 0; + r_shadow_bouncegridtexture = NULL; + r_shadow_bouncegriddirectional = false; + r_shadow_attenuationgradienttexture = NULL; + r_shadow_attenuation2dtexture = NULL; + r_shadow_attenuation3dtexture = NULL; + r_shadow_shadowmode = R_SHADOW_SHADOWMODE_STENCIL; + r_shadow_shadowmap2ddepthtexture = NULL; + r_shadow_shadowmap2ddepthbuffer = NULL; + r_shadow_shadowmapvsdcttexture = NULL; + r_shadow_shadowmapmaxsize = 0; + r_shadow_shadowmapsize = 0; + r_shadow_shadowmaplod = 0; + r_shadow_shadowmapfilterquality = -1; + r_shadow_shadowmapdepthbits = 0; + r_shadow_shadowmapvsdct = false; + r_shadow_shadowmapsampler = false; + r_shadow_shadowmappcf = 0; + r_shadow_fbo2d = 0; + + R_Shadow_FreeShadowMaps(); + + r_shadow_texturepool = NULL; + r_shadow_filters_texturepool = NULL; + R_Shadow_ValidateCvars(); + R_Shadow_MakeTextures(); + maxshadowtriangles = 0; + shadowelements = NULL; + maxshadowvertices = 0; + shadowvertex3f = NULL; + maxvertexupdate = 0; + vertexupdate = NULL; + vertexremap = NULL; + vertexupdatenum = 0; + maxshadowmark = 0; + numshadowmark = 0; + shadowmark = NULL; + shadowmarklist = NULL; + shadowmarkcount = 0; + maxshadowsides = 0; + numshadowsides = 0; + shadowsides = NULL; + shadowsideslist = NULL; + r_shadow_buffer_numleafpvsbytes = 0; + r_shadow_buffer_visitingleafpvs = NULL; + r_shadow_buffer_leafpvs = NULL; + r_shadow_buffer_leaflist = NULL; + r_shadow_buffer_numsurfacepvsbytes = 0; + r_shadow_buffer_surfacepvs = NULL; + r_shadow_buffer_surfacelist = NULL; + r_shadow_buffer_surfacesides = NULL; + r_shadow_buffer_numshadowtrispvsbytes = 0; + r_shadow_buffer_shadowtrispvs = NULL; + r_shadow_buffer_numlighttrispvsbytes = 0; + r_shadow_buffer_lighttrispvs = NULL; + + r_shadow_usingdeferredprepass = false; + r_shadow_prepass_width = r_shadow_prepass_height = 0; +} + +static void R_Shadow_FreeDeferred(void); +static void r_shadow_shutdown(void) +{ + CHECKGLERROR + R_Shadow_UncompileWorldLights(); + + R_Shadow_FreeShadowMaps(); + + r_shadow_usingdeferredprepass = false; + if (r_shadow_prepass_width) + R_Shadow_FreeDeferred(); + r_shadow_prepass_width = r_shadow_prepass_height = 0; + + CHECKGLERROR + r_shadow_bouncegridtexture = NULL; + r_shadow_bouncegridpixels = NULL; + r_shadow_bouncegridhighpixels = NULL; + r_shadow_bouncegridnumpixels = 0; + r_shadow_bouncegriddirectional = false; + r_shadow_attenuationgradienttexture = NULL; + r_shadow_attenuation2dtexture = NULL; + r_shadow_attenuation3dtexture = NULL; + R_FreeTexturePool(&r_shadow_texturepool); + R_FreeTexturePool(&r_shadow_filters_texturepool); + maxshadowtriangles = 0; + if (shadowelements) + Mem_Free(shadowelements); + shadowelements = NULL; + if (shadowvertex3f) + Mem_Free(shadowvertex3f); + shadowvertex3f = NULL; + maxvertexupdate = 0; + if (vertexupdate) + Mem_Free(vertexupdate); + vertexupdate = NULL; + if (vertexremap) + Mem_Free(vertexremap); + vertexremap = NULL; + vertexupdatenum = 0; + maxshadowmark = 0; + numshadowmark = 0; + if (shadowmark) + Mem_Free(shadowmark); + shadowmark = NULL; + if (shadowmarklist) + Mem_Free(shadowmarklist); + shadowmarklist = NULL; + shadowmarkcount = 0; + maxshadowsides = 0; + numshadowsides = 0; + if (shadowsides) + Mem_Free(shadowsides); + shadowsides = NULL; + if (shadowsideslist) + Mem_Free(shadowsideslist); + shadowsideslist = NULL; + r_shadow_buffer_numleafpvsbytes = 0; + if (r_shadow_buffer_visitingleafpvs) + Mem_Free(r_shadow_buffer_visitingleafpvs); + r_shadow_buffer_visitingleafpvs = NULL; + if (r_shadow_buffer_leafpvs) + Mem_Free(r_shadow_buffer_leafpvs); + r_shadow_buffer_leafpvs = NULL; + if (r_shadow_buffer_leaflist) + Mem_Free(r_shadow_buffer_leaflist); + r_shadow_buffer_leaflist = NULL; + r_shadow_buffer_numsurfacepvsbytes = 0; + if (r_shadow_buffer_surfacepvs) + Mem_Free(r_shadow_buffer_surfacepvs); + r_shadow_buffer_surfacepvs = NULL; + if (r_shadow_buffer_surfacelist) + Mem_Free(r_shadow_buffer_surfacelist); + r_shadow_buffer_surfacelist = NULL; + if (r_shadow_buffer_surfacesides) + Mem_Free(r_shadow_buffer_surfacesides); + r_shadow_buffer_surfacesides = NULL; + r_shadow_buffer_numshadowtrispvsbytes = 0; + if (r_shadow_buffer_shadowtrispvs) + Mem_Free(r_shadow_buffer_shadowtrispvs); + r_shadow_buffer_numlighttrispvsbytes = 0; + if (r_shadow_buffer_lighttrispvs) + Mem_Free(r_shadow_buffer_lighttrispvs); +} + +static void r_shadow_newmap(void) +{ + if (r_shadow_bouncegridtexture) R_FreeTexture(r_shadow_bouncegridtexture);r_shadow_bouncegridtexture = NULL; + if (r_shadow_lightcorona) R_SkinFrame_MarkUsed(r_shadow_lightcorona); + if (r_editlights_sprcursor) R_SkinFrame_MarkUsed(r_editlights_sprcursor); + if (r_editlights_sprlight) R_SkinFrame_MarkUsed(r_editlights_sprlight); + if (r_editlights_sprnoshadowlight) R_SkinFrame_MarkUsed(r_editlights_sprnoshadowlight); + if (r_editlights_sprcubemaplight) R_SkinFrame_MarkUsed(r_editlights_sprcubemaplight); + if (r_editlights_sprcubemapnoshadowlight) R_SkinFrame_MarkUsed(r_editlights_sprcubemapnoshadowlight); + if (r_editlights_sprselection) R_SkinFrame_MarkUsed(r_editlights_sprselection); + if (strncmp(cl.worldname, r_shadow_mapname, sizeof(r_shadow_mapname))) + R_Shadow_EditLights_Reload_f(); +} + +void R_Shadow_Init(void) +{ + Cvar_RegisterVariable(&r_shadow_bumpscale_basetexture); + Cvar_RegisterVariable(&r_shadow_bumpscale_bumpmap); + Cvar_RegisterVariable(&r_shadow_usebihculling); + Cvar_RegisterVariable(&r_shadow_usenormalmap); + Cvar_RegisterVariable(&r_shadow_debuglight); + Cvar_RegisterVariable(&r_shadow_deferred); + Cvar_RegisterVariable(&r_shadow_gloss); + Cvar_RegisterVariable(&r_shadow_gloss2intensity); + Cvar_RegisterVariable(&r_shadow_glossintensity); + Cvar_RegisterVariable(&r_shadow_glossexponent); + Cvar_RegisterVariable(&r_shadow_gloss2exponent); + Cvar_RegisterVariable(&r_shadow_glossexact); + Cvar_RegisterVariable(&r_shadow_lightattenuationdividebias); + Cvar_RegisterVariable(&r_shadow_lightattenuationlinearscale); + Cvar_RegisterVariable(&r_shadow_lightintensityscale); + Cvar_RegisterVariable(&r_shadow_lightradiusscale); + Cvar_RegisterVariable(&r_shadow_projectdistance); + Cvar_RegisterVariable(&r_shadow_frontsidecasting); + Cvar_RegisterVariable(&r_shadow_realtime_dlight); + Cvar_RegisterVariable(&r_shadow_realtime_dlight_shadows); + Cvar_RegisterVariable(&r_shadow_realtime_dlight_svbspculling); + Cvar_RegisterVariable(&r_shadow_realtime_dlight_portalculling); + Cvar_RegisterVariable(&r_shadow_realtime_world); + Cvar_RegisterVariable(&r_shadow_realtime_world_lightmaps); + Cvar_RegisterVariable(&r_shadow_realtime_world_shadows); + Cvar_RegisterVariable(&r_shadow_realtime_world_compile); + Cvar_RegisterVariable(&r_shadow_realtime_world_compileshadow); + Cvar_RegisterVariable(&r_shadow_realtime_world_compilesvbsp); + Cvar_RegisterVariable(&r_shadow_realtime_world_compileportalculling); + Cvar_RegisterVariable(&r_shadow_scissor); + Cvar_RegisterVariable(&r_shadow_shadowmapping); + Cvar_RegisterVariable(&r_shadow_shadowmapping_vsdct); + Cvar_RegisterVariable(&r_shadow_shadowmapping_filterquality); + Cvar_RegisterVariable(&r_shadow_shadowmapping_useshadowsampler); + Cvar_RegisterVariable(&r_shadow_shadowmapping_depthbits); + Cvar_RegisterVariable(&r_shadow_shadowmapping_precision); + Cvar_RegisterVariable(&r_shadow_shadowmapping_maxsize); + Cvar_RegisterVariable(&r_shadow_shadowmapping_minsize); +// Cvar_RegisterVariable(&r_shadow_shadowmapping_lod_bias); +// Cvar_RegisterVariable(&r_shadow_shadowmapping_lod_scale); + Cvar_RegisterVariable(&r_shadow_shadowmapping_bordersize); + Cvar_RegisterVariable(&r_shadow_shadowmapping_nearclip); + Cvar_RegisterVariable(&r_shadow_shadowmapping_bias); + Cvar_RegisterVariable(&r_shadow_shadowmapping_polygonfactor); + Cvar_RegisterVariable(&r_shadow_shadowmapping_polygonoffset); + Cvar_RegisterVariable(&r_shadow_sortsurfaces); + Cvar_RegisterVariable(&r_shadow_polygonfactor); + Cvar_RegisterVariable(&r_shadow_polygonoffset); + Cvar_RegisterVariable(&r_shadow_texture3d); + Cvar_RegisterVariable(&r_shadow_bouncegrid); + Cvar_RegisterVariable(&r_shadow_bouncegrid_bounceanglediffuse); + Cvar_RegisterVariable(&r_shadow_bouncegrid_directionalshading); + Cvar_RegisterVariable(&r_shadow_bouncegrid_dlightparticlemultiplier); + Cvar_RegisterVariable(&r_shadow_bouncegrid_hitmodels); + Cvar_RegisterVariable(&r_shadow_bouncegrid_includedirectlighting); + Cvar_RegisterVariable(&r_shadow_bouncegrid_intensity); + Cvar_RegisterVariable(&r_shadow_bouncegrid_lightradiusscale); + Cvar_RegisterVariable(&r_shadow_bouncegrid_maxbounce); + Cvar_RegisterVariable(&r_shadow_bouncegrid_particlebounceintensity); + Cvar_RegisterVariable(&r_shadow_bouncegrid_particleintensity); + Cvar_RegisterVariable(&r_shadow_bouncegrid_photons); + Cvar_RegisterVariable(&r_shadow_bouncegrid_spacing); + Cvar_RegisterVariable(&r_shadow_bouncegrid_stablerandom); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static_directionalshading); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static_lightradiusscale); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static_maxbounce); + Cvar_RegisterVariable(&r_shadow_bouncegrid_static_photons); + Cvar_RegisterVariable(&r_shadow_bouncegrid_updateinterval); + Cvar_RegisterVariable(&r_shadow_bouncegrid_x); + Cvar_RegisterVariable(&r_shadow_bouncegrid_y); + Cvar_RegisterVariable(&r_shadow_bouncegrid_z); + Cvar_RegisterVariable(&r_coronas); + Cvar_RegisterVariable(&r_coronas_occlusionsizescale); + Cvar_RegisterVariable(&r_coronas_occlusionquery); + Cvar_RegisterVariable(&gl_flashblend); + Cvar_RegisterVariable(&gl_ext_separatestencil); + Cvar_RegisterVariable(&gl_ext_stenciltwoside); + R_Shadow_EditLights_Init(); + Mem_ExpandableArray_NewArray(&r_shadow_worldlightsarray, r_main_mempool, sizeof(dlight_t), 128); + maxshadowtriangles = 0; + shadowelements = NULL; + maxshadowvertices = 0; + shadowvertex3f = NULL; + maxvertexupdate = 0; + vertexupdate = NULL; + vertexremap = NULL; + vertexupdatenum = 0; + maxshadowmark = 0; + numshadowmark = 0; + shadowmark = NULL; + shadowmarklist = NULL; + shadowmarkcount = 0; + maxshadowsides = 0; + numshadowsides = 0; + shadowsides = NULL; + shadowsideslist = NULL; + r_shadow_buffer_numleafpvsbytes = 0; + r_shadow_buffer_visitingleafpvs = NULL; + r_shadow_buffer_leafpvs = NULL; + r_shadow_buffer_leaflist = NULL; + r_shadow_buffer_numsurfacepvsbytes = 0; + r_shadow_buffer_surfacepvs = NULL; + r_shadow_buffer_surfacelist = NULL; + r_shadow_buffer_surfacesides = NULL; + r_shadow_buffer_shadowtrispvs = NULL; + r_shadow_buffer_lighttrispvs = NULL; + R_RegisterModule("R_Shadow", r_shadow_start, r_shadow_shutdown, r_shadow_newmap, NULL, NULL); +} + +matrix4x4_t matrix_attenuationxyz = +{ + { + {0.5, 0.0, 0.0, 0.5}, + {0.0, 0.5, 0.0, 0.5}, + {0.0, 0.0, 0.5, 0.5}, + {0.0, 0.0, 0.0, 1.0} + } +}; + +matrix4x4_t matrix_attenuationz = +{ + { + {0.0, 0.0, 0.5, 0.5}, + {0.0, 0.0, 0.0, 0.5}, + {0.0, 0.0, 0.0, 0.5}, + {0.0, 0.0, 0.0, 1.0} + } +}; + +static void R_Shadow_ResizeShadowArrays(int numvertices, int numtriangles, int vertscale, int triscale) +{ + numvertices = ((numvertices + 255) & ~255) * vertscale; + numtriangles = ((numtriangles + 255) & ~255) * triscale; + // make sure shadowelements is big enough for this volume + if (maxshadowtriangles < numtriangles) + { + maxshadowtriangles = numtriangles; + if (shadowelements) + Mem_Free(shadowelements); + shadowelements = (int *)Mem_Alloc(r_main_mempool, maxshadowtriangles * sizeof(int[3])); + } + // make sure shadowvertex3f is big enough for this volume + if (maxshadowvertices < numvertices) + { + maxshadowvertices = numvertices; + if (shadowvertex3f) + Mem_Free(shadowvertex3f); + shadowvertex3f = (float *)Mem_Alloc(r_main_mempool, maxshadowvertices * sizeof(float[3])); + } +} + +static void R_Shadow_EnlargeLeafSurfaceTrisBuffer(int numleafs, int numsurfaces, int numshadowtriangles, int numlighttriangles) +{ + int numleafpvsbytes = (((numleafs + 7) >> 3) + 255) & ~255; + int numsurfacepvsbytes = (((numsurfaces + 7) >> 3) + 255) & ~255; + int numshadowtrispvsbytes = (((numshadowtriangles + 7) >> 3) + 255) & ~255; + int numlighttrispvsbytes = (((numlighttriangles + 7) >> 3) + 255) & ~255; + if (r_shadow_buffer_numleafpvsbytes < numleafpvsbytes) + { + if (r_shadow_buffer_visitingleafpvs) + Mem_Free(r_shadow_buffer_visitingleafpvs); + if (r_shadow_buffer_leafpvs) + Mem_Free(r_shadow_buffer_leafpvs); + if (r_shadow_buffer_leaflist) + Mem_Free(r_shadow_buffer_leaflist); + r_shadow_buffer_numleafpvsbytes = numleafpvsbytes; + r_shadow_buffer_visitingleafpvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes); + r_shadow_buffer_leafpvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes); + r_shadow_buffer_leaflist = (int *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes * 8 * sizeof(*r_shadow_buffer_leaflist)); + } + if (r_shadow_buffer_numsurfacepvsbytes < numsurfacepvsbytes) + { + if (r_shadow_buffer_surfacepvs) + Mem_Free(r_shadow_buffer_surfacepvs); + if (r_shadow_buffer_surfacelist) + Mem_Free(r_shadow_buffer_surfacelist); + if (r_shadow_buffer_surfacesides) + Mem_Free(r_shadow_buffer_surfacesides); + r_shadow_buffer_numsurfacepvsbytes = numsurfacepvsbytes; + r_shadow_buffer_surfacepvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes); + r_shadow_buffer_surfacelist = (int *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes * 8 * sizeof(*r_shadow_buffer_surfacelist)); + r_shadow_buffer_surfacesides = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes * 8 * sizeof(*r_shadow_buffer_surfacelist)); + } + if (r_shadow_buffer_numshadowtrispvsbytes < numshadowtrispvsbytes) + { + if (r_shadow_buffer_shadowtrispvs) + Mem_Free(r_shadow_buffer_shadowtrispvs); + r_shadow_buffer_numshadowtrispvsbytes = numshadowtrispvsbytes; + r_shadow_buffer_shadowtrispvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numshadowtrispvsbytes); + } + if (r_shadow_buffer_numlighttrispvsbytes < numlighttrispvsbytes) + { + if (r_shadow_buffer_lighttrispvs) + Mem_Free(r_shadow_buffer_lighttrispvs); + r_shadow_buffer_numlighttrispvsbytes = numlighttrispvsbytes; + r_shadow_buffer_lighttrispvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numlighttrispvsbytes); + } +} + +void R_Shadow_PrepareShadowMark(int numtris) +{ + // make sure shadowmark is big enough for this volume + if (maxshadowmark < numtris) + { + maxshadowmark = numtris; + if (shadowmark) + Mem_Free(shadowmark); + if (shadowmarklist) + Mem_Free(shadowmarklist); + shadowmark = (int *)Mem_Alloc(r_main_mempool, maxshadowmark * sizeof(*shadowmark)); + shadowmarklist = (int *)Mem_Alloc(r_main_mempool, maxshadowmark * sizeof(*shadowmarklist)); + shadowmarkcount = 0; + } + shadowmarkcount++; + // if shadowmarkcount wrapped we clear the array and adjust accordingly + if (shadowmarkcount == 0) + { + shadowmarkcount = 1; + memset(shadowmark, 0, maxshadowmark * sizeof(*shadowmark)); + } + numshadowmark = 0; +} + +void R_Shadow_PrepareShadowSides(int numtris) +{ + if (maxshadowsides < numtris) + { + maxshadowsides = numtris; + if (shadowsides) + Mem_Free(shadowsides); + if (shadowsideslist) + Mem_Free(shadowsideslist); + shadowsides = (unsigned char *)Mem_Alloc(r_main_mempool, maxshadowsides * sizeof(*shadowsides)); + shadowsideslist = (int *)Mem_Alloc(r_main_mempool, maxshadowsides * sizeof(*shadowsideslist)); + } + numshadowsides = 0; +} + +static int R_Shadow_ConstructShadowVolume_ZFail(int innumvertices, int innumtris, const int *inelement3i, const int *inneighbor3i, const float *invertex3f, int *outnumvertices, int *outelement3i, float *outvertex3f, const float *projectorigin, const float *projectdirection, float projectdistance, int numshadowmarktris, const int *shadowmarktris) +{ + int i, j; + int outtriangles = 0, outvertices = 0; + const int *element; + const float *vertex; + float ratio, direction[3], projectvector[3]; + + if (projectdirection) + VectorScale(projectdirection, projectdistance, projectvector); + else + VectorClear(projectvector); + + // create the vertices + if (projectdirection) + { + for (i = 0;i < numshadowmarktris;i++) + { + element = inelement3i + shadowmarktris[i] * 3; + for (j = 0;j < 3;j++) + { + if (vertexupdate[element[j]] != vertexupdatenum) + { + vertexupdate[element[j]] = vertexupdatenum; + vertexremap[element[j]] = outvertices; + vertex = invertex3f + element[j] * 3; + // project one copy of the vertex according to projectvector + VectorCopy(vertex, outvertex3f); + VectorAdd(vertex, projectvector, (outvertex3f + 3)); + outvertex3f += 6; + outvertices += 2; + } + } + } + } + else + { + for (i = 0;i < numshadowmarktris;i++) + { + element = inelement3i + shadowmarktris[i] * 3; + for (j = 0;j < 3;j++) + { + if (vertexupdate[element[j]] != vertexupdatenum) + { + vertexupdate[element[j]] = vertexupdatenum; + vertexremap[element[j]] = outvertices; + vertex = invertex3f + element[j] * 3; + // project one copy of the vertex to the sphere radius of the light + // (FIXME: would projecting it to the light box be better?) + VectorSubtract(vertex, projectorigin, direction); + ratio = projectdistance / VectorLength(direction); + VectorCopy(vertex, outvertex3f); + VectorMA(projectorigin, ratio, direction, (outvertex3f + 3)); + outvertex3f += 6; + outvertices += 2; + } + } + } + } + + if (r_shadow_frontsidecasting.integer) + { + for (i = 0;i < numshadowmarktris;i++) + { + int remappedelement[3]; + int markindex; + const int *neighbortriangle; + + markindex = shadowmarktris[i] * 3; + element = inelement3i + markindex; + neighbortriangle = inneighbor3i + markindex; + // output the front and back triangles + outelement3i[0] = vertexremap[element[0]]; + outelement3i[1] = vertexremap[element[1]]; + outelement3i[2] = vertexremap[element[2]]; + outelement3i[3] = vertexremap[element[2]] + 1; + outelement3i[4] = vertexremap[element[1]] + 1; + outelement3i[5] = vertexremap[element[0]] + 1; + + outelement3i += 6; + outtriangles += 2; + // output the sides (facing outward from this triangle) + if (shadowmark[neighbortriangle[0]] != shadowmarkcount) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[1] = vertexremap[element[1]]; + outelement3i[0] = remappedelement[1]; + outelement3i[1] = remappedelement[0]; + outelement3i[2] = remappedelement[0] + 1; + outelement3i[3] = remappedelement[1]; + outelement3i[4] = remappedelement[0] + 1; + outelement3i[5] = remappedelement[1] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (shadowmark[neighbortriangle[1]] != shadowmarkcount) + { + remappedelement[1] = vertexremap[element[1]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[2]; + outelement3i[1] = remappedelement[1]; + outelement3i[2] = remappedelement[1] + 1; + outelement3i[3] = remappedelement[2]; + outelement3i[4] = remappedelement[1] + 1; + outelement3i[5] = remappedelement[2] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (shadowmark[neighbortriangle[2]] != shadowmarkcount) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[0]; + outelement3i[1] = remappedelement[2]; + outelement3i[2] = remappedelement[2] + 1; + outelement3i[3] = remappedelement[0]; + outelement3i[4] = remappedelement[2] + 1; + outelement3i[5] = remappedelement[0] + 1; + + outelement3i += 6; + outtriangles += 2; + } + } + } + else + { + for (i = 0;i < numshadowmarktris;i++) + { + int remappedelement[3]; + int markindex; + const int *neighbortriangle; + + markindex = shadowmarktris[i] * 3; + element = inelement3i + markindex; + neighbortriangle = inneighbor3i + markindex; + // output the front and back triangles + outelement3i[0] = vertexremap[element[2]]; + outelement3i[1] = vertexremap[element[1]]; + outelement3i[2] = vertexremap[element[0]]; + outelement3i[3] = vertexremap[element[0]] + 1; + outelement3i[4] = vertexremap[element[1]] + 1; + outelement3i[5] = vertexremap[element[2]] + 1; + + outelement3i += 6; + outtriangles += 2; + // output the sides (facing outward from this triangle) + if (shadowmark[neighbortriangle[0]] != shadowmarkcount) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[1] = vertexremap[element[1]]; + outelement3i[0] = remappedelement[0]; + outelement3i[1] = remappedelement[1]; + outelement3i[2] = remappedelement[1] + 1; + outelement3i[3] = remappedelement[0]; + outelement3i[4] = remappedelement[1] + 1; + outelement3i[5] = remappedelement[0] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (shadowmark[neighbortriangle[1]] != shadowmarkcount) + { + remappedelement[1] = vertexremap[element[1]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[1]; + outelement3i[1] = remappedelement[2]; + outelement3i[2] = remappedelement[2] + 1; + outelement3i[3] = remappedelement[1]; + outelement3i[4] = remappedelement[2] + 1; + outelement3i[5] = remappedelement[1] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (shadowmark[neighbortriangle[2]] != shadowmarkcount) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[2]; + outelement3i[1] = remappedelement[0]; + outelement3i[2] = remappedelement[0] + 1; + outelement3i[3] = remappedelement[2]; + outelement3i[4] = remappedelement[0] + 1; + outelement3i[5] = remappedelement[2] + 1; + + outelement3i += 6; + outtriangles += 2; + } + } + } + if (outnumvertices) + *outnumvertices = outvertices; + return outtriangles; +} + +static int R_Shadow_ConstructShadowVolume_ZPass(int innumvertices, int innumtris, const int *inelement3i, const int *inneighbor3i, const float *invertex3f, int *outnumvertices, int *outelement3i, float *outvertex3f, const float *projectorigin, const float *projectdirection, float projectdistance, int numshadowmarktris, const int *shadowmarktris) +{ + int i, j, k; + int outtriangles = 0, outvertices = 0; + const int *element; + const float *vertex; + float ratio, direction[3], projectvector[3]; + qboolean side[4]; + + if (projectdirection) + VectorScale(projectdirection, projectdistance, projectvector); + else + VectorClear(projectvector); + + for (i = 0;i < numshadowmarktris;i++) + { + int remappedelement[3]; + int markindex; + const int *neighbortriangle; + + markindex = shadowmarktris[i] * 3; + neighbortriangle = inneighbor3i + markindex; + side[0] = shadowmark[neighbortriangle[0]] == shadowmarkcount; + side[1] = shadowmark[neighbortriangle[1]] == shadowmarkcount; + side[2] = shadowmark[neighbortriangle[2]] == shadowmarkcount; + if (side[0] + side[1] + side[2] == 0) + continue; + + side[3] = side[0]; + element = inelement3i + markindex; + + // create the vertices + for (j = 0;j < 3;j++) + { + if (side[j] + side[j+1] == 0) + continue; + k = element[j]; + if (vertexupdate[k] != vertexupdatenum) + { + vertexupdate[k] = vertexupdatenum; + vertexremap[k] = outvertices; + vertex = invertex3f + k * 3; + VectorCopy(vertex, outvertex3f); + if (projectdirection) + { + // project one copy of the vertex according to projectvector + VectorAdd(vertex, projectvector, (outvertex3f + 3)); + } + else + { + // project one copy of the vertex to the sphere radius of the light + // (FIXME: would projecting it to the light box be better?) + VectorSubtract(vertex, projectorigin, direction); + ratio = projectdistance / VectorLength(direction); + VectorMA(projectorigin, ratio, direction, (outvertex3f + 3)); + } + outvertex3f += 6; + outvertices += 2; + } + } + + // output the sides (facing outward from this triangle) + if (!side[0]) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[1] = vertexremap[element[1]]; + outelement3i[0] = remappedelement[1]; + outelement3i[1] = remappedelement[0]; + outelement3i[2] = remappedelement[0] + 1; + outelement3i[3] = remappedelement[1]; + outelement3i[4] = remappedelement[0] + 1; + outelement3i[5] = remappedelement[1] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (!side[1]) + { + remappedelement[1] = vertexremap[element[1]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[2]; + outelement3i[1] = remappedelement[1]; + outelement3i[2] = remappedelement[1] + 1; + outelement3i[3] = remappedelement[2]; + outelement3i[4] = remappedelement[1] + 1; + outelement3i[5] = remappedelement[2] + 1; + + outelement3i += 6; + outtriangles += 2; + } + if (!side[2]) + { + remappedelement[0] = vertexremap[element[0]]; + remappedelement[2] = vertexremap[element[2]]; + outelement3i[0] = remappedelement[0]; + outelement3i[1] = remappedelement[2]; + outelement3i[2] = remappedelement[2] + 1; + outelement3i[3] = remappedelement[0]; + outelement3i[4] = remappedelement[2] + 1; + outelement3i[5] = remappedelement[0] + 1; + + outelement3i += 6; + outtriangles += 2; + } + } + if (outnumvertices) + *outnumvertices = outvertices; + return outtriangles; +} + +void R_Shadow_MarkVolumeFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs) +{ + int t, tend; + const int *e; + const float *v[3]; + float normal[3]; + if (!BoxesOverlap(lightmins, lightmaxs, surfacemins, surfacemaxs)) + return; + tend = firsttriangle + numtris; + if (BoxInsideBox(surfacemins, surfacemaxs, lightmins, lightmaxs)) + { + // surface box entirely inside light box, no box cull + if (projectdirection) + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + TriangleNormal(invertex3f + e[0] * 3, invertex3f + e[1] * 3, invertex3f + e[2] * 3, normal); + if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0)) + shadowmarklist[numshadowmark++] = t; + } + } + else + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, invertex3f + e[0] * 3, invertex3f + e[1] * 3, invertex3f + e[2] * 3)) + shadowmarklist[numshadowmark++] = t; + } + } + else + { + // surface box not entirely inside light box, cull each triangle + if (projectdirection) + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3; + v[1] = invertex3f + e[1] * 3; + v[2] = invertex3f + e[2] * 3; + TriangleNormal(v[0], v[1], v[2], normal); + if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0) + && TriangleBBoxOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) + shadowmarklist[numshadowmark++] = t; + } + } + else + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3; + v[1] = invertex3f + e[1] * 3; + v[2] = invertex3f + e[2] * 3; + if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2]) + && TriangleBBoxOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) + shadowmarklist[numshadowmark++] = t; + } + } + } +} + +static qboolean R_Shadow_UseZPass(vec3_t mins, vec3_t maxs) +{ +#if 1 + return false; +#else + if (r_shadow_compilingrtlight || !r_shadow_frontsidecasting.integer || !r_shadow_usezpassifpossible.integer) + return false; + // check if the shadow volume intersects the near plane + // + // a ray between the eye and light origin may intersect the caster, + // indicating that the shadow may touch the eye location, however we must + // test the near plane (a polygon), not merely the eye location, so it is + // easiest to enlarge the caster bounding shape slightly for this. + // TODO + return true; +#endif +} + +void R_Shadow_VolumeFromList(int numverts, int numtris, const float *invertex3f, const int *elements, const int *neighbors, const vec3_t projectorigin, const vec3_t projectdirection, float projectdistance, int nummarktris, const int *marktris, vec3_t trismins, vec3_t trismaxs) +{ + int i, tris, outverts; + if (projectdistance < 0.1) + { + Con_Printf("R_Shadow_Volume: projectdistance %f\n", projectdistance); + return; + } + if (!numverts || !nummarktris) + return; + // make sure shadowelements is big enough for this volume + if (maxshadowtriangles < nummarktris*8 || maxshadowvertices < numverts*2) + R_Shadow_ResizeShadowArrays(numverts, nummarktris, 2, 8); + + if (maxvertexupdate < numverts) + { + maxvertexupdate = numverts; + if (vertexupdate) + Mem_Free(vertexupdate); + if (vertexremap) + Mem_Free(vertexremap); + vertexupdate = (int *)Mem_Alloc(r_main_mempool, maxvertexupdate * sizeof(int)); + vertexremap = (int *)Mem_Alloc(r_main_mempool, maxvertexupdate * sizeof(int)); + vertexupdatenum = 0; + } + vertexupdatenum++; + if (vertexupdatenum == 0) + { + vertexupdatenum = 1; + memset(vertexupdate, 0, maxvertexupdate * sizeof(int)); + memset(vertexremap, 0, maxvertexupdate * sizeof(int)); + } + + for (i = 0;i < nummarktris;i++) + shadowmark[marktris[i]] = shadowmarkcount; + + if (r_shadow_compilingrtlight) + { + // if we're compiling an rtlight, capture the mesh + //tris = R_Shadow_ConstructShadowVolume_ZPass(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + //Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zpass, NULL, NULL, NULL, shadowvertex3f, NULL, NULL, NULL, NULL, tris, shadowelements); + tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zfail, NULL, NULL, NULL, shadowvertex3f, NULL, NULL, NULL, NULL, tris, shadowelements); + } + else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_VISIBLEVOLUMES) + { + tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + R_Mesh_PrepareVertices_Vertex3f(outverts, shadowvertex3f, NULL, 0); + R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); + } + else + { + // decide which type of shadow to generate and set stencil mode + R_Shadow_RenderMode_StencilShadowVolumes(R_Shadow_UseZPass(trismins, trismaxs)); + // generate the sides or a solid volume, depending on type + if (r_shadow_rendermode >= R_SHADOW_RENDERMODE_ZPASS_STENCIL && r_shadow_rendermode <= R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE) + tris = R_Shadow_ConstructShadowVolume_ZPass(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + else + tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); + r_refdef.stats[r_stat_lights_dynamicshadowtriangles] += tris; + r_refdef.stats[r_stat_lights_shadowtriangles] += tris; + if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZPASS_STENCIL) + { + // increment stencil if frontface is infront of depthbuffer + GL_CullFace(r_refdef.view.cullface_front); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, 128, 255); + R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); + // decrement stencil if backface is infront of depthbuffer + GL_CullFace(r_refdef.view.cullface_back); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_ALWAYS, 128, 255); + } + else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZFAIL_STENCIL) + { + // decrement stencil if backface is behind depthbuffer + GL_CullFace(r_refdef.view.cullface_front); + R_SetStencil(true, 255, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, 128, 255); + //Android hack + R_Mesh_PrepareVertices_Vertex3f(outverts, shadowvertex3f, NULL, 0); + R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); + // increment stencil if frontface is behind depthbuffer + GL_CullFace(r_refdef.view.cullface_back); + R_SetStencil(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_ALWAYS, 128, 255); + } + //R_Mesh_PrepareVertices_Vertex3f(outverts, shadowvertex3f, NULL, 0); + R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); + } +} + +int R_Shadow_CalcTriangleSideMask(const vec3_t p1, const vec3_t p2, const vec3_t p3, float bias) +{ + // p1, p2, p3 are in the cubemap's local coordinate system + // bias = border/(size - border) + int mask = 0x3F; + + float dp1 = p1[0] + p1[1], dn1 = p1[0] - p1[1], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = p2[0] + p2[1], dn2 = p2[0] - p2[1], ap2 = fabs(dp2), an2 = fabs(dn2), + dp3 = p3[0] + p3[1], dn3 = p3[0] - p3[1], ap3 = fabs(dp3), an3 = fabs(dn3); + if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) + mask &= (3<<4) + | (dp1 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) + | (dp2 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) + | (dp3 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) + mask &= (3<<4) + | (dn1 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) + | (dn2 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) + | (dn3 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + + dp1 = p1[1] + p1[2], dn1 = p1[1] - p1[2], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = p2[1] + p2[2], dn2 = p2[1] - p2[2], ap2 = fabs(dp2), an2 = fabs(dn2), + dp3 = p3[1] + p3[2], dn3 = p3[1] - p3[2], ap3 = fabs(dp3), an3 = fabs(dn3); + if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) + mask &= (3<<0) + | (dp1 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) + | (dp2 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) + | (dp3 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) + mask &= (3<<0) + | (dn1 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) + | (dn2 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) + | (dn3 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + + dp1 = p1[2] + p1[0], dn1 = p1[2] - p1[0], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = p2[2] + p2[0], dn2 = p2[2] - p2[0], ap2 = fabs(dp2), an2 = fabs(dn2), + dp3 = p3[2] + p3[0], dn3 = p3[2] - p3[0], ap3 = fabs(dp3), an3 = fabs(dn3); + if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) + mask &= (3<<2) + | (dp1 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) + | (dp2 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) + | (dp3 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) + mask &= (3<<2) + | (dn1 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) + | (dn2 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) + | (dn3 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + + return mask; +} + +static int R_Shadow_CalcBBoxSideMask(const vec3_t mins, const vec3_t maxs, const matrix4x4_t *worldtolight, const matrix4x4_t *radiustolight, float bias) +{ + vec3_t center, radius, lightcenter, lightradius, pmin, pmax; + float dp1, dn1, ap1, an1, dp2, dn2, ap2, an2; + int mask = 0x3F; + + VectorSubtract(maxs, mins, radius); + VectorScale(radius, 0.5f, radius); + VectorAdd(mins, radius, center); + Matrix4x4_Transform(worldtolight, center, lightcenter); + Matrix4x4_Transform3x3(radiustolight, radius, lightradius); + VectorSubtract(lightcenter, lightradius, pmin); + VectorAdd(lightcenter, lightradius, pmax); + + dp1 = pmax[0] + pmax[1], dn1 = pmax[0] - pmin[1], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = pmin[0] + pmin[1], dn2 = pmin[0] - pmax[1], ap2 = fabs(dp2), an2 = fabs(dn2); + if(ap1 > bias*an1 && ap2 > bias*an2) + mask &= (3<<4) + | (dp1 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) + | (dp2 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + if(an1 > bias*ap1 && an2 > bias*ap2) + mask &= (3<<4) + | (dn1 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) + | (dn2 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + + dp1 = pmax[1] + pmax[2], dn1 = pmax[1] - pmin[2], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = pmin[1] + pmin[2], dn2 = pmin[1] - pmax[2], ap2 = fabs(dp2), an2 = fabs(dn2); + if(ap1 > bias*an1 && ap2 > bias*an2) + mask &= (3<<0) + | (dp1 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) + | (dp2 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + if(an1 > bias*ap1 && an2 > bias*ap2) + mask &= (3<<0) + | (dn1 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) + | (dn2 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + + dp1 = pmax[2] + pmax[0], dn1 = pmax[2] - pmin[0], ap1 = fabs(dp1), an1 = fabs(dn1), + dp2 = pmin[2] + pmin[0], dn2 = pmin[2] - pmax[0], ap2 = fabs(dp2), an2 = fabs(dn2); + if(ap1 > bias*an1 && ap2 > bias*an2) + mask &= (3<<2) + | (dp1 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) + | (dp2 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + if(an1 > bias*ap1 && an2 > bias*ap2) + mask &= (3<<2) + | (dn1 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) + | (dn2 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + + return mask; +} + +#define R_Shadow_CalcEntitySideMask(ent, worldtolight, radiustolight, bias) R_Shadow_CalcBBoxSideMask((ent)->mins, (ent)->maxs, worldtolight, radiustolight, bias) + +int R_Shadow_CalcSphereSideMask(const vec3_t p, float radius, float bias) +{ + // p is in the cubemap's local coordinate system + // bias = border/(size - border) + float dxyp = p[0] + p[1], dxyn = p[0] - p[1], axyp = fabs(dxyp), axyn = fabs(dxyn); + float dyzp = p[1] + p[2], dyzn = p[1] - p[2], ayzp = fabs(dyzp), ayzn = fabs(dyzn); + float dzxp = p[2] + p[0], dzxn = p[2] - p[0], azxp = fabs(dzxp), azxn = fabs(dzxn); + int mask = 0x3F; + if(axyp > bias*axyn + radius) mask &= dxyp < 0 ? ~((1<<0)|(1<<2)) : ~((2<<0)|(2<<2)); + if(axyn > bias*axyp + radius) mask &= dxyn < 0 ? ~((1<<0)|(2<<2)) : ~((2<<0)|(1<<2)); + if(ayzp > bias*ayzn + radius) mask &= dyzp < 0 ? ~((1<<2)|(1<<4)) : ~((2<<2)|(2<<4)); + if(ayzn > bias*ayzp + radius) mask &= dyzn < 0 ? ~((1<<2)|(2<<4)) : ~((2<<2)|(1<<4)); + if(azxp > bias*azxn + radius) mask &= dzxp < 0 ? ~((1<<4)|(1<<0)) : ~((2<<4)|(2<<0)); + if(azxn > bias*azxp + radius) mask &= dzxn < 0 ? ~((1<<4)|(2<<0)) : ~((2<<4)|(1<<0)); + return mask; +} + +static int R_Shadow_CullFrustumSides(rtlight_t *rtlight, float size, float border) +{ + int i; + vec3_t o, p, n; + int sides = 0x3F, masks[6] = { 3<<4, 3<<4, 3<<0, 3<<0, 3<<2, 3<<2 }; + float scale = (size - 2*border)/size, len; + float bias = border / (float)(size - border), dp, dn, ap, an; + // check if cone enclosing side would cross frustum plane + scale = 2 / (scale*scale + 2); + Matrix4x4_OriginFromMatrix(&rtlight->matrix_lighttoworld, o); + for (i = 0;i < 5;i++) + { + if (PlaneDiff(o, &r_refdef.view.frustum[i]) > -0.03125) + continue; + Matrix4x4_Transform3x3(&rtlight->matrix_worldtolight, r_refdef.view.frustum[i].normal, n); + len = scale*VectorLength2(n); + if(n[0]*n[0] > len) sides &= n[0] < 0 ? ~(1<<0) : ~(2 << 0); + if(n[1]*n[1] > len) sides &= n[1] < 0 ? ~(1<<2) : ~(2 << 2); + if(n[2]*n[2] > len) sides &= n[2] < 0 ? ~(1<<4) : ~(2 << 4); + } + if (PlaneDiff(o, &r_refdef.view.frustum[4]) >= r_refdef.farclip - r_refdef.nearclip + 0.03125) + { + Matrix4x4_Transform3x3(&rtlight->matrix_worldtolight, r_refdef.view.frustum[4].normal, n); + len = scale*VectorLength2(n); + if(n[0]*n[0] > len) sides &= n[0] >= 0 ? ~(1<<0) : ~(2 << 0); + if(n[1]*n[1] > len) sides &= n[1] >= 0 ? ~(1<<2) : ~(2 << 2); + if(n[2]*n[2] > len) sides &= n[2] >= 0 ? ~(1<<4) : ~(2 << 4); + } + // this next test usually clips off more sides than the former, but occasionally clips fewer/different ones, so do both and combine results + // check if frustum corners/origin cross plane sides +#if 1 + // infinite version, assumes frustum corners merely give direction and extend to infinite distance + Matrix4x4_Transform(&rtlight->matrix_worldtolight, r_refdef.view.origin, p); + dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn); + masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn); + masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn); + masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + for (i = 0;i < 4;i++) + { + Matrix4x4_Transform(&rtlight->matrix_worldtolight, r_refdef.view.frustumcorner[i], n); + VectorSubtract(n, p, n); + dp = n[0] + n[1], dn = n[0] - n[1], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[0] |= dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2); + if(an > 0) masks[1] |= dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2); + dp = n[1] + n[2], dn = n[1] - n[2], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[2] |= dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4); + if(an > 0) masks[3] |= dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4); + dp = n[2] + n[0], dn = n[2] - n[0], ap = fabs(dp), an = fabs(dn); + if(ap > 0) masks[4] |= dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0); + if(an > 0) masks[5] |= dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0); + } +#else + // finite version, assumes corners are a finite distance from origin dependent on far plane + for (i = 0;i < 5;i++) + { + Matrix4x4_Transform(&rtlight->matrix_worldtolight, !i ? r_refdef.view.origin : r_refdef.view.frustumcorner[i-1], p); + dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn); + masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); + masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); + dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn); + masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); + masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); + dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn); + masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); + masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); + } +#endif + return sides & masks[0] & masks[1] & masks[2] & masks[3] & masks[4] & masks[5]; +} + +int R_Shadow_ChooseSidesFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const matrix4x4_t *worldtolight, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs, int *totals) +{ + int t, tend; + const int *e; + const float *v[3]; + float normal[3]; + vec3_t p[3]; + float bias; + int mask, surfacemask = 0; + if (!BoxesOverlap(lightmins, lightmaxs, surfacemins, surfacemaxs)) + return 0; + bias = r_shadow_shadowmapborder / (float)(r_shadow_shadowmapmaxsize - r_shadow_shadowmapborder); + tend = firsttriangle + numtris; + if (BoxInsideBox(surfacemins, surfacemaxs, lightmins, lightmaxs)) + { + // surface box entirely inside light box, no box cull + if (projectdirection) + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; + TriangleNormal(v[0], v[1], v[2], normal); + if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0)) + { + Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); + mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); + surfacemask |= mask; + if(totals) + { + totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; + shadowsides[numshadowsides] = mask; + shadowsideslist[numshadowsides++] = t; + } + } + } + } + else + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; + if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2])) + { + Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); + mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); + surfacemask |= mask; + if(totals) + { + totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; + shadowsides[numshadowsides] = mask; + shadowsideslist[numshadowsides++] = t; + } + } + } + } + } + else + { + // surface box not entirely inside light box, cull each triangle + if (projectdirection) + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; + TriangleNormal(v[0], v[1], v[2], normal); + if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0) + && TriangleBBoxOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) + { + Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); + mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); + surfacemask |= mask; + if(totals) + { + totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; + shadowsides[numshadowsides] = mask; + shadowsideslist[numshadowsides++] = t; + } + } + } + } + else + { + for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) + { + v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; + if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2]) + && TriangleBBoxOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) + { + Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); + mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); + surfacemask |= mask; + if(totals) + { + totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; + shadowsides[numshadowsides] = mask; + shadowsideslist[numshadowsides++] = t; + } + } + } + } + } + return surfacemask; +} + +void R_Shadow_ShadowMapFromList(int numverts, int numtris, const float *vertex3f, const int *elements, int numsidetris, const int *sidetotals, const unsigned char *sides, const int *sidetris) +{ + int i, j, outtriangles = 0; + int *outelement3i[6]; + if (!numverts || !numsidetris || !r_shadow_compilingrtlight) + return; + outtriangles = sidetotals[0] + sidetotals[1] + sidetotals[2] + sidetotals[3] + sidetotals[4] + sidetotals[5]; + // make sure shadowelements is big enough for this mesh + if (maxshadowtriangles < outtriangles) + R_Shadow_ResizeShadowArrays(0, outtriangles, 0, 1); + + // compute the offset and size of the separate index lists for each cubemap side + outtriangles = 0; + for (i = 0;i < 6;i++) + { + outelement3i[i] = shadowelements + outtriangles * 3; + r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap->sideoffsets[i] = outtriangles; + r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap->sidetotals[i] = sidetotals[i]; + outtriangles += sidetotals[i]; + } + + // gather up the (sparse) triangles into separate index lists for each cubemap side + for (i = 0;i < numsidetris;i++) + { + const int *element = elements + sidetris[i] * 3; + for (j = 0;j < 6;j++) + { + if (sides[i] & (1 << j)) + { + outelement3i[j][0] = element[0]; + outelement3i[j][1] = element[1]; + outelement3i[j][2] = element[2]; + outelement3i[j] += 3; + } + } + } + + Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap, NULL, NULL, NULL, vertex3f, NULL, NULL, NULL, NULL, outtriangles, shadowelements); +} + +static void R_Shadow_MakeTextures_MakeCorona(void) +{ + float dx, dy; + int x, y, a; + unsigned char pixels[32][32][4]; + for (y = 0;y < 32;y++) + { + dy = (y - 15.5f) * (1.0f / 16.0f); + for (x = 0;x < 32;x++) + { + dx = (x - 15.5f) * (1.0f / 16.0f); + a = (int)(((1.0f / (dx * dx + dy * dy + 0.2f)) - (1.0f / (1.0f + 0.2))) * 32.0f / (1.0f / (1.0f + 0.2))); + a = bound(0, a, 255); + pixels[y][x][0] = a; + pixels[y][x][1] = a; + pixels[y][x][2] = a; + pixels[y][x][3] = 255; + } + } + r_shadow_lightcorona = R_SkinFrame_LoadInternalBGRA("lightcorona", TEXF_FORCELINEAR, &pixels[0][0][0], 32, 32, false); +} + +static unsigned int R_Shadow_MakeTextures_SamplePoint(float x, float y, float z) +{ + float dist = sqrt(x*x+y*y+z*z); + float intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; + // note this code could suffer byte order issues except that it is multiplying by an integer that reads the same both ways + return (unsigned char)bound(0, intensity * 256.0f, 255) * 0x01010101; +} + +static void R_Shadow_MakeTextures(void) +{ + int x, y, z; + float intensity, dist; + unsigned int *data; + R_Shadow_FreeShadowMaps(); + R_FreeTexturePool(&r_shadow_texturepool); + r_shadow_texturepool = R_AllocTexturePool(); + r_shadow_attenlinearscale = r_shadow_lightattenuationlinearscale.value; + r_shadow_attendividebias = r_shadow_lightattenuationdividebias.value; + data = (unsigned int *)Mem_Alloc(tempmempool, max(max(ATTEN3DSIZE*ATTEN3DSIZE*ATTEN3DSIZE, ATTEN2DSIZE*ATTEN2DSIZE), ATTEN1DSIZE) * 4); + // the table includes one additional value to avoid the need to clamp indexing due to minor math errors + for (x = 0;x <= ATTENTABLESIZE;x++) + { + dist = (x + 0.5f) * (1.0f / ATTENTABLESIZE) * (1.0f / 0.9375); + intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; + r_shadow_attentable[x] = bound(0, intensity, 1); + } + // 1D gradient texture + for (x = 0;x < ATTEN1DSIZE;x++) + data[x] = R_Shadow_MakeTextures_SamplePoint((x + 0.5f) * (1.0f / ATTEN1DSIZE) * (1.0f / 0.9375), 0, 0); + r_shadow_attenuationgradienttexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation1d", ATTEN1DSIZE, 1, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); + // 2D circle texture + for (y = 0;y < ATTEN2DSIZE;y++) + for (x = 0;x < ATTEN2DSIZE;x++) + data[y*ATTEN2DSIZE+x] = R_Shadow_MakeTextures_SamplePoint(((x + 0.5f) * (2.0f / ATTEN2DSIZE) - 1.0f) * (1.0f / 0.9375), ((y + 0.5f) * (2.0f / ATTEN2DSIZE) - 1.0f) * (1.0f / 0.9375), 0); + r_shadow_attenuation2dtexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation2d", ATTEN2DSIZE, ATTEN2DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); + // 3D sphere texture + if (r_shadow_texture3d.integer && vid.support.ext_texture_3d) + { + for (z = 0;z < ATTEN3DSIZE;z++) + for (y = 0;y < ATTEN3DSIZE;y++) + for (x = 0;x < ATTEN3DSIZE;x++) + data[(z*ATTEN3DSIZE+y)*ATTEN3DSIZE+x] = R_Shadow_MakeTextures_SamplePoint(((x + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375), ((y + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375), ((z + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375)); + r_shadow_attenuation3dtexture = R_LoadTexture3D(r_shadow_texturepool, "attenuation3d", ATTEN3DSIZE, ATTEN3DSIZE, ATTEN3DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); + } + else + r_shadow_attenuation3dtexture = NULL; + Mem_Free(data); + + R_Shadow_MakeTextures_MakeCorona(); + + // Editor light sprites + r_editlights_sprcursor = R_SkinFrame_LoadInternal8bit("gfx/editlights/cursor", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + ".3............3." + "..5...2332...5.." + "...7.3....3.7..." + "....7......7...." + "...3.7....7.3..." + "..2...7..7...2.." + "..3..........3.." + "..3..........3.." + "..2...7..7...2.." + "...3.7....7.3..." + "....7......7...." + "...7.3....3.7..." + "..5...2332...5.." + ".3............3." + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/light", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + "................" + "......1111......" + "....11233211...." + "...1234554321..." + "...1356776531..." + "..124677776421.." + "..135777777531.." + "..135777777531.." + "..124677776421.." + "...1356776531..." + "...1234554321..." + "....11233211...." + "......1111......" + "................" + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprnoshadowlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/noshadow", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + "................" + "......1111......" + "....11233211...." + "...1234554321..." + "...1356226531..." + "..12462..26421.." + "..1352....2531.." + "..1352....2531.." + "..12462..26421.." + "...1356226531..." + "...1234554321..." + "....11233211...." + "......1111......" + "................" + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprcubemaplight = R_SkinFrame_LoadInternal8bit("gfx/editlights/cubemaplight", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + "................" + "......2772......" + "....27755772...." + "..277533335772.." + "..753333333357.." + "..777533335777.." + "..735775577537.." + "..733357753337.." + "..733337733337.." + "..753337733357.." + "..277537735772.." + "....27777772...." + "......2772......" + "................" + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprcubemapnoshadowlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/cubemapnoshadowlight", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) + "................" + "................" + "......2772......" + "....27722772...." + "..2772....2772.." + "..72........27.." + "..7772....2777.." + "..7.27722772.7.." + "..7...2772...7.." + "..7....77....7.." + "..72...77...27.." + "..2772.77.2772.." + "....27777772...." + "......2772......" + "................" + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); + r_editlights_sprselection = R_SkinFrame_LoadInternal8bit("gfx/editlights/selection", TEXF_ALPHA | TEXF_CLAMP, (unsigned char *) + "................" + ".777752..257777." + ".742........247." + ".72..........27." + ".7............7." + ".5............5." + ".2............2." + "................" + "................" + ".2............2." + ".5............5." + ".7............7." + ".72..........27." + ".742........247." + ".777752..257777." + "................" + , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); +} + +void R_Shadow_ValidateCvars(void) +{ + if (r_shadow_texture3d.integer && !vid.support.ext_texture_3d) + Cvar_SetValueQuick(&r_shadow_texture3d, 0); + if (gl_ext_separatestencil.integer && !vid.support.ati_separate_stencil) + Cvar_SetValueQuick(&gl_ext_separatestencil, 0); + if (gl_ext_stenciltwoside.integer && !vid.support.ext_stencil_two_side) + Cvar_SetValueQuick(&gl_ext_stenciltwoside, 0); +} + +void R_Shadow_RenderMode_Begin(void) +{ +#if 0 + GLint drawbuffer; + GLint readbuffer; +#endif + R_Shadow_ValidateCvars(); + + if (!r_shadow_attenuation2dtexture + || (!r_shadow_attenuation3dtexture && r_shadow_texture3d.integer) + || r_shadow_lightattenuationdividebias.value != r_shadow_attendividebias + || r_shadow_lightattenuationlinearscale.value != r_shadow_attenlinearscale) + R_Shadow_MakeTextures(); + + CHECKGLERROR + R_Mesh_ResetTextureState(); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_DepthRange(0, 1); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); + GL_DepthTest(true); + GL_DepthMask(false); + GL_Color(0, 0, 0, 1); + GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + + r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; + + if (gl_ext_separatestencil.integer && vid.support.ati_separate_stencil) + { + r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL; + r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL; + } + else if (gl_ext_stenciltwoside.integer && vid.support.ext_stencil_two_side) + { + r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE; + r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE; + } + else + { + r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_STENCIL; + r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_STENCIL; + } + + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_GLSL; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + if (r_textureunits.integer >= 2 && vid.texunits >= 2 && r_shadow_texture3d.integer && r_shadow_attenuation3dtexture) + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN; + else if (r_textureunits.integer >= 3 && vid.texunits >= 3) + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN; + else if (r_textureunits.integer >= 2 && vid.texunits >= 2) + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN; + else + r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX; + break; + } + + CHECKGLERROR +#if 0 + qglGetIntegerv(GL_DRAW_BUFFER, &drawbuffer);CHECKGLERROR + qglGetIntegerv(GL_READ_BUFFER, &readbuffer);CHECKGLERROR + r_shadow_drawbuffer = drawbuffer; + r_shadow_readbuffer = readbuffer; +#endif + r_shadow_cullface_front = r_refdef.view.cullface_front; + r_shadow_cullface_back = r_refdef.view.cullface_back; +} + +void R_Shadow_RenderMode_ActiveLight(const rtlight_t *rtlight) +{ + rsurface.rtlight = rtlight; +} + +void R_Shadow_RenderMode_Reset(void) +{ + R_Mesh_ResetTextureState(); + R_Mesh_SetRenderTargets(r_shadow_fb_fbo, r_shadow_fb_depthtexture, r_shadow_fb_colortexture, NULL, NULL, NULL); + R_SetViewport(&r_refdef.view.viewport); + GL_Scissor(r_shadow_lightscissor[0], r_shadow_lightscissor[1], r_shadow_lightscissor[2], r_shadow_lightscissor[3]); + GL_DepthRange(0, 1); + GL_DepthTest(true); + GL_DepthMask(false); + GL_DepthFunc(GL_LEQUAL); + GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset);CHECKGLERROR + r_refdef.view.cullface_front = r_shadow_cullface_front; + r_refdef.view.cullface_back = r_shadow_cullface_back; + GL_CullFace(r_refdef.view.cullface_back); + GL_Color(1, 1, 1, 1); + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + GL_BlendFunc(GL_ONE, GL_ZERO); + R_SetupShader_Generic_NoTexture(false, false); + r_shadow_usingshadowmap2d = false; + r_shadow_usingshadowmaportho = false; + R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); +} + +void R_Shadow_ClearStencil(void) +{ + GL_Clear(GL_STENCIL_BUFFER_BIT, NULL, 1.0f, 128); + r_refdef.stats[r_stat_lights_clears]++; +} + +void R_Shadow_RenderMode_StencilShadowVolumes(qboolean zpass) +{ + r_shadow_rendermode_t mode = zpass ? r_shadow_shadowingrendermode_zpass : r_shadow_shadowingrendermode_zfail; + if (r_shadow_rendermode == mode) + return; + R_Shadow_RenderMode_Reset(); + GL_DepthFunc(GL_LESS); + GL_ColorMask(0, 0, 0, 0); + GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset);CHECKGLERROR + GL_CullFace(GL_NONE); + R_SetupShader_DepthOrShadow(false, false, false); // FIXME test if we have a skeletal model? + r_shadow_rendermode = mode; + switch(mode) + { + default: + break; + case R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE: + case R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL: + R_SetStencilSeparate(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, GL_ALWAYS, 128, 255); + break; + case R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE: + case R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL: + R_SetStencilSeparate(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, GL_ALWAYS, 128, 255); + break; + } +} + +static void R_Shadow_MakeVSDCT(void) +{ + // maps to a 2x3 texture rectangle with normalized coordinates + // +- + // XX + // YY + // ZZ + // stores abs(dir.xy), offset.xy/2.5 + unsigned char data[4*6] = + { + 255, 0, 0x33, 0x33, // +X: <1, 0>, <0.5, 0.5> + 255, 0, 0x99, 0x33, // -X: <1, 0>, <1.5, 0.5> + 0, 255, 0x33, 0x99, // +Y: <0, 1>, <0.5, 1.5> + 0, 255, 0x99, 0x99, // -Y: <0, 1>, <1.5, 1.5> + 0, 0, 0x33, 0xFF, // +Z: <0, 0>, <0.5, 2.5> + 0, 0, 0x99, 0xFF, // -Z: <0, 0>, <1.5, 2.5> + }; + r_shadow_shadowmapvsdcttexture = R_LoadTextureCubeMap(r_shadow_texturepool, "shadowmapvsdct", 1, data, TEXTYPE_RGBA, TEXF_FORCENEAREST | TEXF_CLAMP | TEXF_ALPHA, -1, NULL); +} + +static void R_Shadow_MakeShadowMap(int side, int size) +{ + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (r_shadow_shadowmap2ddepthtexture) return; + if (r_fb.usedepthtextures) + { + r_shadow_shadowmap2ddepthtexture = R_LoadTextureShadowMap2D(r_shadow_texturepool, "shadowmap", size*2, size*(vid.support.arb_texture_non_power_of_two ? 3 : 4), r_shadow_shadowmapdepthbits >= 24 ? (r_shadow_shadowmapsampler ? TEXTYPE_SHADOWMAP24_COMP : TEXTYPE_SHADOWMAP24_RAW) : (r_shadow_shadowmapsampler ? TEXTYPE_SHADOWMAP16_COMP : TEXTYPE_SHADOWMAP16_RAW), r_shadow_shadowmapsampler); + r_shadow_shadowmap2ddepthbuffer = NULL; + r_shadow_fbo2d = R_Mesh_CreateFramebufferObject(r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL, NULL); + } + else + { + r_shadow_shadowmap2ddepthtexture = R_LoadTexture2D(r_shadow_texturepool, "shadowmaprendertarget", size*2, size*(vid.support.arb_texture_non_power_of_two ? 3 : 4), NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCENEAREST | TEXF_CLAMP | TEXF_ALPHA, -1, NULL); + r_shadow_shadowmap2ddepthbuffer = R_LoadTextureRenderBuffer(r_shadow_texturepool, "shadowmap", size*2, size*(vid.support.arb_texture_non_power_of_two ? 3 : 4), r_shadow_shadowmapdepthbits >= 24 ? TEXTYPE_DEPTHBUFFER24 : TEXTYPE_DEPTHBUFFER16); + r_shadow_fbo2d = R_Mesh_CreateFramebufferObject(r_shadow_shadowmap2ddepthbuffer, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL); + } + break; + default: + return; + } +} + +static void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size) +{ + float nearclip, farclip, bias; + r_viewport_t viewport; + int flipped; + GLuint fbo2d = 0; + float clearcolor[4]; + nearclip = r_shadow_shadowmapping_nearclip.value / rsurface.rtlight->radius; + farclip = 1.0f; + bias = r_shadow_shadowmapping_bias.value * nearclip * (1024.0f / size);// * rsurface.rtlight->radius; + r_shadow_shadowmap_parameters[1] = -nearclip * farclip / (farclip - nearclip) - 0.5f * bias; + r_shadow_shadowmap_parameters[3] = 0.5f + 0.5f * (farclip + nearclip) / (farclip - nearclip); + r_shadow_shadowmapside = side; + r_shadow_shadowmapsize = size; + + r_shadow_shadowmap_parameters[0] = 0.5f * (size - r_shadow_shadowmapborder); + r_shadow_shadowmap_parameters[2] = r_shadow_shadowmapvsdct ? 2.5f*size : size; + R_Viewport_InitRectSideView(&viewport, &rsurface.rtlight->matrix_lighttoworld, side, size, r_shadow_shadowmapborder, nearclip, farclip, NULL); + if (r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAP2D) goto init_done; + + // complex unrolled cube approach (more flexible) + if (r_shadow_shadowmapvsdct && !r_shadow_shadowmapvsdcttexture) + R_Shadow_MakeVSDCT(); + if (!r_shadow_shadowmap2ddepthtexture) + R_Shadow_MakeShadowMap(side, r_shadow_shadowmapmaxsize); + fbo2d = r_shadow_fbo2d; + r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2ddepthtexture); + r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2ddepthtexture); + r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D; + + R_Mesh_ResetTextureState(); + R_Shadow_RenderMode_Reset(); + if (r_shadow_shadowmap2ddepthbuffer) + R_Mesh_SetRenderTargets(fbo2d, r_shadow_shadowmap2ddepthbuffer, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL); + else + R_Mesh_SetRenderTargets(fbo2d, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL, NULL); + R_SetupShader_DepthOrShadow(true, r_shadow_shadowmap2ddepthbuffer != NULL, false); // FIXME test if we have a skeletal model? + GL_PolygonOffset(r_shadow_shadowmapping_polygonfactor.value, r_shadow_shadowmapping_polygonoffset.value); + GL_DepthMask(true); + GL_DepthTest(true); + +init_done: + R_SetViewport(&viewport); + flipped = (side & 1) ^ (side >> 2); + r_refdef.view.cullface_front = flipped ? r_shadow_cullface_back : r_shadow_cullface_front; + r_refdef.view.cullface_back = flipped ? r_shadow_cullface_front : r_shadow_cullface_back; + if (r_shadow_shadowmap2ddepthbuffer) + { + // completely different meaning than in depthtexture approach + r_shadow_shadowmap_parameters[1] = 0; + r_shadow_shadowmap_parameters[3] = -bias; + } + Vector4Set(clearcolor, 1,1,1,1); + if (r_shadow_shadowmap2ddepthbuffer) + GL_ColorMask(1,1,1,1); + else + GL_ColorMask(0,0,0,0); + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + GL_CullFace(r_refdef.view.cullface_back); + // OpenGL lets us scissor larger than the viewport, so go ahead and clear all views at once + if ((clear & ((2 << side) - 1)) == (1 << side)) // only clear if the side is the first in the mask + { + // get tightest scissor rectangle that encloses all viewports in the clear mask + int x1 = clear & 0x15 ? 0 : size; + int x2 = clear & 0x2A ? 2 * size : size; + int y1 = clear & 0x03 ? 0 : (clear & 0xC ? size : 2 * size); + int y2 = clear & 0x30 ? 3 * size : (clear & 0xC ? 2 * size : size); + GL_Scissor(x1, y1, x2 - x1, y2 - y1); + if (clear) + { + if (r_shadow_shadowmap2ddepthbuffer) + GL_Clear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT, clearcolor, 1.0f, 0); + else + GL_Clear(GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + } + } + GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + // we invert the cull mode because we flip the projection matrix + // NOTE: this actually does nothing because the DrawShadowMap code sets it to doublesided... + GL_CullFace(r_refdef.view.cullface_front); + // D3D considers it an error to use a scissor larger than the viewport... clear just this view + GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); + if (clear) + { + if (r_shadow_shadowmap2ddepthbuffer) + GL_Clear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT, clearcolor, 1.0f, 0); + else + GL_Clear(GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + } + break; + } +} + +void R_Shadow_RenderMode_Lighting(qboolean stenciltest, qboolean transparent, qboolean shadowmapping) +{ + R_Mesh_ResetTextureState(); + if (transparent) + { + r_shadow_lightscissor[0] = r_refdef.view.viewport.x; + r_shadow_lightscissor[1] = r_refdef.view.viewport.y; + r_shadow_lightscissor[2] = r_refdef.view.viewport.width; + r_shadow_lightscissor[3] = r_refdef.view.viewport.height; + } + R_Shadow_RenderMode_Reset(); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + if (!transparent) + GL_DepthFunc(GL_EQUAL); + // do global setup needed for the chosen lighting mode + if (r_shadow_rendermode == R_SHADOW_RENDERMODE_LIGHT_GLSL) + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 0); + r_shadow_usingshadowmap2d = shadowmapping; + r_shadow_rendermode = r_shadow_lightingrendermode; + // only draw light where this geometry was already rendered AND the + // stencil is 128 (values other than this mean shadow) + if (stenciltest) + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); + else + R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); +} + +static const unsigned short bboxelements[36] = +{ + 5, 1, 3, 5, 3, 7, + 6, 2, 0, 6, 0, 4, + 7, 3, 2, 7, 2, 6, + 4, 0, 1, 4, 1, 5, + 4, 5, 7, 4, 7, 6, + 1, 0, 2, 1, 2, 3, +}; + +static const float bboxpoints[8][3] = +{ + {-1,-1,-1}, + { 1,-1,-1}, + {-1, 1,-1}, + { 1, 1,-1}, + {-1,-1, 1}, + { 1,-1, 1}, + {-1, 1, 1}, + { 1, 1, 1}, +}; + +void R_Shadow_RenderMode_DrawDeferredLight(qboolean stenciltest, qboolean shadowmapping) +{ + int i; + float vertex3f[8*3]; + const matrix4x4_t *matrix = &rsurface.rtlight->matrix_lighttoworld; +// do global setup needed for the chosen lighting mode + R_Shadow_RenderMode_Reset(); + r_shadow_rendermode = r_shadow_lightingrendermode; + R_EntityMatrix(&identitymatrix); + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); + // only draw light where this geometry was already rendered AND the + // stencil is 128 (values other than this mean shadow) + R_SetStencil(stenciltest, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); + if (rsurface.rtlight->specularscale > 0 && r_shadow_gloss.integer > 0) + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); + else + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusefbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); + + r_shadow_usingshadowmap2d = shadowmapping; + + // render the lighting + R_SetupShader_DeferredLight(rsurface.rtlight); + for (i = 0;i < 8;i++) + Matrix4x4_Transform(matrix, bboxpoints[i], vertex3f + i*3); + GL_ColorMask(1,1,1,1); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(true); + GL_DepthFunc(GL_GREATER); + GL_CullFace(r_refdef.view.cullface_back); + R_Mesh_PrepareVertices_Vertex3f(8, vertex3f, NULL, 0); + R_Mesh_Draw(0, 8, 0, 12, NULL, NULL, 0, bboxelements, NULL, 0); +} + +void R_Shadow_UpdateBounceGridTexture(void) +{ +#define MAXBOUNCEGRIDPARTICLESPERLIGHT 1048576 + dlight_t *light; + int flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + int bouncecount; + int hitsupercontentsmask; + int maxbounce; + int numpixels; + int resolution[3]; + int shootparticles; + int shotparticles; + int photoncount; + int tex[3]; + trace_t cliptrace; + //trace_t cliptrace2; + //trace_t cliptrace3; + unsigned char *pixel; + unsigned char *pixels; + float *highpixel; + float *highpixels; + unsigned int lightindex; + unsigned int range; + unsigned int range1; + unsigned int range2; + unsigned int seed = (unsigned int)(realtime * 1000.0f); + vec3_t shotcolor; + vec3_t baseshotcolor; + vec3_t surfcolor; + vec3_t clipend; + vec3_t clipstart; + vec3_t clipdiff; + vec3_t ispacing; + vec3_t maxs; + vec3_t mins; + vec3_t size; + vec3_t spacing; + vec3_t lightcolor; + vec3_t steppos; + vec3_t stepdelta; + vec3_t cullmins, cullmaxs; + vec_t radius; + vec_t s; + vec_t lightintensity; + vec_t photonscaling; + vec_t photonresidual; + float m[16]; + float texlerp[2][3]; + float splatcolor[32]; + float pixelweight[8]; + float w; + int c[4]; + int pixelindex[8]; + int corner; + int pixelsperband; + int pixelband; + int pixelbands; + int numsteps; + int step; + int x, y, z; + rtlight_t *rtlight; + r_shadow_bouncegrid_settings_t settings; + qboolean enable = r_shadow_bouncegrid.integer != 0 && r_refdef.scene.worldmodel; + qboolean allowdirectionalshading = false; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + allowdirectionalshading = true; + if (!vid.support.ext_texture_3d) + return; + break; + case RENDERPATH_GLES2: + // for performance reasons, do not use directional shading on GLES devices + if (!vid.support.ext_texture_3d) + return; + break; + // these renderpaths do not currently have the code to display the bouncegrid, so disable it on them... + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_SOFT: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + return; + } + + r_shadow_bouncegridintensity = r_shadow_bouncegrid_intensity.value; + + // see if there are really any lights to render... + if (enable && r_shadow_bouncegrid_static.integer) + { + enable = false; + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light || !(light->flags & flag)) + continue; + rtlight = &light->rtlight; + // when static, we skip styled lights because they tend to change... + if (rtlight->style > 0) + continue; + VectorScale(rtlight->color, (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale), lightcolor); + if (!VectorLength2(lightcolor)) + continue; + enable = true; + break; + } + } + + if (!enable) + { + if (r_shadow_bouncegridtexture) + { + R_FreeTexture(r_shadow_bouncegridtexture); + r_shadow_bouncegridtexture = NULL; + } + if (r_shadow_bouncegridpixels) + Mem_Free(r_shadow_bouncegridpixels); + r_shadow_bouncegridpixels = NULL; + if (r_shadow_bouncegridhighpixels) + Mem_Free(r_shadow_bouncegridhighpixels); + r_shadow_bouncegridhighpixels = NULL; + r_shadow_bouncegridnumpixels = 0; + r_shadow_bouncegriddirectional = false; + return; + } + + // build up a complete collection of the desired settings, so that memcmp can be used to compare parameters + memset(&settings, 0, sizeof(settings)); + settings.staticmode = r_shadow_bouncegrid_static.integer != 0; + settings.bounceanglediffuse = r_shadow_bouncegrid_bounceanglediffuse.integer != 0; + settings.directionalshading = (r_shadow_bouncegrid_static.integer != 0 ? r_shadow_bouncegrid_static_directionalshading.integer != 0 : r_shadow_bouncegrid_directionalshading.integer != 0) && allowdirectionalshading; + settings.dlightparticlemultiplier = r_shadow_bouncegrid_dlightparticlemultiplier.value; + settings.hitmodels = r_shadow_bouncegrid_hitmodels.integer != 0; + settings.includedirectlighting = r_shadow_bouncegrid_includedirectlighting.integer != 0 || r_shadow_bouncegrid.integer == 2; + settings.lightradiusscale = (r_shadow_bouncegrid_static.integer != 0 ? r_shadow_bouncegrid_static_lightradiusscale.value : r_shadow_bouncegrid_lightradiusscale.value); + settings.maxbounce = (r_shadow_bouncegrid_static.integer != 0 ? r_shadow_bouncegrid_static_maxbounce.integer : r_shadow_bouncegrid_maxbounce.integer); + settings.particlebounceintensity = r_shadow_bouncegrid_particlebounceintensity.value; + settings.particleintensity = r_shadow_bouncegrid_particleintensity.value * 16384.0f * (settings.directionalshading ? 4.0f : 1.0f) / (r_shadow_bouncegrid_spacing.value * r_shadow_bouncegrid_spacing.value); + settings.photons = r_shadow_bouncegrid_static.integer ? r_shadow_bouncegrid_static_photons.integer : r_shadow_bouncegrid_photons.integer; + settings.spacing[0] = r_shadow_bouncegrid_spacing.value; + settings.spacing[1] = r_shadow_bouncegrid_spacing.value; + settings.spacing[2] = r_shadow_bouncegrid_spacing.value; + settings.stablerandom = r_shadow_bouncegrid_stablerandom.integer; + + // bound the values for sanity + settings.photons = bound(1, settings.photons, 1048576); + settings.lightradiusscale = bound(0.0001f, settings.lightradiusscale, 1024.0f); + settings.maxbounce = bound(0, settings.maxbounce, 16); + settings.spacing[0] = bound(1, settings.spacing[0], 512); + settings.spacing[1] = bound(1, settings.spacing[1], 512); + settings.spacing[2] = bound(1, settings.spacing[2], 512); + + // get the spacing values + spacing[0] = settings.spacing[0]; + spacing[1] = settings.spacing[1]; + spacing[2] = settings.spacing[2]; + ispacing[0] = 1.0f / spacing[0]; + ispacing[1] = 1.0f / spacing[1]; + ispacing[2] = 1.0f / spacing[2]; + + // calculate texture size enclosing entire world bounds at the spacing + VectorMA(r_refdef.scene.worldmodel->normalmins, -2.0f, spacing, mins); + VectorMA(r_refdef.scene.worldmodel->normalmaxs, 2.0f, spacing, maxs); + VectorSubtract(maxs, mins, size); + // now we can calculate the resolution we want + c[0] = (int)floor(size[0] / spacing[0] + 0.5f); + c[1] = (int)floor(size[1] / spacing[1] + 0.5f); + c[2] = (int)floor(size[2] / spacing[2] + 0.5f); + // figure out the exact texture size (honoring power of 2 if required) + c[0] = bound(4, c[0], (int)vid.maxtexturesize_3d); + c[1] = bound(4, c[1], (int)vid.maxtexturesize_3d); + c[2] = bound(4, c[2], (int)vid.maxtexturesize_3d); + if (vid.support.arb_texture_non_power_of_two) + { + resolution[0] = c[0]; + resolution[1] = c[1]; + resolution[2] = c[2]; + } + else + { + for (resolution[0] = 4;resolution[0] < c[0];resolution[0]*=2) ; + for (resolution[1] = 4;resolution[1] < c[1];resolution[1]*=2) ; + for (resolution[2] = 4;resolution[2] < c[2];resolution[2]*=2) ; + } + size[0] = spacing[0] * resolution[0]; + size[1] = spacing[1] * resolution[1]; + size[2] = spacing[2] * resolution[2]; + + // if dynamic we may or may not want to use the world bounds + // if the dynamic size is smaller than the world bounds, use it instead + if (!settings.staticmode && (r_shadow_bouncegrid_x.integer * r_shadow_bouncegrid_y.integer * r_shadow_bouncegrid_z.integer < resolution[0] * resolution[1] * resolution[2])) + { + // we know the resolution we want + c[0] = r_shadow_bouncegrid_x.integer; + c[1] = r_shadow_bouncegrid_y.integer; + c[2] = r_shadow_bouncegrid_z.integer; + // now we can calculate the texture size (power of 2 if required) + c[0] = bound(4, c[0], (int)vid.maxtexturesize_3d); + c[1] = bound(4, c[1], (int)vid.maxtexturesize_3d); + c[2] = bound(4, c[2], (int)vid.maxtexturesize_3d); + if (vid.support.arb_texture_non_power_of_two) + { + resolution[0] = c[0]; + resolution[1] = c[1]; + resolution[2] = c[2]; + } + else + { + for (resolution[0] = 4;resolution[0] < c[0];resolution[0]*=2) ; + for (resolution[1] = 4;resolution[1] < c[1];resolution[1]*=2) ; + for (resolution[2] = 4;resolution[2] < c[2];resolution[2]*=2) ; + } + size[0] = spacing[0] * resolution[0]; + size[1] = spacing[1] * resolution[1]; + size[2] = spacing[2] * resolution[2]; + // center the rendering on the view + mins[0] = floor(r_refdef.view.origin[0] * ispacing[0] + 0.5f) * spacing[0] - 0.5f * size[0]; + mins[1] = floor(r_refdef.view.origin[1] * ispacing[1] + 0.5f) * spacing[1] - 0.5f * size[1]; + mins[2] = floor(r_refdef.view.origin[2] * ispacing[2] + 0.5f) * spacing[2] - 0.5f * size[2]; + } + + // recalculate the maxs in case the resolution was not satisfactory + VectorAdd(mins, size, maxs); + + // if all the settings seem identical to the previous update, return + if (r_shadow_bouncegridtexture && (settings.staticmode || realtime < r_shadow_bouncegridtime + r_shadow_bouncegrid_updateinterval.value) && !memcmp(&r_shadow_bouncegridsettings, &settings, sizeof(settings))) + return; + + // store the new settings + r_shadow_bouncegridsettings = settings; + + pixelbands = settings.directionalshading ? 8 : 1; + pixelsperband = resolution[0]*resolution[1]*resolution[2]; + numpixels = pixelsperband*pixelbands; + + // we're going to update the bouncegrid, update the matrix... + memset(m, 0, sizeof(m)); + m[0] = 1.0f / size[0]; + m[3] = -mins[0] * m[0]; + m[5] = 1.0f / size[1]; + m[7] = -mins[1] * m[5]; + m[10] = 1.0f / size[2]; + m[11] = -mins[2] * m[10]; + m[15] = 1.0f; + Matrix4x4_FromArrayFloatD3D(&r_shadow_bouncegridmatrix, m); + // reallocate pixels for this update if needed... + if (r_shadow_bouncegridnumpixels != numpixels || !r_shadow_bouncegridpixels || !r_shadow_bouncegridhighpixels) + { + if (r_shadow_bouncegridtexture) + { + R_FreeTexture(r_shadow_bouncegridtexture); + r_shadow_bouncegridtexture = NULL; + } + r_shadow_bouncegridpixels = (unsigned char *)Mem_Realloc(r_main_mempool, r_shadow_bouncegridpixels, numpixels * sizeof(unsigned char[4])); + r_shadow_bouncegridhighpixels = (float *)Mem_Realloc(r_main_mempool, r_shadow_bouncegridhighpixels, numpixels * sizeof(float[4])); + } + r_shadow_bouncegridnumpixels = numpixels; + pixels = r_shadow_bouncegridpixels; + highpixels = r_shadow_bouncegridhighpixels; + x = pixelsperband*4; + for (pixelband = 0;pixelband < pixelbands;pixelband++) + { + if (pixelband == 1) + memset(pixels + pixelband * x, 128, x); + else + memset(pixels + pixelband * x, 0, x); + } + memset(highpixels, 0, numpixels * sizeof(float[4])); + // figure out what we want to interact with + if (settings.hitmodels) + hitsupercontentsmask = SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY;// | SUPERCONTENTS_LIQUIDSMASK; + else + hitsupercontentsmask = SUPERCONTENTS_SOLID;// | SUPERCONTENTS_LIQUIDSMASK; + maxbounce = settings.maxbounce; + // clear variables that produce warnings otherwise + memset(splatcolor, 0, sizeof(splatcolor)); + // iterate world rtlights + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + range1 = settings.staticmode ? 0 : r_refdef.scene.numlights; + range2 = range + range1; + photoncount = 0; + for (lightindex = 0;lightindex < range2;lightindex++) + { + if (lightindex < range) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + rtlight = &light->rtlight; + VectorClear(rtlight->photoncolor); + rtlight->photons = 0; + if (!(light->flags & flag)) + continue; + if (settings.staticmode) + { + // when static, we skip styled lights because they tend to change... + if (rtlight->style > 0 && r_shadow_bouncegrid.integer != 2) + continue; + } + } + else + { + rtlight = r_refdef.scene.lights[lightindex - range]; + VectorClear(rtlight->photoncolor); + rtlight->photons = 0; + } + // draw only visible lights (major speedup) + radius = rtlight->radius * settings.lightradiusscale; + cullmins[0] = rtlight->shadoworigin[0] - radius; + cullmins[1] = rtlight->shadoworigin[1] - radius; + cullmins[2] = rtlight->shadoworigin[2] - radius; + cullmaxs[0] = rtlight->shadoworigin[0] + radius; + cullmaxs[1] = rtlight->shadoworigin[1] + radius; + cullmaxs[2] = rtlight->shadoworigin[2] + radius; + if (R_CullBox(cullmins, cullmaxs)) + continue; + if (r_refdef.scene.worldmodel + && r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs + && !r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs(r_refdef.scene.worldmodel, r_refdef.viewcache.world_leafvisible, cullmins, cullmaxs)) + continue; + w = r_shadow_lightintensityscale.value * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale); + if (w * VectorLength2(rtlight->color) == 0.0f) + continue; + w *= (rtlight->style >= 0 ? r_refdef.scene.rtlightstylevalue[rtlight->style] : 1); + VectorScale(rtlight->color, w, rtlight->photoncolor); + //if (!VectorLength2(rtlight->photoncolor)) + // continue; + // shoot particles from this light + // use a calculation for the number of particles that will not + // vary with lightstyle, otherwise we get randomized particle + // distribution, the seeded random is only consistent for a + // consistent number of particles on this light... + s = rtlight->radius; + lightintensity = VectorLength(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale); + if (lightindex >= range) + lightintensity *= settings.dlightparticlemultiplier; + rtlight->photons = max(0.0f, lightintensity * s * s); + photoncount += rtlight->photons; + } + photonscaling = (float)settings.photons / max(1, photoncount); + photonresidual = 0.0f; + for (lightindex = 0;lightindex < range2;lightindex++) + { + if (lightindex < range) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + rtlight = &light->rtlight; + } + else + rtlight = r_refdef.scene.lights[lightindex - range]; + // skip a light with no photons + if (rtlight->photons == 0.0f) + continue; + // skip a light with no photon color) + if (VectorLength2(rtlight->photoncolor) == 0.0f) + continue; + photonresidual += rtlight->photons * photonscaling; + shootparticles = (int)bound(0, photonresidual, MAXBOUNCEGRIDPARTICLESPERLIGHT); + if (!shootparticles) + continue; + photonresidual -= shootparticles; + radius = rtlight->radius * settings.lightradiusscale; + s = settings.particleintensity / shootparticles; + VectorScale(rtlight->photoncolor, s, baseshotcolor); + r_refdef.stats[r_stat_bouncegrid_lights]++; + r_refdef.stats[r_stat_bouncegrid_particles] += shootparticles; + for (shotparticles = 0;shotparticles < shootparticles;shotparticles++) + { + if (settings.stablerandom > 0) + seed = lightindex * 11937 + shotparticles; + VectorCopy(baseshotcolor, shotcolor); + VectorCopy(rtlight->shadoworigin, clipstart); + if (settings.stablerandom < 0) + VectorRandom(clipend); + else + VectorCheeseRandom(clipend); + VectorMA(clipstart, radius, clipend, clipend); + for (bouncecount = 0;;bouncecount++) + { + r_refdef.stats[r_stat_bouncegrid_traces]++; + //r_refdef.scene.worldmodel->TraceLineAgainstSurfaces(r_refdef.scene.worldmodel, NULL, NULL, &cliptrace, clipstart, clipend, hitsupercontentsmask); + //r_refdef.scene.worldmodel->TraceLine(r_refdef.scene.worldmodel, NULL, NULL, &cliptrace2, clipstart, clipend, hitsupercontentsmask); + if (settings.staticmode) + { + // static mode fires a LOT of rays but none of them are identical, so they are not cached + cliptrace = CL_TraceLine(clipstart, clipend, settings.staticmode ? MOVE_WORLDONLY : (settings.hitmodels ? MOVE_HITMODEL : MOVE_NOMONSTERS), NULL, hitsupercontentsmask, true, false, NULL, true, true); + } + else + { + // dynamic mode fires many rays and most will match the cache from the previous frame + cliptrace = CL_Cache_TraceLineSurfaces(clipstart, clipend, settings.staticmode ? MOVE_WORLDONLY : (settings.hitmodels ? MOVE_HITMODEL : MOVE_NOMONSTERS), hitsupercontentsmask); + } + if (bouncecount > 0 || settings.includedirectlighting) + { + // calculate second order spherical harmonics values (average, slopeX, slopeY, slopeZ) + // accumulate average shotcolor + w = VectorLength(shotcolor); + splatcolor[ 0] = shotcolor[0]; + splatcolor[ 1] = shotcolor[1]; + splatcolor[ 2] = shotcolor[2]; + splatcolor[ 3] = 0.0f; + if (pixelbands > 1) + { + VectorSubtract(clipstart, cliptrace.endpos, clipdiff); + VectorNormalize(clipdiff); + // store bentnormal in case the shader has a use for it + splatcolor[ 4] = clipdiff[0] * w; + splatcolor[ 5] = clipdiff[1] * w; + splatcolor[ 6] = clipdiff[2] * w; + splatcolor[ 7] = w; + // accumulate directional contributions (+X, +Y, +Z, -X, -Y, -Z) + splatcolor[ 8] = shotcolor[0] * max(0.0f, clipdiff[0]); + splatcolor[ 9] = shotcolor[0] * max(0.0f, clipdiff[1]); + splatcolor[10] = shotcolor[0] * max(0.0f, clipdiff[2]); + splatcolor[11] = 0.0f; + splatcolor[12] = shotcolor[1] * max(0.0f, clipdiff[0]); + splatcolor[13] = shotcolor[1] * max(0.0f, clipdiff[1]); + splatcolor[14] = shotcolor[1] * max(0.0f, clipdiff[2]); + splatcolor[15] = 0.0f; + splatcolor[16] = shotcolor[2] * max(0.0f, clipdiff[0]); + splatcolor[17] = shotcolor[2] * max(0.0f, clipdiff[1]); + splatcolor[18] = shotcolor[2] * max(0.0f, clipdiff[2]); + splatcolor[19] = 0.0f; + splatcolor[20] = shotcolor[0] * max(0.0f, -clipdiff[0]); + splatcolor[21] = shotcolor[0] * max(0.0f, -clipdiff[1]); + splatcolor[22] = shotcolor[0] * max(0.0f, -clipdiff[2]); + splatcolor[23] = 0.0f; + splatcolor[24] = shotcolor[1] * max(0.0f, -clipdiff[0]); + splatcolor[25] = shotcolor[1] * max(0.0f, -clipdiff[1]); + splatcolor[26] = shotcolor[1] * max(0.0f, -clipdiff[2]); + splatcolor[27] = 0.0f; + splatcolor[28] = shotcolor[2] * max(0.0f, -clipdiff[0]); + splatcolor[29] = shotcolor[2] * max(0.0f, -clipdiff[1]); + splatcolor[30] = shotcolor[2] * max(0.0f, -clipdiff[2]); + splatcolor[31] = 0.0f; + } + // calculate the number of steps we need to traverse this distance + VectorSubtract(cliptrace.endpos, clipstart, stepdelta); + numsteps = (int)(VectorLength(stepdelta) * ispacing[0]); + numsteps = bound(1, numsteps, 1024); + w = 1.0f / numsteps; + VectorScale(stepdelta, w, stepdelta); + VectorMA(clipstart, 0.5f, stepdelta, steppos); + for (step = 0;step < numsteps;step++) + { + r_refdef.stats[r_stat_bouncegrid_splats]++; + // figure out which texture pixel this is in + texlerp[1][0] = ((steppos[0] - mins[0]) * ispacing[0]) - 0.5f; + texlerp[1][1] = ((steppos[1] - mins[1]) * ispacing[1]) - 0.5f; + texlerp[1][2] = ((steppos[2] - mins[2]) * ispacing[2]) - 0.5f; + tex[0] = (int)floor(texlerp[1][0]); + tex[1] = (int)floor(texlerp[1][1]); + tex[2] = (int)floor(texlerp[1][2]); + if (tex[0] >= 1 && tex[1] >= 1 && tex[2] >= 1 && tex[0] < resolution[0] - 2 && tex[1] < resolution[1] - 2 && tex[2] < resolution[2] - 2) + { + // it is within bounds... do the real work now + // calculate the lerp factors + texlerp[1][0] -= tex[0]; + texlerp[1][1] -= tex[1]; + texlerp[1][2] -= tex[2]; + texlerp[0][0] = 1.0f - texlerp[1][0]; + texlerp[0][1] = 1.0f - texlerp[1][1]; + texlerp[0][2] = 1.0f - texlerp[1][2]; + // calculate individual pixel indexes and weights + pixelindex[0] = (((tex[2] )*resolution[1]+tex[1] )*resolution[0]+tex[0] );pixelweight[0] = (texlerp[0][0]*texlerp[0][1]*texlerp[0][2]); + pixelindex[1] = (((tex[2] )*resolution[1]+tex[1] )*resolution[0]+tex[0]+1);pixelweight[1] = (texlerp[1][0]*texlerp[0][1]*texlerp[0][2]); + pixelindex[2] = (((tex[2] )*resolution[1]+tex[1]+1)*resolution[0]+tex[0] );pixelweight[2] = (texlerp[0][0]*texlerp[1][1]*texlerp[0][2]); + pixelindex[3] = (((tex[2] )*resolution[1]+tex[1]+1)*resolution[0]+tex[0]+1);pixelweight[3] = (texlerp[1][0]*texlerp[1][1]*texlerp[0][2]); + pixelindex[4] = (((tex[2]+1)*resolution[1]+tex[1] )*resolution[0]+tex[0] );pixelweight[4] = (texlerp[0][0]*texlerp[0][1]*texlerp[1][2]); + pixelindex[5] = (((tex[2]+1)*resolution[1]+tex[1] )*resolution[0]+tex[0]+1);pixelweight[5] = (texlerp[1][0]*texlerp[0][1]*texlerp[1][2]); + pixelindex[6] = (((tex[2]+1)*resolution[1]+tex[1]+1)*resolution[0]+tex[0] );pixelweight[6] = (texlerp[0][0]*texlerp[1][1]*texlerp[1][2]); + pixelindex[7] = (((tex[2]+1)*resolution[1]+tex[1]+1)*resolution[0]+tex[0]+1);pixelweight[7] = (texlerp[1][0]*texlerp[1][1]*texlerp[1][2]); + // update the 8 pixels... + for (pixelband = 0;pixelband < pixelbands;pixelband++) + { + for (corner = 0;corner < 8;corner++) + { + // calculate address for pixel + w = pixelweight[corner]; + pixel = pixels + 4 * pixelindex[corner] + pixelband * pixelsperband * 4; + highpixel = highpixels + 4 * pixelindex[corner] + pixelband * pixelsperband * 4; + // add to the high precision pixel color + highpixel[0] += (splatcolor[pixelband*4+0]*w); + highpixel[1] += (splatcolor[pixelband*4+1]*w); + highpixel[2] += (splatcolor[pixelband*4+2]*w); + highpixel[3] += (splatcolor[pixelband*4+3]*w); + // flag the low precision pixel as needing to be updated + pixel[3] = 255; + // advance to next band of coefficients + //pixel += pixelsperband*4; + //highpixel += pixelsperband*4; + } + } + } + VectorAdd(steppos, stepdelta, steppos); + } + } + if (cliptrace.fraction >= 1.0f) + break; + r_refdef.stats[r_stat_bouncegrid_hits]++; + if (bouncecount >= maxbounce) + break; + // scale down shot color by bounce intensity and texture color (or 50% if no texture reported) + // also clamp the resulting color to never add energy, even if the user requests extreme values + if (cliptrace.hittexture && cliptrace.hittexture->currentskinframe) + VectorCopy(cliptrace.hittexture->currentskinframe->avgcolor, surfcolor); + else + VectorSet(surfcolor, 0.5f, 0.5f, 0.5f); + VectorScale(surfcolor, settings.particlebounceintensity, surfcolor); + surfcolor[0] = min(surfcolor[0], 1.0f); + surfcolor[1] = min(surfcolor[1], 1.0f); + surfcolor[2] = min(surfcolor[2], 1.0f); + VectorMultiply(shotcolor, surfcolor, shotcolor); + if (VectorLength2(baseshotcolor) == 0.0f) + break; + r_refdef.stats[r_stat_bouncegrid_bounces]++; + if (settings.bounceanglediffuse) + { + // random direction, primarily along plane normal + s = VectorDistance(cliptrace.endpos, clipend); + if (settings.stablerandom < 0) + VectorRandom(clipend); + else + VectorCheeseRandom(clipend); + VectorMA(cliptrace.plane.normal, 0.95f, clipend, clipend); + VectorNormalize(clipend); + VectorScale(clipend, s, clipend); + } + else + { + // reflect the remaining portion of the line across plane normal + VectorSubtract(clipend, cliptrace.endpos, clipdiff); + VectorReflect(clipdiff, 1.0, cliptrace.plane.normal, clipend); + } + // calculate the new line start and end + VectorCopy(cliptrace.endpos, clipstart); + VectorAdd(clipstart, clipend, clipend); + } + } + } + // generate pixels array from highpixels array + // skip first and last columns, rows, and layers as these are blank + // the pixel[3] value was written above, so we can use it to detect only pixels that need to be calculated + for (pixelband = 0;pixelband < pixelbands;pixelband++) + { + for (z = 1;z < resolution[2]-1;z++) + { + for (y = 1;y < resolution[1]-1;y++) + { + for (x = 1, pixelindex[0] = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x, pixel = pixels + 4*pixelindex[0], highpixel = highpixels + 4*pixelindex[0];x < resolution[0]-1;x++, pixel += 4, highpixel += 4) + { + // only convert pixels that were hit by photons + if (pixel[3] == 255) + { + // normalize the bentnormal... + if (pixelband == 1) + { + VectorNormalize(highpixel); + c[0] = (int)(highpixel[0]*128.0f+128.0f); + c[1] = (int)(highpixel[1]*128.0f+128.0f); + c[2] = (int)(highpixel[2]*128.0f+128.0f); + c[3] = (int)(highpixel[3]*128.0f+128.0f); + } + else + { + c[0] = (int)(highpixel[0]*256.0f); + c[1] = (int)(highpixel[1]*256.0f); + c[2] = (int)(highpixel[2]*256.0f); + c[3] = (int)(highpixel[3]*256.0f); + } + pixel[2] = (unsigned char)bound(0, c[0], 255); + pixel[1] = (unsigned char)bound(0, c[1], 255); + pixel[0] = (unsigned char)bound(0, c[2], 255); + pixel[3] = (unsigned char)bound(0, c[3], 255); + } + } + } + } + } + if (r_shadow_bouncegridtexture && r_shadow_bouncegridresolution[0] == resolution[0] && r_shadow_bouncegridresolution[1] == resolution[1] && r_shadow_bouncegridresolution[2] == resolution[2] && r_shadow_bouncegriddirectional == settings.directionalshading) + R_UpdateTexture(r_shadow_bouncegridtexture, pixels, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands); + else + { + VectorCopy(resolution, r_shadow_bouncegridresolution); + r_shadow_bouncegriddirectional = settings.directionalshading; + if (r_shadow_bouncegridtexture) + R_FreeTexture(r_shadow_bouncegridtexture); + r_shadow_bouncegridtexture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, pixels, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL); + } + r_shadow_bouncegridtime = realtime; +} + +void R_Shadow_RenderMode_VisibleShadowVolumes(void) +{ + R_Shadow_RenderMode_Reset(); + GL_BlendFunc(GL_ONE, GL_ONE); + GL_DepthRange(0, 1); + GL_DepthTest(r_showshadowvolumes.integer < 2); + GL_Color(0.0, 0.0125 * r_refdef.view.colorscale, 0.1 * r_refdef.view.colorscale, 1); + GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset);CHECKGLERROR + GL_CullFace(GL_NONE); + r_shadow_rendermode = R_SHADOW_RENDERMODE_VISIBLEVOLUMES; +} + +void R_Shadow_RenderMode_VisibleLighting(qboolean stenciltest, qboolean transparent) +{ + R_Shadow_RenderMode_Reset(); + GL_BlendFunc(GL_ONE, GL_ONE); + GL_DepthRange(0, 1); + GL_DepthTest(r_showlighting.integer < 2); + GL_Color(0.1 * r_refdef.view.colorscale, 0.0125 * r_refdef.view.colorscale, 0, 1); + if (!transparent) + GL_DepthFunc(GL_EQUAL); + R_SetStencil(stenciltest, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); + r_shadow_rendermode = R_SHADOW_RENDERMODE_VISIBLELIGHTING; +} + +void R_Shadow_RenderMode_End(void) +{ + R_Shadow_RenderMode_Reset(); + R_Shadow_RenderMode_ActiveLight(NULL); + GL_DepthMask(true); + GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; +} + +int bboxedges[12][2] = +{ + // top + {0, 1}, // +X + {0, 2}, // +Y + {1, 3}, // Y, +X + {2, 3}, // X, +Y + // bottom + {4, 5}, // +X + {4, 6}, // +Y + {5, 7}, // Y, +X + {6, 7}, // X, +Y + // verticals + {0, 4}, // +Z + {1, 5}, // X, +Z + {2, 6}, // Y, +Z + {3, 7}, // XY, +Z +}; + +qboolean R_Shadow_ScissorForBBox(const float *mins, const float *maxs) +{ + if (!r_shadow_scissor.integer || r_shadow_usingdeferredprepass || r_trippy.integer) + { + r_shadow_lightscissor[0] = r_refdef.view.viewport.x; + r_shadow_lightscissor[1] = r_refdef.view.viewport.y; + r_shadow_lightscissor[2] = r_refdef.view.viewport.width; + r_shadow_lightscissor[3] = r_refdef.view.viewport.height; + return false; + } + if(R_ScissorForBBox(mins, maxs, r_shadow_lightscissor)) + return true; // invisible + if(r_shadow_lightscissor[0] != r_refdef.view.viewport.x + || r_shadow_lightscissor[1] != r_refdef.view.viewport.y + || r_shadow_lightscissor[2] != r_refdef.view.viewport.width + || r_shadow_lightscissor[3] != r_refdef.view.viewport.height) + r_refdef.stats[r_stat_lights_scissored]++; + return false; +} + +static void R_Shadow_RenderLighting_Light_Vertex_Shading(int firstvertex, int numverts, const float *diffusecolor, const float *ambientcolor) +{ + int i; + const float *vertex3f; + const float *normal3f; + float *color4f; + float dist, dot, distintensity, shadeintensity, v[3], n[3]; + switch (r_shadow_rendermode) + { + case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: + if (VectorLength2(diffusecolor) > 0) + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); + if ((dot = DotProduct(n, v)) < 0) + { + shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); + VectorMA(ambientcolor, shadeintensity, diffusecolor, color4f); + } + else + VectorCopy(ambientcolor, color4f); + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + color4f[3] = 1; + } + } + else + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) + { + VectorCopy(ambientcolor, color4f); + if (r_refdef.fogenabled) + { + float f; + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f + 4*i, f, color4f); + } + color4f[3] = 1; + } + } + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: + if (VectorLength2(diffusecolor) > 0) + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + if ((dist = fabs(v[2])) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) + { + Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); + if ((dot = DotProduct(n, v)) < 0) + { + shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); + color4f[0] = (ambientcolor[0] + shadeintensity * diffusecolor[0]) * distintensity; + color4f[1] = (ambientcolor[1] + shadeintensity * diffusecolor[1]) * distintensity; + color4f[2] = (ambientcolor[2] + shadeintensity * diffusecolor[2]) * distintensity; + } + else + { + color4f[0] = ambientcolor[0] * distintensity; + color4f[1] = ambientcolor[1] * distintensity; + color4f[2] = ambientcolor[2] * distintensity; + } + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + } + else + VectorClear(color4f); + color4f[3] = 1; + } + } + else + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + if ((dist = fabs(v[2])) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) + { + color4f[0] = ambientcolor[0] * distintensity; + color4f[1] = ambientcolor[1] * distintensity; + color4f[2] = ambientcolor[2] * distintensity; + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + } + else + VectorClear(color4f); + color4f[3] = 1; + } + } + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX: + if (VectorLength2(diffusecolor) > 0) + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + if ((dist = VectorLength(v)) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) + { + distintensity = (1 - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); + Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); + if ((dot = DotProduct(n, v)) < 0) + { + shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); + color4f[0] = (ambientcolor[0] + shadeintensity * diffusecolor[0]) * distintensity; + color4f[1] = (ambientcolor[1] + shadeintensity * diffusecolor[1]) * distintensity; + color4f[2] = (ambientcolor[2] + shadeintensity * diffusecolor[2]) * distintensity; + } + else + { + color4f[0] = ambientcolor[0] * distintensity; + color4f[1] = ambientcolor[1] * distintensity; + color4f[2] = ambientcolor[2] * distintensity; + } + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + } + else + VectorClear(color4f); + color4f[3] = 1; + } + } + else + { + for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) + { + Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); + if ((dist = VectorLength(v)) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) + { + distintensity = (1 - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); + color4f[0] = ambientcolor[0] * distintensity; + color4f[1] = ambientcolor[1] * distintensity; + color4f[2] = ambientcolor[2] * distintensity; + if (r_refdef.fogenabled) + { + float f; + f = RSurf_FogVertex(vertex3f); + VectorScale(color4f, f, color4f); + } + } + else + VectorClear(color4f); + color4f[3] = 1; + } + } + break; + default: + break; + } +} + +static void R_Shadow_RenderLighting_VisibleLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + // used to display how many times a surface is lit for level design purposes + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); + RSurf_DrawBatch(); +} + +static void R_Shadow_RenderLighting_Light_GLSL(int texturenumsurfaces, const msurface_t **texturesurfacelist, const vec3_t lightcolor, float ambientscale, float diffusescale, float specularscale) +{ + // ARB2 GLSL shader path (GFFX5200, Radeon 9500) + R_SetupShader_Surface(lightcolor, false, ambientscale, diffusescale, specularscale, RSURFPASS_RTLIGHT, texturenumsurfaces, texturesurfacelist, NULL, false); + RSurf_DrawBatch(); +} + +static void R_Shadow_RenderLighting_Light_Vertex_Pass(int firstvertex, int numvertices, int numtriangles, const int *element3i, vec3_t diffusecolor2, vec3_t ambientcolor2) +{ + int renders; + int i; + int stop; + int newfirstvertex; + int newlastvertex; + int newnumtriangles; + int *newe; + const int *e; + float *c; + int maxtriangles = 1024; + int newelements[1024*3]; + R_Shadow_RenderLighting_Light_Vertex_Shading(firstvertex, numvertices, diffusecolor2, ambientcolor2); + for (renders = 0;renders < 4;renders++) + { + stop = true; + newfirstvertex = 0; + newlastvertex = 0; + newnumtriangles = 0; + newe = newelements; + // due to low fillrate on the cards this vertex lighting path is + // designed for, we manually cull all triangles that do not + // contain a lit vertex + // this builds batches of triangles from multiple surfaces and + // renders them at once + for (i = 0, e = element3i;i < numtriangles;i++, e += 3) + { + if (VectorLength2(rsurface.passcolor4f + e[0] * 4) + VectorLength2(rsurface.passcolor4f + e[1] * 4) + VectorLength2(rsurface.passcolor4f + e[2] * 4) >= 0.01) + { + if (newnumtriangles) + { + newfirstvertex = min(newfirstvertex, e[0]); + newlastvertex = max(newlastvertex, e[0]); + } + else + { + newfirstvertex = e[0]; + newlastvertex = e[0]; + } + newfirstvertex = min(newfirstvertex, e[1]); + newlastvertex = max(newlastvertex, e[1]); + newfirstvertex = min(newfirstvertex, e[2]); + newlastvertex = max(newlastvertex, e[2]); + newe[0] = e[0]; + newe[1] = e[1]; + newe[2] = e[2]; + newnumtriangles++; + newe += 3; + if (newnumtriangles >= maxtriangles) + { + R_Mesh_Draw(newfirstvertex, newlastvertex - newfirstvertex + 1, 0, newnumtriangles, newelements, NULL, 0, NULL, NULL, 0); + newnumtriangles = 0; + newe = newelements; + stop = false; + } + } + } + if (newnumtriangles >= 1) + { + R_Mesh_Draw(newfirstvertex, newlastvertex - newfirstvertex + 1, 0, newnumtriangles, newelements, NULL, 0, NULL, NULL, 0); + stop = false; + } + // if we couldn't find any lit triangles, exit early + if (stop) + break; + // now reduce the intensity for the next overbright pass + // we have to clamp to 0 here incase the drivers have improper + // handling of negative colors + // (some old drivers even have improper handling of >1 color) + stop = true; + for (i = 0, c = rsurface.passcolor4f + 4 * firstvertex;i < numvertices;i++, c += 4) + { + if (c[0] > 1 || c[1] > 1 || c[2] > 1) + { + c[0] = max(0, c[0] - 1); + c[1] = max(0, c[1] - 1); + c[2] = max(0, c[2] - 1); + stop = false; + } + else + VectorClear(c); + } + // another check... + if (stop) + break; + } +} + +static void R_Shadow_RenderLighting_Light_Vertex(int texturenumsurfaces, const msurface_t **texturesurfacelist, const vec3_t lightcolor, float ambientscale, float diffusescale) +{ + // OpenGL 1.1 path (anything) + float ambientcolorbase[3], diffusecolorbase[3]; + float ambientcolorpants[3], diffusecolorpants[3]; + float ambientcolorshirt[3], diffusecolorshirt[3]; + const float *surfacecolor = rsurface.texture->dlightcolor; + const float *surfacepants = rsurface.colormap_pantscolor; + const float *surfaceshirt = rsurface.colormap_shirtcolor; + rtexture_t *basetexture = rsurface.texture->basetexture; + rtexture_t *pantstexture = rsurface.texture->pantstexture; + rtexture_t *shirttexture = rsurface.texture->shirttexture; + qboolean dopants = pantstexture && VectorLength2(surfacepants) >= (1.0f / 1048576.0f); + qboolean doshirt = shirttexture && VectorLength2(surfaceshirt) >= (1.0f / 1048576.0f); + ambientscale *= 2 * r_refdef.view.colorscale; + diffusescale *= 2 * r_refdef.view.colorscale; + ambientcolorbase[0] = lightcolor[0] * ambientscale * surfacecolor[0];ambientcolorbase[1] = lightcolor[1] * ambientscale * surfacecolor[1];ambientcolorbase[2] = lightcolor[2] * ambientscale * surfacecolor[2]; + diffusecolorbase[0] = lightcolor[0] * diffusescale * surfacecolor[0];diffusecolorbase[1] = lightcolor[1] * diffusescale * surfacecolor[1];diffusecolorbase[2] = lightcolor[2] * diffusescale * surfacecolor[2]; + ambientcolorpants[0] = ambientcolorbase[0] * surfacepants[0];ambientcolorpants[1] = ambientcolorbase[1] * surfacepants[1];ambientcolorpants[2] = ambientcolorbase[2] * surfacepants[2]; + diffusecolorpants[0] = diffusecolorbase[0] * surfacepants[0];diffusecolorpants[1] = diffusecolorbase[1] * surfacepants[1];diffusecolorpants[2] = diffusecolorbase[2] * surfacepants[2]; + ambientcolorshirt[0] = ambientcolorbase[0] * surfaceshirt[0];ambientcolorshirt[1] = ambientcolorbase[1] * surfaceshirt[1];ambientcolorshirt[2] = ambientcolorbase[2] * surfaceshirt[2]; + diffusecolorshirt[0] = diffusecolorbase[0] * surfaceshirt[0];diffusecolorshirt[1] = diffusecolorbase[1] * surfaceshirt[1];diffusecolorshirt[2] = diffusecolorbase[2] * surfaceshirt[2]; + RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | (diffusescale > 0 ? BATCHNEED_ARRAY_NORMAL : 0) | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); + rsurface.passcolor4f = (float *)R_FrameData_Alloc((rsurface.batchfirstvertex + rsurface.batchnumvertices) * sizeof(float[4])); + R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); + R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); + R_Mesh_TexBind(0, basetexture); + R_Mesh_TexMatrix(0, &rsurface.texture->currenttexmatrix); + R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); + switch(r_shadow_rendermode) + { + case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: + R_Mesh_TexBind(1, r_shadow_attenuation3dtexture); + R_Mesh_TexMatrix(1, &rsurface.entitytoattenuationxyz); + R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: + R_Mesh_TexBind(2, r_shadow_attenuation2dtexture); + R_Mesh_TexMatrix(2, &rsurface.entitytoattenuationz); + R_Mesh_TexCombine(2, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + // fall through + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: + R_Mesh_TexBind(1, r_shadow_attenuation2dtexture); + R_Mesh_TexMatrix(1, &rsurface.entitytoattenuationxyz); + R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, 1, 1); + R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX: + break; + default: + break; + } + //R_Mesh_TexBind(0, basetexture); + R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorbase, ambientcolorbase); + if (dopants) + { + R_Mesh_TexBind(0, pantstexture); + R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorpants, ambientcolorpants); + } + if (doshirt) + { + R_Mesh_TexBind(0, shirttexture); + R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorshirt, ambientcolorshirt); + } +} + +extern cvar_t gl_lightmaps; +void R_Shadow_RenderLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist) +{ + float ambientscale, diffusescale, specularscale; + qboolean negated; + float lightcolor[3]; + VectorCopy(rsurface.rtlight->currentcolor, lightcolor); + ambientscale = rsurface.rtlight->ambientscale + rsurface.texture->rtlightambient; + diffusescale = rsurface.rtlight->diffusescale * max(0, 1.0 - rsurface.texture->rtlightambient); + specularscale = rsurface.rtlight->specularscale * rsurface.texture->specularscale; + if (!r_shadow_usenormalmap.integer) + { + ambientscale += 1.0f * diffusescale; + diffusescale = 0; + specularscale = 0; + } + if ((ambientscale + diffusescale) * VectorLength2(lightcolor) + specularscale * VectorLength2(lightcolor) < (1.0f / 1048576.0f)) + return; + negated = (lightcolor[0] + lightcolor[1] + lightcolor[2] < 0) && vid.support.ext_blend_subtract; + if(negated) + { + VectorNegate(lightcolor, lightcolor); + GL_BlendEquationSubtract(true); + } + RSurf_SetupDepthAndCulling(); + switch (r_shadow_rendermode) + { + case R_SHADOW_RENDERMODE_VISIBLELIGHTING: + GL_DepthTest(!(rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) && !r_showdisabledepthtest.integer); + R_Shadow_RenderLighting_VisibleLighting(texturenumsurfaces, texturesurfacelist); + break; + case R_SHADOW_RENDERMODE_LIGHT_GLSL: + R_Shadow_RenderLighting_Light_GLSL(texturenumsurfaces, texturesurfacelist, lightcolor, ambientscale, diffusescale, specularscale); + break; + case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: + case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: + case R_SHADOW_RENDERMODE_LIGHT_VERTEX: + R_Shadow_RenderLighting_Light_Vertex(texturenumsurfaces, texturesurfacelist, lightcolor, ambientscale, diffusescale); + break; + default: + Con_Printf("R_Shadow_RenderLighting: unknown r_shadow_rendermode %i\n", r_shadow_rendermode); + break; + } + if(negated) + GL_BlendEquationSubtract(false); +} + +void R_RTLight_Update(rtlight_t *rtlight, int isstatic, matrix4x4_t *matrix, vec3_t color, int style, const char *cubemapname, int shadow, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) +{ + matrix4x4_t tempmatrix = *matrix; + Matrix4x4_Scale(&tempmatrix, r_shadow_lightradiusscale.value, 1); + + // if this light has been compiled before, free the associated data + R_RTLight_Uncompile(rtlight); + + // clear it completely to avoid any lingering data + memset(rtlight, 0, sizeof(*rtlight)); + + // copy the properties + rtlight->matrix_lighttoworld = tempmatrix; + Matrix4x4_Invert_Simple(&rtlight->matrix_worldtolight, &tempmatrix); + Matrix4x4_OriginFromMatrix(&tempmatrix, rtlight->shadoworigin); + rtlight->radius = Matrix4x4_ScaleFromMatrix(&tempmatrix); + VectorCopy(color, rtlight->color); + rtlight->cubemapname[0] = 0; + if (cubemapname && cubemapname[0]) + strlcpy(rtlight->cubemapname, cubemapname, sizeof(rtlight->cubemapname)); + rtlight->shadow = shadow; + rtlight->corona = corona; + rtlight->style = style; + rtlight->isstatic = isstatic; + rtlight->coronasizescale = coronasizescale; + rtlight->ambientscale = ambientscale; + rtlight->diffusescale = diffusescale; + rtlight->specularscale = specularscale; + rtlight->flags = flags; + + // compute derived data + //rtlight->cullradius = rtlight->radius; + //rtlight->cullradius2 = rtlight->radius * rtlight->radius; + rtlight->cullmins[0] = rtlight->shadoworigin[0] - rtlight->radius; + rtlight->cullmins[1] = rtlight->shadoworigin[1] - rtlight->radius; + rtlight->cullmins[2] = rtlight->shadoworigin[2] - rtlight->radius; + rtlight->cullmaxs[0] = rtlight->shadoworigin[0] + rtlight->radius; + rtlight->cullmaxs[1] = rtlight->shadoworigin[1] + rtlight->radius; + rtlight->cullmaxs[2] = rtlight->shadoworigin[2] + rtlight->radius; +} + +// compiles rtlight geometry +// (undone by R_FreeCompiledRTLight, which R_UpdateLight calls) +void R_RTLight_Compile(rtlight_t *rtlight) +{ + int i; + int numsurfaces, numleafs, numleafpvsbytes, numshadowtrispvsbytes, numlighttrispvsbytes; + int lighttris, shadowtris, shadowzpasstris, shadowzfailtris; + entity_render_t *ent = r_refdef.scene.worldentity; + dp_model_t *model = r_refdef.scene.worldmodel; + unsigned char *data; + shadowmesh_t *mesh; + + // compile the light + rtlight->compiled = true; + rtlight->shadowmode = rtlight->shadow ? (int)r_shadow_shadowmode : -1; + rtlight->static_numleafs = 0; + rtlight->static_numleafpvsbytes = 0; + rtlight->static_leaflist = NULL; + rtlight->static_leafpvs = NULL; + rtlight->static_numsurfaces = 0; + rtlight->static_surfacelist = NULL; + rtlight->static_shadowmap_receivers = 0x3F; + rtlight->static_shadowmap_casters = 0x3F; + rtlight->cullmins[0] = rtlight->shadoworigin[0] - rtlight->radius; + rtlight->cullmins[1] = rtlight->shadoworigin[1] - rtlight->radius; + rtlight->cullmins[2] = rtlight->shadoworigin[2] - rtlight->radius; + rtlight->cullmaxs[0] = rtlight->shadoworigin[0] + rtlight->radius; + rtlight->cullmaxs[1] = rtlight->shadoworigin[1] + rtlight->radius; + rtlight->cullmaxs[2] = rtlight->shadoworigin[2] + rtlight->radius; + + if (model && model->GetLightInfo) + { + // this variable must be set for the CompileShadowVolume/CompileShadowMap code + r_shadow_compilingrtlight = rtlight; + R_FrameData_SetMark(); + model->GetLightInfo(ent, rtlight->shadoworigin, rtlight->radius, rtlight->cullmins, rtlight->cullmaxs, r_shadow_buffer_leaflist, r_shadow_buffer_leafpvs, &numleafs, r_shadow_buffer_surfacelist, r_shadow_buffer_surfacepvs, &numsurfaces, r_shadow_buffer_shadowtrispvs, r_shadow_buffer_lighttrispvs, r_shadow_buffer_visitingleafpvs, 0, NULL); + R_FrameData_ReturnToMark(); + numleafpvsbytes = (model->brush.num_leafs + 7) >> 3; + numshadowtrispvsbytes = ((model->brush.shadowmesh ? model->brush.shadowmesh->numtriangles : model->surfmesh.num_triangles) + 7) >> 3; + numlighttrispvsbytes = (model->surfmesh.num_triangles + 7) >> 3; + data = (unsigned char *)Mem_Alloc(r_main_mempool, sizeof(int) * numsurfaces + sizeof(int) * numleafs + numleafpvsbytes + numshadowtrispvsbytes + numlighttrispvsbytes); + rtlight->static_numsurfaces = numsurfaces; + rtlight->static_surfacelist = (int *)data;data += sizeof(int) * numsurfaces; + rtlight->static_numleafs = numleafs; + rtlight->static_leaflist = (int *)data;data += sizeof(int) * numleafs; + rtlight->static_numleafpvsbytes = numleafpvsbytes; + rtlight->static_leafpvs = (unsigned char *)data;data += numleafpvsbytes; + rtlight->static_numshadowtrispvsbytes = numshadowtrispvsbytes; + rtlight->static_shadowtrispvs = (unsigned char *)data;data += numshadowtrispvsbytes; + rtlight->static_numlighttrispvsbytes = numlighttrispvsbytes; + rtlight->static_lighttrispvs = (unsigned char *)data;data += numlighttrispvsbytes; + if (rtlight->static_numsurfaces) + memcpy(rtlight->static_surfacelist, r_shadow_buffer_surfacelist, rtlight->static_numsurfaces * sizeof(*rtlight->static_surfacelist)); + if (rtlight->static_numleafs) + memcpy(rtlight->static_leaflist, r_shadow_buffer_leaflist, rtlight->static_numleafs * sizeof(*rtlight->static_leaflist)); + if (rtlight->static_numleafpvsbytes) + memcpy(rtlight->static_leafpvs, r_shadow_buffer_leafpvs, rtlight->static_numleafpvsbytes); + if (rtlight->static_numshadowtrispvsbytes) + memcpy(rtlight->static_shadowtrispvs, r_shadow_buffer_shadowtrispvs, rtlight->static_numshadowtrispvsbytes); + if (rtlight->static_numlighttrispvsbytes) + memcpy(rtlight->static_lighttrispvs, r_shadow_buffer_lighttrispvs, rtlight->static_numlighttrispvsbytes); + R_FrameData_SetMark(); + switch (rtlight->shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (model->CompileShadowMap && rtlight->shadow) + model->CompileShadowMap(ent, rtlight->shadoworigin, NULL, rtlight->radius, numsurfaces, r_shadow_buffer_surfacelist); + break; + default: + if (model->CompileShadowVolume && rtlight->shadow) + model->CompileShadowVolume(ent, rtlight->shadoworigin, NULL, rtlight->radius, numsurfaces, r_shadow_buffer_surfacelist); + break; + } + R_FrameData_ReturnToMark(); + // now we're done compiling the rtlight + r_shadow_compilingrtlight = NULL; + } + + + // use smallest available cullradius - box radius or light radius + //rtlight->cullradius = RadiusFromBoundsAndOrigin(rtlight->cullmins, rtlight->cullmaxs, rtlight->shadoworigin); + //rtlight->cullradius = min(rtlight->cullradius, rtlight->radius); + + shadowzpasstris = 0; + if (rtlight->static_meshchain_shadow_zpass) + for (mesh = rtlight->static_meshchain_shadow_zpass;mesh;mesh = mesh->next) + shadowzpasstris += mesh->numtriangles; + + shadowzfailtris = 0; + if (rtlight->static_meshchain_shadow_zfail) + for (mesh = rtlight->static_meshchain_shadow_zfail;mesh;mesh = mesh->next) + shadowzfailtris += mesh->numtriangles; + + lighttris = 0; + if (rtlight->static_numlighttrispvsbytes) + for (i = 0;i < rtlight->static_numlighttrispvsbytes*8;i++) + if (CHECKPVSBIT(rtlight->static_lighttrispvs, i)) + lighttris++; + + shadowtris = 0; + if (rtlight->static_numlighttrispvsbytes) + for (i = 0;i < rtlight->static_numshadowtrispvsbytes*8;i++) + if (CHECKPVSBIT(rtlight->static_shadowtrispvs, i)) + shadowtris++; + + if (developer_extra.integer) + Con_DPrintf("static light built: %f %f %f : %f %f %f box, %i light triangles, %i shadow triangles, %i zpass/%i zfail compiled shadow volume triangles\n", rtlight->cullmins[0], rtlight->cullmins[1], rtlight->cullmins[2], rtlight->cullmaxs[0], rtlight->cullmaxs[1], rtlight->cullmaxs[2], lighttris, shadowtris, shadowzpasstris, shadowzfailtris); +} + +void R_RTLight_Uncompile(rtlight_t *rtlight) +{ + if (rtlight->compiled) + { + if (rtlight->static_meshchain_shadow_zpass) + Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_zpass); + rtlight->static_meshchain_shadow_zpass = NULL; + if (rtlight->static_meshchain_shadow_zfail) + Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_zfail); + rtlight->static_meshchain_shadow_zfail = NULL; + if (rtlight->static_meshchain_shadow_shadowmap) + Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_shadowmap); + rtlight->static_meshchain_shadow_shadowmap = NULL; + // these allocations are grouped + if (rtlight->static_surfacelist) + Mem_Free(rtlight->static_surfacelist); + rtlight->static_numleafs = 0; + rtlight->static_numleafpvsbytes = 0; + rtlight->static_leaflist = NULL; + rtlight->static_leafpvs = NULL; + rtlight->static_numsurfaces = 0; + rtlight->static_surfacelist = NULL; + rtlight->static_numshadowtrispvsbytes = 0; + rtlight->static_shadowtrispvs = NULL; + rtlight->static_numlighttrispvsbytes = 0; + rtlight->static_lighttrispvs = NULL; + rtlight->compiled = false; + } +} + +void R_Shadow_UncompileWorldLights(void) +{ + size_t lightindex; + dlight_t *light; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + R_RTLight_Uncompile(&light->rtlight); + } +} + +static void R_Shadow_ComputeShadowCasterCullingPlanes(rtlight_t *rtlight) +{ + int i, j; + mplane_t plane; + // reset the count of frustum planes + // see rtlight->cached_frustumplanes definition for how much this array + // can hold + rtlight->cached_numfrustumplanes = 0; + + if (r_trippy.integer) + return; + + // haven't implemented a culling path for ortho rendering + if (!r_refdef.view.useperspective) + { + // check if the light is on screen and copy the 4 planes if it is + for (i = 0;i < 4;i++) + if (PlaneDiff(rtlight->shadoworigin, &r_refdef.view.frustum[i]) < -0.03125) + break; + if (i == 4) + for (i = 0;i < 4;i++) + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = r_refdef.view.frustum[i]; + return; + } + +#if 1 + // generate a deformed frustum that includes the light origin, this is + // used to cull shadow casting surfaces that can not possibly cast a + // shadow onto the visible light-receiving surfaces, which can be a + // performance gain + // + // if the light origin is onscreen the result will be 4 planes exactly + // if the light origin is offscreen on only one axis the result will + // be exactly 5 planes (split-side case) + // if the light origin is offscreen on two axes the result will be + // exactly 4 planes (stretched corner case) + for (i = 0;i < 4;i++) + { + // quickly reject standard frustum planes that put the light + // origin outside the frustum + if (PlaneDiff(rtlight->shadoworigin, &r_refdef.view.frustum[i]) < -0.03125) + continue; + // copy the plane + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = r_refdef.view.frustum[i]; + } + // if all the standard frustum planes were accepted, the light is onscreen + // otherwise we need to generate some more planes below... + if (rtlight->cached_numfrustumplanes < 4) + { + // at least one of the stock frustum planes failed, so we need to + // create one or two custom planes to enclose the light origin + for (i = 0;i < 4;i++) + { + // create a plane using the view origin and light origin, and a + // single point from the frustum corner set + TriangleNormal(r_refdef.view.origin, r_refdef.view.frustumcorner[i], rtlight->shadoworigin, plane.normal); + VectorNormalize(plane.normal); + plane.dist = DotProduct(r_refdef.view.origin, plane.normal); + // see if this plane is backwards and flip it if so + for (j = 0;j < 4;j++) + if (j != i && DotProduct(r_refdef.view.frustumcorner[j], plane.normal) - plane.dist < -0.03125) + break; + if (j < 4) + { + VectorNegate(plane.normal, plane.normal); + plane.dist *= -1; + // flipped plane, test again to see if it is now valid + for (j = 0;j < 4;j++) + if (j != i && DotProduct(r_refdef.view.frustumcorner[j], plane.normal) - plane.dist < -0.03125) + break; + // if the plane is still not valid, then it is dividing the + // frustum and has to be rejected + if (j < 4) + continue; + } + // we have created a valid plane, compute extra info + PlaneClassify(&plane); + // copy the plane + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; +#if 1 + // if we've found 5 frustum planes then we have constructed a + // proper split-side case and do not need to keep searching for + // planes to enclose the light origin + if (rtlight->cached_numfrustumplanes == 5) + break; +#endif + } + } +#endif + +#if 0 + for (i = 0;i < rtlight->cached_numfrustumplanes;i++) + { + plane = rtlight->cached_frustumplanes[i]; + Con_Printf("light %p plane #%i %f %f %f : %f (%f %f %f %f %f)\n", rtlight, i, plane.normal[0], plane.normal[1], plane.normal[2], plane.dist, PlaneDiff(r_refdef.view.frustumcorner[0], &plane), PlaneDiff(r_refdef.view.frustumcorner[1], &plane), PlaneDiff(r_refdef.view.frustumcorner[2], &plane), PlaneDiff(r_refdef.view.frustumcorner[3], &plane), PlaneDiff(rtlight->shadoworigin, &plane)); + } +#endif + +#if 0 + // now add the light-space box planes if the light box is rotated, as any + // caster outside the oriented light box is irrelevant (even if it passed + // the worldspace light box, which is axial) + if (rtlight->matrix_lighttoworld.m[0][0] != 1 || rtlight->matrix_lighttoworld.m[1][1] != 1 || rtlight->matrix_lighttoworld.m[2][2] != 1) + { + for (i = 0;i < 6;i++) + { + vec3_t v; + VectorClear(v); + v[i >> 1] = (i & 1) ? -1 : 1; + Matrix4x4_Transform(&rtlight->matrix_lighttoworld, v, plane.normal); + VectorSubtract(plane.normal, rtlight->shadoworigin, plane.normal); + plane.dist = VectorNormalizeLength(plane.normal); + plane.dist += DotProduct(plane.normal, rtlight->shadoworigin); + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; + } + } +#endif + +#if 0 + // add the world-space reduced box planes + for (i = 0;i < 6;i++) + { + VectorClear(plane.normal); + plane.normal[i >> 1] = (i & 1) ? -1 : 1; + plane.dist = (i & 1) ? -rtlight->cached_cullmaxs[i >> 1] : rtlight->cached_cullmins[i >> 1]; + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; + } +#endif + +#if 0 + { + int j, oldnum; + vec3_t points[8]; + vec_t bestdist; + // reduce all plane distances to tightly fit the rtlight cull box, which + // is in worldspace + VectorSet(points[0], rtlight->cached_cullmins[0], rtlight->cached_cullmins[1], rtlight->cached_cullmins[2]); + VectorSet(points[1], rtlight->cached_cullmaxs[0], rtlight->cached_cullmins[1], rtlight->cached_cullmins[2]); + VectorSet(points[2], rtlight->cached_cullmins[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmins[2]); + VectorSet(points[3], rtlight->cached_cullmaxs[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmins[2]); + VectorSet(points[4], rtlight->cached_cullmins[0], rtlight->cached_cullmins[1], rtlight->cached_cullmaxs[2]); + VectorSet(points[5], rtlight->cached_cullmaxs[0], rtlight->cached_cullmins[1], rtlight->cached_cullmaxs[2]); + VectorSet(points[6], rtlight->cached_cullmins[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmaxs[2]); + VectorSet(points[7], rtlight->cached_cullmaxs[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmaxs[2]); + oldnum = rtlight->cached_numfrustumplanes; + rtlight->cached_numfrustumplanes = 0; + for (j = 0;j < oldnum;j++) + { + // find the nearest point on the box to this plane + bestdist = DotProduct(rtlight->cached_frustumplanes[j].normal, points[0]); + for (i = 1;i < 8;i++) + { + dist = DotProduct(rtlight->cached_frustumplanes[j].normal, points[i]); + if (bestdist > dist) + bestdist = dist; + } + Con_Printf("light %p %splane #%i %f %f %f : %f < %f\n", rtlight, rtlight->cached_frustumplanes[j].dist < bestdist + 0.03125 ? "^2" : "^1", j, rtlight->cached_frustumplanes[j].normal[0], rtlight->cached_frustumplanes[j].normal[1], rtlight->cached_frustumplanes[j].normal[2], rtlight->cached_frustumplanes[j].dist, bestdist); + // if the nearest point is near or behind the plane, we want this + // plane, otherwise the plane is useless as it won't cull anything + if (rtlight->cached_frustumplanes[j].dist < bestdist + 0.03125) + { + PlaneClassify(&rtlight->cached_frustumplanes[j]); + rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = rtlight->cached_frustumplanes[j]; + } + } + } +#endif +} + +static void R_Shadow_DrawWorldShadow_ShadowMap(int numsurfaces, int *surfacelist, const unsigned char *trispvs, const unsigned char *surfacesides) +{ + shadowmesh_t *mesh; + + RSurf_ActiveWorldEntity(); + + if (rsurface.rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) + { + CHECKGLERROR + GL_CullFace(GL_NONE); + mesh = rsurface.rtlight->static_meshchain_shadow_shadowmap; + for (;mesh;mesh = mesh->next) + { + if (!mesh->sidetotals[r_shadow_shadowmapside]) + continue; + r_refdef.stats[r_stat_lights_shadowtriangles] += mesh->sidetotals[r_shadow_shadowmapside]; + R_Mesh_PrepareVertices_Vertex3f(mesh->numverts, mesh->vertex3f, mesh->vbo_vertexbuffer, mesh->vbooffset_vertex3f); + R_Mesh_Draw(0, mesh->numverts, mesh->sideoffsets[r_shadow_shadowmapside], mesh->sidetotals[r_shadow_shadowmapside], mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); + } + CHECKGLERROR + } + else if (r_refdef.scene.worldentity->model) + r_refdef.scene.worldmodel->DrawShadowMap(r_shadow_shadowmapside, r_refdef.scene.worldentity, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius, numsurfaces, surfacelist, surfacesides, rsurface.rtlight->cached_cullmins, rsurface.rtlight->cached_cullmaxs); + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +static void R_Shadow_DrawWorldShadow_ShadowVolume(int numsurfaces, int *surfacelist, const unsigned char *trispvs) +{ + qboolean zpass = false; + shadowmesh_t *mesh; + int t, tend; + int surfacelistindex; + msurface_t *surface; + + // if triangle neighbors are disabled, shadowvolumes are disabled + if (r_refdef.scene.worldmodel->brush.shadowmesh ? !r_refdef.scene.worldmodel->brush.shadowmesh->neighbor3i : !r_refdef.scene.worldmodel->surfmesh.data_neighbor3i) + return; + + RSurf_ActiveWorldEntity(); + + if (rsurface.rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) + { + CHECKGLERROR + if (r_shadow_rendermode != R_SHADOW_RENDERMODE_VISIBLEVOLUMES) + { + zpass = R_Shadow_UseZPass(r_refdef.scene.worldmodel->normalmins, r_refdef.scene.worldmodel->normalmaxs); + R_Shadow_RenderMode_StencilShadowVolumes(zpass); + } + mesh = zpass ? rsurface.rtlight->static_meshchain_shadow_zpass : rsurface.rtlight->static_meshchain_shadow_zfail; + for (;mesh;mesh = mesh->next) + { + r_refdef.stats[r_stat_lights_shadowtriangles] += mesh->numtriangles; + R_Mesh_PrepareVertices_Vertex3f(mesh->numverts, mesh->vertex3f, mesh->vbo_vertexbuffer, mesh->vbooffset_vertex3f); + if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZPASS_STENCIL) + { + // increment stencil if frontface is infront of depthbuffer + GL_CullFace(r_refdef.view.cullface_back); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_ALWAYS, 128, 255); + R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); + // decrement stencil if backface is infront of depthbuffer + GL_CullFace(r_refdef.view.cullface_front); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, 128, 255); + } + else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZFAIL_STENCIL) + { + // decrement stencil if backface is behind depthbuffer + GL_CullFace(r_refdef.view.cullface_front); + R_SetStencil(true, 255, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, 128, 255); + R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); + // increment stencil if frontface is behind depthbuffer + GL_CullFace(r_refdef.view.cullface_back); + R_SetStencil(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_ALWAYS, 128, 255); + } + R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); + } + CHECKGLERROR + } + else if (numsurfaces && r_refdef.scene.worldmodel->brush.shadowmesh) + { + // use the shadow trispvs calculated earlier by GetLightInfo to cull world triangles on this dynamic light + R_Shadow_PrepareShadowMark(r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles); + for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) + { + surface = r_refdef.scene.worldmodel->data_surfaces + surfacelist[surfacelistindex]; + for (t = surface->num_firstshadowmeshtriangle, tend = t + surface->num_triangles;t < tend;t++) + if (CHECKPVSBIT(trispvs, t)) + shadowmarklist[numshadowmark++] = t; + } + R_Shadow_VolumeFromList(r_refdef.scene.worldmodel->brush.shadowmesh->numverts, r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles, r_refdef.scene.worldmodel->brush.shadowmesh->vertex3f, r_refdef.scene.worldmodel->brush.shadowmesh->element3i, r_refdef.scene.worldmodel->brush.shadowmesh->neighbor3i, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius + r_refdef.scene.worldmodel->radius*2 + r_shadow_projectdistance.value, numshadowmark, shadowmarklist, r_refdef.scene.worldmodel->normalmins, r_refdef.scene.worldmodel->normalmaxs); + } + else if (numsurfaces) + { + r_refdef.scene.worldmodel->DrawShadowVolume(r_refdef.scene.worldentity, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius, numsurfaces, surfacelist, rsurface.rtlight->cached_cullmins, rsurface.rtlight->cached_cullmaxs); + } + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +static void R_Shadow_DrawEntityShadow(entity_render_t *ent) +{ + vec3_t relativeshadoworigin, relativeshadowmins, relativeshadowmaxs; + vec_t relativeshadowradius; + RSurf_ActiveModelEntity(ent, false, false, false); + Matrix4x4_Transform(&ent->inversematrix, rsurface.rtlight->shadoworigin, relativeshadoworigin); + // we need to re-init the shader for each entity because the matrix changed + relativeshadowradius = rsurface.rtlight->radius / ent->scale; + relativeshadowmins[0] = relativeshadoworigin[0] - relativeshadowradius; + relativeshadowmins[1] = relativeshadoworigin[1] - relativeshadowradius; + relativeshadowmins[2] = relativeshadoworigin[2] - relativeshadowradius; + relativeshadowmaxs[0] = relativeshadoworigin[0] + relativeshadowradius; + relativeshadowmaxs[1] = relativeshadoworigin[1] + relativeshadowradius; + relativeshadowmaxs[2] = relativeshadoworigin[2] + relativeshadowradius; + switch (r_shadow_rendermode) + { + case R_SHADOW_RENDERMODE_SHADOWMAP2D: + ent->model->DrawShadowMap(r_shadow_shadowmapside, ent, relativeshadoworigin, NULL, relativeshadowradius, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, NULL, relativeshadowmins, relativeshadowmaxs); + break; + default: + ent->model->DrawShadowVolume(ent, relativeshadoworigin, NULL, relativeshadowradius, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, relativeshadowmins, relativeshadowmaxs); + break; + } + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +void R_Shadow_SetupEntityLight(const entity_render_t *ent) +{ + // set up properties for rendering light onto this entity + RSurf_ActiveModelEntity(ent, true, true, false); + Matrix4x4_Concat(&rsurface.entitytolight, &rsurface.rtlight->matrix_worldtolight, &ent->matrix); + Matrix4x4_Concat(&rsurface.entitytoattenuationxyz, &matrix_attenuationxyz, &rsurface.entitytolight); + Matrix4x4_Concat(&rsurface.entitytoattenuationz, &matrix_attenuationz, &rsurface.entitytolight); + Matrix4x4_Transform(&ent->inversematrix, rsurface.rtlight->shadoworigin, rsurface.entitylightorigin); +} + +static void R_Shadow_DrawWorldLight(int numsurfaces, int *surfacelist, const unsigned char *lighttrispvs) +{ + if (!r_refdef.scene.worldmodel->DrawLight) + return; + + // set up properties for rendering light onto this entity + RSurf_ActiveWorldEntity(); + rsurface.entitytolight = rsurface.rtlight->matrix_worldtolight; + Matrix4x4_Concat(&rsurface.entitytoattenuationxyz, &matrix_attenuationxyz, &rsurface.entitytolight); + Matrix4x4_Concat(&rsurface.entitytoattenuationz, &matrix_attenuationz, &rsurface.entitytolight); + VectorCopy(rsurface.rtlight->shadoworigin, rsurface.entitylightorigin); + + r_refdef.scene.worldmodel->DrawLight(r_refdef.scene.worldentity, numsurfaces, surfacelist, lighttrispvs); + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +static void R_Shadow_DrawEntityLight(entity_render_t *ent) +{ + dp_model_t *model = ent->model; + if (!model->DrawLight) + return; + + R_Shadow_SetupEntityLight(ent); + + model->DrawLight(ent, model->nummodelsurfaces, model->sortedmodelsurfaces, NULL); + + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity +} + +static void R_Shadow_PrepareLight(rtlight_t *rtlight) +{ + int i; + float f; + int numleafs, numsurfaces; + int *leaflist, *surfacelist; + unsigned char *leafpvs; + unsigned char *shadowtrispvs; + unsigned char *lighttrispvs; + //unsigned char *surfacesides; + int numlightentities; + int numlightentities_noselfshadow; + int numshadowentities; + int numshadowentities_noselfshadow; + static entity_render_t *lightentities[MAX_EDICTS]; + static entity_render_t *lightentities_noselfshadow[MAX_EDICTS]; + static entity_render_t *shadowentities[MAX_EDICTS]; + static entity_render_t *shadowentities_noselfshadow[MAX_EDICTS]; + qboolean nolight; + + rtlight->draw = false; + rtlight->cached_numlightentities = 0; + rtlight->cached_numlightentities_noselfshadow = 0; + rtlight->cached_numshadowentities = 0; + rtlight->cached_numshadowentities_noselfshadow = 0; + rtlight->cached_numsurfaces = 0; + rtlight->cached_lightentities = NULL; + rtlight->cached_lightentities_noselfshadow = NULL; + rtlight->cached_shadowentities = NULL; + rtlight->cached_shadowentities_noselfshadow = NULL; + rtlight->cached_shadowtrispvs = NULL; + rtlight->cached_lighttrispvs = NULL; + rtlight->cached_surfacelist = NULL; + + // skip lights that don't light because of ambientscale+diffusescale+specularscale being 0 (corona only lights) + // skip lights that are basically invisible (color 0 0 0) + nolight = VectorLength2(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale) < (1.0f / 1048576.0f); + + // loading is done before visibility checks because loading should happen + // all at once at the start of a level, not when it stalls gameplay. + // (especially important to benchmarks) + // compile light + if (rtlight->isstatic && !nolight && (!rtlight->compiled || (rtlight->shadow && rtlight->shadowmode != (int)r_shadow_shadowmode)) && r_shadow_realtime_world_compile.integer) + { + if (rtlight->compiled) + R_RTLight_Uncompile(rtlight); + R_RTLight_Compile(rtlight); + } + + // load cubemap + rtlight->currentcubemap = rtlight->cubemapname[0] ? R_GetCubemap(rtlight->cubemapname) : r_texture_whitecube; + + // look up the light style value at this time + f = (rtlight->style >= 0 ? r_refdef.scene.rtlightstylevalue[rtlight->style] : 1) * r_shadow_lightintensityscale.value; + VectorScale(rtlight->color, f, rtlight->currentcolor); + /* + if (rtlight->selected) + { + f = 2 + sin(realtime * M_PI * 4.0); + VectorScale(rtlight->currentcolor, f, rtlight->currentcolor); + } + */ + + // if lightstyle is currently off, don't draw the light + if (VectorLength2(rtlight->currentcolor) < (1.0f / 1048576.0f)) + return; + + // skip processing on corona-only lights + if (nolight) + return; + + // if the light box is offscreen, skip it + if (R_CullBox(rtlight->cullmins, rtlight->cullmaxs)) + return; + + VectorCopy(rtlight->cullmins, rtlight->cached_cullmins); + VectorCopy(rtlight->cullmaxs, rtlight->cached_cullmaxs); + + R_Shadow_ComputeShadowCasterCullingPlanes(rtlight); + + // don't allow lights to be drawn if using r_shadow_bouncegrid 2, except if we're using static bouncegrid where dynamic lights still need to draw + if (r_shadow_bouncegrid.integer == 2 && (rtlight->isstatic || !r_shadow_bouncegrid_static.integer)) + return; + + if (rtlight->compiled && r_shadow_realtime_world_compile.integer) + { + // compiled light, world available and can receive realtime lighting + // retrieve leaf information + numleafs = rtlight->static_numleafs; + leaflist = rtlight->static_leaflist; + leafpvs = rtlight->static_leafpvs; + numsurfaces = rtlight->static_numsurfaces; + surfacelist = rtlight->static_surfacelist; + //surfacesides = NULL; + shadowtrispvs = rtlight->static_shadowtrispvs; + lighttrispvs = rtlight->static_lighttrispvs; + } + else if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->GetLightInfo) + { + // dynamic light, world available and can receive realtime lighting + // calculate lit surfaces and leafs + r_refdef.scene.worldmodel->GetLightInfo(r_refdef.scene.worldentity, rtlight->shadoworigin, rtlight->radius, rtlight->cached_cullmins, rtlight->cached_cullmaxs, r_shadow_buffer_leaflist, r_shadow_buffer_leafpvs, &numleafs, r_shadow_buffer_surfacelist, r_shadow_buffer_surfacepvs, &numsurfaces, r_shadow_buffer_shadowtrispvs, r_shadow_buffer_lighttrispvs, r_shadow_buffer_visitingleafpvs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes); + R_Shadow_ComputeShadowCasterCullingPlanes(rtlight); + leaflist = r_shadow_buffer_leaflist; + leafpvs = r_shadow_buffer_leafpvs; + surfacelist = r_shadow_buffer_surfacelist; + //surfacesides = r_shadow_buffer_surfacesides; + shadowtrispvs = r_shadow_buffer_shadowtrispvs; + lighttrispvs = r_shadow_buffer_lighttrispvs; + // if the reduced leaf bounds are offscreen, skip it + if (R_CullBox(rtlight->cached_cullmins, rtlight->cached_cullmaxs)) + return; + } + else + { + // no world + numleafs = 0; + leaflist = NULL; + leafpvs = NULL; + numsurfaces = 0; + surfacelist = NULL; + //surfacesides = NULL; + shadowtrispvs = NULL; + lighttrispvs = NULL; + } + // check if light is illuminating any visible leafs + if (numleafs) + { + for (i = 0;i < numleafs;i++) + if (r_refdef.viewcache.world_leafvisible[leaflist[i]]) + break; + if (i == numleafs) + return; + } + + // make a list of lit entities and shadow casting entities + numlightentities = 0; + numlightentities_noselfshadow = 0; + numshadowentities = 0; + numshadowentities_noselfshadow = 0; + + // add dynamic entities that are lit by the light + for (i = 0;i < r_refdef.scene.numentities;i++) + { + dp_model_t *model; + entity_render_t *ent = r_refdef.scene.entities[i]; + vec3_t org; + if (!BoxesOverlap(ent->mins, ent->maxs, rtlight->cached_cullmins, rtlight->cached_cullmaxs)) + continue; + // skip the object entirely if it is not within the valid + // shadow-casting region (which includes the lit region) + if (R_CullBoxCustomPlanes(ent->mins, ent->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) + continue; + if (!(model = ent->model)) + continue; + if (r_refdef.viewcache.entityvisible[i] && model->DrawLight && (ent->flags & RENDER_LIGHT)) + { + // this entity wants to receive light, is visible, and is + // inside the light box + // TODO: check if the surfaces in the model can receive light + // so now check if it's in a leaf seen by the light + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS && !r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS(r_refdef.scene.worldmodel, leafpvs, ent->mins, ent->maxs)) + continue; + if (ent->flags & RENDER_NOSELFSHADOW) + lightentities_noselfshadow[numlightentities_noselfshadow++] = ent; + else + lightentities[numlightentities++] = ent; + // since it is lit, it probably also casts a shadow... + // about the VectorDistance2 - light emitting entities should not cast their own shadow + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + if ((ent->flags & RENDER_SHADOW) && model->DrawShadowVolume && VectorDistance2(org, rtlight->shadoworigin) > 0.1) + { + // note: exterior models without the RENDER_NOSELFSHADOW + // flag still create a RENDER_NOSELFSHADOW shadow but + // are lit normally, this means that they are + // self-shadowing but do not shadow other + // RENDER_NOSELFSHADOW entities such as the gun + // (very weird, but keeps the player shadow off the gun) + if (ent->flags & (RENDER_NOSELFSHADOW | RENDER_EXTERIORMODEL)) + shadowentities_noselfshadow[numshadowentities_noselfshadow++] = ent; + else + shadowentities[numshadowentities++] = ent; + } + } + else if (ent->flags & RENDER_SHADOW) + { + // this entity is not receiving light, but may still need to + // cast a shadow... + // TODO: check if the surfaces in the model can cast shadow + // now check if it is in a leaf seen by the light + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS && !r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS(r_refdef.scene.worldmodel, leafpvs, ent->mins, ent->maxs)) + continue; + // about the VectorDistance2 - light emitting entities should not cast their own shadow + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + if ((ent->flags & RENDER_SHADOW) && model->DrawShadowVolume && VectorDistance2(org, rtlight->shadoworigin) > 0.1) + { + if (ent->flags & (RENDER_NOSELFSHADOW | RENDER_EXTERIORMODEL)) + shadowentities_noselfshadow[numshadowentities_noselfshadow++] = ent; + else + shadowentities[numshadowentities++] = ent; + } + } + } + + // return if there's nothing at all to light + if (numsurfaces + numlightentities + numlightentities_noselfshadow == 0) + return; + + // count this light in the r_speeds + r_refdef.stats[r_stat_lights]++; + + // flag it as worth drawing later + rtlight->draw = true; + + // cache all the animated entities that cast a shadow but are not visible + for (i = 0;i < numshadowentities;i++) + R_AnimCache_GetEntity(shadowentities[i], false, false); + for (i = 0;i < numshadowentities_noselfshadow;i++) + R_AnimCache_GetEntity(shadowentities_noselfshadow[i], false, false); + + // allocate some temporary memory for rendering this light later in the frame + // reusable buffers need to be copied, static data can be used as-is + rtlight->cached_numlightentities = numlightentities; + rtlight->cached_numlightentities_noselfshadow = numlightentities_noselfshadow; + rtlight->cached_numshadowentities = numshadowentities; + rtlight->cached_numshadowentities_noselfshadow = numshadowentities_noselfshadow; + rtlight->cached_numsurfaces = numsurfaces; + rtlight->cached_lightentities = (entity_render_t**)R_FrameData_Store(numlightentities*sizeof(entity_render_t*), (void*)lightentities); + rtlight->cached_lightentities_noselfshadow = (entity_render_t**)R_FrameData_Store(numlightentities_noselfshadow*sizeof(entity_render_t*), (void*)lightentities_noselfshadow); + rtlight->cached_shadowentities = (entity_render_t**)R_FrameData_Store(numshadowentities*sizeof(entity_render_t*), (void*)shadowentities); + rtlight->cached_shadowentities_noselfshadow = (entity_render_t**)R_FrameData_Store(numshadowentities_noselfshadow*sizeof(entity_render_t *), (void*)shadowentities_noselfshadow); + if (shadowtrispvs == r_shadow_buffer_shadowtrispvs) + { + int numshadowtrispvsbytes = (((r_refdef.scene.worldmodel->brush.shadowmesh ? r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles : r_refdef.scene.worldmodel->surfmesh.num_triangles) + 7) >> 3); + int numlighttrispvsbytes = ((r_refdef.scene.worldmodel->surfmesh.num_triangles + 7) >> 3); + rtlight->cached_shadowtrispvs = (unsigned char *)R_FrameData_Store(numshadowtrispvsbytes, shadowtrispvs); + rtlight->cached_lighttrispvs = (unsigned char *)R_FrameData_Store(numlighttrispvsbytes, lighttrispvs); + rtlight->cached_surfacelist = (int*)R_FrameData_Store(numsurfaces*sizeof(int), (void*)surfacelist); + } + else + { + // compiled light data + rtlight->cached_shadowtrispvs = shadowtrispvs; + rtlight->cached_lighttrispvs = lighttrispvs; + rtlight->cached_surfacelist = surfacelist; + } +} + +static void R_Shadow_DrawLight(rtlight_t *rtlight) +{ + int i; + int numsurfaces; + unsigned char *shadowtrispvs, *lighttrispvs, *surfacesides; + int numlightentities; + int numlightentities_noselfshadow; + int numshadowentities; + int numshadowentities_noselfshadow; + entity_render_t **lightentities; + entity_render_t **lightentities_noselfshadow; + entity_render_t **shadowentities; + entity_render_t **shadowentities_noselfshadow; + int *surfacelist; + static unsigned char entitysides[MAX_EDICTS]; + static unsigned char entitysides_noselfshadow[MAX_EDICTS]; + vec3_t nearestpoint; + vec_t distance; + qboolean castshadows; + int lodlinear; + + // check if we cached this light this frame (meaning it is worth drawing) + if (!rtlight->draw) + return; + + numlightentities = rtlight->cached_numlightentities; + numlightentities_noselfshadow = rtlight->cached_numlightentities_noselfshadow; + numshadowentities = rtlight->cached_numshadowentities; + numshadowentities_noselfshadow = rtlight->cached_numshadowentities_noselfshadow; + numsurfaces = rtlight->cached_numsurfaces; + lightentities = rtlight->cached_lightentities; + lightentities_noselfshadow = rtlight->cached_lightentities_noselfshadow; + shadowentities = rtlight->cached_shadowentities; + shadowentities_noselfshadow = rtlight->cached_shadowentities_noselfshadow; + shadowtrispvs = rtlight->cached_shadowtrispvs; + lighttrispvs = rtlight->cached_lighttrispvs; + surfacelist = rtlight->cached_surfacelist; + + // set up a scissor rectangle for this light + if (R_Shadow_ScissorForBBox(rtlight->cached_cullmins, rtlight->cached_cullmaxs)) + return; + + // don't let sound skip if going slow + if (r_refdef.scene.extraupdate) + S_ExtraUpdate (); + + // make this the active rtlight for rendering purposes + R_Shadow_RenderMode_ActiveLight(rtlight); + + if (r_showshadowvolumes.integer && r_refdef.view.showdebug && numsurfaces + numshadowentities + numshadowentities_noselfshadow && rtlight->shadow && (rtlight->isstatic ? r_refdef.scene.rtworldshadows : r_refdef.scene.rtdlightshadows)) + { + // optionally draw visible shape of the shadow volumes + // for performance analysis by level designers + R_Shadow_RenderMode_VisibleShadowVolumes(); + if (numsurfaces) + R_Shadow_DrawWorldShadow_ShadowVolume(numsurfaces, surfacelist, shadowtrispvs); + for (i = 0;i < numshadowentities;i++) + R_Shadow_DrawEntityShadow(shadowentities[i]); + for (i = 0;i < numshadowentities_noselfshadow;i++) + R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); + R_Shadow_RenderMode_VisibleLighting(false, false); + } + + if (r_showlighting.integer && r_refdef.view.showdebug && numsurfaces + numlightentities + numlightentities_noselfshadow) + { + // optionally draw the illuminated areas + // for performance analysis by level designers + R_Shadow_RenderMode_VisibleLighting(false, false); + if (numsurfaces) + R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); + for (i = 0;i < numlightentities;i++) + R_Shadow_DrawEntityLight(lightentities[i]); + for (i = 0;i < numlightentities_noselfshadow;i++) + R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); + } + + castshadows = numsurfaces + numshadowentities + numshadowentities_noselfshadow > 0 && rtlight->shadow && (rtlight->isstatic ? r_refdef.scene.rtworldshadows : r_refdef.scene.rtdlightshadows); + + nearestpoint[0] = bound(rtlight->cullmins[0], r_refdef.view.origin[0], rtlight->cullmaxs[0]); + nearestpoint[1] = bound(rtlight->cullmins[1], r_refdef.view.origin[1], rtlight->cullmaxs[1]); + nearestpoint[2] = bound(rtlight->cullmins[2], r_refdef.view.origin[2], rtlight->cullmaxs[2]); + distance = VectorDistance(nearestpoint, r_refdef.view.origin); + + lodlinear = (rtlight->radius * r_shadow_shadowmapping_precision.value) / sqrt(max(1.0f, distance/rtlight->radius)); + //lodlinear = (int)(r_shadow_shadowmapping_lod_bias.value + r_shadow_shadowmapping_lod_scale.value * rtlight->radius / max(1.0f, distance)); + lodlinear = bound(r_shadow_shadowmapping_minsize.integer, lodlinear, r_shadow_shadowmapmaxsize); + + if (castshadows && r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAP2D) + { + float borderbias; + int side; + int size; + int castermask = 0; + int receivermask = 0; + matrix4x4_t radiustolight = rtlight->matrix_worldtolight; + Matrix4x4_Abs(&radiustolight); + + r_shadow_shadowmaplod = 0; + for (i = 1;i < R_SHADOW_SHADOWMAP_NUMCUBEMAPS;i++) + if ((r_shadow_shadowmapmaxsize >> i) > lodlinear) + r_shadow_shadowmaplod = i; + + size = bound(r_shadow_shadowmapborder, lodlinear, r_shadow_shadowmapmaxsize); + + borderbias = r_shadow_shadowmapborder / (float)(size - r_shadow_shadowmapborder); + + surfacesides = NULL; + if (numsurfaces) + { + if (rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) + { + castermask = rtlight->static_shadowmap_casters; + receivermask = rtlight->static_shadowmap_receivers; + } + else + { + surfacesides = r_shadow_buffer_surfacesides; + for(i = 0;i < numsurfaces;i++) + { + msurface_t *surface = r_refdef.scene.worldmodel->data_surfaces + surfacelist[i]; + surfacesides[i] = R_Shadow_CalcBBoxSideMask(surface->mins, surface->maxs, &rtlight->matrix_worldtolight, &radiustolight, borderbias); + castermask |= surfacesides[i]; + receivermask |= surfacesides[i]; + } + } + } + if (receivermask < 0x3F) + { + for (i = 0;i < numlightentities;i++) + receivermask |= R_Shadow_CalcEntitySideMask(lightentities[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias); + if (receivermask < 0x3F) + for(i = 0; i < numlightentities_noselfshadow;i++) + receivermask |= R_Shadow_CalcEntitySideMask(lightentities_noselfshadow[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias); + } + + receivermask &= R_Shadow_CullFrustumSides(rtlight, size, r_shadow_shadowmapborder); + + if (receivermask) + { + for (i = 0;i < numshadowentities;i++) + castermask |= (entitysides[i] = R_Shadow_CalcEntitySideMask(shadowentities[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias)); + for (i = 0;i < numshadowentities_noselfshadow;i++) + castermask |= (entitysides_noselfshadow[i] = R_Shadow_CalcEntitySideMask(shadowentities_noselfshadow[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias)); + } + + //Con_Printf("distance %f lodlinear %i (lod %i) size %i\n", distance, lodlinear, r_shadow_shadowmaplod, size); + + // render shadow casters into 6 sided depth texture + for (side = 0;side < 6;side++) if (receivermask & (1 << side)) + { + R_Shadow_RenderMode_ShadowMap(side, receivermask, size); + if (! (castermask & (1 << side))) continue; + if (numsurfaces) + R_Shadow_DrawWorldShadow_ShadowMap(numsurfaces, surfacelist, shadowtrispvs, surfacesides); + for (i = 0;i < numshadowentities;i++) if (entitysides[i] & (1 << side)) + R_Shadow_DrawEntityShadow(shadowentities[i]); + } + + if (numlightentities_noselfshadow) + { + // render lighting using the depth texture as shadowmap + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(false, false, true); + for (i = 0;i < numlightentities_noselfshadow;i++) + R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); + } + + // render shadow casters into 6 sided depth texture + if (numshadowentities_noselfshadow) + { + for (side = 0;side < 6;side++) if ((receivermask & castermask) & (1 << side)) + { + R_Shadow_RenderMode_ShadowMap(side, 0, size); + for (i = 0;i < numshadowentities_noselfshadow;i++) if (entitysides_noselfshadow[i] & (1 << side)) + R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); + } + } + + // render lighting using the depth texture as shadowmap + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(false, false, true); + // draw lighting in the unmasked areas + if (numsurfaces) + R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); + for (i = 0;i < numlightentities;i++) + R_Shadow_DrawEntityLight(lightentities[i]); + } + else if (castshadows && vid.stencil) + { + // draw stencil shadow volumes to mask off pixels that are in shadow + // so that they won't receive lighting + GL_Scissor(r_shadow_lightscissor[0], r_shadow_lightscissor[1], r_shadow_lightscissor[2], r_shadow_lightscissor[3]); + R_Shadow_ClearStencil(); + + if (numsurfaces) + R_Shadow_DrawWorldShadow_ShadowVolume(numsurfaces, surfacelist, shadowtrispvs); + for (i = 0;i < numshadowentities;i++) + R_Shadow_DrawEntityShadow(shadowentities[i]); + + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(true, false, false); + for (i = 0;i < numlightentities_noselfshadow;i++) + R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); + + for (i = 0;i < numshadowentities_noselfshadow;i++) + R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); + + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(true, false, false); + if (numsurfaces) + R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); + for (i = 0;i < numlightentities;i++) + R_Shadow_DrawEntityLight(lightentities[i]); + } + else + { + // draw lighting in the unmasked areas + R_Shadow_RenderMode_Lighting(false, false, false); + if (numsurfaces) + R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); + for (i = 0;i < numlightentities;i++) + R_Shadow_DrawEntityLight(lightentities[i]); + for (i = 0;i < numlightentities_noselfshadow;i++) + R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); + } + + if (r_shadow_usingdeferredprepass) + { + // when rendering deferred lighting, we simply rasterize the box + if (castshadows && r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAP2D) + R_Shadow_RenderMode_DrawDeferredLight(false, true); + else if (castshadows && vid.stencil) + R_Shadow_RenderMode_DrawDeferredLight(true, false); + else + R_Shadow_RenderMode_DrawDeferredLight(false, false); + } +} + +static void R_Shadow_FreeDeferred(void) +{ + R_Mesh_DestroyFramebufferObject(r_shadow_prepassgeometryfbo); + r_shadow_prepassgeometryfbo = 0; + + R_Mesh_DestroyFramebufferObject(r_shadow_prepasslightingdiffusespecularfbo); + r_shadow_prepasslightingdiffusespecularfbo = 0; + + R_Mesh_DestroyFramebufferObject(r_shadow_prepasslightingdiffusefbo); + r_shadow_prepasslightingdiffusefbo = 0; + + if (r_shadow_prepassgeometrydepthbuffer) + R_FreeTexture(r_shadow_prepassgeometrydepthbuffer); + r_shadow_prepassgeometrydepthbuffer = NULL; + + if (r_shadow_prepassgeometrynormalmaptexture) + R_FreeTexture(r_shadow_prepassgeometrynormalmaptexture); + r_shadow_prepassgeometrynormalmaptexture = NULL; + + if (r_shadow_prepasslightingdiffusetexture) + R_FreeTexture(r_shadow_prepasslightingdiffusetexture); + r_shadow_prepasslightingdiffusetexture = NULL; + + if (r_shadow_prepasslightingspeculartexture) + R_FreeTexture(r_shadow_prepasslightingspeculartexture); + r_shadow_prepasslightingspeculartexture = NULL; +} + +void R_Shadow_DrawPrepass(void) +{ + int i; + int flag; + int lnum; + size_t lightindex; + dlight_t *light; + size_t range; + entity_render_t *ent; + float clearcolor[4]; + + R_Mesh_ResetTextureState(); + GL_DepthMask(true); + GL_ColorMask(1,1,1,1); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_Color(1,1,1,1); + GL_DepthTest(true); + R_Mesh_SetRenderTargets(r_shadow_prepassgeometryfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepassgeometrynormalmaptexture, NULL, NULL, NULL); + Vector4Set(clearcolor, 0.5f,0.5f,0.5f,1.0f); + GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + if (r_timereport_active) + R_TimeReport("prepasscleargeom"); + + if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawPrepass) + r_refdef.scene.worldmodel->DrawPrepass(r_refdef.scene.worldentity); + if (r_timereport_active) + R_TimeReport("prepassworld"); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + if (!r_refdef.viewcache.entityvisible[i]) + continue; + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawPrepass != NULL) + ent->model->DrawPrepass(ent); + } + + if (r_timereport_active) + R_TimeReport("prepassmodels"); + + GL_DepthMask(false); + GL_ColorMask(1,1,1,1); + GL_Color(1,1,1,1); + GL_DepthTest(true); + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); + Vector4Set(clearcolor, 0, 0, 0, 0); + GL_Clear(GL_COLOR_BUFFER_BIT, clearcolor, 1.0f, 0); + if (r_timereport_active) + R_TimeReport("prepassclearlit"); + + R_Shadow_RenderMode_Begin(); + + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + if (r_shadow_debuglight.integer >= 0) + { + lightindex = r_shadow_debuglight.integer; + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light && (light->flags & flag) && light->rtlight.draw) + R_Shadow_DrawLight(&light->rtlight); + } + else + { + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light && (light->flags & flag) && light->rtlight.draw) + R_Shadow_DrawLight(&light->rtlight); + } + } + if (r_refdef.scene.rtdlight) + for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) + if (r_refdef.scene.lights[lnum]->draw) + R_Shadow_DrawLight(r_refdef.scene.lights[lnum]); + + R_Shadow_RenderMode_End(); + + if (r_timereport_active) + R_TimeReport("prepasslights"); +} + +void R_Shadow_DrawLightSprites(void); +void R_Shadow_PrepareLights(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + int flag; + int lnum; + size_t lightindex; + dlight_t *light; + size_t range; + float f; + + if (r_shadow_shadowmapmaxsize != bound(1, r_shadow_shadowmapping_maxsize.integer, (int)vid.maxtexturesize_2d / 4) || + (r_shadow_shadowmode != R_SHADOW_SHADOWMODE_STENCIL) != (r_shadow_shadowmapping.integer || r_shadow_deferred.integer) || + r_shadow_shadowmapvsdct != (r_shadow_shadowmapping_vsdct.integer != 0 && vid.renderpath == RENDERPATH_GL20) || + r_shadow_shadowmapfilterquality != r_shadow_shadowmapping_filterquality.integer || + r_shadow_shadowmapshadowsampler != (vid.support.arb_shadow && r_shadow_shadowmapping_useshadowsampler.integer) || + r_shadow_shadowmapdepthbits != r_shadow_shadowmapping_depthbits.integer || + r_shadow_shadowmapborder != bound(0, r_shadow_shadowmapping_bordersize.integer, 16) || + r_shadow_shadowmapdepthtexture != r_fb.usedepthtextures) + R_Shadow_FreeShadowMaps(); + + r_shadow_fb_fbo = fbo; + r_shadow_fb_depthtexture = depthtexture; + r_shadow_fb_colortexture = colortexture; + + r_shadow_usingshadowmaportho = false; + + switch (vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: +#ifndef USE_GLES2 + if (!r_shadow_deferred.integer || r_shadow_shadowmode == R_SHADOW_SHADOWMODE_STENCIL || !vid.support.ext_framebuffer_object || vid.maxdrawbuffers < 2) + { + r_shadow_usingdeferredprepass = false; + if (r_shadow_prepass_width) + R_Shadow_FreeDeferred(); + r_shadow_prepass_width = r_shadow_prepass_height = 0; + break; + } + + if (r_shadow_prepass_width != vid.width || r_shadow_prepass_height != vid.height) + { + R_Shadow_FreeDeferred(); + + r_shadow_usingdeferredprepass = true; + r_shadow_prepass_width = vid.width; + r_shadow_prepass_height = vid.height; + r_shadow_prepassgeometrydepthbuffer = R_LoadTextureRenderBuffer(r_shadow_texturepool, "prepassgeometrydepthbuffer", vid.width, vid.height, TEXTYPE_DEPTHBUFFER24); + r_shadow_prepassgeometrynormalmaptexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrynormalmap", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER32F, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + r_shadow_prepasslightingdiffusetexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingdiffuse", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER16F, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + r_shadow_prepasslightingspeculartexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingspecular", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER16F, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); + + // set up the geometry pass fbo (depth + normalmap) + r_shadow_prepassgeometryfbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthbuffer, r_shadow_prepassgeometrynormalmaptexture, NULL, NULL, NULL); + R_Mesh_SetRenderTargets(r_shadow_prepassgeometryfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepassgeometrynormalmaptexture, NULL, NULL, NULL); + // render depth into a renderbuffer and other important properties into the normalmap texture + + // set up the lighting pass fbo (diffuse + specular) + r_shadow_prepasslightingdiffusespecularfbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); + // render diffuse into one texture and specular into another, + // with depth and normalmap bound as textures, + // with depth bound as attachment as well + + // set up the lighting pass fbo (diffuse) + r_shadow_prepasslightingdiffusefbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); + R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusefbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); + // render diffuse into one texture, + // with depth and normalmap bound as textures, + // with depth bound as attachment as well + } +#endif + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + r_shadow_usingdeferredprepass = false; + break; + } + + R_Shadow_EnlargeLeafSurfaceTrisBuffer(r_refdef.scene.worldmodel->brush.num_leafs, r_refdef.scene.worldmodel->num_surfaces, r_refdef.scene.worldmodel->brush.shadowmesh ? r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles : r_refdef.scene.worldmodel->surfmesh.num_triangles, r_refdef.scene.worldmodel->surfmesh.num_triangles); + + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + if (r_shadow_debuglight.integer >= 0) + { + lightindex = r_shadow_debuglight.integer; + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light) + R_Shadow_PrepareLight(&light->rtlight); + } + else + { + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light && (light->flags & flag)) + R_Shadow_PrepareLight(&light->rtlight); + } + } + if (r_refdef.scene.rtdlight) + { + for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) + R_Shadow_PrepareLight(r_refdef.scene.lights[lnum]); + } + else if(gl_flashblend.integer) + { + for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) + { + rtlight_t *rtlight = r_refdef.scene.lights[lnum]; + f = (rtlight->style >= 0 ? r_refdef.scene.lightstylevalue[rtlight->style] : 1) * r_shadow_lightintensityscale.value; + VectorScale(rtlight->color, f, rtlight->currentcolor); + } + } + + if (r_editlights.integer) + R_Shadow_DrawLightSprites(); +} + +void R_Shadow_DrawLights(void) +{ + int flag; + int lnum; + size_t lightindex; + dlight_t *light; + size_t range; + + R_Shadow_RenderMode_Begin(); + + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + if (r_shadow_debuglight.integer >= 0) + { + lightindex = r_shadow_debuglight.integer; + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light) + R_Shadow_DrawLight(&light->rtlight); + } + else + { + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light && (light->flags & flag)) + R_Shadow_DrawLight(&light->rtlight); + } + } + if (r_refdef.scene.rtdlight) + for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) + R_Shadow_DrawLight(r_refdef.scene.lights[lnum]); + + R_Shadow_RenderMode_End(); +} + +#define MAX_MODELSHADOWS 1024 +static int r_shadow_nummodelshadows; +static entity_render_t *r_shadow_modelshadows[MAX_MODELSHADOWS]; + +void R_Shadow_PrepareModelShadows(void) +{ + int i; + float scale, size, radius, dot1, dot2; + prvm_vec3_t prvmshadowdir, prvmshadowfocus; + vec3_t shadowdir, shadowforward, shadowright, shadoworigin, shadowfocus, shadowmins, shadowmaxs; + entity_render_t *ent; + + r_shadow_nummodelshadows = 0; + if (!r_refdef.scene.numentities) + return; + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (r_shadows.integer >= 2) + break; + // fall through + case R_SHADOW_SHADOWMODE_STENCIL: + if (!vid.stencil) + return; + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + if (ent->model && ent->model->DrawShadowVolume != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + { + if (r_shadow_nummodelshadows >= MAX_MODELSHADOWS) + break; + r_shadow_modelshadows[r_shadow_nummodelshadows++] = ent; + R_AnimCache_GetEntity(ent, false, false); + } + } + return; + default: + return; + } + + size = 2*r_shadow_shadowmapmaxsize; + scale = r_shadow_shadowmapping_precision.value * r_shadows_shadowmapscale.value; + radius = 0.5f * size / scale; + + Math_atov(r_shadows_throwdirection.string, prvmshadowdir); + VectorCopy(prvmshadowdir, shadowdir); + VectorNormalize(shadowdir); + dot1 = DotProduct(r_refdef.view.forward, shadowdir); + dot2 = DotProduct(r_refdef.view.up, shadowdir); + if (fabs(dot1) <= fabs(dot2)) + VectorMA(r_refdef.view.forward, -dot1, shadowdir, shadowforward); + else + VectorMA(r_refdef.view.up, -dot2, shadowdir, shadowforward); + VectorNormalize(shadowforward); + CrossProduct(shadowdir, shadowforward, shadowright); + Math_atov(r_shadows_focus.string, prvmshadowfocus); + VectorCopy(prvmshadowfocus, shadowfocus); + VectorM(shadowfocus[0], r_refdef.view.right, shadoworigin); + VectorMA(shadoworigin, shadowfocus[1], r_refdef.view.up, shadoworigin); + VectorMA(shadoworigin, -shadowfocus[2], r_refdef.view.forward, shadoworigin); + VectorAdd(shadoworigin, r_refdef.view.origin, shadoworigin); + if (shadowfocus[0] || shadowfocus[1] || shadowfocus[2]) + dot1 = 1; + VectorMA(shadoworigin, (1.0f - fabs(dot1)) * radius, shadowforward, shadoworigin); + + shadowmins[0] = shadoworigin[0] - r_shadows_throwdistance.value * fabs(shadowdir[0]) - radius * (fabs(shadowforward[0]) + fabs(shadowright[0])); + shadowmins[1] = shadoworigin[1] - r_shadows_throwdistance.value * fabs(shadowdir[1]) - radius * (fabs(shadowforward[1]) + fabs(shadowright[1])); + shadowmins[2] = shadoworigin[2] - r_shadows_throwdistance.value * fabs(shadowdir[2]) - radius * (fabs(shadowforward[2]) + fabs(shadowright[2])); + shadowmaxs[0] = shadoworigin[0] + r_shadows_throwdistance.value * fabs(shadowdir[0]) + radius * (fabs(shadowforward[0]) + fabs(shadowright[0])); + shadowmaxs[1] = shadoworigin[1] + r_shadows_throwdistance.value * fabs(shadowdir[1]) + radius * (fabs(shadowforward[1]) + fabs(shadowright[1])); + shadowmaxs[2] = shadoworigin[2] + r_shadows_throwdistance.value * fabs(shadowdir[2]) + radius * (fabs(shadowforward[2]) + fabs(shadowright[2])); + + for (i = 0;i < r_refdef.scene.numentities;i++) + { + ent = r_refdef.scene.entities[i]; + if (!BoxesOverlap(ent->mins, ent->maxs, shadowmins, shadowmaxs)) + continue; + // cast shadows from anything of the map (submodels are optional) + if (ent->model && ent->model->DrawShadowMap != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) + { + if (r_shadow_nummodelshadows >= MAX_MODELSHADOWS) + break; + r_shadow_modelshadows[r_shadow_nummodelshadows++] = ent; + R_AnimCache_GetEntity(ent, false, false); + } + } +} + +void R_DrawModelShadowMaps(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + int i; + float relativethrowdistance, scale, size, radius, nearclip, farclip, bias, dot1, dot2; + entity_render_t *ent; + vec3_t relativelightorigin; + vec3_t relativelightdirection, relativeforward, relativeright; + vec3_t relativeshadowmins, relativeshadowmaxs; + vec3_t shadowdir, shadowforward, shadowright, shadoworigin, shadowfocus; + prvm_vec3_t prvmshadowdir, prvmshadowfocus; + float m[12]; + matrix4x4_t shadowmatrix, cameramatrix, mvpmatrix, invmvpmatrix, scalematrix, texmatrix; + r_viewport_t viewport; + GLuint shadowfbo = 0; + float clearcolor[4]; + + if (!r_shadow_nummodelshadows) + return; + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + break; + default: + return; + } + + r_shadow_fb_fbo = fbo; + r_shadow_fb_depthtexture = depthtexture; + r_shadow_fb_colortexture = colortexture; + + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + R_Shadow_RenderMode_Begin(); + R_Shadow_RenderMode_ActiveLight(NULL); + + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + if (!r_shadow_shadowmap2ddepthtexture) + R_Shadow_MakeShadowMap(0, r_shadow_shadowmapmaxsize); + shadowfbo = r_shadow_fbo2d; + r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2ddepthtexture); + r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2ddepthtexture); + r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D; + break; + default: + break; + } + + size = 2*r_shadow_shadowmapmaxsize; + scale = (r_shadow_shadowmapping_precision.value * r_shadows_shadowmapscale.value) / size; + radius = 0.5f / scale; + nearclip = -r_shadows_throwdistance.value; + farclip = r_shadows_throwdistance.value; + bias = (r_shadows_shadowmapbias.value < 0) ? r_shadow_shadowmapping_bias.value : r_shadows_shadowmapbias.value * r_shadow_shadowmapping_nearclip.value / (2 * r_shadows_throwdistance.value) * (1024.0f / size); + + r_shadow_shadowmap_parameters[0] = size; + r_shadow_shadowmap_parameters[1] = size; + r_shadow_shadowmap_parameters[2] = 1.0; + r_shadow_shadowmap_parameters[3] = bound(0.0f, 1.0f - r_shadows_darken.value, 1.0f); + + Math_atov(r_shadows_throwdirection.string, prvmshadowdir); + VectorCopy(prvmshadowdir, shadowdir); + VectorNormalize(shadowdir); + Math_atov(r_shadows_focus.string, prvmshadowfocus); + VectorCopy(prvmshadowfocus, shadowfocus); + VectorM(shadowfocus[0], r_refdef.view.right, shadoworigin); + VectorMA(shadoworigin, shadowfocus[1], r_refdef.view.up, shadoworigin); + VectorMA(shadoworigin, -shadowfocus[2], r_refdef.view.forward, shadoworigin); + VectorAdd(shadoworigin, r_refdef.view.origin, shadoworigin); + dot1 = DotProduct(r_refdef.view.forward, shadowdir); + dot2 = DotProduct(r_refdef.view.up, shadowdir); + if (fabs(dot1) <= fabs(dot2)) + VectorMA(r_refdef.view.forward, -dot1, shadowdir, shadowforward); + else + VectorMA(r_refdef.view.up, -dot2, shadowdir, shadowforward); + VectorNormalize(shadowforward); + VectorM(scale, shadowforward, &m[0]); + if (shadowfocus[0] || shadowfocus[1] || shadowfocus[2]) + dot1 = 1; + m[3] = fabs(dot1) * 0.5f - DotProduct(shadoworigin, &m[0]); + CrossProduct(shadowdir, shadowforward, shadowright); + VectorM(scale, shadowright, &m[4]); + m[7] = 0.5f - DotProduct(shadoworigin, &m[4]); + VectorM(1.0f / (farclip - nearclip), shadowdir, &m[8]); + m[11] = 0.5f - DotProduct(shadoworigin, &m[8]); + Matrix4x4_FromArray12FloatD3D(&shadowmatrix, m); + Matrix4x4_Invert_Full(&cameramatrix, &shadowmatrix); + R_Viewport_InitOrtho(&viewport, &cameramatrix, 0, 0, size, size, 0, 0, 1, 1, 0, -1, NULL); + + VectorMA(shadoworigin, (1.0f - fabs(dot1)) * radius, shadowforward, shadoworigin); + + if (r_shadow_shadowmap2ddepthbuffer) + R_Mesh_SetRenderTargets(shadowfbo, r_shadow_shadowmap2ddepthbuffer, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL); + else + R_Mesh_SetRenderTargets(shadowfbo, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL, NULL); + R_SetupShader_DepthOrShadow(true, r_shadow_shadowmap2ddepthbuffer != NULL, false); // FIXME test if we have a skeletal model? + GL_PolygonOffset(r_shadow_shadowmapping_polygonfactor.value, r_shadow_shadowmapping_polygonoffset.value); + GL_DepthMask(true); + GL_DepthTest(true); + R_SetViewport(&viewport); + GL_Scissor(viewport.x, viewport.y, min(viewport.width + r_shadow_shadowmapborder, 2*r_shadow_shadowmapmaxsize), viewport.height + r_shadow_shadowmapborder); + Vector4Set(clearcolor, 1,1,1,1); + // in D3D9 we have to render to a color texture shadowmap + // in GL we render directly to a depth texture only + if (r_shadow_shadowmap2ddepthbuffer) + GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + else + GL_Clear(GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); + // render into a slightly restricted region so that the borders of the + // shadowmap area fade away, rather than streaking across everything + // outside the usable area + GL_Scissor(viewport.x + r_shadow_shadowmapborder, viewport.y + r_shadow_shadowmapborder, viewport.width - 2*r_shadow_shadowmapborder, viewport.height - 2*r_shadow_shadowmapborder); + + for (i = 0;i < r_shadow_nummodelshadows;i++) + { + ent = r_shadow_modelshadows[i]; + relativethrowdistance = r_shadows_throwdistance.value * Matrix4x4_ScaleFromMatrix(&ent->inversematrix); + Matrix4x4_Transform(&ent->inversematrix, shadoworigin, relativelightorigin); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowdir, relativelightdirection); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowforward, relativeforward); + Matrix4x4_Transform3x3(&ent->inversematrix, shadowright, relativeright); + relativeshadowmins[0] = relativelightorigin[0] - r_shadows_throwdistance.value * fabs(relativelightdirection[0]) - radius * (fabs(relativeforward[0]) + fabs(relativeright[0])); + relativeshadowmins[1] = relativelightorigin[1] - r_shadows_throwdistance.value * fabs(relativelightdirection[1]) - radius * (fabs(relativeforward[1]) + fabs(relativeright[1])); + relativeshadowmins[2] = relativelightorigin[2] - r_shadows_throwdistance.value * fabs(relativelightdirection[2]) - radius * (fabs(relativeforward[2]) + fabs(relativeright[2])); + relativeshadowmaxs[0] = relativelightorigin[0] + r_shadows_throwdistance.value * fabs(relativelightdirection[0]) + radius * (fabs(relativeforward[0]) + fabs(relativeright[0])); + relativeshadowmaxs[1] = relativelightorigin[1] + r_shadows_throwdistance.value * fabs(relativelightdirection[1]) + radius * (fabs(relativeforward[1]) + fabs(relativeright[1])); + relativeshadowmaxs[2] = relativelightorigin[2] + r_shadows_throwdistance.value * fabs(relativelightdirection[2]) + radius * (fabs(relativeforward[2]) + fabs(relativeright[2])); + RSurf_ActiveModelEntity(ent, false, false, false); + ent->model->DrawShadowMap(0, ent, relativelightorigin, relativelightdirection, relativethrowdistance, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, NULL, relativeshadowmins, relativeshadowmaxs); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + } + +#if 0 + if (r_test.integer) + { + unsigned char *rawpixels = Z_Malloc(viewport.width*viewport.height*4); + CHECKGLERROR + qglReadPixels(viewport.x, viewport.y, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, rawpixels); + CHECKGLERROR + Image_WriteTGABGRA("r_shadows_2.tga", viewport.width, viewport.height, rawpixels); + Cvar_SetValueQuick(&r_test, 0); + Z_Free(rawpixels); + } +#endif + + R_Shadow_RenderMode_End(); + + Matrix4x4_Concat(&mvpmatrix, &r_refdef.view.viewport.projectmatrix, &r_refdef.view.viewport.viewmatrix); + Matrix4x4_Invert_Full(&invmvpmatrix, &mvpmatrix); + Matrix4x4_CreateScale3(&scalematrix, size, -size, 1); + Matrix4x4_AdjustOrigin(&scalematrix, 0, size, -0.5f * bias); + Matrix4x4_Concat(&texmatrix, &scalematrix, &shadowmatrix); + Matrix4x4_Concat(&r_shadow_shadowmapmatrix, &texmatrix, &invmvpmatrix); + + switch (vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_SOFT: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + break; + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: +#ifdef MATRIX4x4_OPENGLORIENTATION + r_shadow_shadowmapmatrix.m[0][0] *= -1.0f; + r_shadow_shadowmapmatrix.m[0][1] *= -1.0f; + r_shadow_shadowmapmatrix.m[0][2] *= -1.0f; + r_shadow_shadowmapmatrix.m[0][3] *= -1.0f; +#else + r_shadow_shadowmapmatrix.m[0][0] *= -1.0f; + r_shadow_shadowmapmatrix.m[1][0] *= -1.0f; + r_shadow_shadowmapmatrix.m[2][0] *= -1.0f; + r_shadow_shadowmapmatrix.m[3][0] *= -1.0f; +#endif + break; + } + + r_shadow_usingshadowmaportho = true; + switch (r_shadow_shadowmode) + { + case R_SHADOW_SHADOWMODE_SHADOWMAP2D: + r_shadow_usingshadowmap2d = true; + break; + default: + break; + } +} + +void R_DrawModelShadows(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) +{ + int i; + float relativethrowdistance; + entity_render_t *ent; + vec3_t relativelightorigin; + vec3_t relativelightdirection; + vec3_t relativeshadowmins, relativeshadowmaxs; + vec3_t tmp, shadowdir; + prvm_vec3_t prvmshadowdir; + + if (!r_shadow_nummodelshadows || (r_shadow_shadowmode != R_SHADOW_SHADOWMODE_STENCIL && r_shadows.integer != 1)) + return; + + r_shadow_fb_fbo = fbo; + r_shadow_fb_depthtexture = depthtexture; + r_shadow_fb_colortexture = colortexture; + + R_ResetViewRendering3D(fbo, depthtexture, colortexture); + //GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); + //GL_Scissor(r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height); + R_Shadow_RenderMode_Begin(); + R_Shadow_RenderMode_ActiveLight(NULL); + r_shadow_lightscissor[0] = r_refdef.view.x; + r_shadow_lightscissor[1] = vid.height - r_refdef.view.y - r_refdef.view.height; + r_shadow_lightscissor[2] = r_refdef.view.width; + r_shadow_lightscissor[3] = r_refdef.view.height; + R_Shadow_RenderMode_StencilShadowVolumes(false); + + // get shadow dir + if (r_shadows.integer == 2) + { + Math_atov(r_shadows_throwdirection.string, prvmshadowdir); + VectorCopy(prvmshadowdir, shadowdir); + VectorNormalize(shadowdir); + } + + R_Shadow_ClearStencil(); + + for (i = 0;i < r_shadow_nummodelshadows;i++) + { + ent = r_shadow_modelshadows[i]; + + // cast shadows from anything of the map (submodels are optional) + relativethrowdistance = r_shadows_throwdistance.value * Matrix4x4_ScaleFromMatrix(&ent->inversematrix); + VectorSet(relativeshadowmins, -relativethrowdistance, -relativethrowdistance, -relativethrowdistance); + VectorSet(relativeshadowmaxs, relativethrowdistance, relativethrowdistance, relativethrowdistance); + if (r_shadows.integer == 2) // 2: simpler mode, throw shadows always in same direction + Matrix4x4_Transform3x3(&ent->inversematrix, shadowdir, relativelightdirection); + else + { + if(ent->entitynumber != 0) + { + if(ent->entitynumber >= MAX_EDICTS) // csqc entity + { + // FIXME handle this + VectorNegate(ent->modellight_lightdir, relativelightdirection); + } + else + { + // networked entity - might be attached in some way (then we should use the parent's light direction, to not tear apart attached entities) + int entnum, entnum2, recursion; + entnum = entnum2 = ent->entitynumber; + for(recursion = 32; recursion > 0; --recursion) + { + entnum2 = cl.entities[entnum].state_current.tagentity; + if(entnum2 >= 1 && entnum2 < cl.num_entities && cl.entities_active[entnum2]) + entnum = entnum2; + else + break; + } + if(recursion && recursion != 32) // if we followed a valid non-empty attachment chain + { + VectorNegate(cl.entities[entnum].render.modellight_lightdir, relativelightdirection); + // transform into modelspace of OUR entity + Matrix4x4_Transform3x3(&cl.entities[entnum].render.matrix, relativelightdirection, tmp); + Matrix4x4_Transform3x3(&ent->inversematrix, tmp, relativelightdirection); + } + else + VectorNegate(ent->modellight_lightdir, relativelightdirection); + } + } + else + VectorNegate(ent->modellight_lightdir, relativelightdirection); + } + + VectorScale(relativelightdirection, -relativethrowdistance, relativelightorigin); + RSurf_ActiveModelEntity(ent, false, false, false); + ent->model->DrawShadowVolume(ent, relativelightorigin, relativelightdirection, relativethrowdistance, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, relativeshadowmins, relativeshadowmaxs); + rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity + } + + // not really the right mode, but this will disable any silly stencil features + R_Shadow_RenderMode_End(); + + // set up ortho view for rendering this pass + //GL_Scissor(r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height); + //GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + //GL_ScissorTest(true); + //R_EntityMatrix(&identitymatrix); + //R_Mesh_ResetTextureState(); + R_ResetViewRendering2D(fbo, depthtexture, colortexture); + + // set up a darkening blend on shadowed areas + GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //GL_DepthRange(0, 1); + //GL_DepthTest(false); + //GL_DepthMask(false); + //GL_PolygonOffset(0, 0);CHECKGLERROR + GL_Color(0, 0, 0, r_shadows_darken.value); + //GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + //GL_DepthFunc(GL_ALWAYS); + R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_NOTEQUAL, 128, 255); + + // apply the blend to the shadowed areas + R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); + R_SetupShader_Generic_NoTexture(false, true); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + + // restore the viewport + R_SetViewport(&r_refdef.view.viewport); + + // restore other state to normal + //R_Shadow_RenderMode_End(); +} + +static void R_BeginCoronaQuery(rtlight_t *rtlight, float scale, qboolean usequery) +{ + float zdist; + vec3_t centerorigin; + float vertex3f[12]; + // if it's too close, skip it + if (VectorLength(rtlight->currentcolor) < (1.0f / 256.0f)) + return; + zdist = (DotProduct(rtlight->shadoworigin, r_refdef.view.forward) - DotProduct(r_refdef.view.origin, r_refdef.view.forward)); + if (zdist < 32) + return; + if (usequery && r_numqueries + 2 <= r_maxqueries) + { + rtlight->corona_queryindex_allpixels = r_queries[r_numqueries++]; + rtlight->corona_queryindex_visiblepixels = r_queries[r_numqueries++]; + // we count potential samples in the middle of the screen, we count actual samples at the light location, this allows counting potential samples of off-screen lights + VectorMA(r_refdef.view.origin, zdist, r_refdef.view.forward, centerorigin); + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: +#ifdef GL_SAMPLES_PASSED_ARB + CHECKGLERROR + // NOTE: GL_DEPTH_TEST must be enabled or ATI won't count samples, so use GL_DepthFunc instead + qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, rtlight->corona_queryindex_allpixels); + GL_DepthFunc(GL_ALWAYS); + R_CalcSprite_Vertex3f(vertex3f, centerorigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); + R_Mesh_PrepareVertices_Vertex3f(4, vertex3f, NULL, 0); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + qglEndQueryARB(GL_SAMPLES_PASSED_ARB); + GL_DepthFunc(GL_LEQUAL); + qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, rtlight->corona_queryindex_visiblepixels); + R_CalcSprite_Vertex3f(vertex3f, rtlight->shadoworigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); + R_Mesh_PrepareVertices_Vertex3f(4, vertex3f, NULL, 0); + R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); + qglEndQueryARB(GL_SAMPLES_PASSED_ARB); + CHECKGLERROR +#endif + break; + case RENDERPATH_D3D9: + Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + } + rtlight->corona_visibility = bound(0, (zdist - 32) / 32, 1); +} + +static float spritetexcoord2f[4*2] = {0, 1, 0, 0, 1, 0, 1, 1}; + +static void R_DrawCorona(rtlight_t *rtlight, float cscale, float scale) +{ + vec3_t color; + GLint allpixels = 0, visiblepixels = 0; + // now we have to check the query result + if (rtlight->corona_queryindex_visiblepixels) + { + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: +#ifdef GL_SAMPLES_PASSED_ARB + CHECKGLERROR + qglGetQueryObjectivARB(rtlight->corona_queryindex_visiblepixels, GL_QUERY_RESULT_ARB, &visiblepixels); + qglGetQueryObjectivARB(rtlight->corona_queryindex_allpixels, GL_QUERY_RESULT_ARB, &allpixels); + CHECKGLERROR +#endif + break; + case RENDERPATH_D3D9: + Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + //Con_Printf("%i of %i pixels\n", (int)visiblepixels, (int)allpixels); + if (visiblepixels < 1 || allpixels < 1) + return; + rtlight->corona_visibility *= bound(0, (float)visiblepixels / (float)allpixels, 1); + cscale *= rtlight->corona_visibility; + } + else + { + // FIXME: these traces should scan all render entities instead of cl.world + if (CL_TraceLine(r_refdef.view.origin, rtlight->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction < 1) + return; + } + VectorScale(rtlight->currentcolor, cscale, color); + if (VectorLength(color) > (1.0f / 256.0f)) + { + float vertex3f[12]; + qboolean negated = (color[0] + color[1] + color[2] < 0) && vid.support.ext_blend_subtract; + if(negated) + { + VectorNegate(color, color); + GL_BlendEquationSubtract(true); + } + R_CalcSprite_Vertex3f(vertex3f, rtlight->shadoworigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, RENDER_NODEPTHTEST, 0, color[0], color[1], color[2], 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + R_DrawCustomSurface(r_shadow_lightcorona, &identitymatrix, MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST, 0, 4, 0, 2, false, false); + if(negated) + GL_BlendEquationSubtract(false); + } +} + +void R_Shadow_DrawCoronas(void) +{ + int i, flag; + qboolean usequery = false; + size_t lightindex; + dlight_t *light; + rtlight_t *rtlight; + size_t range; + if (r_coronas.value < (1.0f / 256.0f) && !gl_flashblend.integer) + return; + if (r_fb.water.renderingscene) + return; + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + R_EntityMatrix(&identitymatrix); + + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + + // check occlusion of coronas + // use GL_ARB_occlusion_query if available + // otherwise use raytraces + r_numqueries = 0; + switch (vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + case RENDERPATH_GLES1: + case RENDERPATH_GLES2: + usequery = vid.support.arb_occlusion_query && r_coronas_occlusionquery.integer; +#ifdef GL_SAMPLES_PASSED_ARB + if (usequery) + { + GL_ColorMask(0,0,0,0); + if (r_maxqueries < (range + r_refdef.scene.numlights) * 2) + if (r_maxqueries < MAX_OCCLUSION_QUERIES) + { + i = r_maxqueries; + r_maxqueries = (range + r_refdef.scene.numlights) * 4; + r_maxqueries = min(r_maxqueries, MAX_OCCLUSION_QUERIES); + CHECKGLERROR + qglGenQueriesARB(r_maxqueries - i, r_queries + i); + CHECKGLERROR + } + RSurf_ActiveWorldEntity(); + GL_BlendFunc(GL_ONE, GL_ZERO); + GL_CullFace(GL_NONE); + GL_DepthMask(false); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); + GL_DepthTest(true); + R_Mesh_ResetTextureState(); + R_SetupShader_Generic_NoTexture(false, false); + } +#endif + break; + case RENDERPATH_D3D9: + usequery = false; + //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D10: + Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_D3D11: + Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + case RENDERPATH_SOFT: + usequery = false; + //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); + break; + } + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + rtlight = &light->rtlight; + rtlight->corona_visibility = 0; + rtlight->corona_queryindex_visiblepixels = 0; + rtlight->corona_queryindex_allpixels = 0; + if (!(rtlight->flags & flag)) + continue; + if (rtlight->corona <= 0) + continue; + if (r_shadow_debuglight.integer >= 0 && r_shadow_debuglight.integer != (int)lightindex) + continue; + R_BeginCoronaQuery(rtlight, rtlight->radius * rtlight->coronasizescale * r_coronas_occlusionsizescale.value, usequery); + } + for (i = 0;i < r_refdef.scene.numlights;i++) + { + rtlight = r_refdef.scene.lights[i]; + rtlight->corona_visibility = 0; + rtlight->corona_queryindex_visiblepixels = 0; + rtlight->corona_queryindex_allpixels = 0; + if (!(rtlight->flags & flag)) + continue; + if (rtlight->corona <= 0) + continue; + R_BeginCoronaQuery(rtlight, rtlight->radius * rtlight->coronasizescale * r_coronas_occlusionsizescale.value, usequery); + } + if (usequery) + GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); + + // now draw the coronas using the query data for intensity info + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + rtlight = &light->rtlight; + if (rtlight->corona_visibility <= 0) + continue; + R_DrawCorona(rtlight, rtlight->corona * r_coronas.value * 0.25f, rtlight->radius * rtlight->coronasizescale); + } + for (i = 0;i < r_refdef.scene.numlights;i++) + { + rtlight = r_refdef.scene.lights[i]; + if (rtlight->corona_visibility <= 0) + continue; + if (gl_flashblend.integer) + R_DrawCorona(rtlight, rtlight->corona, rtlight->radius * rtlight->coronasizescale * 2.0f); + else + R_DrawCorona(rtlight, rtlight->corona * r_coronas.value * 0.25f, rtlight->radius * rtlight->coronasizescale); + } +} + + + +static dlight_t *R_Shadow_NewWorldLight(void) +{ + return (dlight_t *)Mem_ExpandableArray_AllocRecord(&r_shadow_worldlightsarray); +} + +static void R_Shadow_UpdateWorldLight(dlight_t *light, vec3_t origin, vec3_t angles, vec3_t color, vec_t radius, vec_t corona, int style, int shadowenable, const char *cubemapname, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) +{ + matrix4x4_t matrix; + // validate parameters + if (style < 0 || style >= MAX_LIGHTSTYLES) + { + Con_Printf("R_Shadow_NewWorldLight: invalid light style number %i, must be >= 0 and < %i\n", light->style, MAX_LIGHTSTYLES); + style = 0; + } + if (!cubemapname) + cubemapname = ""; + + // copy to light properties + VectorCopy(origin, light->origin); + light->angles[0] = angles[0] - 360 * floor(angles[0] / 360); + light->angles[1] = angles[1] - 360 * floor(angles[1] / 360); + light->angles[2] = angles[2] - 360 * floor(angles[2] / 360); + /* + light->color[0] = max(color[0], 0); + light->color[1] = max(color[1], 0); + light->color[2] = max(color[2], 0); + */ + light->color[0] = color[0]; + light->color[1] = color[1]; + light->color[2] = color[2]; + light->radius = max(radius, 0); + light->style = style; + light->shadow = shadowenable; + light->corona = corona; + strlcpy(light->cubemapname, cubemapname, sizeof(light->cubemapname)); + light->coronasizescale = coronasizescale; + light->ambientscale = ambientscale; + light->diffusescale = diffusescale; + light->specularscale = specularscale; + light->flags = flags; + + // update renderable light data + Matrix4x4_CreateFromQuakeEntity(&matrix, light->origin[0], light->origin[1], light->origin[2], light->angles[0], light->angles[1], light->angles[2], light->radius); + R_RTLight_Update(&light->rtlight, true, &matrix, light->color, light->style, light->cubemapname[0] ? light->cubemapname : NULL, light->shadow, light->corona, light->coronasizescale, light->ambientscale, light->diffusescale, light->specularscale, light->flags); +} + +static void R_Shadow_FreeWorldLight(dlight_t *light) +{ + if (r_shadow_selectedlight == light) + r_shadow_selectedlight = NULL; + R_RTLight_Uncompile(&light->rtlight); + Mem_ExpandableArray_FreeRecord(&r_shadow_worldlightsarray, light); +} + +void R_Shadow_ClearWorldLights(void) +{ + size_t lightindex; + dlight_t *light; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light) + R_Shadow_FreeWorldLight(light); + } + r_shadow_selectedlight = NULL; +} + +static void R_Shadow_SelectLight(dlight_t *light) +{ + if (r_shadow_selectedlight) + r_shadow_selectedlight->selected = false; + r_shadow_selectedlight = light; + if (r_shadow_selectedlight) + r_shadow_selectedlight->selected = true; +} + +static void R_Shadow_DrawCursor_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + // this is never batched (there can be only one) + float vertex3f[12]; + R_CalcSprite_Vertex3f(vertex3f, r_editlights_cursorlocation, r_refdef.view.right, r_refdef.view.up, EDLIGHTSPRSIZE, -EDLIGHTSPRSIZE, -EDLIGHTSPRSIZE, EDLIGHTSPRSIZE); + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, 1, 1, 1, 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + R_DrawCustomSurface(r_editlights_sprcursor, &identitymatrix, MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); +} + +static void R_Shadow_DrawLightSprite_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + float intensity; + float s; + vec3_t spritecolor; + skinframe_t *skinframe; + float vertex3f[12]; + + // this is never batched (due to the ent parameter changing every time) + // so numsurfaces == 1 and surfacelist[0] == lightnumber + const dlight_t *light = (dlight_t *)ent; + s = EDLIGHTSPRSIZE; + + R_CalcSprite_Vertex3f(vertex3f, light->origin, r_refdef.view.right, r_refdef.view.up, s, -s, -s, s); + + intensity = 0.5f; + VectorScale(light->color, intensity, spritecolor); + if (VectorLength(spritecolor) < 0.1732f) + VectorSet(spritecolor, 0.1f, 0.1f, 0.1f); + if (VectorLength(spritecolor) > 1.0f) + VectorNormalize(spritecolor); + + // draw light sprite + if (light->cubemapname[0] && !light->shadow) + skinframe = r_editlights_sprcubemapnoshadowlight; + else if (light->cubemapname[0]) + skinframe = r_editlights_sprcubemaplight; + else if (!light->shadow) + skinframe = r_editlights_sprnoshadowlight; + else + skinframe = r_editlights_sprlight; + + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, spritecolor[0], spritecolor[1], spritecolor[2], 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + R_DrawCustomSurface(skinframe, &identitymatrix, MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); + + // draw selection sprite if light is selected + if (light->selected) + { + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, 1, 1, 1, 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + R_DrawCustomSurface(r_editlights_sprselection, &identitymatrix, MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); + // VorteX todo: add normalmode/realtime mode light overlay sprites? + } +} + +void R_Shadow_DrawLightSprites(void) +{ + size_t lightindex; + dlight_t *light; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (light) + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, light->origin, R_Shadow_DrawLightSprite_TransparentCallback, (entity_render_t *)light, 5, &light->rtlight); + } + if (!r_editlights_lockcursor) + R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, r_editlights_cursorlocation, R_Shadow_DrawCursor_TransparentCallback, NULL, 0, NULL); +} + +int R_Shadow_GetRTLightInfo(unsigned int lightindex, float *origin, float *radius, float *color) +{ + unsigned int range; + dlight_t *light; + rtlight_t *rtlight; + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); + if (lightindex >= range) + return -1; + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + return 0; + rtlight = &light->rtlight; + //if (!(rtlight->flags & flag)) + // return 0; + VectorCopy(rtlight->shadoworigin, origin); + *radius = rtlight->radius; + VectorCopy(rtlight->color, color); + return 1; +} + +static void R_Shadow_SelectLightInView(void) +{ + float bestrating, rating, temp[3]; + dlight_t *best; + size_t lightindex; + dlight_t *light; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + best = NULL; + bestrating = 0; + + if (r_editlights_lockcursor) + return; + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + VectorSubtract(light->origin, r_refdef.view.origin, temp); + rating = (DotProduct(temp, r_refdef.view.forward) / sqrt(DotProduct(temp, temp))); + if (rating >= 0.95) + { + rating /= (1 + 0.0625f * sqrt(DotProduct(temp, temp))); + if (bestrating < rating && CL_TraceLine(light->origin, r_refdef.view.origin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction == 1.0f) + { + bestrating = rating; + best = light; + } + } + } + R_Shadow_SelectLight(best); +} + +void R_Shadow_LoadWorldLights(void) +{ + int n, a, style, shadow, flags; + char tempchar, *lightsstring, *s, *t, name[MAX_QPATH], cubemapname[MAX_QPATH]; + float origin[3], radius, color[3], angles[3], corona, coronasizescale, ambientscale, diffusescale, specularscale; + if (cl.worldmodel == NULL) + { + Con_Print("No map loaded.\n"); + return; + } + dpsnprintf(name, sizeof(name), "%s.rtlights", cl.worldnamenoextension); + lightsstring = (char *)FS_LoadFile(name, tempmempool, false, NULL); + if (lightsstring) + { + s = lightsstring; + n = 0; + while (*s) + { + /* + t = s; + shadow = true; + for (;COM_Parse(t, true) && strcmp( + if (COM_Parse(t, true)) + { + if (com_token[0] == '!') + { + shadow = false; + origin[0] = atof(com_token+1); + } + else + origin[0] = atof(com_token); + if (Com_Parse(t + } + */ + t = s; + while (*s && *s != '\n' && *s != '\r') + s++; + if (!*s) + break; + tempchar = *s; + shadow = true; + // check for modifier flags + if (*t == '!') + { + shadow = false; + t++; + } + *s = 0; +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + cubemapname[sizeof(cubemapname)-1] = 0; +#if MAX_QPATH != 128 +#error update this code if MAX_QPATH changes +#endif + a = sscanf(t, "%f %f %f %f %f %f %f %d %127s %f %f %f %f %f %f %f %f %i", &origin[0], &origin[1], &origin[2], &radius, &color[0], &color[1], &color[2], &style, cubemapname +#if _MSC_VER >= 1400 +, sizeof(cubemapname) +#endif +, &corona, &angles[0], &angles[1], &angles[2], &coronasizescale, &ambientscale, &diffusescale, &specularscale, &flags); + *s = tempchar; + if (a < 18) + flags = LIGHTFLAG_REALTIMEMODE; + if (a < 17) + specularscale = 1; + if (a < 16) + diffusescale = 1; + if (a < 15) + ambientscale = 0; + if (a < 14) + coronasizescale = 0.25f; + if (a < 13) + VectorClear(angles); + if (a < 10) + corona = 0; + if (a < 9 || !strcmp(cubemapname, "\"\"")) + cubemapname[0] = 0; + // remove quotes on cubemapname + if (cubemapname[0] == '"' && cubemapname[strlen(cubemapname) - 1] == '"') + { + size_t namelen; + namelen = strlen(cubemapname) - 2; + memmove(cubemapname, cubemapname + 1, namelen); + cubemapname[namelen] = '\0'; + } + if (a < 8) + { + Con_Printf("found %d parameters on line %i, should be 8 or more parameters (origin[0] origin[1] origin[2] radius color[0] color[1] color[2] style \"cubemapname\" corona angles[0] angles[1] angles[2] coronasizescale ambientscale diffusescale specularscale flags)\n", a, n + 1); + break; + } + R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, angles, color, radius, corona, style, shadow, cubemapname, coronasizescale, ambientscale, diffusescale, specularscale, flags); + if (*s == '\r') + s++; + if (*s == '\n') + s++; + n++; + } + if (*s) + Con_Printf("invalid rtlights file \"%s\"\n", name); + Mem_Free(lightsstring); + } +} + +void R_Shadow_SaveWorldLights(void) +{ + size_t lightindex; + dlight_t *light; + size_t bufchars, bufmaxchars; + char *buf, *oldbuf; + char name[MAX_QPATH]; + char line[MAX_INPUTLINE]; + size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked, assuming the dpsnprintf mess doesn't screw it up... + // I hate lines which are 3 times my screen size :( --blub + if (!range) + return; + if (cl.worldmodel == NULL) + { + Con_Print("No map loaded.\n"); + return; + } + dpsnprintf(name, sizeof(name), "%s.rtlights", cl.worldnamenoextension); + bufchars = bufmaxchars = 0; + buf = NULL; + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + if (light->coronasizescale != 0.25f || light->ambientscale != 0 || light->diffusescale != 1 || light->specularscale != 1 || light->flags != LIGHTFLAG_REALTIMEMODE) + dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d \"%s\" %f %f %f %f %f %f %f %f %i\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style, light->cubemapname, light->corona, light->angles[0], light->angles[1], light->angles[2], light->coronasizescale, light->ambientscale, light->diffusescale, light->specularscale, light->flags); + else if (light->cubemapname[0] || light->corona || light->angles[0] || light->angles[1] || light->angles[2]) + dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d \"%s\" %f %f %f %f\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style, light->cubemapname, light->corona, light->angles[0], light->angles[1], light->angles[2]); + else + dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style); + if (bufchars + strlen(line) > bufmaxchars) + { + bufmaxchars = bufchars + strlen(line) + 2048; + oldbuf = buf; + buf = (char *)Mem_Alloc(tempmempool, bufmaxchars); + if (oldbuf) + { + if (bufchars) + memcpy(buf, oldbuf, bufchars); + Mem_Free(oldbuf); + } + } + if (strlen(line)) + { + memcpy(buf + bufchars, line, strlen(line)); + bufchars += strlen(line); + } + } + if (bufchars) + FS_WriteFile(name, buf, (fs_offset_t)bufchars); + if (buf) + Mem_Free(buf); +} + +void R_Shadow_LoadLightsFile(void) +{ + int n, a, style; + char tempchar, *lightsstring, *s, *t, name[MAX_QPATH]; + float origin[3], radius, color[3], subtract, spotdir[3], spotcone, falloff, distbias; + if (cl.worldmodel == NULL) + { + Con_Print("No map loaded.\n"); + return; + } + dpsnprintf(name, sizeof(name), "%s.lights", cl.worldnamenoextension); + lightsstring = (char *)FS_LoadFile(name, tempmempool, false, NULL); + if (lightsstring) + { + s = lightsstring; + n = 0; + while (*s) + { + t = s; + while (*s && *s != '\n' && *s != '\r') + s++; + if (!*s) + break; + tempchar = *s; + *s = 0; + a = sscanf(t, "%f %f %f %f %f %f %f %f %f %f %f %f %f %d", &origin[0], &origin[1], &origin[2], &falloff, &color[0], &color[1], &color[2], &subtract, &spotdir[0], &spotdir[1], &spotdir[2], &spotcone, &distbias, &style); + *s = tempchar; + if (a < 14) + { + Con_Printf("invalid lights file, found %d parameters on line %i, should be 14 parameters (origin[0] origin[1] origin[2] falloff light[0] light[1] light[2] subtract spotdir[0] spotdir[1] spotdir[2] spotcone distancebias style)\n", a, n + 1); + break; + } + radius = sqrt(DotProduct(color, color) / (falloff * falloff * 8192.0f * 8192.0f)); + radius = bound(15, radius, 4096); + VectorScale(color, (2.0f / (8388608.0f)), color); + R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, vec3_origin, color, radius, 0, style, true, NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); + if (*s == '\r') + s++; + if (*s == '\n') + s++; + n++; + } + if (*s) + Con_Printf("invalid lights file \"%s\"\n", name); + Mem_Free(lightsstring); + } +} + +// tyrlite/hmap2 light types in the delay field +typedef enum lighttype_e {LIGHTTYPE_MINUSX, LIGHTTYPE_RECIPX, LIGHTTYPE_RECIPXX, LIGHTTYPE_NONE, LIGHTTYPE_SUN, LIGHTTYPE_MINUSXX} lighttype_t; + +void R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(void) +{ + int entnum; + int style; + int islight; + int skin; + int pflags; + //int effects; + int type; + int n; + char *entfiledata; + const char *data; + float origin[3], angles[3], radius, color[3], light[4], fadescale, lightscale, originhack[3], overridecolor[3], vec[4]; + char key[256], value[MAX_INPUTLINE]; + char vabuf[1024]; + + if (cl.worldmodel == NULL) + { + Con_Print("No map loaded.\n"); + return; + } + // try to load a .ent file first + dpsnprintf(key, sizeof(key), "%s.ent", cl.worldnamenoextension); + data = entfiledata = (char *)FS_LoadFile(key, tempmempool, true, NULL); + // and if that is not found, fall back to the bsp file entity string + if (!data) + data = cl.worldmodel->brush.entities; + if (!data) + return; + for (entnum = 0;COM_ParseToken_Simple(&data, false, false, true) && com_token[0] == '{';entnum++) + { + type = LIGHTTYPE_MINUSX; + origin[0] = origin[1] = origin[2] = 0; + originhack[0] = originhack[1] = originhack[2] = 0; + angles[0] = angles[1] = angles[2] = 0; + color[0] = color[1] = color[2] = 1; + light[0] = light[1] = light[2] = 1;light[3] = 300; + overridecolor[0] = overridecolor[1] = overridecolor[2] = 1; + fadescale = 1; + lightscale = 1; + style = 0; + skin = 0; + pflags = 0; + //effects = 0; + islight = false; + while (1) + { + if (!COM_ParseToken_Simple(&data, false, false, true)) + break; // error + if (com_token[0] == '}') + break; // end of entity + if (com_token[0] == '_') + strlcpy(key, com_token + 1, sizeof(key)); + else + strlcpy(key, com_token, sizeof(key)); + while (key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + if (!COM_ParseToken_Simple(&data, false, false, true)) + break; // error + strlcpy(value, com_token, sizeof(value)); + + // now that we have the key pair worked out... + if (!strcmp("light", key)) + { + n = sscanf(value, "%f %f %f %f", &vec[0], &vec[1], &vec[2], &vec[3]); + if (n == 1) + { + // quake + light[0] = vec[0] * (1.0f / 256.0f); + light[1] = vec[0] * (1.0f / 256.0f); + light[2] = vec[0] * (1.0f / 256.0f); + light[3] = vec[0]; + } + else if (n == 4) + { + // halflife + light[0] = vec[0] * (1.0f / 255.0f); + light[1] = vec[1] * (1.0f / 255.0f); + light[2] = vec[2] * (1.0f / 255.0f); + light[3] = vec[3]; + } + } + else if (!strcmp("delay", key)) + type = atoi(value); + else if (!strcmp("origin", key)) + sscanf(value, "%f %f %f", &origin[0], &origin[1], &origin[2]); + else if (!strcmp("angle", key)) + angles[0] = 0, angles[1] = atof(value), angles[2] = 0; + else if (!strcmp("angles", key)) + sscanf(value, "%f %f %f", &angles[0], &angles[1], &angles[2]); + else if (!strcmp("color", key)) + sscanf(value, "%f %f %f", &color[0], &color[1], &color[2]); + else if (!strcmp("wait", key)) + fadescale = atof(value); + else if (!strcmp("classname", key)) + { + if (!strncmp(value, "light", 5)) + { + islight = true; + if (!strcmp(value, "light_fluoro")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 1; + overridecolor[2] = 1; + } + if (!strcmp(value, "light_fluorospark")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 1; + overridecolor[2] = 1; + } + if (!strcmp(value, "light_globe")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.8; + overridecolor[2] = 0.4; + } + if (!strcmp(value, "light_flame_large_yellow")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.5; + overridecolor[2] = 0.1; + } + if (!strcmp(value, "light_flame_small_yellow")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.5; + overridecolor[2] = 0.1; + } + if (!strcmp(value, "light_torch_small_white")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.5; + overridecolor[2] = 0.1; + } + if (!strcmp(value, "light_torch_small_walltorch")) + { + originhack[0] = 0; + originhack[1] = 0; + originhack[2] = 0; + overridecolor[0] = 1; + overridecolor[1] = 0.5; + overridecolor[2] = 0.1; + } + } + } + else if (!strcmp("style", key)) + style = atoi(value); + else if (!strcmp("skin", key)) + skin = (int)atof(value); + else if (!strcmp("pflags", key)) + pflags = (int)atof(value); + //else if (!strcmp("effects", key)) + // effects = (int)atof(value); + else if (cl.worldmodel->type == mod_brushq3) + { + if (!strcmp("scale", key)) + lightscale = atof(value); + if (!strcmp("fade", key)) + fadescale = atof(value); + } + } + if (!islight) + continue; + if (lightscale <= 0) + lightscale = 1; + if (fadescale <= 0) + fadescale = 1; + if (color[0] == color[1] && color[0] == color[2]) + { + color[0] *= overridecolor[0]; + color[1] *= overridecolor[1]; + color[2] *= overridecolor[2]; + } + radius = light[3] * r_editlights_quakelightsizescale.value * lightscale / fadescale; + color[0] = color[0] * light[0]; + color[1] = color[1] * light[1]; + color[2] = color[2] * light[2]; + switch (type) + { + case LIGHTTYPE_MINUSX: + break; + case LIGHTTYPE_RECIPX: + radius *= 2; + VectorScale(color, (1.0f / 16.0f), color); + break; + case LIGHTTYPE_RECIPXX: + radius *= 2; + VectorScale(color, (1.0f / 16.0f), color); + break; + default: + case LIGHTTYPE_NONE: + break; + case LIGHTTYPE_SUN: + break; + case LIGHTTYPE_MINUSXX: + break; + } + VectorAdd(origin, originhack, origin); + if (radius >= 1) + R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, angles, color, radius, (pflags & PFLAGS_CORONA) != 0, style, (pflags & PFLAGS_NOSHADOW) == 0, skin >= 16 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", skin) : NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); + } + if (entfiledata) + Mem_Free(entfiledata); +} + + +static void R_Shadow_SetCursorLocationForView(void) +{ + vec_t dist, push; + vec3_t dest, endpos; + trace_t trace; + VectorMA(r_refdef.view.origin, r_editlights_cursordistance.value, r_refdef.view.forward, dest); + trace = CL_TraceLine(r_refdef.view.origin, dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true); + if (trace.fraction < 1) + { + dist = trace.fraction * r_editlights_cursordistance.value; + push = r_editlights_cursorpushback.value; + if (push > dist) + push = dist; + push = -push; + VectorMA(trace.endpos, push, r_refdef.view.forward, endpos); + VectorMA(endpos, r_editlights_cursorpushoff.value, trace.plane.normal, endpos); + } + else + { + VectorClear( endpos ); + } + r_editlights_cursorlocation[0] = floor(endpos[0] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; + r_editlights_cursorlocation[1] = floor(endpos[1] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; + r_editlights_cursorlocation[2] = floor(endpos[2] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; +} + +void R_Shadow_UpdateWorldLightSelection(void) +{ + if (r_editlights.integer) + { + R_Shadow_SetCursorLocationForView(); + R_Shadow_SelectLightInView(); + } + else + R_Shadow_SelectLight(NULL); +} + +static void R_Shadow_EditLights_Clear_f(void) +{ + R_Shadow_ClearWorldLights(); +} + +void R_Shadow_EditLights_Reload_f(void) +{ + if (!cl.worldmodel) + return; + strlcpy(r_shadow_mapname, cl.worldname, sizeof(r_shadow_mapname)); + R_Shadow_ClearWorldLights(); + R_Shadow_LoadWorldLights(); + if (!Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)) + { + R_Shadow_LoadLightsFile(); + if (!Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)) + R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(); + } +} + +static void R_Shadow_EditLights_Save_f(void) +{ + if (!cl.worldmodel) + return; + R_Shadow_SaveWorldLights(); +} + +static void R_Shadow_EditLights_ImportLightEntitiesFromMap_f(void) +{ + R_Shadow_ClearWorldLights(); + R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(); +} + +static void R_Shadow_EditLights_ImportLightsFile_f(void) +{ + R_Shadow_ClearWorldLights(); + R_Shadow_LoadLightsFile(); +} + +static void R_Shadow_EditLights_Spawn_f(void) +{ + vec3_t color; + if (!r_editlights.integer) + { + Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (Cmd_Argc() != 1) + { + Con_Print("r_editlights_spawn does not take parameters\n"); + return; + } + color[0] = color[1] = color[2] = 1; + R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), r_editlights_cursorlocation, vec3_origin, color, 200, 0, 0, true, NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); +} + +static void R_Shadow_EditLights_Edit_f(void) +{ + vec3_t origin, angles, color; + vec_t radius, corona, coronasizescale, ambientscale, diffusescale, specularscale; + int style, shadows, flags, normalmode, realtimemode; + char cubemapname[MAX_INPUTLINE]; + if (!r_editlights.integer) + { + Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + VectorCopy(r_shadow_selectedlight->origin, origin); + VectorCopy(r_shadow_selectedlight->angles, angles); + VectorCopy(r_shadow_selectedlight->color, color); + radius = r_shadow_selectedlight->radius; + style = r_shadow_selectedlight->style; + if (r_shadow_selectedlight->cubemapname) + strlcpy(cubemapname, r_shadow_selectedlight->cubemapname, sizeof(cubemapname)); + else + cubemapname[0] = 0; + shadows = r_shadow_selectedlight->shadow; + corona = r_shadow_selectedlight->corona; + coronasizescale = r_shadow_selectedlight->coronasizescale; + ambientscale = r_shadow_selectedlight->ambientscale; + diffusescale = r_shadow_selectedlight->diffusescale; + specularscale = r_shadow_selectedlight->specularscale; + flags = r_shadow_selectedlight->flags; + normalmode = (flags & LIGHTFLAG_NORMALMODE) != 0; + realtimemode = (flags & LIGHTFLAG_REALTIMEMODE) != 0; + if (!strcmp(Cmd_Argv(1), "origin")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); + return; + } + origin[0] = atof(Cmd_Argv(2)); + origin[1] = atof(Cmd_Argv(3)); + origin[2] = atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "originscale")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); + return; + } + origin[0] *= atof(Cmd_Argv(2)); + origin[1] *= atof(Cmd_Argv(3)); + origin[2] *= atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "originx")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[0] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "originy")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[1] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "originz")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[2] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "move")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); + return; + } + origin[0] += atof(Cmd_Argv(2)); + origin[1] += atof(Cmd_Argv(3)); + origin[2] += atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "movex")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[0] += atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "movey")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[1] += atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "movez")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + origin[2] += atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "angles")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); + return; + } + angles[0] = atof(Cmd_Argv(2)); + angles[1] = atof(Cmd_Argv(3)); + angles[2] = atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "anglesx")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + angles[0] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "anglesy")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + angles[1] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "anglesz")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + angles[2] = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "color")) + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s red green blue\n", Cmd_Argv(1)); + return; + } + color[0] = atof(Cmd_Argv(2)); + color[1] = atof(Cmd_Argv(3)); + color[2] = atof(Cmd_Argv(4)); + } + else if (!strcmp(Cmd_Argv(1), "radius")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + radius = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "colorscale")) + { + if (Cmd_Argc() == 3) + { + double scale = atof(Cmd_Argv(2)); + color[0] *= scale; + color[1] *= scale; + color[2] *= scale; + } + else + { + if (Cmd_Argc() != 5) + { + Con_Printf("usage: r_editlights_edit %s red green blue (OR grey instead of red green blue)\n", Cmd_Argv(1)); + return; + } + color[0] *= atof(Cmd_Argv(2)); + color[1] *= atof(Cmd_Argv(3)); + color[2] *= atof(Cmd_Argv(4)); + } + } + else if (!strcmp(Cmd_Argv(1), "radiusscale") || !strcmp(Cmd_Argv(1), "sizescale")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + radius *= atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "style")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + style = atoi(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "cubemap")) + { + if (Cmd_Argc() > 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + if (Cmd_Argc() == 3) + strlcpy(cubemapname, Cmd_Argv(2), sizeof(cubemapname)); + else + cubemapname[0] = 0; + } + else if (!strcmp(Cmd_Argv(1), "shadows")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + shadows = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "corona")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + corona = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "coronasize")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + coronasizescale = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "ambient")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + ambientscale = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "diffuse")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + diffusescale = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "specular")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + specularscale = atof(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "normalmode")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + normalmode = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); + } + else if (!strcmp(Cmd_Argv(1), "realtimemode")) + { + if (Cmd_Argc() != 3) + { + Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); + return; + } + realtimemode = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); + } + else + { + Con_Print("usage: r_editlights_edit [property] [value]\n"); + Con_Print("Selected light's properties:\n"); + Con_Printf("Origin : %f %f %f\n", r_shadow_selectedlight->origin[0], r_shadow_selectedlight->origin[1], r_shadow_selectedlight->origin[2]); + Con_Printf("Angles : %f %f %f\n", r_shadow_selectedlight->angles[0], r_shadow_selectedlight->angles[1], r_shadow_selectedlight->angles[2]); + Con_Printf("Color : %f %f %f\n", r_shadow_selectedlight->color[0], r_shadow_selectedlight->color[1], r_shadow_selectedlight->color[2]); + Con_Printf("Radius : %f\n", r_shadow_selectedlight->radius); + Con_Printf("Corona : %f\n", r_shadow_selectedlight->corona); + Con_Printf("Style : %i\n", r_shadow_selectedlight->style); + Con_Printf("Shadows : %s\n", r_shadow_selectedlight->shadow ? "yes" : "no"); + Con_Printf("Cubemap : %s\n", r_shadow_selectedlight->cubemapname); + Con_Printf("CoronaSize : %f\n", r_shadow_selectedlight->coronasizescale); + Con_Printf("Ambient : %f\n", r_shadow_selectedlight->ambientscale); + Con_Printf("Diffuse : %f\n", r_shadow_selectedlight->diffusescale); + Con_Printf("Specular : %f\n", r_shadow_selectedlight->specularscale); + Con_Printf("NormalMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_NORMALMODE) ? "yes" : "no"); + Con_Printf("RealTimeMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_REALTIMEMODE) ? "yes" : "no"); + return; + } + flags = (normalmode ? LIGHTFLAG_NORMALMODE : 0) | (realtimemode ? LIGHTFLAG_REALTIMEMODE : 0); + R_Shadow_UpdateWorldLight(r_shadow_selectedlight, origin, angles, color, radius, corona, style, shadows, cubemapname, coronasizescale, ambientscale, diffusescale, specularscale, flags); +} + +static void R_Shadow_EditLights_EditAll_f(void) +{ + size_t lightindex; + dlight_t *light, *oldselected; + size_t range; + + if (!r_editlights.integer) + { + Con_Print("Cannot edit lights when not in editing mode. Set r_editlights to 1.\n"); + return; + } + + oldselected = r_shadow_selectedlight; + // EditLights doesn't seem to have a "remove" command or something so: + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + R_Shadow_SelectLight(light); + R_Shadow_EditLights_Edit_f(); + } + // return to old selected (to not mess editing once selection is locked) + R_Shadow_SelectLight(oldselected); +} + +void R_Shadow_EditLights_DrawSelectedLightProperties(void) +{ + int lightnumber, lightcount; + size_t lightindex, range; + dlight_t *light; + char temp[256]; + float x, y; + + if (!r_editlights.integer) + return; + + // update cvars so QC can query them + if (r_shadow_selectedlight) + { + dpsnprintf(temp, sizeof(temp), "%f %f %f", r_shadow_selectedlight->origin[0], r_shadow_selectedlight->origin[1], r_shadow_selectedlight->origin[2]); + Cvar_SetQuick(&r_editlights_current_origin, temp); + dpsnprintf(temp, sizeof(temp), "%f %f %f", r_shadow_selectedlight->angles[0], r_shadow_selectedlight->angles[1], r_shadow_selectedlight->angles[2]); + Cvar_SetQuick(&r_editlights_current_angles, temp); + dpsnprintf(temp, sizeof(temp), "%f %f %f", r_shadow_selectedlight->color[0], r_shadow_selectedlight->color[1], r_shadow_selectedlight->color[2]); + Cvar_SetQuick(&r_editlights_current_color, temp); + Cvar_SetValueQuick(&r_editlights_current_radius, r_shadow_selectedlight->radius); + Cvar_SetValueQuick(&r_editlights_current_corona, r_shadow_selectedlight->corona); + Cvar_SetValueQuick(&r_editlights_current_coronasize, r_shadow_selectedlight->coronasizescale); + Cvar_SetValueQuick(&r_editlights_current_style, r_shadow_selectedlight->style); + Cvar_SetValueQuick(&r_editlights_current_shadows, r_shadow_selectedlight->shadow); + Cvar_SetQuick(&r_editlights_current_cubemap, r_shadow_selectedlight->cubemapname); + Cvar_SetValueQuick(&r_editlights_current_ambient, r_shadow_selectedlight->ambientscale); + Cvar_SetValueQuick(&r_editlights_current_diffuse, r_shadow_selectedlight->diffusescale); + Cvar_SetValueQuick(&r_editlights_current_specular, r_shadow_selectedlight->specularscale); + Cvar_SetValueQuick(&r_editlights_current_normalmode, (r_shadow_selectedlight->flags & LIGHTFLAG_NORMALMODE) ? 1 : 0); + Cvar_SetValueQuick(&r_editlights_current_realtimemode, (r_shadow_selectedlight->flags & LIGHTFLAG_REALTIMEMODE) ? 1 : 0); + } + + // draw properties on screen + if (!r_editlights_drawproperties.integer) + return; + x = vid_conwidth.value - 240; + y = 5; + DrawQ_Pic(x-5, y-5, NULL, 250, 155, 0, 0, 0, 0.75, 0); + lightnumber = -1; + lightcount = 0; + range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked + for (lightindex = 0;lightindex < range;lightindex++) + { + light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); + if (!light) + continue; + if (light == r_shadow_selectedlight) + lightnumber = lightindex; + lightcount++; + } + dpsnprintf(temp, sizeof(temp), "Cursor origin: %.0f %.0f %.0f", r_editlights_cursorlocation[0], r_editlights_cursorlocation[1], r_editlights_cursorlocation[2]); DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Total lights : %i active (%i total)", lightcount, (int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)); DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_DEFAULT);y += 8; + y += 8; + if (r_shadow_selectedlight == NULL) + return; + dpsnprintf(temp, sizeof(temp), "Light #%i properties:", lightnumber);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Origin : %.0f %.0f %.0f\n", r_shadow_selectedlight->origin[0], r_shadow_selectedlight->origin[1], r_shadow_selectedlight->origin[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Angles : %.0f %.0f %.0f\n", r_shadow_selectedlight->angles[0], r_shadow_selectedlight->angles[1], r_shadow_selectedlight->angles[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Color : %.2f %.2f %.2f\n", r_shadow_selectedlight->color[0], r_shadow_selectedlight->color[1], r_shadow_selectedlight->color[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Radius : %.0f\n", r_shadow_selectedlight->radius);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Corona : %.0f\n", r_shadow_selectedlight->corona);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Style : %i\n", r_shadow_selectedlight->style);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Shadows : %s\n", r_shadow_selectedlight->shadow ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Cubemap : %s\n", r_shadow_selectedlight->cubemapname);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "CoronaSize : %.2f\n", r_shadow_selectedlight->coronasizescale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Ambient : %.2f\n", r_shadow_selectedlight->ambientscale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Diffuse : %.2f\n", r_shadow_selectedlight->diffusescale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "Specular : %.2f\n", r_shadow_selectedlight->specularscale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "NormalMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_NORMALMODE) ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; + dpsnprintf(temp, sizeof(temp), "RealTimeMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_REALTIMEMODE) ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; +} + +static void R_Shadow_EditLights_ToggleShadow_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_selectedlight->angles, r_shadow_selectedlight->color, r_shadow_selectedlight->radius, r_shadow_selectedlight->corona, r_shadow_selectedlight->style, !r_shadow_selectedlight->shadow, r_shadow_selectedlight->cubemapname, r_shadow_selectedlight->coronasizescale, r_shadow_selectedlight->ambientscale, r_shadow_selectedlight->diffusescale, r_shadow_selectedlight->specularscale, r_shadow_selectedlight->flags); +} + +static void R_Shadow_EditLights_ToggleCorona_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_selectedlight->angles, r_shadow_selectedlight->color, r_shadow_selectedlight->radius, !r_shadow_selectedlight->corona, r_shadow_selectedlight->style, r_shadow_selectedlight->shadow, r_shadow_selectedlight->cubemapname, r_shadow_selectedlight->coronasizescale, r_shadow_selectedlight->ambientscale, r_shadow_selectedlight->diffusescale, r_shadow_selectedlight->specularscale, r_shadow_selectedlight->flags); +} + +static void R_Shadow_EditLights_Remove_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot remove light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + R_Shadow_FreeWorldLight(r_shadow_selectedlight); + r_shadow_selectedlight = NULL; +} + +static void R_Shadow_EditLights_Help_f(void) +{ + Con_Print( +"Documentation on r_editlights system:\n" +"Settings:\n" +"r_editlights : enable/disable editing mode\n" +"r_editlights_cursordistance : maximum distance of cursor from eye\n" +"r_editlights_cursorpushback : push back cursor this far from surface\n" +"r_editlights_cursorpushoff : push cursor off surface this far\n" +"r_editlights_cursorgrid : snap cursor to grid of this size\n" +"r_editlights_quakelightsizescale : imported quake light entity size scaling\n" +"Commands:\n" +"r_editlights_help : this help\n" +"r_editlights_clear : remove all lights\n" +"r_editlights_reload : reload .rtlights, .lights file, or entities\n" +"r_editlights_lock : lock selection to current light, if already locked - unlock\n" +"r_editlights_save : save to .rtlights file\n" +"r_editlights_spawn : create a light with default settings\n" +"r_editlights_edit command : edit selected light - more documentation below\n" +"r_editlights_remove : remove selected light\n" +"r_editlights_toggleshadow : toggles on/off selected light's shadow property\n" +"r_editlights_importlightentitiesfrommap : reload light entities\n" +"r_editlights_importlightsfile : reload .light file (produced by hlight)\n" +"Edit commands:\n" +"origin x y z : set light location\n" +"originx x: set x component of light location\n" +"originy y: set y component of light location\n" +"originz z: set z component of light location\n" +"move x y z : adjust light location\n" +"movex x: adjust x component of light location\n" +"movey y: adjust y component of light location\n" +"movez z: adjust z component of light location\n" +"angles x y z : set light angles\n" +"anglesx x: set x component of light angles\n" +"anglesy y: set y component of light angles\n" +"anglesz z: set z component of light angles\n" +"color r g b : set color of light (can be brighter than 1 1 1)\n" +"radius radius : set radius (size) of light\n" +"colorscale grey : multiply color of light (1 does nothing)\n" +"colorscale r g b : multiply color of light (1 1 1 does nothing)\n" +"radiusscale scale : multiply radius (size) of light (1 does nothing)\n" +"sizescale scale : multiply radius (size) of light (1 does nothing)\n" +"originscale x y z : multiply origin of light (1 1 1 does nothing)\n" +"style style : set lightstyle of light (flickering patterns, switches, etc)\n" +"cubemap basename : set filter cubemap of light\n" +"shadows 1/0 : turn on/off shadows\n" +"corona n : set corona intensity\n" +"coronasize n : set corona size (0-1)\n" +"ambient n : set ambient intensity (0-1)\n" +"diffuse n : set diffuse intensity (0-1)\n" +"specular n : set specular intensity (0-1)\n" +"normalmode 1/0 : turn on/off rendering of this light in rtworld 0 mode\n" +"realtimemode 1/0 : turn on/off rendering of this light in rtworld 1 mode\n" +" : print light properties to console\n" + ); +} + +static void R_Shadow_EditLights_CopyInfo_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot copy light info when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + VectorCopy(r_shadow_selectedlight->angles, r_shadow_bufferlight.angles); + VectorCopy(r_shadow_selectedlight->color, r_shadow_bufferlight.color); + r_shadow_bufferlight.radius = r_shadow_selectedlight->radius; + r_shadow_bufferlight.style = r_shadow_selectedlight->style; + if (r_shadow_selectedlight->cubemapname) + strlcpy(r_shadow_bufferlight.cubemapname, r_shadow_selectedlight->cubemapname, sizeof(r_shadow_bufferlight.cubemapname)); + else + r_shadow_bufferlight.cubemapname[0] = 0; + r_shadow_bufferlight.shadow = r_shadow_selectedlight->shadow; + r_shadow_bufferlight.corona = r_shadow_selectedlight->corona; + r_shadow_bufferlight.coronasizescale = r_shadow_selectedlight->coronasizescale; + r_shadow_bufferlight.ambientscale = r_shadow_selectedlight->ambientscale; + r_shadow_bufferlight.diffusescale = r_shadow_selectedlight->diffusescale; + r_shadow_bufferlight.specularscale = r_shadow_selectedlight->specularscale; + r_shadow_bufferlight.flags = r_shadow_selectedlight->flags; +} + +static void R_Shadow_EditLights_PasteInfo_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot paste light info when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light.\n"); + return; + } + R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_bufferlight.angles, r_shadow_bufferlight.color, r_shadow_bufferlight.radius, r_shadow_bufferlight.corona, r_shadow_bufferlight.style, r_shadow_bufferlight.shadow, r_shadow_bufferlight.cubemapname, r_shadow_bufferlight.coronasizescale, r_shadow_bufferlight.ambientscale, r_shadow_bufferlight.diffusescale, r_shadow_bufferlight.specularscale, r_shadow_bufferlight.flags); +} + +static void R_Shadow_EditLights_Lock_f(void) +{ + if (!r_editlights.integer) + { + Con_Print("Cannot lock on light when not in editing mode. Set r_editlights to 1.\n"); + return; + } + if (r_editlights_lockcursor) + { + r_editlights_lockcursor = false; + return; + } + if (!r_shadow_selectedlight) + { + Con_Print("No selected light to lock on.\n"); + return; + } + r_editlights_lockcursor = true; +} + +static void R_Shadow_EditLights_Init(void) +{ + Cvar_RegisterVariable(&r_editlights); + Cvar_RegisterVariable(&r_editlights_cursordistance); + Cvar_RegisterVariable(&r_editlights_cursorpushback); + Cvar_RegisterVariable(&r_editlights_cursorpushoff); + Cvar_RegisterVariable(&r_editlights_cursorgrid); + Cvar_RegisterVariable(&r_editlights_quakelightsizescale); + Cvar_RegisterVariable(&r_editlights_drawproperties); + Cvar_RegisterVariable(&r_editlights_current_origin); + Cvar_RegisterVariable(&r_editlights_current_angles); + Cvar_RegisterVariable(&r_editlights_current_color); + Cvar_RegisterVariable(&r_editlights_current_radius); + Cvar_RegisterVariable(&r_editlights_current_corona); + Cvar_RegisterVariable(&r_editlights_current_coronasize); + Cvar_RegisterVariable(&r_editlights_current_style); + Cvar_RegisterVariable(&r_editlights_current_shadows); + Cvar_RegisterVariable(&r_editlights_current_cubemap); + Cvar_RegisterVariable(&r_editlights_current_ambient); + Cvar_RegisterVariable(&r_editlights_current_diffuse); + Cvar_RegisterVariable(&r_editlights_current_specular); + Cvar_RegisterVariable(&r_editlights_current_normalmode); + Cvar_RegisterVariable(&r_editlights_current_realtimemode); + Cmd_AddCommand("r_editlights_help", R_Shadow_EditLights_Help_f, "prints documentation on console commands and variables in rtlight editing system"); + Cmd_AddCommand("r_editlights_clear", R_Shadow_EditLights_Clear_f, "removes all world lights (let there be darkness!)"); + Cmd_AddCommand("r_editlights_reload", R_Shadow_EditLights_Reload_f, "reloads rtlights file (or imports from .lights file or .ent file or the map itself)"); + Cmd_AddCommand("r_editlights_save", R_Shadow_EditLights_Save_f, "save .rtlights file for current level"); + Cmd_AddCommand("r_editlights_spawn", R_Shadow_EditLights_Spawn_f, "creates a light with default properties (let there be light!)"); + Cmd_AddCommand("r_editlights_edit", R_Shadow_EditLights_Edit_f, "changes a property on the selected light"); + Cmd_AddCommand("r_editlights_editall", R_Shadow_EditLights_EditAll_f, "changes a property on ALL lights at once (tip: use radiusscale and colorscale to alter these properties)"); + Cmd_AddCommand("r_editlights_remove", R_Shadow_EditLights_Remove_f, "remove selected light"); + Cmd_AddCommand("r_editlights_toggleshadow", R_Shadow_EditLights_ToggleShadow_f, "toggle on/off the shadow option on the selected light"); + Cmd_AddCommand("r_editlights_togglecorona", R_Shadow_EditLights_ToggleCorona_f, "toggle on/off the corona option on the selected light"); + Cmd_AddCommand("r_editlights_importlightentitiesfrommap", R_Shadow_EditLights_ImportLightEntitiesFromMap_f, "load lights from .ent file or map entities (ignoring .rtlights or .lights file)"); + Cmd_AddCommand("r_editlights_importlightsfile", R_Shadow_EditLights_ImportLightsFile_f, "load lights from .lights file (ignoring .rtlights or .ent files and map entities)"); + Cmd_AddCommand("r_editlights_copyinfo", R_Shadow_EditLights_CopyInfo_f, "store a copy of all properties (except origin) of the selected light"); + Cmd_AddCommand("r_editlights_pasteinfo", R_Shadow_EditLights_PasteInfo_f, "apply the stored properties onto the selected light (making it exactly identical except for origin)"); + Cmd_AddCommand("r_editlights_lock", R_Shadow_EditLights_Lock_f, "lock selection to current light, if already locked - unlock"); +} + + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +void R_LightPoint(float *color, const vec3_t p, const int flags) +{ + int i, numlights, flag; + float f, relativepoint[3], dist, dist2, lightradius2; + vec3_t diffuse, n; + rtlight_t *light; + dlight_t *dlight; + + if (r_fullbright.integer) + { + VectorSet(color, 1, 1, 1); + return; + } + + VectorClear(color); + + if (flags & LP_LIGHTMAP) + { + if (!r_fullbright.integer && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) + { + VectorClear(diffuse); + r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, color, diffuse, n); + VectorAdd(color, diffuse, color); + } + else + VectorSet(color, 1, 1, 1); + color[0] += r_refdef.scene.ambient; + color[1] += r_refdef.scene.ambient; + color[2] += r_refdef.scene.ambient; + } + + if (flags & LP_RTWORLD) + { + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + numlights = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); + for (i = 0; i < numlights; i++) + { + dlight = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, i); + if (!dlight) + continue; + light = &dlight->rtlight; + if (!(light->flags & flag)) + continue; + // sample + lightradius2 = light->radius * light->radius; + VectorSubtract(light->shadoworigin, p, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + dist = sqrt(dist2) / light->radius; + f = dist < 1 ? (r_shadow_lightintensityscale.value * ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist))) : 0; + if (f <= 0) + continue; + // todo: add to both ambient and diffuse + if (!light->shadow || CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction == 1) + VectorMA(color, f, light->currentcolor, color); + } + } + if (flags & LP_DYNLIGHT) + { + // sample dlights + for (i = 0;i < r_refdef.scene.numlights;i++) + { + light = r_refdef.scene.lights[i]; + // sample + lightradius2 = light->radius * light->radius; + VectorSubtract(light->shadoworigin, p, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + dist = sqrt(dist2) / light->radius; + f = dist < 1 ? (r_shadow_lightintensityscale.value * ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist))) : 0; + if (f <= 0) + continue; + // todo: add to both ambient and diffuse + if (!light->shadow || CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction == 1) + VectorMA(color, f, light->color, color); + } + } +} + +void R_CompleteLightPoint(vec3_t ambient, vec3_t diffuse, vec3_t lightdir, const vec3_t p, const int flags) +{ + int i, numlights, flag; + rtlight_t *light; + dlight_t *dlight; + float relativepoint[3]; + float color[3]; + float dir[3]; + float dist; + float dist2; + float intensity; + float sample[5*3]; + float lightradius2; + + if (r_fullbright.integer) + { + VectorSet(ambient, 1, 1, 1); + VectorClear(diffuse); + VectorClear(lightdir); + return; + } + + if (flags == LP_LIGHTMAP) + { + VectorSet(ambient, r_refdef.scene.ambient, r_refdef.scene.ambient, r_refdef.scene.ambient); + VectorClear(diffuse); + VectorClear(lightdir); + if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) + r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, ambient, diffuse, lightdir); + else + VectorSet(ambient, 1, 1, 1); + return; + } + + memset(sample, 0, sizeof(sample)); + VectorSet(sample, r_refdef.scene.ambient, r_refdef.scene.ambient, r_refdef.scene.ambient); + + if ((flags & LP_LIGHTMAP) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) + { + vec3_t tempambient; + VectorClear(tempambient); + VectorClear(color); + VectorClear(relativepoint); + r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, tempambient, color, relativepoint); + VectorScale(tempambient, r_refdef.lightmapintensity, tempambient); + VectorScale(color, r_refdef.lightmapintensity, color); + VectorAdd(sample, tempambient, sample); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity = VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } + + if (flags & LP_RTWORLD) + { + flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; + numlights = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); + for (i = 0; i < numlights; i++) + { + dlight = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, i); + if (!dlight) + continue; + light = &dlight->rtlight; + if (!(light->flags & flag)) + continue; + // sample + lightradius2 = light->radius * light->radius; + VectorSubtract(light->shadoworigin, p, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + dist = sqrt(dist2) / light->radius; + intensity = min(1.0f, (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) * r_shadow_lightintensityscale.value; + if (intensity <= 0.0f) + continue; + if (light->shadow && CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction < 1) + continue; + // scale down intensity to add to both ambient and diffuse + //intensity *= 0.5f; + VectorNormalize(relativepoint); + VectorScale(light->currentcolor, intensity, color); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity *= VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } + // FIXME: sample bouncegrid too! + } + + if (flags & LP_DYNLIGHT) + { + // sample dlights + for (i = 0;i < r_refdef.scene.numlights;i++) + { + light = r_refdef.scene.lights[i]; + // sample + lightradius2 = light->radius * light->radius; + VectorSubtract(light->shadoworigin, p, relativepoint); + dist2 = VectorLength2(relativepoint); + if (dist2 >= lightradius2) + continue; + dist = sqrt(dist2) / light->radius; + intensity = (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist) * r_shadow_lightintensityscale.value; + if (intensity <= 0.0f) + continue; + if (light->shadow && CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false, true).fraction < 1) + continue; + // scale down intensity to add to both ambient and diffuse + //intensity *= 0.5f; + VectorNormalize(relativepoint); + VectorScale(light->currentcolor, intensity, color); + VectorMA(sample , 0.5f , color, sample ); + VectorMA(sample + 3, relativepoint[0], color, sample + 3); + VectorMA(sample + 6, relativepoint[1], color, sample + 6); + VectorMA(sample + 9, relativepoint[2], color, sample + 9); + // calculate a weighted average light direction as well + intensity *= VectorLength(color); + VectorMA(sample + 12, intensity, relativepoint, sample + 12); + } + } + + // calculate the direction we'll use to reduce the sample to a directional light source + VectorCopy(sample + 12, dir); + //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); + VectorNormalize(dir); + // extract the diffuse color along the chosen direction and scale it + diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]); + diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]); + diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]); + // subtract some of diffuse from ambient + VectorMA(sample, -0.333f, diffuse, ambient); + // store the normalized lightdir + VectorCopy(dir, lightdir); +} diff --git a/app/jni/r_shadow.h b/app/jni/r_shadow.h new file mode 100644 index 0000000..907173d --- /dev/null +++ b/app/jni/r_shadow.h @@ -0,0 +1,110 @@ + +#ifndef R_SHADOW_H +#define R_SHADOW_H + +#define R_SHADOW_SHADOWMAP_NUMCUBEMAPS 8 + +extern cvar_t r_shadow_bumpscale_basetexture; +extern cvar_t r_shadow_bumpscale_bumpmap; +extern cvar_t r_shadow_debuglight; +extern cvar_t r_shadow_gloss; +extern cvar_t r_shadow_gloss2intensity; +extern cvar_t r_shadow_glossintensity; +extern cvar_t r_shadow_glossexponent; +extern cvar_t r_shadow_gloss2exponent; +extern cvar_t r_shadow_glossexact; +extern cvar_t r_shadow_lightattenuationpower; +extern cvar_t r_shadow_lightattenuationscale; +extern cvar_t r_shadow_lightintensityscale; +extern cvar_t r_shadow_lightradiusscale; +extern cvar_t r_shadow_projectdistance; +extern cvar_t r_shadow_frontsidecasting; +extern cvar_t r_shadow_realtime_dlight; +extern cvar_t r_shadow_realtime_dlight_shadows; +extern cvar_t r_shadow_realtime_dlight_svbspculling; +extern cvar_t r_shadow_realtime_dlight_portalculling; +extern cvar_t r_shadow_realtime_world; +extern cvar_t r_shadow_realtime_world_lightmaps; +extern cvar_t r_shadow_realtime_world_shadows; +extern cvar_t r_shadow_realtime_world_compile; +extern cvar_t r_shadow_realtime_world_compileshadow; +extern cvar_t r_shadow_realtime_world_compilesvbsp; +extern cvar_t r_shadow_realtime_world_compileportalculling; +extern cvar_t r_shadow_scissor; +extern cvar_t r_shadow_polygonfactor; +extern cvar_t r_shadow_polygonoffset; +extern cvar_t r_shadow_texture3d; +extern cvar_t gl_ext_separatestencil; +extern cvar_t gl_ext_stenciltwoside; + +// used by shader for bouncegrid feature +extern rtexture_t *r_shadow_bouncegridtexture; +extern matrix4x4_t r_shadow_bouncegridmatrix; +extern vec_t r_shadow_bouncegridintensity; +extern qboolean r_shadow_bouncegriddirectional; + +void R_Shadow_Init(void); +qboolean R_Shadow_ShadowMappingEnabled(void); +void R_Shadow_VolumeFromList(int numverts, int numtris, const float *invertex3f, const int *elements, const int *neighbors, const vec3_t projectorigin, const vec3_t projectdirection, float projectdistance, int nummarktris, const int *marktris, vec3_t trismins, vec3_t trismaxs); +void R_Shadow_ShadowMapFromList(int numverts, int numtris, const float *vertex3f, const int *elements, int numsidetris, const int *sidetotals, const unsigned char *sides, const int *sidetris); +void R_Shadow_MarkVolumeFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs); +int R_Shadow_CalcTriangleSideMask(const vec3_t p1, const vec3_t p2, const vec3_t p3, float bias); +int R_Shadow_CalcSphereSideMask(const vec3_t p1, float radius, float bias); +int R_Shadow_ChooseSidesFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const matrix4x4_t *worldtolight, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs, int *totals); +void R_Shadow_RenderLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist); +void R_Shadow_RenderMode_Begin(void); +void R_Shadow_RenderMode_ActiveLight(const rtlight_t *rtlight); +void R_Shadow_RenderMode_Reset(void); +void R_Shadow_RenderMode_StencilShadowVolumes(qboolean zpass); +void R_Shadow_RenderMode_Lighting(qboolean stenciltest, qboolean transparent, qboolean shadowmapping); +void R_Shadow_RenderMode_DrawDeferredLight(qboolean stenciltest, qboolean shadowmapping); +void R_Shadow_RenderMode_VisibleShadowVolumes(void); +void R_Shadow_RenderMode_VisibleLighting(qboolean stenciltest, qboolean transparent); +void R_Shadow_RenderMode_End(void); +void R_Shadow_ClearStencil(void); +void R_Shadow_SetupEntityLight(const entity_render_t *ent); + +qboolean R_Shadow_ScissorForBBox(const float *mins, const float *maxs); + +// these never change, they are used to create attenuation matrices +extern matrix4x4_t matrix_attenuationxyz; +extern matrix4x4_t matrix_attenuationz; + +void R_Shadow_UpdateWorldLightSelection(void); + +extern rtlight_t *r_shadow_compilingrtlight; + +void R_RTLight_Update(rtlight_t *rtlight, int isstatic, matrix4x4_t *matrix, vec3_t color, int style, const char *cubemapname, int shadow, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags); +void R_RTLight_Compile(rtlight_t *rtlight); +void R_RTLight_Uncompile(rtlight_t *rtlight); + +void R_Shadow_PrepareLights(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); +void R_Shadow_DrawPrepass(void); +void R_Shadow_DrawLights(void); +void R_Shadow_DrawCoronas(void); + +extern int maxshadowmark; +extern int numshadowmark; +extern int *shadowmark; +extern int *shadowmarklist; +extern int shadowmarkcount; +void R_Shadow_PrepareShadowMark(int numtris); + +extern int maxshadowsides; +extern int numshadowsides; +extern unsigned char *shadowsides; +extern int *shadowsideslist; +void R_Shadow_PrepareShadowSides(int numtris); + +void R_Shadow_PrepareModelShadows(void); + +#define LP_LIGHTMAP 1 +#define LP_RTWORLD 2 +#define LP_DYNLIGHT 4 +void R_LightPoint(float *color, const vec3_t p, const int flags); +void R_CompleteLightPoint(float *ambientcolor, float *diffusecolor, float *diffusenormal, const vec3_t p, const int flags); + +void R_DrawModelShadowMaps(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); +void R_DrawModelShadows(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); + +#endif diff --git a/app/jni/r_sky.c b/app/jni/r_sky.c new file mode 100644 index 0000000..97b2daf --- /dev/null +++ b/app/jni/r_sky.c @@ -0,0 +1,461 @@ + +#include "quakedef.h" +#include "image.h" + +// FIXME: fix skybox after vid_restart +cvar_t r_sky = {CVAR_SAVE, "r_sky", "1", "enables sky rendering (black otherwise)"}; +cvar_t r_skyscroll1 = {CVAR_SAVE, "r_skyscroll1", "1", "speed at which upper clouds layer scrolls in quake sky"}; +cvar_t r_skyscroll2 = {CVAR_SAVE, "r_skyscroll2", "2", "speed at which lower clouds layer scrolls in quake sky"}; +int skyrenderlater; +int skyrendermasked; + +static int skyrendersphere; +static int skyrenderbox; +static rtexturepool_t *skytexturepool; +static char skyname[MAX_QPATH]; +static matrix4x4_t skymatrix; +static matrix4x4_t skyinversematrix; + +typedef struct suffixinfo_s +{ + const char *suffix; + qboolean flipx, flipy, flipdiagonal; +} +suffixinfo_t; +static const suffixinfo_t suffix[3][6] = +{ + { + {"px", false, false, false}, + {"nx", false, false, false}, + {"py", false, false, false}, + {"ny", false, false, false}, + {"pz", false, false, false}, + {"nz", false, false, false} + }, + { + {"posx", false, false, false}, + {"negx", false, false, false}, + {"posy", false, false, false}, + {"negy", false, false, false}, + {"posz", false, false, false}, + {"negz", false, false, false} + }, + { + {"rt", false, false, true}, + {"lf", true, true, true}, + {"bk", false, true, false}, + {"ft", true, false, false}, + {"up", false, false, true}, + {"dn", false, false, true} + } +}; + +static skinframe_t *skyboxskinframe[6]; + +void R_SkyStartFrame(void) +{ + skyrendersphere = false; + skyrenderbox = false; + skyrendermasked = false; + // for depth-masked sky, we need to know whether any sky was rendered + skyrenderlater = false; + if (r_sky.integer) + { + if (skyboxskinframe[0] || skyboxskinframe[1] || skyboxskinframe[2] || skyboxskinframe[3] || skyboxskinframe[4] || skyboxskinframe[5]) + skyrenderbox = true; + else if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.solidskyskinframe) + skyrendersphere = true; + skyrendermasked = true; + } +} + +/* +================== +R_SetSkyBox +================== +*/ +static void R_UnloadSkyBox(void) +{ + int i; + int c = 0; + for (i = 0;i < 6;i++) + { + if (skyboxskinframe[i]) + { + // TODO: make a R_SkinFrame_Purge for single skins... + c++; + } + skyboxskinframe[i] = NULL; + } + if (c && developer_loading.integer) + Con_Printf("unloading skybox\n"); +} + +static int R_LoadSkyBox(void) +{ + int i, j, success; + int indices[4] = {0,1,2,3}; + char name[MAX_INPUTLINE]; + unsigned char *image_buffer; + unsigned char *temp; + char vabuf[1024]; + + R_UnloadSkyBox(); + + if (!skyname[0]) + return true; + + for (j=0; j<3; j++) + { + success = 0; + for (i=0; i<6; i++) + { + if (dpsnprintf(name, sizeof(name), "%s_%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + { + if (dpsnprintf(name, sizeof(name), "%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + { + if (dpsnprintf(name, sizeof(name), "env/%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + { + if (dpsnprintf(name, sizeof(name), "gfx/env/%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) + continue; + } + } + } + temp = (unsigned char *)Mem_Alloc(tempmempool, image_width*image_height*4); + Image_CopyMux (temp, image_buffer, image_width, image_height, suffix[j][i].flipx, suffix[j][i].flipy, suffix[j][i].flipdiagonal, 4, 4, indices); + skyboxskinframe[i] = R_SkinFrame_LoadInternalBGRA(va(vabuf, sizeof(vabuf), "skyboxside%d", i), TEXF_CLAMP | (gl_texturecompression_sky.integer ? TEXF_COMPRESS : 0), temp, image_width, image_height, vid.sRGB3D); + Mem_Free(image_buffer); + Mem_Free(temp); + success++; + } + + if (success) + break; + } + + if (j == 3) + return false; + + if (developer_loading.integer) + Con_Printf("loading skybox \"%s\"\n", name); + + return true; +} + +int R_SetSkyBox(const char *sky) +{ + if (strcmp(sky, skyname) == 0) // no change + return true; + + if (strlen(sky) > 1000) + { + Con_Printf("sky name too long (%i, max is 1000)\n", (int)strlen(sky)); + return false; + } + + strlcpy(skyname, sky, sizeof(skyname)); + + return R_LoadSkyBox(); +} + +// LordHavoc: added LoadSky console command +static void LoadSky_f (void) +{ + switch (Cmd_Argc()) + { + case 1: + if (skyname[0]) + Con_Printf("current sky: %s\n", skyname); + else + Con_Print("no skybox has been set\n"); + break; + case 2: + if (R_SetSkyBox(Cmd_Argv(1))) + { + if (skyname[0]) + Con_Printf("skybox set to %s\n", skyname); + else + Con_Print("skybox disabled\n"); + } + else + Con_Printf("failed to load skybox %s\n", Cmd_Argv(1)); + break; + default: + Con_Print("usage: loadsky skyname\n"); + break; + } +} + +static const float skyboxvertex3f[6*4*3] = +{ + // skyside[0] + 16, -16, 16, + 16, -16, -16, + 16, 16, -16, + 16, 16, 16, + // skyside[1] + -16, 16, 16, + -16, 16, -16, + -16, -16, -16, + -16, -16, 16, + // skyside[2] + 16, 16, 16, + 16, 16, -16, + -16, 16, -16, + -16, 16, 16, + // skyside[3] + -16, -16, 16, + -16, -16, -16, + 16, -16, -16, + 16, -16, 16, + // skyside[4] + -16, -16, 16, + 16, -16, 16, + 16, 16, 16, + -16, 16, 16, + // skyside[5] + 16, -16, -16, + -16, -16, -16, + -16, 16, -16, + 16, 16, -16 +}; + +static const float skyboxtexcoord2f[6*4*2] = +{ + // skyside[0] + 0, 1, + 1, 1, + 1, 0, + 0, 0, + // skyside[1] + 1, 0, + 0, 0, + 0, 1, + 1, 1, + // skyside[2] + 1, 1, + 1, 0, + 0, 0, + 0, 1, + // skyside[3] + 0, 0, + 0, 1, + 1, 1, + 1, 0, + // skyside[4] + 0, 1, + 1, 1, + 1, 0, + 0, 0, + // skyside[5] + 0, 1, + 1, 1, + 1, 0, + 0, 0 +}; + +static const int skyboxelement3i[6*2*3] = +{ + // skyside[3] + 0, 1, 2, + 0, 2, 3, + // skyside[1] + 4, 5, 6, + 4, 6, 7, + // skyside[0] + 8, 9, 10, + 8, 10, 11, + // skyside[2] + 12, 13, 14, + 12, 14, 15, + // skyside[4] + 16, 17, 18, + 16, 18, 19, + // skyside[5] + 20, 21, 22, + 20, 22, 23 +}; + +static const unsigned short skyboxelement3s[6*2*3] = +{ + // skyside[3] + 0, 1, 2, + 0, 2, 3, + // skyside[1] + 4, 5, 6, + 4, 6, 7, + // skyside[0] + 8, 9, 10, + 8, 10, 11, + // skyside[2] + 12, 13, 14, + 12, 14, 15, + // skyside[4] + 16, 17, 18, + 16, 18, 19, + // skyside[5] + 20, 21, 22, + 20, 22, 23 +}; + +static void R_SkyBox(void) +{ + int i; + RSurf_ActiveCustomEntity(&skymatrix, &skyinversematrix, 0, 0, 1, 1, 1, 1, 6*4, skyboxvertex3f, skyboxtexcoord2f, NULL, NULL, NULL, NULL, 6*2, skyboxelement3i, skyboxelement3s, false, false); + for (i = 0;i < 6;i++) + if(skyboxskinframe[i]) + R_DrawCustomSurface(skyboxskinframe[i], &identitymatrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST, i*4, 4, i*2, 2, false, false); +} + +#define skygridx 32 +#define skygridx1 (skygridx + 1) +#define skygridxrecip (1.0f / (skygridx)) +#define skygridy 32 +#define skygridy1 (skygridy + 1) +#define skygridyrecip (1.0f / (skygridy)) +#define skysphere_numverts (skygridx1 * skygridy1) +#define skysphere_numtriangles (skygridx * skygridy * 2) +static float skysphere_vertex3f[skysphere_numverts * 3]; +static float skysphere_texcoord2f[skysphere_numverts * 2]; +static int skysphere_element3i[skysphere_numtriangles * 3]; +static unsigned short skysphere_element3s[skysphere_numtriangles * 3]; + +static void skyspherecalc(void) +{ + int i, j; + unsigned short *e; + float a, b, x, ax, ay, v[3], length, *vertex3f, *texcoord2f; + float dx, dy, dz; + dx = 16.0f; + dy = 16.0f; + dz = 16.0f / 3.0f; + vertex3f = skysphere_vertex3f; + texcoord2f = skysphere_texcoord2f; + for (j = 0;j <= skygridy;j++) + { + a = j * skygridyrecip; + ax = cos(a * M_PI * 2); + ay = -sin(a * M_PI * 2); + for (i = 0;i <= skygridx;i++) + { + b = i * skygridxrecip; + x = cos((b + 0.5) * M_PI); + v[0] = ax*x * dx; + v[1] = ay*x * dy; + v[2] = -sin((b + 0.5) * M_PI) * dz; + length = 3.0f / sqrt(v[0]*v[0]+v[1]*v[1]+(v[2]*v[2]*9)); + *texcoord2f++ = v[0] * length; + *texcoord2f++ = v[1] * length; + *vertex3f++ = v[0]; + *vertex3f++ = v[1]; + *vertex3f++ = v[2]; + } + } + e = skysphere_element3s; + for (j = 0;j < skygridy;j++) + { + for (i = 0;i < skygridx;i++) + { + *e++ = j * skygridx1 + i; + *e++ = j * skygridx1 + i + 1; + *e++ = (j + 1) * skygridx1 + i; + + *e++ = j * skygridx1 + i + 1; + *e++ = (j + 1) * skygridx1 + i + 1; + *e++ = (j + 1) * skygridx1 + i; + } + } + for (i = 0;i < skysphere_numtriangles*3;i++) + skysphere_element3i[i] = skysphere_element3s[i]; +} + +static void R_SkySphere(void) +{ + double speedscale; + static qboolean skysphereinitialized = false; + matrix4x4_t scroll1matrix, scroll2matrix; + if (!skysphereinitialized) + { + skysphereinitialized = true; + skyspherecalc(); + } + + // wrap the scroll values just to be extra kind to float accuracy + + // scroll speed for upper layer + speedscale = r_refdef.scene.time*r_skyscroll1.value*8.0/128.0; + speedscale -= floor(speedscale); + Matrix4x4_CreateTranslate(&scroll1matrix, speedscale, speedscale, 0); + // scroll speed for lower layer (transparent layer) + speedscale = r_refdef.scene.time*r_skyscroll2.value*8.0/128.0; + speedscale -= floor(speedscale); + Matrix4x4_CreateTranslate(&scroll2matrix, speedscale, speedscale, 0); + + RSurf_ActiveCustomEntity(&skymatrix, &skyinversematrix, 0, 0, 1, 1, 1, 1, skysphere_numverts, skysphere_vertex3f, skysphere_texcoord2f, NULL, NULL, NULL, NULL, skysphere_numtriangles, skysphere_element3i, skysphere_element3s, false, false); + R_DrawCustomSurface(r_refdef.scene.worldmodel->brush.solidskyskinframe, &scroll1matrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST , 0, skysphere_numverts, 0, skysphere_numtriangles, false, false); + R_DrawCustomSurface(r_refdef.scene.worldmodel->brush.alphaskyskinframe, &scroll2matrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED, 0, skysphere_numverts, 0, skysphere_numtriangles, false, false); +} + +void R_Sky(void) +{ + Matrix4x4_CreateFromQuakeEntity(&skymatrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], 0, 0, 0, r_refdef.farclip * (0.5f / 16.0f)); + Matrix4x4_Invert_Simple(&skyinversematrix, &skymatrix); + + if (skyrendersphere) + { + // this does not modify depth buffer + R_SkySphere(); + } + else if (skyrenderbox) + { + // this does not modify depth buffer + R_SkyBox(); + } + /* this will be skyroom someday + else + { + // this modifies the depth buffer so we have to clear it afterward + //R_SkyRoom(); + // clear the depthbuffer that was used while rendering the skyroom + //GL_Clear(GL_DEPTH_BUFFER_BIT); + } + */ +} + +//=============================================================== + +void R_ResetSkyBox(void) +{ + R_UnloadSkyBox(); + skyname[0] = 0; + R_LoadSkyBox(); +} + +static void r_sky_start(void) +{ + skytexturepool = R_AllocTexturePool(); + R_LoadSkyBox(); +} + +static void r_sky_shutdown(void) +{ + R_UnloadSkyBox(); + R_FreeTexturePool(&skytexturepool); +} + +static void r_sky_newmap(void) +{ +} + + +void R_Sky_Init(void) +{ + Cmd_AddCommand ("loadsky", &LoadSky_f, "load a skybox by basename (for example loadsky mtnsun_ loads mtnsun_ft.tga and so on)"); + Cvar_RegisterVariable (&r_sky); + Cvar_RegisterVariable (&r_skyscroll1); + Cvar_RegisterVariable (&r_skyscroll2); + memset(&skyboxskinframe, 0, sizeof(skyboxskinframe)); + skyname[0] = 0; + R_RegisterModule("R_Sky", r_sky_start, r_sky_shutdown, r_sky_newmap, NULL, NULL); +} + diff --git a/app/jni/r_sprites.c b/app/jni/r_sprites.c new file mode 100644 index 0000000..a45a3e7 --- /dev/null +++ b/app/jni/r_sprites.c @@ -0,0 +1,429 @@ + +#include "quakedef.h" +#include "r_shadow.h" + +extern cvar_t r_labelsprites_scale; +extern cvar_t r_labelsprites_roundtopixels; +extern cvar_t r_track_sprites; +extern cvar_t r_track_sprites_flags; +extern cvar_t r_track_sprites_scalew; +extern cvar_t r_track_sprites_scaleh; +extern cvar_t r_overheadsprites_perspective; +extern cvar_t r_overheadsprites_pushback; +extern cvar_t r_overheadsprites_scalex; +extern cvar_t r_overheadsprites_scaley; + +#define TSF_ROTATE 1 +#define TSF_ROTATE_CONTINOUSLY 2 + +// use same epsilon as in sv_phys.c, it's not in any header, that's why i redefine it +// MIN_EPSILON is for accurateness' sake :) +#ifndef EPSILON +# define EPSILON (1.0f / 32.0f) +# define MIN_EPSILON 0.0001f +#endif + +/* R_Track_Sprite + If the sprite is out of view, track it. + `origin`, `left` and `up` are changed by this function to achive a rotation around + the hotspot. + + --blub + */ +#define SIDE_TOP 1 +#define SIDE_LEFT 2 +#define SIDE_BOTTOM 3 +#define SIDE_RIGHT 4 + +static void R_TrackSprite(const entity_render_t *ent, vec3_t origin, vec3_t left, vec3_t up, int *edge, float *dir_angle) +{ + float distance; + vec3_t bCoord; // body coordinates of object + unsigned int i; + + // temporarily abuse bCoord as the vector player->sprite-origin + VectorSubtract(origin, r_refdef.view.origin, bCoord); + distance = VectorLength(bCoord); + + // Now get the bCoords :) + Matrix4x4_Transform(&r_refdef.view.inverse_matrix, origin, bCoord); + + *edge = 0; // FIXME::should assume edge == 0, which is correct currently + for(i = 0; i < 4; ++i) + { + if(PlaneDiff(origin, &r_refdef.view.frustum[i]) < -EPSILON) + break; + } + + // If it wasn't outside a plane, no tracking needed + if(i < 4) + { + float x, y; // screen X and Y coordinates + float ax, ay; // absolute coords, used for division + // I divide x and y by the greater absolute value to get ranges -1.0 to +1.0 + + bCoord[2] *= r_refdef.view.frustum_x; + bCoord[1] *= r_refdef.view.frustum_y; + + //Con_Printf("%f %f %f\n", bCoord[0], bCoord[1], bCoord[2]); + + ax = fabs(bCoord[1]); + ay = fabs(bCoord[2]); + // get the greater value and determine the screen edge it's on + if(ax < ay) + { + ax = ay; + // 180 or 0 degrees + if(bCoord[2] < 0.0f) + *edge = SIDE_BOTTOM; + else + *edge = SIDE_TOP; + } else { + if(bCoord[1] < 0.0f) + *edge = SIDE_RIGHT; + else + *edge = SIDE_LEFT; + } + + // umm... + if(ax < MIN_EPSILON) // this was == 0.0f before --blub + ax = MIN_EPSILON; + // get the -1 to +1 range + x = bCoord[1] / ax; + y = bCoord[2] / ax; + + ax = (1.0f / VectorLength(left)); + ay = (1.0f / VectorLength(up)); + // Using the placement below the distance of a sprite is + // real dist = sqrt(d*d + dfxa*dfxa + dgyb*dgyb) + // d is the distance we use + // f is frustum X + // x is x + // a is ax + // g is frustum Y + // y is y + // b is ay + + // real dist (r) shall be d, so + // r*r = d*d + dfxa*dfxa + dgyb*dgyb + // r*r = d*d * (1 + fxa*fxa + gyb*gyb) + // d*d = r*r / (1 + fxa*fxa + gyb*gyb) + // d = sqrt(r*r / (1 + fxa*fxa + gyb*gyb)) + // thus: + distance = sqrt((distance*distance) / (1.0 + + r_refdef.view.frustum_x*r_refdef.view.frustum_x * x*x * ax*ax + + r_refdef.view.frustum_y*r_refdef.view.frustum_y * y*y * ay*ay)); + // ^ the one we want ^ the one we have ^ our factors + + // Place the sprite a few units ahead of the player + VectorCopy(r_refdef.view.origin, origin); + VectorMA(origin, distance, r_refdef.view.forward, origin); + // Move the sprite left / up the screeen height + VectorMA(origin, distance * r_refdef.view.frustum_x * x * ax, left, origin); + VectorMA(origin, distance * r_refdef.view.frustum_y * y * ay, up, origin); + + if(r_track_sprites_flags.integer & TSF_ROTATE_CONTINOUSLY) + { + // compute the rotation, negate y axis, we're pointing outwards + *dir_angle = atan(-y / x) * 180.0f/M_PI; + // we need the real, full angle + if(x < 0.0f) + *dir_angle += 180.0f; + } + + left[0] *= r_track_sprites_scalew.value; + left[1] *= r_track_sprites_scalew.value; + left[2] *= r_track_sprites_scalew.value; + + up[0] *= r_track_sprites_scaleh.value; + up[1] *= r_track_sprites_scaleh.value; + up[2] *= r_track_sprites_scaleh.value; + } +} + +static void R_RotateSprite(const mspriteframe_t *frame, vec3_t origin, vec3_t left, vec3_t up, int edge, float dir_angle) +{ + if(!(r_track_sprites_flags.integer & TSF_ROTATE)) + { + // move down by its size if on top, otherwise it's invisible + if(edge == SIDE_TOP) + VectorMA(origin, -(fabs(frame->up)+fabs(frame->down)), up, origin); + } else { + static float rotation_angles[5] = + { + 0, // no edge + -90.0f, //top + 0.0f, // left + 90.0f, // bottom + 180.0f, // right + }; + + // rotate around the hotspot according to which edge it's on + // since the hotspot == the origin, only rotate the vectors + matrix4x4_t rotm; + vec3_t axis; + vec3_t temp; + vec2_t dir; + float angle; + + if(edge < 1 || edge > 4) + return; // this usually means something went wrong somewhere, there's no way to get a wrong edge value currently + + dir[0] = frame->right + frame->left; + dir[1] = frame->down + frame->up; + + // only rotate when the hotspot isn't the center though. + if(dir[0] < MIN_EPSILON && dir[1] < MIN_EPSILON) + { + return; + } + + // Now that we've kicked center-hotspotted sprites, rotate using the appropriate matrix :) + + // determine the angle of a sprite, we could only do that once though and + // add a `qboolean initialized' to the mspriteframe_t struct... let's get the direction vector of it :) + + angle = atan(dir[1] / dir[0]) * 180.0f/M_PI; + + // we need the real, full angle + if(dir[0] < 0.0f) + angle += 180.0f; + + // Rotate around rotation_angle - frame_angle + // The axis SHOULD equal r_refdef.view.forward, but let's generalize this: + CrossProduct(up, left, axis); + if(r_track_sprites_flags.integer & TSF_ROTATE_CONTINOUSLY) + Matrix4x4_CreateRotate(&rotm, dir_angle - angle, axis[0], axis[1], axis[2]); + else + Matrix4x4_CreateRotate(&rotm, rotation_angles[edge] - angle, axis[0], axis[1], axis[2]); + Matrix4x4_Transform(&rotm, up, temp); + VectorCopy(temp, up); + Matrix4x4_Transform(&rotm, left, temp); + VectorCopy(temp, left); + } +} + +static float spritetexcoord2f[4*2] = {0, 1, 0, 0, 1, 0, 1, 1}; + +static void R_Model_Sprite_Draw_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) +{ + int i; + dp_model_t *model = ent->model; + vec3_t left, up, org, mforward, mleft, mup, middle; + float scale, dx, dy, hud_vs_screen; + int edge = 0; + float dir_angle = 0.0f; + float vertex3f[12]; + + // nudge it toward the view to make sure it isn't in a wall + Matrix4x4_ToVectors(&ent->matrix, mforward, mleft, mup, org); + VectorSubtract(org, r_refdef.view.forward, org); + switch(model->sprite.sprnum_type) + { + case SPR_VP_PARALLEL_UPRIGHT: + // flames and such + // vertical beam sprite, faces view plane + scale = ent->scale / sqrt(r_refdef.view.forward[0]*r_refdef.view.forward[0]+r_refdef.view.forward[1]*r_refdef.view.forward[1]); + left[0] = -r_refdef.view.forward[1] * scale; + left[1] = r_refdef.view.forward[0] * scale; + left[2] = 0; + up[0] = 0; + up[1] = 0; + up[2] = ent->scale; + break; + case SPR_FACING_UPRIGHT: + // flames and such + // vertical beam sprite, faces viewer's origin (not the view plane) + scale = ent->scale / sqrt((org[0] - r_refdef.view.origin[0])*(org[0] - r_refdef.view.origin[0])+(org[1] - r_refdef.view.origin[1])*(org[1] - r_refdef.view.origin[1])); + left[0] = (org[1] - r_refdef.view.origin[1]) * scale; + left[1] = -(org[0] - r_refdef.view.origin[0]) * scale; + left[2] = 0; + up[0] = 0; + up[1] = 0; + up[2] = ent->scale; + break; + default: + Con_Printf("R_SpriteSetup: unknown sprite type %i\n", model->sprite.sprnum_type); + // fall through to normal sprite + case SPR_VP_PARALLEL: + // normal sprite + // faces view plane + VectorScale(r_refdef.view.left, ent->scale, left); + VectorScale(r_refdef.view.up, ent->scale, up); + break; + case SPR_LABEL_SCALE: + // normal sprite + // faces view plane + // fixed HUD pixel size specified in sprite + // honors scale + // honors a global label scaling cvar + + if(r_fb.water.renderingscene) // labels are considered HUD items, and don't appear in reflections + return; + + // See the R_TrackSprite definition for a reason for this copying + VectorCopy(r_refdef.view.left, left); + VectorCopy(r_refdef.view.up, up); + // It has to be done before the calculations, because it moves the origin. + if(r_track_sprites.integer) + R_TrackSprite(ent, org, left, up, &edge, &dir_angle); + + scale = 2 * ent->scale * (DotProduct(r_refdef.view.forward, org) - DotProduct(r_refdef.view.forward, r_refdef.view.origin)) * r_labelsprites_scale.value; + VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer, left); // 1px + VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer, up); // 1px + break; + case SPR_LABEL: + // normal sprite + // faces view plane + // fixed pixel size specified in sprite + // tries to get the right size in HUD units, if possible + // ignores scale + // honors a global label scaling cvar before the rounding + // FIXME assumes that 1qu is 1 pixel in the sprite like in SPR32 format. Should not do that, but instead query the source image! This bug only applies to the roundtopixels case, though. + + if(r_fb.water.renderingscene) // labels are considered HUD items, and don't appear in reflections + return; + + // See the R_TrackSprite definition for a reason for this copying + VectorCopy(r_refdef.view.left, left); + VectorCopy(r_refdef.view.up, up); + // It has to be done before the calculations, because it moves the origin. + if(r_track_sprites.integer) + R_TrackSprite(ent, org, left, up, &edge, &dir_angle); + + scale = 2 * (DotProduct(r_refdef.view.forward, org) - DotProduct(r_refdef.view.forward, r_refdef.view.origin)); + + if(r_labelsprites_roundtopixels.integer) + { + hud_vs_screen = max( + vid_conwidth.integer / (float) r_refdef.view.width, + vid_conheight.integer / (float) r_refdef.view.height + ) / max(0.125, r_labelsprites_scale.value); + + // snap to "good sizes" + // 1 for (0.6, 1.41] + // 2 for (1.8, 3.33] + if(hud_vs_screen <= 0.6) + hud_vs_screen = 0; // don't, use real HUD pixels + else if(hud_vs_screen <= 1.41) + hud_vs_screen = 1; + else if(hud_vs_screen <= 3.33) + hud_vs_screen = 2; + else + hud_vs_screen = 0; // don't, use real HUD pixels + + if(hud_vs_screen) + { + // use screen pixels + VectorScale(left, scale * r_refdef.view.frustum_x / (r_refdef.view.width * hud_vs_screen), left); // 1px + VectorScale(up, scale * r_refdef.view.frustum_y / (r_refdef.view.height * hud_vs_screen), up); // 1px + } + else + { + // use HUD pixels + VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer * r_labelsprites_scale.value, left); // 1px + VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer * r_labelsprites_scale.value, up); // 1px + } + + if(hud_vs_screen == 1) + { + VectorMA(r_refdef.view.origin, scale, r_refdef.view.forward, middle); // center of screen in distance scale + dx = 0.5 - fmod(r_refdef.view.width * 0.5 + (DotProduct(org, left) - DotProduct(middle, left)) / DotProduct(left, left) + 0.5, 1.0); + dy = 0.5 - fmod(r_refdef.view.height * 0.5 + (DotProduct(org, up) - DotProduct(middle, up)) / DotProduct(up, up) + 0.5, 1.0); + VectorMAMAM(1, org, dx, left, dy, up, org); + } + } + else + { + // use HUD pixels + VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer * r_labelsprites_scale.value, left); // 1px + VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer * r_labelsprites_scale.value, up); // 1px + } + break; + case SPR_ORIENTED: + // bullet marks on walls + // ignores viewer entirely + VectorCopy(mleft, left); + VectorCopy(mup, up); + break; + case SPR_VP_PARALLEL_ORIENTED: + // I have no idea what people would use this for... + // oriented relative to view space + // FIXME: test this and make sure it mimicks software + left[0] = mleft[0] * r_refdef.view.forward[0] + mleft[1] * r_refdef.view.left[0] + mleft[2] * r_refdef.view.up[0]; + left[1] = mleft[0] * r_refdef.view.forward[1] + mleft[1] * r_refdef.view.left[1] + mleft[2] * r_refdef.view.up[1]; + left[2] = mleft[0] * r_refdef.view.forward[2] + mleft[1] * r_refdef.view.left[2] + mleft[2] * r_refdef.view.up[2]; + up[0] = mup[0] * r_refdef.view.forward[0] + mup[1] * r_refdef.view.left[0] + mup[2] * r_refdef.view.up[0]; + up[1] = mup[0] * r_refdef.view.forward[1] + mup[1] * r_refdef.view.left[1] + mup[2] * r_refdef.view.up[1]; + up[2] = mup[0] * r_refdef.view.forward[2] + mup[1] * r_refdef.view.left[2] + mup[2] * r_refdef.view.up[2]; + break; + case SPR_OVERHEAD: + // Overhead games sprites, have some special hacks to look good + VectorScale(r_refdef.view.left, ent->scale * r_overheadsprites_scalex.value, left); + VectorScale(r_refdef.view.up, ent->scale * r_overheadsprites_scaley.value, up); + VectorSubtract(org, r_refdef.view.origin, middle); + VectorNormalize(middle); + // offset and rotate + dir_angle = r_overheadsprites_perspective.value * (1 - fabs(DotProduct(middle, r_refdef.view.forward))); + up[2] = up[2] + dir_angle; + VectorNormalize(up); + VectorScale(up, ent->scale * r_overheadsprites_scaley.value, up); + // offset (move nearer to player, yz is camera plane) + org[0] = org[0] - middle[0]*r_overheadsprites_pushback.value; + org[1] = org[1] - middle[1]*r_overheadsprites_pushback.value; + org[2] = org[2] - middle[2]*r_overheadsprites_pushback.value; + // little perspective effect + up[2] = up[2] + dir_angle * 0.3; + // a bit of counter-camera rotation + up[0] = up[0] + r_refdef.view.forward[0] * 0.07; + up[1] = up[1] + r_refdef.view.forward[1] * 0.07; + up[2] = up[2] + r_refdef.view.forward[2] * 0.07; + break; + } + + // LordHavoc: interpolated sprite rendering + for (i = 0;i < MAX_FRAMEBLENDS;i++) + { + if (ent->frameblend[i].lerp >= 0.01f) + { + mspriteframe_t *frame; + texture_t *texture; + RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, ent->flags, 0, ent->colormod[0], ent->colormod[1], ent->colormod[2], ent->alpha * ent->frameblend[i].lerp, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); + frame = model->sprite.sprdata_frames + ent->frameblend[i].subframe; + texture = R_GetCurrentTexture(model->data_textures + ent->frameblend[i].subframe); + + // lit sprite by lightgrid if it is not fullbright, lit only ambient + if (!(texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) + VectorAdd(ent->modellight_ambient, ent->modellight_diffuse, rsurface.modellight_ambient); // sprites dont use lightdirection + + // SPR_LABEL should not use depth test AT ALL + if(model->sprite.sprnum_type == SPR_LABEL || model->sprite.sprnum_type == SPR_LABEL_SCALE) + if(texture->currentmaterialflags & MATERIALFLAG_SHORTDEPTHRANGE) + texture->currentmaterialflags = (texture->currentmaterialflags & ~MATERIALFLAG_SHORTDEPTHRANGE) | MATERIALFLAG_NODEPTHTEST; + + if(edge) + { + // FIXME:: save vectors/origin and re-rotate? necessary if the hotspot can change per frame + R_RotateSprite(frame, org, left, up, edge, dir_angle); + edge = 0; + } + + R_CalcSprite_Vertex3f(vertex3f, org, left, up, frame->left, frame->right, frame->down, frame->up); + + R_DrawCustomSurface_Texture(texture, &identitymatrix, texture->currentmaterialflags, 0, 4, 0, 2, false, false); + } + } + + rsurface.entity = NULL; +} + +void R_Model_Sprite_Draw(entity_render_t *ent) +{ + vec3_t org; + if (ent->frameblend[0].subframe < 0) + return; + + Matrix4x4_OriginFromMatrix(&ent->matrix, org); + R_MeshQueue_AddTransparent((ent->flags & RENDER_WORLDOBJECT) ? TRANSPARENTSORT_SKY : (ent->flags & RENDER_NODEPTHTEST) ? TRANSPARENTSORT_HUD : TRANSPARENTSORT_DISTANCE, org, R_Model_Sprite_Draw_TransparentCallback, ent, 0, rsurface.rtlight); +} + diff --git a/app/jni/r_textures.h b/app/jni/r_textures.h new file mode 100644 index 0000000..e9c9aab --- /dev/null +++ b/app/jni/r_textures.h @@ -0,0 +1,222 @@ + +#ifndef R_TEXTURES_H +#define R_TEXTURES_H + +// transparent +#define TEXF_ALPHA 0x00000001 +// mipmapped +#define TEXF_MIPMAP 0x00000002 +// multiply RGB by A channel before uploading +#define TEXF_RGBMULTIPLYBYALPHA 0x00000004 +// indicates texture coordinates should be clamped rather than wrapping +#define TEXF_CLAMP 0x00000020 +// indicates texture should be uploaded using GL_NEAREST or GL_NEAREST_MIPMAP_NEAREST mode +#define TEXF_FORCENEAREST 0x00000040 +// indicates texture should be uploaded using GL_LINEAR or GL_LINEAR_MIPMAP_NEAREST or GL_LINEAR_MIPMAP_LINEAR mode +#define TEXF_FORCELINEAR 0x00000080 +// indicates texture should be affected by gl_picmip and gl_max_size cvar +#define TEXF_PICMIP 0x00000100 +// indicates texture should be compressed if possible +#define TEXF_COMPRESS 0x00000200 +// use this flag to block R_PurgeTexture from freeing a texture (only used by r_texture_white and similar which may be used in skinframe_t) +#define TEXF_PERSISTENT 0x00000400 +// indicates texture should use GL_COMPARE_R_TO_TEXTURE mode +#define TEXF_COMPARE 0x00000800 +// indicates texture should use lower precision where supported +#define TEXF_LOWPRECISION 0x00001000 +// indicates texture should support R_UpdateTexture on small regions, actual uploads may be delayed until R_Mesh_TexBind if gl_nopartialtextureupdates is on +#define TEXF_ALLOWUPDATES 0x00002000 +// indicates texture should be affected by gl_picmip_world and r_picmipworld (maybe others in the future) instead of gl_picmip_other +#define TEXF_ISWORLD 0x00004000 +// indicates texture should be affected by gl_picmip_sprites and r_picmipsprites (maybe others in the future) instead of gl_picmip_other +#define TEXF_ISSPRITE 0x00008000 +// indicates the texture will be used as a render target (D3D hint) +#define TEXF_RENDERTARGET 0x0010000 +// used for checking if textures mismatch +#define TEXF_IMPORTANTBITS (TEXF_ALPHA | TEXF_MIPMAP | TEXF_RGBMULTIPLYBYALPHA | TEXF_CLAMP | TEXF_FORCENEAREST | TEXF_FORCELINEAR | TEXF_PICMIP | TEXF_COMPRESS | TEXF_COMPARE | TEXF_LOWPRECISION | TEXF_RENDERTARGET) +// set as a flag to force the texture to be reloaded +#define TEXF_FORCE_RELOAD 0x80000000 + +typedef enum textype_e +{ + // 8bit paletted + TEXTYPE_PALETTE, + // 32bit RGBA + TEXTYPE_RGBA, + // 32bit BGRA (preferred format due to faster uploads on most hardware) + TEXTYPE_BGRA, + // 8bit ALPHA (used for freetype fonts) + TEXTYPE_ALPHA, + // 4x4 block compressed 15bit color (4 bits per pixel) + TEXTYPE_DXT1, + // 4x4 block compressed 15bit color plus 1bit alpha (4 bits per pixel) + TEXTYPE_DXT1A, + // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) + TEXTYPE_DXT3, + // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) + TEXTYPE_DXT5, + + // 8bit paletted in sRGB colorspace + TEXTYPE_SRGB_PALETTE, + // 32bit RGBA in sRGB colorspace + TEXTYPE_SRGB_RGBA, + // 32bit BGRA (preferred format due to faster uploads on most hardware) in sRGB colorspace + TEXTYPE_SRGB_BGRA, + // 4x4 block compressed 15bit color (4 bits per pixel) in sRGB colorspace + TEXTYPE_SRGB_DXT1, + // 4x4 block compressed 15bit color plus 1bit alpha (4 bits per pixel) in sRGB colorspace + TEXTYPE_SRGB_DXT1A, + // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) in sRGB colorspace + TEXTYPE_SRGB_DXT3, + // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) in sRGB colorspace + TEXTYPE_SRGB_DXT5, + + // this represents the same format as the framebuffer, for fast copies + TEXTYPE_COLORBUFFER, + // this represents an RGBA half_float texture (4 16bit floats) + TEXTYPE_COLORBUFFER16F, + // this represents an RGBA float texture (4 32bit floats) + TEXTYPE_COLORBUFFER32F, + // depth-stencil buffer (or texture) + TEXTYPE_DEPTHBUFFER16, + // depth-stencil buffer (or texture) + TEXTYPE_DEPTHBUFFER24, + // 32bit D24S8 buffer (24bit depth, 8bit stencil), not supported on OpenGL ES + TEXTYPE_DEPTHBUFFER24STENCIL8, + // shadowmap-friendly format with depth comparison (not supported on some hardware) + TEXTYPE_SHADOWMAP16_COMP, + // shadowmap-friendly format with raw reading (not supported on some hardware) + TEXTYPE_SHADOWMAP16_RAW, + // shadowmap-friendly format with depth comparison (not supported on some hardware) + TEXTYPE_SHADOWMAP24_COMP, + // shadowmap-friendly format with raw reading (not supported on some hardware) + TEXTYPE_SHADOWMAP24_RAW, +} +textype_t; + +/* +#ifdef WIN32 +#define SUPPORTD3D +#define SUPPORTDIRECTX +#ifdef SUPPORTD3D +#include +#endif +#endif +*/ + +// contents of this structure are mostly private to gl_textures.c +typedef struct rtexture_s +{ + // this is exposed (rather than private) for speed reasons only + int texnum; // GL texture slot number + int renderbuffernum; // GL renderbuffer slot number + qboolean dirty; // indicates that R_RealGetTexture should be called + qboolean glisdepthstencil; // indicates that FBO attachment has to be GL_DEPTH_STENCIL_ATTACHMENT + int gltexturetypeenum; // used by R_Mesh_TexBind + // d3d stuff the backend needs + void *d3dtexture; + void *d3dsurface; +#ifdef SUPPORTD3D + qboolean d3disrendertargetsurface; + qboolean d3disdepthstencilsurface; + int d3dformat; + int d3dusage; + int d3dpool; + int d3daddressu; + int d3daddressv; + int d3daddressw; + int d3dmagfilter; + int d3dminfilter; + int d3dmipfilter; + int d3dmaxmiplevelfilter; + int d3dmipmaplodbias; + int d3dmaxmiplevel; +#endif +} +rtexture_t; + +// contents of this structure are private to gl_textures.c +typedef struct rtexturepool_s +{ + int useless; +} +rtexturepool_t; + +typedef void (*updatecallback_t)(rtexture_t *rt, void *data); + +// allocate a texture pool, to be used with R_LoadTexture +rtexturepool_t *R_AllocTexturePool(void); +// free a texture pool (textures can not be freed individually) +void R_FreeTexturePool(rtexturepool_t **rtexturepool); + +// the color/normal/etc cvars should be checked by callers of R_LoadTexture* functions to decide whether to add TEXF_COMPRESS to the flags +extern cvar_t gl_texturecompression; +extern cvar_t gl_texturecompression_color; +extern cvar_t gl_texturecompression_normal; +extern cvar_t gl_texturecompression_gloss; +extern cvar_t gl_texturecompression_glow; +extern cvar_t gl_texturecompression_2d; +extern cvar_t gl_texturecompression_q3bsplightmaps; +extern cvar_t gl_texturecompression_q3bspdeluxemaps; +extern cvar_t gl_texturecompression_sky; +extern cvar_t gl_texturecompression_lightcubemaps; +extern cvar_t gl_texturecompression_reflectmask; +extern cvar_t r_texture_dds_load; +extern cvar_t r_texture_dds_save; + +// add a texture to a pool and optionally precache (upload) it +// (note: data == NULL is perfectly acceptable) +// (note: palette must not be NULL if using TEXTYPE_PALETTE) +rtexture_t *R_LoadTexture2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); +rtexture_t *R_LoadTexture3D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); +rtexture_t *R_LoadTextureCubeMap(rtexturepool_t *rtexturepool, const char *identifier, int width, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); +rtexture_t *R_LoadTextureShadowMap2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, textype_t textype, qboolean filter); +rtexture_t *R_LoadTextureRenderBuffer(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, textype_t textype); +rtexture_t *R_LoadTextureDDSFile(rtexturepool_t *rtexturepool, const char *filename, qboolean srgb, int flags, qboolean *hasalphaflag, float *avgcolor, int miplevel, qboolean optionaltexture); + +// saves a texture to a DDS file +int R_SaveTextureDDSFile(rtexture_t *rt, const char *filename, qboolean skipuncompressed, qboolean hasalpha); + +// free a texture +void R_FreeTexture(rtexture_t *rt); + +// update a portion of the image data of a texture, used by lightmap updates +// and procedural textures such as video playback, actual uploads may be +// delayed by gl_nopartialtextureupdates cvar until R_Mesh_TexBind uses it +void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth); + +// returns the renderer dependent texture slot number (call this before each +// use, as a texture might not have been precached) +#define R_GetTexture(rt) ((rt) ? ((rt)->dirty ? R_RealGetTexture(rt) : (rt)->texnum) : r_texture_white->texnum) +int R_RealGetTexture (rtexture_t *rt); + +// returns width of texture, as was specified when it was uploaded +int R_TextureWidth(rtexture_t *rt); + +// returns height of texture, as was specified when it was uploaded +int R_TextureHeight(rtexture_t *rt); + +// returns flags of texture, as was specified when it was uploaded +int R_TextureFlags(rtexture_t *rt); + +// only frees the texture if TEXF_PERSISTENT is not set +// also resets the variable +void R_PurgeTexture(rtexture_t *prt); + +// frees processing buffers each frame, and may someday animate procedural textures +void R_Textures_Frame(void); + +// maybe rename this - sounds awful? [11/21/2007 Black] +void R_MarkDirtyTexture(rtexture_t *rt); +void R_MakeTextureDynamic(rtexture_t *rt, updatecallback_t updatecallback, void *data); + +// Clear the texture's contents +void R_ClearTexture (rtexture_t *rt); + +// returns the desired picmip level for given TEXF_ flags +int R_PicmipForFlags(int flags); + +void R_TextureStats_Print(qboolean printeach, qboolean printpool, qboolean printtotal); + +#endif + diff --git a/app/jni/render.h b/app/jni/render.h new file mode 100644 index 0000000..b0ccf79 --- /dev/null +++ b/app/jni/render.h @@ -0,0 +1,646 @@ +/* +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. + +*/ + +#ifndef RENDER_H +#define RENDER_H + +#include "svbsp.h" + +// 1.0f / N table +extern float ixtable[4096]; + +// fog stuff +void FOG_clear(void); + +// sky stuff +extern cvar_t r_sky; +extern cvar_t r_skyscroll1; +extern cvar_t r_skyscroll2; +extern int skyrenderlater, skyrendermasked; +int R_SetSkyBox(const char *sky); +void R_SkyStartFrame(void); +void R_Sky(void); +void R_ResetSkyBox(void); + +// SHOWLMP stuff (Nehahra) +void SHOWLMP_decodehide(void); +void SHOWLMP_decodeshow(void); +void SHOWLMP_drawall(void); + +// render profiling stuff +extern int r_timereport_active; + +// lighting stuff +extern cvar_t r_ambient; +extern cvar_t gl_flashblend; + +// vis stuff +extern cvar_t r_novis; + +extern cvar_t r_trippy; + +extern cvar_t r_lerpsprites; +extern cvar_t r_lerpmodels; +extern cvar_t r_lerplightstyles; +extern cvar_t r_waterscroll; + +extern cvar_t developer_texturelogging; + +// shadow volume bsp struct with automatically growing nodes buffer +extern svbsp_t r_svbsp; + +typedef struct rmesh_s +{ + // vertices of this mesh + int maxvertices; + int numvertices; + float *vertex3f; + float *svector3f; + float *tvector3f; + float *normal3f; + float *texcoord2f; + float *texcoordlightmap2f; + float *color4f; + // triangles of this mesh + int maxtriangles; + int numtriangles; + int *element3i; + int *neighbor3i; + // snapping epsilon + float epsilon2; +} +rmesh_t; + +// useful functions for rendering +void R_ModulateColors(float *in, float *out, int verts, float r, float g, float b); +void R_FillColors(float *out, int verts, float r, float g, float b, float a); +int R_Mesh_AddVertex3f(rmesh_t *mesh, const float *v); +void R_Mesh_AddPolygon3f(rmesh_t *mesh, int numvertices, float *vertex3f); +void R_Mesh_AddBrushMeshFromPlanes(rmesh_t *mesh, int numplanes, mplane_t *planes); + +#define TOP_RANGE 16 // soldier uniform colors +#define BOTTOM_RANGE 96 + +//============================================================================= + +extern cvar_t r_nearclip; + +// forces all rendering to draw triangle outlines +extern cvar_t r_showoverdraw; +extern cvar_t r_showtris; +extern cvar_t r_shownormals; +extern cvar_t r_showlighting; +extern cvar_t r_showshadowvolumes; +extern cvar_t r_showcollisionbrushes; +extern cvar_t r_showcollisionbrushes_polygonfactor; +extern cvar_t r_showcollisionbrushes_polygonoffset; +extern cvar_t r_showdisabledepthtest; + +extern cvar_t r_drawentities; +extern cvar_t r_draw2d; +extern qboolean r_draw2d_force; +extern cvar_t r_drawviewmodel; +extern cvar_t r_drawworld; +extern cvar_t r_speeds; +extern cvar_t r_fullbright; +extern cvar_t r_wateralpha; +extern cvar_t r_dynamic; + +void R_Init(void); +void R_UpdateVariables(void); // must call after setting up most of r_refdef, but before calling R_RenderView +void R_RenderView(); // must set r_refdef and call R_UpdateVariables first +void R_RenderView_UpdateViewVectors(void); // just updates r_refdef.view.{forward,left,up,origin,right,inverse_matrix} + +typedef enum r_refdef_scene_type_s { + RST_CLIENT, + RST_MENU, + RST_COUNT +} r_refdef_scene_type_t; + +void R_SelectScene( r_refdef_scene_type_t scenetype ); +r_refdef_scene_t * R_GetScenePointer( r_refdef_scene_type_t scenetype ); + +void R_SkinFrame_PrepareForPurge(void); +void R_SkinFrame_MarkUsed(skinframe_t *skinframe); +void R_SkinFrame_Purge(void); +// set last to NULL to start from the beginning +skinframe_t *R_SkinFrame_FindNextByName( skinframe_t *last, const char *name ); +skinframe_t *R_SkinFrame_Find(const char *name, int textureflags, int comparewidth, int compareheight, int comparecrc, qboolean add); +skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboolean complain); +skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, const unsigned char *skindata, int width, int height, qboolean sRGB); +skinframe_t *R_SkinFrame_LoadInternalQuake(const char *name, int textureflags, int loadpantsandshirt, int loadglowtexture, const unsigned char *skindata, int width, int height); +skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, const unsigned char *skindata, int width, int height, const unsigned int *palette, const unsigned int *alphapalette); +skinframe_t *R_SkinFrame_LoadMissing(void); + +rtexture_t *R_GetCubemap(const char *basename); + +void R_View_WorldVisibility(qboolean forcenovis); +void R_DrawDecals(void); +void R_DrawParticles(void); +void R_DrawExplosions(void); + +#define gl_solid_format 3 +#define gl_alpha_format 4 + +int R_CullBox(const vec3_t mins, const vec3_t maxs); +int R_CullBoxCustomPlanes(const vec3_t mins, const vec3_t maxs, int numplanes, const mplane_t *planes); + +#include "r_modules.h" + +#include "meshqueue.h" + +/// free all R_FrameData memory +void R_FrameData_Reset(void); +/// prepare for a new frame, recycles old buffers if a resize occurred previously +void R_FrameData_NewFrame(void); +/// allocate some temporary memory for your purposes +void *R_FrameData_Alloc(size_t size); +/// allocate some temporary memory and copy this data into it +void *R_FrameData_Store(size_t size, void *data); +/// set a marker that allows you to discard the following temporary memory allocations +void R_FrameData_SetMark(void); +/// discard recent memory allocations (rewind to marker) +void R_FrameData_ReturnToMark(void); + +/// enum of the various types of hardware buffer object used in rendering +/// note that the r_buffermegs[] array must be maintained to match this +typedef enum r_bufferdata_type_e +{ + R_BUFFERDATA_VERTEX, /// vertex buffer + R_BUFFERDATA_INDEX16, /// index buffer - 16bit (because D3D cares) + R_BUFFERDATA_INDEX32, /// index buffer - 32bit (because D3D cares) + R_BUFFERDATA_UNIFORM, /// uniform buffer + R_BUFFERDATA_COUNT /// how many kinds of buffer we have +} +r_bufferdata_type_t; + +/// free all dynamic vertex/index/uniform buffers +void R_BufferData_Reset(void); +/// begin a new frame (recycle old buffers) +void R_BufferData_NewFrame(void); +/// request space in a vertex/index/uniform buffer for the chosen data, returns the buffer pointer and offset, always successful +r_meshbuffer_t *R_BufferData_Store(size_t size, const void *data, r_bufferdata_type_t type, int *returnbufferoffset); + +/// free all R_AnimCache memory +void R_AnimCache_Free(void); +/// clear the animcache pointers on all known render entities +void R_AnimCache_ClearCache(void); +/// get the skeletal data or cached animated mesh data for an entity (optionally with normals and tangents) +qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qboolean wanttangents); +/// generate animcache data for all entities marked visible +void R_AnimCache_CacheVisibleEntities(void); + +#include "r_lerpanim.h" + +extern cvar_t r_render; +extern cvar_t r_renderview; +extern cvar_t r_waterwarp; + +extern cvar_t r_textureunits; + +extern cvar_t r_glsl_offsetmapping; +extern cvar_t r_glsl_offsetmapping_reliefmapping; +extern cvar_t r_glsl_offsetmapping_scale; +extern cvar_t r_glsl_offsetmapping_lod; +extern cvar_t r_glsl_offsetmapping_lod_distance; +extern cvar_t r_glsl_deluxemapping; + +extern cvar_t gl_polyblend; +extern cvar_t gl_dither; + +extern cvar_t cl_deathfade; + +extern cvar_t r_smoothnormals_areaweighting; + +extern cvar_t r_test; + +#include "gl_backend.h" + +extern rtexture_t *r_texture_blanknormalmap; +extern rtexture_t *r_texture_white; +extern rtexture_t *r_texture_grey128; +extern rtexture_t *r_texture_black; +extern rtexture_t *r_texture_notexture; +extern rtexture_t *r_texture_whitecube; +extern rtexture_t *r_texture_normalizationcube; +extern rtexture_t *r_texture_fogattenuation; +extern rtexture_t *r_texture_fogheighttexture; + +extern unsigned int r_queries[MAX_OCCLUSION_QUERIES]; +extern unsigned int r_numqueries; +extern unsigned int r_maxqueries; + +void R_TimeReport(const char *name); + +// r_stain +void R_Stain(const vec3_t origin, float radius, int cr1, int cg1, int cb1, int ca1, int cr2, int cg2, int cb2, int ca2); + +void R_CalcBeam_Vertex3f(float *vert, const float *org1, const float *org2, float width); +void R_CalcSprite_Vertex3f(float *vertex3f, const float *origin, const float *left, const float *up, float scalex1, float scalex2, float scaley1, float scaley2); + +extern mempool_t *r_main_mempool; + +typedef struct rsurfacestate_s +{ + // current model array pointers + // these may point to processing buffers if model is animated, + // otherwise they point to static data. + // these are not directly used for rendering, they are just another level + // of processing + // + // these either point at array_model* buffers (if the model is animated) + // or the model->surfmesh.data_* buffers (if the model is not animated) + // + // these are only set when an entity render begins, they do not change on + // a per surface basis. + // + // this indicates the model* arrays are pointed at array_model* buffers + // (in other words, the model has been animated in software) + qboolean forcecurrenttextureupdate; // set for RSurf_ActiveCustomEntity to force R_GetCurrentTexture to recalculate the texture parameters (such as entity alpha) + qboolean modelgeneratedvertex; + // skeletal animation can be done by entity (animcache) or per batch, + // batch may be non-skeletal even if entity is skeletal, indicating that + // the dynamicvertex code path had to apply skeletal manually for a case + // where gpu-skinning is not possible, for this reason batch has its own + // variables + int entityskeletalnumtransforms; // how many transforms are used for this mesh + float *entityskeletaltransform3x4; // use gpu-skinning shader on this mesh + const r_meshbuffer_t *entityskeletaltransform3x4buffer; // uniform buffer + int entityskeletaltransform3x4offset; + int entityskeletaltransform3x4size; + float *modelvertex3f; + const r_meshbuffer_t *modelvertex3f_vertexbuffer; + int modelvertex3f_bufferoffset; + float *modelsvector3f; + const r_meshbuffer_t *modelsvector3f_vertexbuffer; + int modelsvector3f_bufferoffset; + float *modeltvector3f; + const r_meshbuffer_t *modeltvector3f_vertexbuffer; + int modeltvector3f_bufferoffset; + float *modelnormal3f; + const r_meshbuffer_t *modelnormal3f_vertexbuffer; + int modelnormal3f_bufferoffset; + float *modellightmapcolor4f; + const r_meshbuffer_t *modellightmapcolor4f_vertexbuffer; + int modellightmapcolor4f_bufferoffset; + float *modeltexcoordtexture2f; + const r_meshbuffer_t *modeltexcoordtexture2f_vertexbuffer; + int modeltexcoordtexture2f_bufferoffset; + float *modeltexcoordlightmap2f; + const r_meshbuffer_t *modeltexcoordlightmap2f_vertexbuffer; + int modeltexcoordlightmap2f_bufferoffset; + unsigned char *modelskeletalindex4ub; + const r_meshbuffer_t *modelskeletalindex4ub_vertexbuffer; + int modelskeletalindex4ub_bufferoffset; + unsigned char *modelskeletalweight4ub; + const r_meshbuffer_t *modelskeletalweight4ub_vertexbuffer; + int modelskeletalweight4ub_bufferoffset; + r_vertexmesh_t *modelvertexmesh; + const r_meshbuffer_t *modelvertexmesh_vertexbuffer; + int modelvertexmesh_bufferoffset; + int *modelelement3i; + const r_meshbuffer_t *modelelement3i_indexbuffer; + int modelelement3i_bufferoffset; + unsigned short *modelelement3s; + const r_meshbuffer_t *modelelement3s_indexbuffer; + int modelelement3s_bufferoffset; + int *modellightmapoffsets; + int modelnumvertices; + int modelnumtriangles; + const msurface_t *modelsurfaces; + // current rendering array pointers + // these may point to any of several different buffers depending on how + // much processing was needed to prepare this model for rendering + // these usually equal the model* pointers, they only differ if + // deformvertexes is used in a q3 shader, and consequently these can + // change on a per-surface basis (according to rsurface.texture) + qboolean batchgeneratedvertex; + qboolean batchmultidraw; + int batchmultidrawnumsurfaces; + const msurface_t **batchmultidrawsurfacelist; + int batchfirstvertex; + int batchnumvertices; + int batchfirsttriangle; + int batchnumtriangles; + r_vertexmesh_t *batchvertexmesh; + const r_meshbuffer_t *batchvertexmesh_vertexbuffer; + int batchvertexmesh_bufferoffset; + float *batchvertex3f; + const r_meshbuffer_t *batchvertex3f_vertexbuffer; + int batchvertex3f_bufferoffset; + float *batchsvector3f; + const r_meshbuffer_t *batchsvector3f_vertexbuffer; + int batchsvector3f_bufferoffset; + float *batchtvector3f; + const r_meshbuffer_t *batchtvector3f_vertexbuffer; + int batchtvector3f_bufferoffset; + float *batchnormal3f; + const r_meshbuffer_t *batchnormal3f_vertexbuffer; + int batchnormal3f_bufferoffset; + float *batchlightmapcolor4f; + const r_meshbuffer_t *batchlightmapcolor4f_vertexbuffer; + int batchlightmapcolor4f_bufferoffset; + float *batchtexcoordtexture2f; + const r_meshbuffer_t *batchtexcoordtexture2f_vertexbuffer; + int batchtexcoordtexture2f_bufferoffset; + float *batchtexcoordlightmap2f; + const r_meshbuffer_t *batchtexcoordlightmap2f_vertexbuffer; + int batchtexcoordlightmap2f_bufferoffset; + unsigned char *batchskeletalindex4ub; + const r_meshbuffer_t *batchskeletalindex4ub_vertexbuffer; + int batchskeletalindex4ub_bufferoffset; + unsigned char *batchskeletalweight4ub; + const r_meshbuffer_t *batchskeletalweight4ub_vertexbuffer; + int batchskeletalweight4ub_bufferoffset; + int *batchelement3i; + const r_meshbuffer_t *batchelement3i_indexbuffer; + int batchelement3i_bufferoffset; + unsigned short *batchelement3s; + const r_meshbuffer_t *batchelement3s_indexbuffer; + int batchelement3s_bufferoffset; + int batchskeletalnumtransforms; + float *batchskeletaltransform3x4; + const r_meshbuffer_t *batchskeletaltransform3x4buffer; // uniform buffer + int batchskeletaltransform3x4offset; + int batchskeletaltransform3x4size; + // rendering pass processing arrays in GL11 and GL13 paths + float *passcolor4f; + const r_meshbuffer_t *passcolor4f_vertexbuffer; + int passcolor4f_bufferoffset; + + // some important fields from the entity + int ent_skinnum; + int ent_qwskin; + int ent_flags; + int ent_alttextures; // used by q1bsp animated textures (pressed buttons) + double shadertime; // r_refdef.scene.time - ent->shadertime + // transform matrices to render this entity and effects on this entity + matrix4x4_t matrix; + matrix4x4_t inversematrix; + // scale factors for transforming lengths into/out of entity space + float matrixscale; + float inversematrixscale; + // animation blending state from entity + frameblend_t frameblend[MAX_FRAMEBLENDS]; + skeleton_t *skeleton; + // directional model shading state from entity + vec3_t modellight_ambient; + vec3_t modellight_diffuse; + vec3_t modellight_lightdir; + // colormapping state from entity (these are black if colormapping is off) + vec3_t colormap_pantscolor; + vec3_t colormap_shirtcolor; + // special coloring of ambient/diffuse textures (gloss not affected) + // colormod[3] is the alpha of the entity + float colormod[4]; + // special coloring of glow textures + float glowmod[3]; + // view location in model space + vec3_t localvieworigin; + // polygon offset data for submodels + float basepolygonfactor; + float basepolygonoffset; + // current textures in batching code + texture_t *texture; + rtexture_t *lightmaptexture; + rtexture_t *deluxemaptexture; + // whether lightmapping is active on this batch + // (otherwise vertex colored) + qboolean uselightmaptexture; + // fog plane in model space for direct application to vertices + float fograngerecip; + float fogmasktabledistmultiplier; + float fogplane[4]; + float fogheightfade; + float fogplaneviewdist; + + // rtlight rendering + // light currently being rendered + const rtlight_t *rtlight; + + // this is the location of the light in entity space + vec3_t entitylightorigin; + // this transforms entity coordinates to light filter cubemap coordinates + // (also often used for other purposes) + matrix4x4_t entitytolight; + // based on entitytolight this transforms -1 to +1 to 0 to 1 for purposes + // of attenuation texturing in full 3D (Z result often ignored) + matrix4x4_t entitytoattenuationxyz; + // this transforms only the Z to S, and T is always 0.5 + matrix4x4_t entitytoattenuationz; + + // user wavefunc parameters (from csqc) + float userwavefunc_param[Q3WAVEFUNC_USER_COUNT]; + + // pointer to an entity_render_t used only by R_GetCurrentTexture and + // RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity as a unique id within + // each frame (see r_frame also) + entity_render_t *entity; +} +rsurfacestate_t; + +extern rsurfacestate_t rsurface; + +void R_HDR_UpdateIrisAdaptation(const vec3_t point); + +void RSurf_ActiveWorldEntity(void); +void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, qboolean wanttangents, qboolean prepass); +void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, int entflags, double shadertime, float r, float g, float b, float a, int numvertices, const float *vertex3f, const float *texcoord2f, const float *normal3f, const float *svector3f, const float *tvector3f, const float *color4f, int numtriangles, const int *element3i, const unsigned short *element3s, qboolean wantnormals, qboolean wanttangents); +void RSurf_SetupDepthAndCulling(void); + +void R_Mesh_ResizeArrays(int newvertices); + +texture_t *R_GetCurrentTexture(texture_t *t); +void R_DrawWorldSurfaces(qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass); +void R_DrawModelSurfaces(entity_render_t *ent, qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass); +void R_AddWaterPlanes(entity_render_t *ent); +void R_DrawCustomSurface(skinframe_t *skinframe, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass); +void R_DrawCustomSurface_Texture(texture_t *texture, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass); + +#define BATCHNEED_VERTEXMESH_VERTEX (1<< 1) // set up rsurface.batchvertexmesh +#define BATCHNEED_VERTEXMESH_NORMAL (1<< 2) // set up normals in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchnormal3f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_VECTOR (1<< 3) // set up vectors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchsvector3f and rsurface.batchtvector3f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_VERTEXCOLOR (1<< 4) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_TEXCOORD (1<< 5) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_LIGHTMAP (1<< 6) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_VERTEXMESH_SKELETAL (1<< 7) // set up skeletal index and weight data for vertex shader +#define BATCHNEED_ARRAY_VERTEX (1<< 8) // set up rsurface.batchvertex3f and optionally others +#define BATCHNEED_ARRAY_NORMAL (1<< 9) // set up normals in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchnormal3f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_VECTOR (1<<10) // set up vectors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchsvector3f and rsurface.batchtvector3f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_VERTEXCOLOR (1<<11) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_TEXCOORD (1<<12) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_LIGHTMAP (1<<13) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS +#define BATCHNEED_ARRAY_SKELETAL (1<<14) // set up skeletal index and weight data for vertex shader +#define BATCHNEED_NOGAPS (1<<15) // force vertex copying if firstvertex is not zero or there are gaps +#define BATCHNEED_ALLOWMULTIDRAW (1<<16) // allow multiple draws +void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const msurface_t **texturesurfacelist); +void RSurf_DrawBatch(void); + +void R_DecalSystem_SplatEntities(const vec3_t org, const vec3_t normal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float size); + +typedef enum rsurfacepass_e +{ + RSURFPASS_BASE, + RSURFPASS_BACKGROUND, + RSURFPASS_RTLIGHT, + RSURFPASS_DEFERREDGEOMETRY +} +rsurfacepass_t; + +void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemode, int rgbscale, qboolean usegamma, qboolean notrippy, qboolean suppresstexalpha); +void R_SetupShader_Generic_NoTexture(qboolean usegamma, qboolean notrippy); +void R_SetupShader_DepthOrShadow(qboolean notrippy, qboolean depthrgb, qboolean skeletal); +void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting, float ambientscale, float diffusescale, float specularscale, rsurfacepass_t rsurfacepass, int texturenumsurfaces, const msurface_t **texturesurfacelist, void *waterplane, qboolean notrippy); +void R_SetupShader_DeferredLight(const rtlight_t *rtlight); + +typedef struct r_waterstate_waterplane_s +{ + rtexture_t *texture_refraction; // MATERIALFLAG_WATERSHADER or MATERIALFLAG_REFRACTION + rtexture_t *texture_reflection; // MATERIALFLAG_WATERSHADER or MATERIALFLAG_REFLECTION + rtexture_t *texture_camera; // MATERIALFLAG_CAMERA + int fbo_refraction; + int fbo_reflection; + int fbo_camera; + mplane_t plane; + int materialflags; // combined flags of all water surfaces on this plane + unsigned char pvsbits[(MAX_MAP_LEAFS+7)>>3]; // FIXME: buffer overflow on huge maps + qboolean pvsvalid; + int camera_entity; + vec3_t mins, maxs; +} +r_waterstate_waterplane_t; + +typedef struct r_waterstate_s +{ + int waterwidth, waterheight; + int texturewidth, textureheight; + int camerawidth, cameraheight; + rtexture_t *depthtexture; + + int maxwaterplanes; // same as MAX_WATERPLANES + int numwaterplanes; + r_waterstate_waterplane_t waterplanes[MAX_WATERPLANES]; + + float screenscale[2]; + float screencenter[2]; + + qboolean enabled; + + qboolean renderingscene; // true while rendering a refraction or reflection texture, disables water surfaces + qboolean hideplayer; +} +r_waterstate_t; + +typedef struct r_framebufferstate_s +{ + textype_t textype; // type of color buffer we're using (dependent on r_viewfbo cvar) + int fbo; // non-zero if r_viewfbo is enabled and working + int screentexturewidth, screentextureheight; // dimensions of texture + + rtexture_t *colortexture; // non-NULL if fbo is non-zero + rtexture_t *depthtexture; // non-NULL if fbo is non-zero + rtexture_t *ghosttexture; // for r_motionblur (not recommended on multi-GPU hardware!) + rtexture_t *bloomtexture[2]; // for r_bloom, multi-stage processing + int bloomfbo[2]; // fbos for rendering into bloomtexture[] + int bloomindex; // which bloomtexture[] contains the final image + + int bloomwidth, bloomheight; + int bloomtexturewidth, bloomtextureheight; + + // arrays for rendering the screen passes + float screentexcoord2f[8]; // texcoords for colortexture or ghosttexture + float bloomtexcoord2f[8]; // texcoords for bloomtexture[] + float offsettexcoord2f[8]; // temporary use while updating bloomtexture[] + + r_viewport_t bloomviewport; + + r_waterstate_t water; + + qboolean ghosttexture_valid; // don't draw garbage on first frame with motionblur + qboolean usedepthtextures; // use depth texture instead of depth renderbuffer (faster if you need to read it later anyway) +} +r_framebufferstate_t; + +extern r_framebufferstate_t r_fb; + +extern cvar_t r_viewfbo; + +void R_ResetViewRendering2D_Common(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, float x2, float y2); // this is called by R_ResetViewRendering2D and _DrawQ_Setup and internal +void R_ResetViewRendering2D(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); +void R_ResetViewRendering3D(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); +void R_SetupView(qboolean allowwaterclippingplane, int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); +extern const float r_screenvertex3f[12]; +extern cvar_t r_shadows; +extern cvar_t r_shadows_darken; +extern cvar_t r_shadows_drawafterrtlighting; +extern cvar_t r_shadows_castfrombmodels; +extern cvar_t r_shadows_throwdistance; +extern cvar_t r_shadows_throwdirection; +extern cvar_t r_shadows_focus; +extern cvar_t r_shadows_shadowmapscale; +extern cvar_t r_shadows_shadowmapbias; +extern cvar_t r_transparent_alphatocoverage; +extern cvar_t r_transparent_sortsurfacesbynearest; +extern cvar_t r_transparent_useplanardistance; +extern cvar_t r_transparent_sortarraysize; +extern cvar_t r_transparent_sortmindist; +extern cvar_t r_transparent_sortmaxdist; + +void R_Model_Sprite_Draw(entity_render_t *ent); + +struct prvm_prog_s; +void R_UpdateFog(void); +qboolean CL_VM_UpdateView(double frametime); +void SCR_DrawConsole(void); +void R_Shadow_EditLights_DrawSelectedLightProperties(void); +void R_DecalSystem_Reset(decalsystem_t *decalsystem); +void R_Shadow_UpdateBounceGridTexture(void); +void R_DrawLightningBeams(void); +void VM_CL_AddPolygonsToMeshQueue(struct prvm_prog_s *prog); +void R_DrawPortals(void); +void R_DrawModelShadows(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); +void R_DrawModelShadowMaps(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); +void R_BuildLightMap(const entity_render_t *ent, msurface_t *surface); +void R_Water_AddWaterPlane(msurface_t *surface, int entno); +int R_Shadow_GetRTLightInfo(unsigned int lightindex, float *origin, float *radius, float *color); +dp_font_t *FindFont(const char *title, qboolean allocate_new); +void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset); + +void Render_Init(void); + +// these are called by Render_Init +void R_Textures_Init(void); +void GL_Draw_Init(void); +void GL_Main_Init(void); +void R_Shadow_Init(void); +void R_Sky_Init(void); +void GL_Surf_Init(void); +void R_Particles_Init(void); +void R_Explosion_Init(void); +void gl_backend_init(void); +void Sbar_Init(void); +void R_LightningBeams_Init(void); +void Mod_RenderInit(void); +void Font_Init(void); + +qboolean R_CompileShader_CheckStaticParms(void); +void R_GLSL_Restart_f(void); + +#endif diff --git a/app/jni/resource.h b/app/jni/resource.h new file mode 100644 index 0000000..27fc918 --- /dev/null +++ b/app/jni/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by darkplaces.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/app/jni/sbar.c b/app/jni/sbar.c new file mode 100644 index 0000000..a43a5d3 --- /dev/null +++ b/app/jni/sbar.c @@ -0,0 +1,2238 @@ +/* +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. + +*/ +// sbar.c -- status bar code + +#include "quakedef.h" +#include +#include "cl_collision.h" +#include "csprogs.h" + +cachepic_t *sb_disc; + +#define STAT_MINUS 10 // num frame for '-' stats digit +cachepic_t *sb_nums[2][11]; +cachepic_t *sb_colon, *sb_slash; +cachepic_t *sb_ibar; +cachepic_t *sb_sbar; +cachepic_t *sb_scorebar; +// AK only used by NEX +cachepic_t *sb_sbar_minimal; +cachepic_t *sb_sbar_overlay; + +// AK changed the bound to 9 +cachepic_t *sb_weapons[7][9]; // 0 is active, 1 is owned, 2-5 are flashes +cachepic_t *sb_ammo[4]; +cachepic_t *sb_sigil[4]; +cachepic_t *sb_armor[3]; +cachepic_t *sb_items[32]; + +// 0-4 are based on health (in 20 increments) +// 0 is static, 1 is temporary animation +cachepic_t *sb_faces[5][2]; +cachepic_t *sb_health; // GAME_NEXUIZ + +cachepic_t *sb_face_invis; +cachepic_t *sb_face_quad; +cachepic_t *sb_face_invuln; +cachepic_t *sb_face_invis_invuln; + +qboolean sb_showscores; + +int sb_lines; // scan lines to draw + +cachepic_t *rsb_invbar[2]; +cachepic_t *rsb_weapons[5]; +cachepic_t *rsb_items[2]; +cachepic_t *rsb_ammo[3]; +cachepic_t *rsb_teambord; // PGM 01/19/97 - team color border + +//MED 01/04/97 added two more weapons + 3 alternates for grenade launcher +cachepic_t *hsb_weapons[7][5]; // 0 is active, 1 is owned, 2-5 are flashes +//MED 01/04/97 added array to simplify weapon parsing +int hipweapons[4] = {HIT_LASER_CANNON_BIT,HIT_MJOLNIR_BIT,4,HIT_PROXIMITY_GUN_BIT}; +//MED 01/04/97 added hipnotic items array +cachepic_t *hsb_items[2]; + +cachepic_t *zymsb_crosshair_center; +cachepic_t *zymsb_crosshair_line; +cachepic_t *zymsb_crosshair_health; +cachepic_t *zymsb_crosshair_ammo; +cachepic_t *zymsb_crosshair_clip; +cachepic_t *zymsb_crosshair_background; +cachepic_t *zymsb_crosshair_left1; +cachepic_t *zymsb_crosshair_left2; +cachepic_t *zymsb_crosshair_right; + +cachepic_t *sb_ranking; +cachepic_t *sb_complete; +cachepic_t *sb_inter; +cachepic_t *sb_finale; + +cvar_t showfps = {CVAR_SAVE, "showfps", "0", "shows your rendered fps (frames per second)"}; +cvar_t showsound = {CVAR_SAVE, "showsound", "0", "shows number of active sound sources, sound latency, and other statistics"}; +cvar_t showblur = {CVAR_SAVE, "showblur", "0", "shows the current alpha level of motionblur"}; +cvar_t showspeed = {CVAR_SAVE, "showspeed", "0", "shows your current speed (qu per second); number selects unit: 1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots"}; +cvar_t showtopspeed = {CVAR_SAVE, "showtopspeed", "0", "shows your top speed (kept on screen for max 3 seconds); value -1 takes over the unit from showspeed, otherwise it's an unit number just like in showspeed"}; +cvar_t showtime = {CVAR_SAVE, "showtime", "0", "shows current time of day (useful on screenshots)"}; +cvar_t showtime_format = {CVAR_SAVE, "showtime_format", "%H:%M:%S", "format string for time of day"}; +cvar_t showdate = {CVAR_SAVE, "showdate", "0", "shows current date (useful on screenshots)"}; +cvar_t showdate_format = {CVAR_SAVE, "showdate_format", "%Y-%m-%d", "format string for date"}; +cvar_t showtex = {0, "showtex", "0", "shows the name of the texture on the crosshair (for map debugging)"}; +cvar_t sbar_alpha_bg = {CVAR_SAVE, "sbar_alpha_bg", "0.4", "opacity value of the statusbar background image"}; +cvar_t sbar_alpha_fg = {CVAR_SAVE, "sbar_alpha_fg", "1", "opacity value of the statusbar weapon/item icons and numbers"}; +cvar_t sbar_hudselector = {CVAR_SAVE, "sbar_hudselector", "0", "selects which of the builtin hud layouts to use (meaning is somewhat dependent on gamemode, so nexuiz has a very different set of hud layouts than quake for example)"}; +cvar_t sbar_scorerank = {CVAR_SAVE, "sbar_scorerank", "1", "shows an overlay for your score (or team score) and rank in the scoreboard"}; +cvar_t sbar_gametime = {CVAR_SAVE, "sbar_gametime", "1", "shows an overlay for the time left in the current match/level (or current game time if there is no timelimit set)"}; +cvar_t sbar_miniscoreboard_size = {CVAR_SAVE, "sbar_miniscoreboard_size", "-1", "sets the size of the mini deathmatch overlay in items, or disables it when set to 0, or sets it to a sane default when set to -1"}; +cvar_t sbar_flagstatus_right = {CVAR_SAVE, "sbar_flagstatus_right", "0", "moves Nexuiz flag status icons to the right"}; +cvar_t sbar_flagstatus_pos = {CVAR_SAVE, "sbar_flagstatus_pos", "115", "pixel position of the Nexuiz flag status icons, from the bottom"}; +cvar_t sbar_info_pos = {CVAR_SAVE, "sbar_info_pos", "180", "pixel position of the info strings (such as showfps), from the bottom"}; + +cvar_t cl_deathscoreboard = {0, "cl_deathscoreboard", "1", "shows scoreboard (+showscores) while dead"}; + +cvar_t crosshair_color_red = {CVAR_SAVE, "crosshair_color_red", "1", "customizable crosshair color"}; +cvar_t crosshair_color_green = {CVAR_SAVE, "crosshair_color_green", "0", "customizable crosshair color"}; +cvar_t crosshair_color_blue = {CVAR_SAVE, "crosshair_color_blue", "0", "customizable crosshair color"}; +cvar_t crosshair_color_alpha = {CVAR_SAVE, "crosshair_color_alpha", "1", "how opaque the crosshair should be"}; +cvar_t crosshair_size = {CVAR_SAVE, "crosshair_size", "1", "adjusts size of the crosshair on the screen"}; + +static void Sbar_MiniDeathmatchOverlay (int x, int y); +static void Sbar_DeathmatchOverlay (void); +static void Sbar_IntermissionOverlay (void); +static void Sbar_FinaleOverlay (void); + + +extern qboolean vrMode; +extern vec3_t hmdorientation; +extern cvar_t r_worldscale; + +//Calculate the y-offset of the status bar dependent on where the user is looking +int Sbar_GetYOffset() +{ + if (hmdorientation[PITCH] <= 15.0f || !vrMode) + return 0; + + int offset = (vid_conheight.value * ((hmdorientation[PITCH] - 15.0f) / 90.0f)); + if (offset < 0) offset = 0; + return offset; +} + +int Sbar_GetXOffset() +{ + if (!vrMode) + return 0; + + //This will give the status bar depth in the 3D space + return (r_stereo_side ? -20 : 20); +} + +/* +=============== +Sbar_ShowScores + +Tab key down +=============== +*/ +static void Sbar_ShowScores (void) +{ + if (sb_showscores) + return; + sb_showscores = true; + CL_VM_UpdateShowingScoresState(sb_showscores); +} + +/* +=============== +Sbar_DontShowScores + +Tab key up +=============== +*/ +static void Sbar_DontShowScores (void) +{ + sb_showscores = false; + CL_VM_UpdateShowingScoresState(sb_showscores); +} + +static void sbar_start(void) +{ + char vabuf[1024]; + int i; + + if (gamemode == GAME_DELUXEQUAKE || gamemode == GAME_BLOODOMNICIDE) + { + } + else if (gamemode == GAME_NEXUIZ) + { + for (i = 0;i < 10;i++) + sb_nums[0][i] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/num_%i",i), CACHEPICFLAG_QUIET); + sb_nums[0][10] = Draw_CachePic_Flags ("gfx/num_minus", CACHEPICFLAG_QUIET); + sb_colon = Draw_CachePic_Flags ("gfx/num_colon", CACHEPICFLAG_QUIET); + + sb_ammo[0] = Draw_CachePic_Flags ("gfx/sb_shells", CACHEPICFLAG_QUIET); + sb_ammo[1] = Draw_CachePic_Flags ("gfx/sb_bullets", CACHEPICFLAG_QUIET); + sb_ammo[2] = Draw_CachePic_Flags ("gfx/sb_rocket", CACHEPICFLAG_QUIET); + sb_ammo[3] = Draw_CachePic_Flags ("gfx/sb_cells", CACHEPICFLAG_QUIET); + + sb_armor[0] = Draw_CachePic_Flags ("gfx/sb_armor", CACHEPICFLAG_QUIET); + sb_armor[1] = NULL; + sb_armor[2] = NULL; + + sb_health = Draw_CachePic_Flags ("gfx/sb_health", CACHEPICFLAG_QUIET); + + sb_items[2] = Draw_CachePic_Flags ("gfx/sb_slowmo", CACHEPICFLAG_QUIET); + sb_items[3] = Draw_CachePic_Flags ("gfx/sb_invinc", CACHEPICFLAG_QUIET); + sb_items[4] = Draw_CachePic_Flags ("gfx/sb_energy", CACHEPICFLAG_QUIET); + sb_items[5] = Draw_CachePic_Flags ("gfx/sb_str", CACHEPICFLAG_QUIET); + + sb_items[11] = Draw_CachePic_Flags ("gfx/sb_flag_red_taken", CACHEPICFLAG_QUIET); + sb_items[12] = Draw_CachePic_Flags ("gfx/sb_flag_red_lost", CACHEPICFLAG_QUIET); + sb_items[13] = Draw_CachePic_Flags ("gfx/sb_flag_red_carrying", CACHEPICFLAG_QUIET); + sb_items[14] = Draw_CachePic_Flags ("gfx/sb_key_carrying", CACHEPICFLAG_QUIET); + sb_items[15] = Draw_CachePic_Flags ("gfx/sb_flag_blue_taken", CACHEPICFLAG_QUIET); + sb_items[16] = Draw_CachePic_Flags ("gfx/sb_flag_blue_lost", CACHEPICFLAG_QUIET); + sb_items[17] = Draw_CachePic_Flags ("gfx/sb_flag_blue_carrying", CACHEPICFLAG_QUIET); + + sb_sbar = Draw_CachePic_Flags ("gfx/sbar", CACHEPICFLAG_QUIET); + sb_sbar_minimal = Draw_CachePic_Flags ("gfx/sbar_minimal", CACHEPICFLAG_QUIET); + sb_sbar_overlay = Draw_CachePic_Flags ("gfx/sbar_overlay", CACHEPICFLAG_QUIET); + + for(i = 0; i < 9;i++) + sb_weapons[0][i] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inv_weapon%i",i), CACHEPICFLAG_QUIET); + } + else if (gamemode == GAME_ZYMOTIC) + { + zymsb_crosshair_center = Draw_CachePic_Flags ("gfx/hud/crosshair_center", CACHEPICFLAG_QUIET); + zymsb_crosshair_line = Draw_CachePic_Flags ("gfx/hud/crosshair_line", CACHEPICFLAG_QUIET); + zymsb_crosshair_health = Draw_CachePic_Flags ("gfx/hud/crosshair_health", CACHEPICFLAG_QUIET); + zymsb_crosshair_clip = Draw_CachePic_Flags ("gfx/hud/crosshair_clip", CACHEPICFLAG_QUIET); + zymsb_crosshair_ammo = Draw_CachePic_Flags ("gfx/hud/crosshair_ammo", CACHEPICFLAG_QUIET); + zymsb_crosshair_background = Draw_CachePic_Flags ("gfx/hud/crosshair_background", CACHEPICFLAG_QUIET); + zymsb_crosshair_left1 = Draw_CachePic_Flags ("gfx/hud/crosshair_left1", CACHEPICFLAG_QUIET); + zymsb_crosshair_left2 = Draw_CachePic_Flags ("gfx/hud/crosshair_left2", CACHEPICFLAG_QUIET); + zymsb_crosshair_right = Draw_CachePic_Flags ("gfx/hud/crosshair_right", CACHEPICFLAG_QUIET); + } + else + { + sb_disc = Draw_CachePic_Flags ("gfx/disc", CACHEPICFLAG_QUIET); + + for (i = 0;i < 10;i++) + { + sb_nums[0][i] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/num_%i",i), CACHEPICFLAG_QUIET); + sb_nums[1][i] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/anum_%i",i), CACHEPICFLAG_QUIET); + } + + sb_nums[0][10] = Draw_CachePic_Flags ("gfx/num_minus", CACHEPICFLAG_QUIET); + sb_nums[1][10] = Draw_CachePic_Flags ("gfx/anum_minus", CACHEPICFLAG_QUIET); + + sb_colon = Draw_CachePic_Flags ("gfx/num_colon", CACHEPICFLAG_QUIET); + sb_slash = Draw_CachePic_Flags ("gfx/num_slash", CACHEPICFLAG_QUIET); + + sb_weapons[0][0] = Draw_CachePic_Flags ("gfx/inv_shotgun", CACHEPICFLAG_QUIET); + sb_weapons[0][1] = Draw_CachePic_Flags ("gfx/inv_sshotgun", CACHEPICFLAG_QUIET); + sb_weapons[0][2] = Draw_CachePic_Flags ("gfx/inv_nailgun", CACHEPICFLAG_QUIET); + sb_weapons[0][3] = Draw_CachePic_Flags ("gfx/inv_snailgun", CACHEPICFLAG_QUIET); + sb_weapons[0][4] = Draw_CachePic_Flags ("gfx/inv_rlaunch", CACHEPICFLAG_QUIET); + sb_weapons[0][5] = Draw_CachePic_Flags ("gfx/inv_srlaunch", CACHEPICFLAG_QUIET); + sb_weapons[0][6] = Draw_CachePic_Flags ("gfx/inv_lightng", CACHEPICFLAG_QUIET); + + sb_weapons[1][0] = Draw_CachePic_Flags ("gfx/inv2_shotgun", CACHEPICFLAG_QUIET); + sb_weapons[1][1] = Draw_CachePic_Flags ("gfx/inv2_sshotgun", CACHEPICFLAG_QUIET); + sb_weapons[1][2] = Draw_CachePic_Flags ("gfx/inv2_nailgun", CACHEPICFLAG_QUIET); + sb_weapons[1][3] = Draw_CachePic_Flags ("gfx/inv2_snailgun", CACHEPICFLAG_QUIET); + sb_weapons[1][4] = Draw_CachePic_Flags ("gfx/inv2_rlaunch", CACHEPICFLAG_QUIET); + sb_weapons[1][5] = Draw_CachePic_Flags ("gfx/inv2_srlaunch", CACHEPICFLAG_QUIET); + sb_weapons[1][6] = Draw_CachePic_Flags ("gfx/inv2_lightng", CACHEPICFLAG_QUIET); + + for (i = 0;i < 5;i++) + { + sb_weapons[2+i][0] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_shotgun",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][1] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_sshotgun",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][2] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_nailgun",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][3] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_snailgun",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][4] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_rlaunch",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][5] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_srlaunch",i+1), CACHEPICFLAG_QUIET); + sb_weapons[2+i][6] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_lightng",i+1), CACHEPICFLAG_QUIET); + } + + sb_ammo[0] = Draw_CachePic_Flags ("gfx/sb_shells", CACHEPICFLAG_QUIET); + sb_ammo[1] = Draw_CachePic_Flags ("gfx/sb_nails", CACHEPICFLAG_QUIET); + sb_ammo[2] = Draw_CachePic_Flags ("gfx/sb_rocket", CACHEPICFLAG_QUIET); + sb_ammo[3] = Draw_CachePic_Flags ("gfx/sb_cells", CACHEPICFLAG_QUIET); + + sb_armor[0] = Draw_CachePic_Flags ("gfx/sb_armor1", CACHEPICFLAG_QUIET); + sb_armor[1] = Draw_CachePic_Flags ("gfx/sb_armor2", CACHEPICFLAG_QUIET); + sb_armor[2] = Draw_CachePic_Flags ("gfx/sb_armor3", CACHEPICFLAG_QUIET); + + sb_items[0] = Draw_CachePic_Flags ("gfx/sb_key1", CACHEPICFLAG_QUIET); + sb_items[1] = Draw_CachePic_Flags ("gfx/sb_key2", CACHEPICFLAG_QUIET); + sb_items[2] = Draw_CachePic_Flags ("gfx/sb_invis", CACHEPICFLAG_QUIET); + sb_items[3] = Draw_CachePic_Flags ("gfx/sb_invuln", CACHEPICFLAG_QUIET); + sb_items[4] = Draw_CachePic_Flags ("gfx/sb_suit", CACHEPICFLAG_QUIET); + sb_items[5] = Draw_CachePic_Flags ("gfx/sb_quad", CACHEPICFLAG_QUIET); + + sb_sigil[0] = Draw_CachePic_Flags ("gfx/sb_sigil1", CACHEPICFLAG_QUIET); + sb_sigil[1] = Draw_CachePic_Flags ("gfx/sb_sigil2", CACHEPICFLAG_QUIET); + sb_sigil[2] = Draw_CachePic_Flags ("gfx/sb_sigil3", CACHEPICFLAG_QUIET); + sb_sigil[3] = Draw_CachePic_Flags ("gfx/sb_sigil4", CACHEPICFLAG_QUIET); + + sb_faces[4][0] = Draw_CachePic_Flags ("gfx/face1", CACHEPICFLAG_QUIET); + sb_faces[4][1] = Draw_CachePic_Flags ("gfx/face_p1", CACHEPICFLAG_QUIET); + sb_faces[3][0] = Draw_CachePic_Flags ("gfx/face2", CACHEPICFLAG_QUIET); + sb_faces[3][1] = Draw_CachePic_Flags ("gfx/face_p2", CACHEPICFLAG_QUIET); + sb_faces[2][0] = Draw_CachePic_Flags ("gfx/face3", CACHEPICFLAG_QUIET); + sb_faces[2][1] = Draw_CachePic_Flags ("gfx/face_p3", CACHEPICFLAG_QUIET); + sb_faces[1][0] = Draw_CachePic_Flags ("gfx/face4", CACHEPICFLAG_QUIET); + sb_faces[1][1] = Draw_CachePic_Flags ("gfx/face_p4", CACHEPICFLAG_QUIET); + sb_faces[0][0] = Draw_CachePic_Flags ("gfx/face5", CACHEPICFLAG_QUIET); + sb_faces[0][1] = Draw_CachePic_Flags ("gfx/face_p5", CACHEPICFLAG_QUIET); + + sb_face_invis = Draw_CachePic_Flags ("gfx/face_invis", CACHEPICFLAG_QUIET); + sb_face_invuln = Draw_CachePic_Flags ("gfx/face_invul2", CACHEPICFLAG_QUIET); + sb_face_invis_invuln = Draw_CachePic_Flags ("gfx/face_inv2", CACHEPICFLAG_QUIET); + sb_face_quad = Draw_CachePic_Flags ("gfx/face_quad", CACHEPICFLAG_QUIET); + + sb_sbar = Draw_CachePic_Flags ("gfx/sbar", CACHEPICFLAG_QUIET); + sb_ibar = Draw_CachePic_Flags ("gfx/ibar", CACHEPICFLAG_QUIET); + sb_scorebar = Draw_CachePic_Flags ("gfx/scorebar", CACHEPICFLAG_QUIET); + + //MED 01/04/97 added new hipnotic weapons + if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) + { + hsb_weapons[0][0] = Draw_CachePic_Flags ("gfx/inv_laser", CACHEPICFLAG_QUIET); + hsb_weapons[0][1] = Draw_CachePic_Flags ("gfx/inv_mjolnir", CACHEPICFLAG_QUIET); + hsb_weapons[0][2] = Draw_CachePic_Flags ("gfx/inv_gren_prox", CACHEPICFLAG_QUIET); + hsb_weapons[0][3] = Draw_CachePic_Flags ("gfx/inv_prox_gren", CACHEPICFLAG_QUIET); + hsb_weapons[0][4] = Draw_CachePic_Flags ("gfx/inv_prox", CACHEPICFLAG_QUIET); + + hsb_weapons[1][0] = Draw_CachePic_Flags ("gfx/inv2_laser", CACHEPICFLAG_QUIET); + hsb_weapons[1][1] = Draw_CachePic_Flags ("gfx/inv2_mjolnir", CACHEPICFLAG_QUIET); + hsb_weapons[1][2] = Draw_CachePic_Flags ("gfx/inv2_gren_prox", CACHEPICFLAG_QUIET); + hsb_weapons[1][3] = Draw_CachePic_Flags ("gfx/inv2_prox_gren", CACHEPICFLAG_QUIET); + hsb_weapons[1][4] = Draw_CachePic_Flags ("gfx/inv2_prox", CACHEPICFLAG_QUIET); + + for (i = 0;i < 5;i++) + { + hsb_weapons[2+i][0] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_laser",i+1), CACHEPICFLAG_QUIET); + hsb_weapons[2+i][1] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_mjolnir",i+1), CACHEPICFLAG_QUIET); + hsb_weapons[2+i][2] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_gren_prox",i+1), CACHEPICFLAG_QUIET); + hsb_weapons[2+i][3] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_prox_gren",i+1), CACHEPICFLAG_QUIET); + hsb_weapons[2+i][4] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_prox",i+1), CACHEPICFLAG_QUIET); + } + + hsb_items[0] = Draw_CachePic_Flags ("gfx/sb_wsuit", CACHEPICFLAG_QUIET); + hsb_items[1] = Draw_CachePic_Flags ("gfx/sb_eshld", CACHEPICFLAG_QUIET); + } + else if (gamemode == GAME_ROGUE) + { + rsb_invbar[0] = Draw_CachePic_Flags ("gfx/r_invbar1", CACHEPICFLAG_QUIET); + rsb_invbar[1] = Draw_CachePic_Flags ("gfx/r_invbar2", CACHEPICFLAG_QUIET); + + rsb_weapons[0] = Draw_CachePic_Flags ("gfx/r_lava", CACHEPICFLAG_QUIET); + rsb_weapons[1] = Draw_CachePic_Flags ("gfx/r_superlava", CACHEPICFLAG_QUIET); + rsb_weapons[2] = Draw_CachePic_Flags ("gfx/r_gren", CACHEPICFLAG_QUIET); + rsb_weapons[3] = Draw_CachePic_Flags ("gfx/r_multirock", CACHEPICFLAG_QUIET); + rsb_weapons[4] = Draw_CachePic_Flags ("gfx/r_plasma", CACHEPICFLAG_QUIET); + + rsb_items[0] = Draw_CachePic_Flags ("gfx/r_shield1", CACHEPICFLAG_QUIET); + rsb_items[1] = Draw_CachePic_Flags ("gfx/r_agrav1", CACHEPICFLAG_QUIET); + + // PGM 01/19/97 - team color border + rsb_teambord = Draw_CachePic_Flags ("gfx/r_teambord", CACHEPICFLAG_QUIET); + // PGM 01/19/97 - team color border + + rsb_ammo[0] = Draw_CachePic_Flags ("gfx/r_ammolava", CACHEPICFLAG_QUIET); + rsb_ammo[1] = Draw_CachePic_Flags ("gfx/r_ammomulti", CACHEPICFLAG_QUIET); + rsb_ammo[2] = Draw_CachePic_Flags ("gfx/r_ammoplasma", CACHEPICFLAG_QUIET); + } + } + + sb_ranking = Draw_CachePic_Flags ("gfx/ranking", CACHEPICFLAG_QUIET); + sb_complete = Draw_CachePic_Flags ("gfx/complete", CACHEPICFLAG_QUIET); + sb_inter = Draw_CachePic_Flags ("gfx/inter", CACHEPICFLAG_QUIET); + sb_finale = Draw_CachePic_Flags ("gfx/finale", CACHEPICFLAG_QUIET); +} + +static void sbar_shutdown(void) +{ +} + +static void sbar_newmap(void) +{ +} + +void Sbar_Init (void) +{ + Cmd_AddCommand("+showscores", Sbar_ShowScores, "show scoreboard"); + Cmd_AddCommand("-showscores", Sbar_DontShowScores, "hide scoreboard"); + Cvar_RegisterVariable(&showfps); + Cvar_RegisterVariable(&showsound); + Cvar_RegisterVariable(&showblur); + Cvar_RegisterVariable(&showspeed); + Cvar_RegisterVariable(&showtopspeed); + Cvar_RegisterVariable(&showtime); + Cvar_RegisterVariable(&showtime_format); + Cvar_RegisterVariable(&showdate); + Cvar_RegisterVariable(&showdate_format); + Cvar_RegisterVariable(&showtex); + Cvar_RegisterVariable(&sbar_alpha_bg); + Cvar_RegisterVariable(&sbar_alpha_fg); + Cvar_RegisterVariable(&sbar_hudselector); + Cvar_RegisterVariable(&sbar_scorerank); + Cvar_RegisterVariable(&sbar_gametime); + Cvar_RegisterVariable(&sbar_miniscoreboard_size); + Cvar_RegisterVariable(&sbar_info_pos); + Cvar_RegisterVariable(&cl_deathscoreboard); + + Cvar_RegisterVariable(&crosshair_color_red); + Cvar_RegisterVariable(&crosshair_color_green); + Cvar_RegisterVariable(&crosshair_color_blue); + Cvar_RegisterVariable(&crosshair_color_alpha); + Cvar_RegisterVariable(&crosshair_size); + + Cvar_RegisterVariable(&sbar_flagstatus_right); // (GAME_NEXUZI ONLY) + Cvar_RegisterVariable(&sbar_flagstatus_pos); // (GAME_NEXUIZ ONLY) + + R_RegisterModule("sbar", sbar_start, sbar_shutdown, sbar_newmap, NULL, NULL); +} + + +//============================================================================= + +// drawing routines are relative to the status bar location + +int sbar_x, sbar_y; + +/* +============= +Sbar_DrawPic +============= +*/ +static void Sbar_DrawStretchPic (int x, int y, cachepic_t *pic, float alpha, float overridewidth, float overrideheight) +{ + DrawQ_Pic (sbar_x + x, sbar_y + y, pic, overridewidth, overrideheight, 1, 1, 1, alpha, 0); +} + +static void Sbar_DrawPic (int x, int y, cachepic_t *pic) +{ + DrawQ_Pic (sbar_x + x, sbar_y + y, pic, 0, 0, 1, 1, 1, sbar_alpha_fg.value, 0); +} + +static void Sbar_DrawAlphaPic (int x, int y, cachepic_t *pic, float alpha) +{ + DrawQ_Pic (sbar_x + x, sbar_y + y, pic, 0, 0, 1, 1, 1, alpha, 0); +} + +/* +================ +Sbar_DrawCharacter + +Draws one solid graphics character +================ +*/ +static void Sbar_DrawCharacter (int x, int y, int num) +{ + char vabuf[1024]; + DrawQ_String (sbar_x + x + 4 , sbar_y + y, va(vabuf, sizeof(vabuf), "%c", num), 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, true, FONT_SBAR); +} + +/* +================ +Sbar_DrawString +================ +*/ +static void Sbar_DrawString (int x, int y, char *str) +{ + DrawQ_String (sbar_x + x, sbar_y + y, str, 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR); +} + +/* +============= +Sbar_DrawNum +============= +*/ +static void Sbar_DrawNum (int x, int y, int num, int digits, int color) +{ + char str[32], *ptr; + int l, frame; + + l = dpsnprintf(str, sizeof(str), "%i", num); + ptr = str; + if (l > digits) + ptr += (l-digits); + if (l < digits) + x += (digits-l)*24; + + while (*ptr) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + Sbar_DrawPic (x, y, sb_nums[color][frame]); + x += 24; + + ptr++; + } +} + +/* +============= +Sbar_DrawXNum +============= +*/ + +static void Sbar_DrawXNum (int x, int y, int num, int digits, int lettersize, float r, float g, float b, float a, int flags) +{ + char str[32], *ptr; + int l, frame; + + if (digits < 0) + { + digits = -digits; + l = dpsnprintf(str, sizeof(str), "%0*i", digits, num); + } + else + l = dpsnprintf(str, sizeof(str), "%i", num); + ptr = str; + if (l > digits) + ptr += (l-digits); + if (l < digits) + x += (digits-l) * lettersize; + + while (*ptr) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + DrawQ_Pic (sbar_x + x, sbar_y + y, sb_nums[0][frame],lettersize,lettersize,r,g,b,a * sbar_alpha_fg.value,flags); + x += lettersize; + + ptr++; + } +} + +//============================================================================= + + +static int Sbar_IsTeammatch(void) +{ + // currently only nexuiz uses the team score board + return ((gamemode == GAME_NEXUIZ) + && (teamplay.integer > 0)); +} + +/* +=============== +Sbar_SortFrags +=============== +*/ +static int fragsort[MAX_SCOREBOARD]; +static int scoreboardlines; + +int Sbar_GetSortedPlayerIndex (int index) +{ + return index >= 0 && index < scoreboardlines ? fragsort[index] : -1; +} + +static scoreboard_t teams[MAX_SCOREBOARD]; +static int teamsort[MAX_SCOREBOARD]; +static int teamlines; +void Sbar_SortFrags (void) +{ + int i, j, k, color; + + // sort by frags + scoreboardlines = 0; + for (i=0 ; i max) + str[max] = 0; + + // print the filename and message + Sbar_DrawString(8, 12, str); + + // print the time + Sbar_DrawString(8 + max*8, 12, timestr); + +#else + char str[80]; + int minutes, seconds, tens, units; + int l; + + if (gamemode != GAME_NEXUIZ) { + dpsnprintf (str, sizeof(str), "Monsters:%3i /%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]); + Sbar_DrawString (8, 4, str); + + dpsnprintf (str, sizeof(str), "Secrets :%3i /%3i", cl.stats[STAT_SECRETS], cl.stats[STAT_TOTALSECRETS]); + Sbar_DrawString (8, 12, str); + } + +// time + minutes = (int)(cl.time / 60); + seconds = (int)(cl.time - 60*minutes); + tens = seconds / 10; + units = seconds - 10*tens; + dpsnprintf (str, sizeof(str), "Time :%3i:%i%i", minutes, tens, units); + Sbar_DrawString (184, 4, str); + +// draw level name + if (gamemode == GAME_NEXUIZ) { + l = (int) strlen (cl.worldname); + Sbar_DrawString (232 - l*4, 12, cl.worldname); + } else { + l = (int) strlen (cl.worldmessage); + Sbar_DrawString (232 - l*4, 12, cl.worldmessage); + } +#endif +} + +/* +=============== +Sbar_DrawScoreboard +=============== +*/ +static void Sbar_DrawScoreboard (void) +{ + Sbar_SoloScoreboard (); + // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode + //if (cl.gametype == GAME_DEATHMATCH) + if (!cl.islocalgame) + Sbar_DeathmatchOverlay (); +} + +//============================================================================= + +// AK to make DrawInventory smaller +static void Sbar_DrawWeapon(int nr, float fade, int active) +{ + char vabuf[1024]; + if (sbar_hudselector.integer == 1) + { + // width = 300, height = 100 + const int w_width = 32, w_height = 12, w_space = 2, font_size = 8; + + DrawQ_Pic((vid_conwidth.integer - w_width * 9) * 0.5 + w_width * nr, vid_conheight.integer - w_height, sb_weapons[0][nr], w_width, w_height, (active) ? 1 : 0.6, active ? 1 : 0.6, active ? 1 : 0.6, (active ? 1 : 0.6) * fade * sbar_alpha_fg.value, DRAWFLAG_NORMAL); + // FIXME ?? + DrawQ_String((vid_conwidth.integer - w_width * 9) * 0.5 + w_width * nr + w_space, vid_conheight.integer - w_height + w_space, va(vabuf, sizeof(vabuf), "%i",nr+1), 0, font_size, font_size, 1, 1, 0, sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); + } + else + { + // width = 300, height = 100 + const int w_width = 300, w_height = 100, w_space = 10; + const float w_scale = 0.4; + + DrawQ_Pic(vid_conwidth.integer - (w_width + w_space) * w_scale, (w_height + w_space) * w_scale * nr + w_space, sb_weapons[0][nr], w_width * w_scale, w_height * w_scale, (active) ? 1 : 0.6, active ? 1 : 0.6, active ? 1 : 1, fade * sbar_alpha_fg.value, DRAWFLAG_NORMAL); + //DrawQ_String(vid_conwidth.integer - (w_space + font_size ), (w_height + w_space) * w_scale * nr + w_space, va(vabuf, sizeof(vabuf), "%i",nr+1), 0, font_size, font_size, 1, 0, 0, fade, 0, NULL, true, FONT_DEFAULT); + } +} + +/* +=============== +Sbar_DrawInventory +=============== +*/ +static void Sbar_DrawInventory (void) +{ + int i; + char num[6]; + float time; + int flashon; + + if (gamemode == GAME_ROGUE) + { + if ( cl.stats[STAT_ACTIVEWEAPON] >= RIT_LAVA_NAILGUN ) + Sbar_DrawAlphaPic (0, -24, rsb_invbar[0], sbar_alpha_bg.value); + else + Sbar_DrawAlphaPic (0, -24, rsb_invbar[1], sbar_alpha_bg.value); + } + else + Sbar_DrawAlphaPic (0, -24, sb_ibar, sbar_alpha_bg.value); + + // weapons + for (i=0 ; i<7 ; i++) + { + if (cl.stats[STAT_ITEMS] & (IT_SHOTGUN<= 10) + { + if ( cl.stats[STAT_ACTIVEWEAPON] == (IT_SHOTGUN<= 10) + { + if ( cl.stats[STAT_ACTIVEWEAPON] == (1<= RIT_LAVA_NAILGUN ) + for (i=0;i<5;i++) + if (cl.stats[STAT_ACTIVEWEAPON] == (RIT_LAVA_NAILGUN << i)) + Sbar_DrawPic ((i+2)*24, -16, rsb_weapons[i]); + } + + // ammo counts + for (i=0 ; i<4 ; i++) + { + dpsnprintf (num, sizeof(num), "%4i",cl.stats[STAT_SHELLS+i] ); + if (num[0] != ' ') + Sbar_DrawCharacter ( (6*i+0)*8 - 2, -24, 18 + num[0] - '0'); + if (num[1] != ' ') + Sbar_DrawCharacter ( (6*i+1)*8 - 2, -24, 18 + num[1] - '0'); + if (num[2] != ' ') + Sbar_DrawCharacter ( (6*i+2)*8 - 2, -24, 18 + num[2] - '0'); + if (num[3] != ' ') + Sbar_DrawCharacter ( (6*i+3)*8 - 2, -24, 18 + num[3] - '0'); + } + + // items + for (i=0 ; i<6 ; i++) + if (cl.stats[STAT_ITEMS] & (1<<(17+i))) + { + //MED 01/04/97 changed keys + if (!(gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) || (i>1)) + Sbar_DrawPic (192 + i*16, -16, sb_items[i]); + } + + //MED 01/04/97 added hipnotic items + // hipnotic items + if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) + { + for (i=0 ; i<2 ; i++) + if (cl.stats[STAT_ITEMS] & (1<<(24+i))) + Sbar_DrawPic (288 + i*16, -16, hsb_items[i]); + } + + if (gamemode == GAME_ROGUE) + { + // new rogue items + for (i=0 ; i<2 ; i++) + if (cl.stats[STAT_ITEMS] & (1<<(29+i))) + Sbar_DrawPic (288 + i*16, -16, rsb_items[i]); + } + else + { + // sigils + for (i=0 ; i<4 ; i++) + if (cl.stats[STAT_ITEMS] & (1<<(28+i))) + Sbar_DrawPic (320-32 + i*8, -16, sb_sigil[i]); + } +} + +//============================================================================= + +/* +=============== +Sbar_DrawFrags +=============== +*/ +static void Sbar_DrawFrags (void) +{ + int i, k, l, x, f; + char num[12]; + scoreboard_t *s; + unsigned char *c; + + Sbar_SortFrags (); + + // draw the text + l = min(scoreboardlines, 4); + + x = 23 * 8; + + for (i = 0;i < l;i++) + { + k = fragsort[i]; + s = &cl.scores[k]; + + // draw background + c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; + DrawQ_Fill (sbar_x + x + 10, sbar_y - 23, 28, 4, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + c = palette_rgb_shirtscoreboard[s->colors & 0xf]; + DrawQ_Fill (sbar_x + x + 10, sbar_y + 4 - 23, 28, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + + // draw number + f = s->frags; + dpsnprintf (num, sizeof(num), "%3i",f); + + if (k == cl.viewentity - 1) + { + Sbar_DrawCharacter ( x + 2, -24, 16); + Sbar_DrawCharacter ( x + 32 - 4, -24, 17); + } + Sbar_DrawCharacter (x + 8, -24, num[0]); + Sbar_DrawCharacter (x + 16, -24, num[1]); + Sbar_DrawCharacter (x + 24, -24, num[2]); + x += 32; + } +} + +//============================================================================= + + +/* +=============== +Sbar_DrawFace +=============== +*/ +static void Sbar_DrawFace (void) +{ + int f; + +// PGM 01/19/97 - team color drawing +// PGM 03/02/97 - fixed so color swatch only appears in CTF modes + if (gamemode == GAME_ROGUE && !cl.islocalgame && (teamplay.integer > 3) && (teamplay.integer < 7)) + { + char num[12]; + scoreboard_t *s; + unsigned char *c; + + s = &cl.scores[cl.viewentity - 1]; + // draw background + Sbar_DrawPic (112, 0, rsb_teambord); + c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; + DrawQ_Fill (sbar_x + 113, vid_conheight.integer-SBAR_HEIGHT+3, 22, 9, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + c = palette_rgb_shirtscoreboard[s->colors & 0xf]; + DrawQ_Fill (sbar_x + 113, vid_conheight.integer-SBAR_HEIGHT+12, 22, 9, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + + // draw number + f = s->frags; + dpsnprintf (num, sizeof(num), "%3i",f); + + if ((s->colors & 0xf0)==0) + { + if (num[0] != ' ') + Sbar_DrawCharacter(109, 3, 18 + num[0] - '0'); + if (num[1] != ' ') + Sbar_DrawCharacter(116, 3, 18 + num[1] - '0'); + if (num[2] != ' ') + Sbar_DrawCharacter(123, 3, 18 + num[2] - '0'); + } + else + { + Sbar_DrawCharacter ( 109, 3, num[0]); + Sbar_DrawCharacter ( 116, 3, num[1]); + Sbar_DrawCharacter ( 123, 3, num[2]); + } + + return; + } +// PGM 01/19/97 - team color drawing + + if ( (cl.stats[STAT_ITEMS] & (IT_INVISIBILITY | IT_INVULNERABILITY) ) == (IT_INVISIBILITY | IT_INVULNERABILITY) ) + Sbar_DrawPic (112, 0, sb_face_invis_invuln); + else if (cl.stats[STAT_ITEMS] & IT_QUAD) + Sbar_DrawPic (112, 0, sb_face_quad ); + else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) + Sbar_DrawPic (112, 0, sb_face_invis ); + else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) + Sbar_DrawPic (112, 0, sb_face_invuln); + else + { + f = cl.stats[STAT_HEALTH] / 20; + f = bound(0, f, 4); + Sbar_DrawPic (112, 0, sb_faces[f][cl.time <= cl.faceanimtime]); + } +} +double topspeed = 0; +double topspeedxy = 0; +time_t current_time = 3; +time_t top_time = 0; +time_t topxy_time = 0; + +static void get_showspeed_unit(int unitnumber, double *conversion_factor, const char **unit) +{ + if(unitnumber < 0) + unitnumber = showspeed.integer; + switch(unitnumber) + { + default: + case 1: + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + *unit = "in/s"; + else + *unit = "qu/s"; + *conversion_factor = 1.0; + break; + case 2: + *unit = "m/s"; + *conversion_factor = 0.0254; + if(gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC) *conversion_factor *= 1.5; + // 1qu=1.5in is for non-Nexuiz/Xonotic only - Nexuiz/Xonotic players are overly large, but 1qu=1in fixes that + break; + case 3: + *unit = "km/h"; + *conversion_factor = 0.0254 * 3.6; + if(gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC) *conversion_factor *= 1.5; + break; + case 4: + *unit = "mph"; + *conversion_factor = 0.0254 * 3.6 * 0.6213711922; + if(gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC) *conversion_factor *= 1.5; + break; + case 5: + *unit = "knots"; + *conversion_factor = 0.0254 * 1.943844492; // 1 m/s = 1.943844492 knots, because 1 knot = 1.852 km/h + if(gamemode != GAME_NEXUIZ && gamemode != GAME_XONOTIC) *conversion_factor *= 1.5; + break; + } +} + +static double showfps_nexttime = 0, showfps_lasttime = -1; +static double showfps_framerate = 0; +static int showfps_framecount = 0; + +void Sbar_ShowFPS_Update(void) +{ + double interval = 1; + double newtime; + newtime = realtime; + if (newtime >= showfps_nexttime) + { + showfps_framerate = showfps_framecount / (newtime - showfps_lasttime); + if (showfps_nexttime < newtime - interval * 1.5) + showfps_nexttime = newtime; + showfps_lasttime = newtime; + showfps_nexttime += interval; + showfps_framecount = 0; + } + showfps_framecount++; +} + +void Sbar_ShowFPS(void) +{ + float fps_x, fps_y, fps_scalex, fps_scaley, fps_strings = 0; + char soundstring[32]; + char fpsstring[32]; + char timestring[32]; + char datestring[32]; + char timedemostring1[32]; + char timedemostring2[32]; + char speedstring[32]; + char blurstring[32]; + char topspeedstring[48]; + char texstring[MAX_QPATH]; + qboolean red = false; + soundstring[0] = 0; + fpsstring[0] = 0; + timedemostring1[0] = 0; + timedemostring2[0] = 0; + timestring[0] = 0; + datestring[0] = 0; + speedstring[0] = 0; + blurstring[0] = 0; + texstring[0] = 0; + topspeedstring[0] = 0; + if (showfps.integer) + { + red = (showfps_framerate < 1.0f); + if(showfps.integer == 2) + dpsnprintf(fpsstring, sizeof(fpsstring), "%7.3f mspf", (1000.0 / showfps_framerate)); + else if (red) + dpsnprintf(fpsstring, sizeof(fpsstring), "%4i spf", (int)(1.0 / showfps_framerate + 0.5)); + else + dpsnprintf(fpsstring, sizeof(fpsstring), "%4i fps", (int)(showfps_framerate + 0.5)); + fps_strings++; + if (cls.timedemo) + { + dpsnprintf(timedemostring1, sizeof(timedemostring1), "frame%4i %f", cls.td_frames, realtime - cls.td_starttime); + dpsnprintf(timedemostring2, sizeof(timedemostring2), "%i seconds %3.0f/%3.0f/%3.0f fps", cls.td_onesecondavgcount, cls.td_onesecondminfps, cls.td_onesecondavgfps / max(1, cls.td_onesecondavgcount), cls.td_onesecondmaxfps); + fps_strings++; + fps_strings++; + } + } + if (showtime.integer) + { + strlcpy(timestring, Sys_TimeString(showtime_format.string), sizeof(timestring)); + fps_strings++; + } + if (showdate.integer) + { + strlcpy(datestring, Sys_TimeString(showdate_format.string), sizeof(datestring)); + fps_strings++; + } + if (showblur.integer) + { + dpsnprintf(blurstring, sizeof(blurstring), "%3i%% blur", (int)(cl.motionbluralpha * 100)); + fps_strings++; + } + if (showsound.integer) + { + dpsnprintf(soundstring, sizeof(soundstring), "%4i/4%i at %3ims", cls.soundstats.mixedsounds, cls.soundstats.totalsounds, cls.soundstats.latency_milliseconds); + fps_strings++; + } + if (showspeed.integer || showtopspeed.integer) + { + double speed, speedxy, f; + const char *unit; + speed = VectorLength(cl.movement_velocity); + speedxy = sqrt(cl.movement_velocity[0] * cl.movement_velocity[0] + cl.movement_velocity[1] * cl.movement_velocity[1]); + if (showspeed.integer) + { + get_showspeed_unit(showspeed.integer, &f, &unit); + dpsnprintf(speedstring, sizeof(speedstring), "%.0f (%.0f) %s", f*speed, f*speedxy, unit); + fps_strings++; + } + if (showtopspeed.integer) + { + qboolean topspeed_latched = false, topspeedxy_latched = false; + get_showspeed_unit(showtopspeed.integer, &f, &unit); + if (speed >= topspeed || current_time - top_time > 3) + { + topspeed = speed; + time(&top_time); + } + else + topspeed_latched = true; + if (speedxy >= topspeedxy || current_time - topxy_time > 3) + { + topspeedxy = speedxy; + time(&topxy_time); + } + else + topspeedxy_latched = true; + dpsnprintf(topspeedstring, sizeof(topspeedstring), "%s%.0f%s (%s%.0f%s) %s", + topspeed_latched ? "^1" : "^xf88", f*topspeed, "^xf88", + topspeedxy_latched ? "^1" : "^xf88", f*topspeedxy, "^xf88", + unit); + time(¤t_time); + fps_strings++; + } + } + if (showtex.integer) + { + vec3_t org; + vec3_t dest; + vec3_t temp; + trace_t trace; + + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, org); + VectorSet(temp, 65536, 0, 0); + Matrix4x4_Transform(&r_refdef.view.matrix, temp, dest); + trace.hittexture = NULL; // to make sure + // TODO change this trace to be stopped by anything "visible" (i.e. with a drawsurface), but not stuff like weapclip + // probably needs adding a new SUPERCONTENTS type + trace = CL_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID, true, false, NULL, true, true); + if(trace.hittexture) + strlcpy(texstring, trace.hittexture->name, sizeof(texstring)); + else + strlcpy(texstring, "(no texture hit)", sizeof(texstring)); + fps_strings++; + } + if (fps_strings) + { + fps_scalex = 12; + fps_scaley = 12; + //fps_y = vid_conheight.integer - sb_lines; // yes this may draw over the sbar + //fps_y = bound(0, fps_y, vid_conheight.integer - fps_strings*fps_scaley); + fps_y = vid_conheight.integer - sbar_info_pos.integer - fps_strings*fps_scaley; + if (soundstring[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(soundstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, soundstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (fpsstring[0]) + { + r_draw2d_force = true; + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(fpsstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + if (red) + DrawQ_String(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 0, 0, 1, 0, NULL, true, FONT_INFOBAR); + else + DrawQ_String(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + r_draw2d_force = false; + } + if (timedemostring1[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(timedemostring1, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, timedemostring1, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (timedemostring2[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(timedemostring2, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, timedemostring2, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (timestring[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(timestring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, timestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (datestring[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(datestring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, datestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (speedstring[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(speedstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, speedstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (topspeedstring[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(topspeedstring, 0, fps_scalex, fps_scaley, false, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, topspeedstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, false, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (blurstring[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(blurstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, blurstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + if (texstring[0]) + { + fps_x = (vid_conwidth.integer / 2) - (DrawQ_TextWidth(texstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR) / 2) + Sbar_GetXOffset(); + DrawQ_String(fps_x, fps_y, texstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); + fps_y += fps_scaley; + } + } +} + +static void Sbar_DrawGauge(float x, float y, cachepic_t *pic, float width, float height, float rangey, float rangeheight, float c1, float c2, float c1r, float c1g, float c1b, float c1a, float c2r, float c2g, float c2b, float c2a, float c3r, float c3g, float c3b, float c3a, int drawflags) +{ + float r[5]; + c2 = bound(0, c2, 1); + c1 = bound(0, c1, 1 - c2); + r[0] = 0; + r[1] = rangey + rangeheight * (c2 + c1); + r[2] = rangey + rangeheight * (c2); + r[3] = rangey; + r[4] = height; + if (r[1] > r[0]) + DrawQ_SuperPic(x, y + r[0], pic, width, (r[1] - r[0]), 0,(r[0] / height), c3r,c3g,c3b,c3a, 1,(r[0] / height), c3r,c3g,c3b,c3a, 0,(r[1] / height), c3r,c3g,c3b,c3a, 1,(r[1] / height), c3r,c3g,c3b,c3a, drawflags); + if (r[2] > r[1]) + DrawQ_SuperPic(x, y + r[1], pic, width, (r[2] - r[1]), 0,(r[1] / height), c1r,c1g,c1b,c1a, 1,(r[1] / height), c1r,c1g,c1b,c1a, 0,(r[2] / height), c1r,c1g,c1b,c1a, 1,(r[2] / height), c1r,c1g,c1b,c1a, drawflags); + if (r[3] > r[2]) + DrawQ_SuperPic(x, y + r[2], pic, width, (r[3] - r[2]), 0,(r[2] / height), c2r,c2g,c2b,c2a, 1,(r[2] / height), c2r,c2g,c2b,c2a, 0,(r[3] / height), c2r,c2g,c2b,c2a, 1,(r[3] / height), c2r,c2g,c2b,c2a, drawflags); + if (r[4] > r[3]) + DrawQ_SuperPic(x, y + r[3], pic, width, (r[4] - r[3]), 0,(r[3] / height), c3r,c3g,c3b,c3a, 1,(r[3] / height), c3r,c3g,c3b,c3a, 0,(r[4] / height), c3r,c3g,c3b,c3a, 1,(r[4] / height), c3r,c3g,c3b,c3a, drawflags); +} + +/* +=============== +Sbar_Draw +=============== +*/ +extern float v_dmg_time, v_dmg_roll, v_dmg_pitch; +extern cvar_t v_kicktime; +void Sbar_Score (int margin); +void Sbar_Draw (void) +{ + cachepic_t *pic; + char vabuf[1024]; + + if(cl.csqc_vidvars.drawenginesbar) //[515]: csqc drawsbar + { + if (sb_showscores) + Sbar_DrawScoreboard (); + else if (cl.intermission == 1) + { + if(gamemode == GAME_NEXUIZ) // display full scoreboard (that is, show scores + map name) + { + Sbar_DrawScoreboard(); + return; + } + Sbar_IntermissionOverlay(); + } + else if (cl.intermission == 2) + Sbar_FinaleOverlay(); + else if (gamemode == GAME_DELUXEQUAKE) + { + } + else if (gamemode == GAME_NEXUIZ) + { + if (sb_showscores || (cl.stats[STAT_HEALTH] <= 0 && cl_deathscoreboard.integer)) + { + sbar_x = (vid_conwidth.integer - 640)/2 + Sbar_GetXOffset(); + sbar_y = vid_conheight.integer - (Sbar_GetYOffset() + 40); + Sbar_DrawAlphaPic (0, 0, sb_scorebar, sbar_alpha_bg.value); + Sbar_DrawScoreboard (); + } + else if (sb_lines && sbar_hudselector.integer == 1) + { + int i; + float fade; + int redflag, blueflag; + float x; + + sbar_x = (vid_conwidth.integer - 320)/2 + Sbar_GetXOffset(); + sbar_y = vid_conheight.integer - (Sbar_GetYOffset() + 40); + + // calculate intensity to draw weapons bar at + fade = 3.2 - 2 * (cl.time - cl.weapontime); + fade = bound(0.7, fade, 1); + for (i = 0; i < 8;i++) + if (cl.stats[STAT_ITEMS] & (1 << i)) + Sbar_DrawWeapon(i + 1, fade, (i + 2 == cl.stats[STAT_ACTIVEWEAPON])); + if((cl.stats[STAT_ITEMS] & (1<<12))) + Sbar_DrawWeapon(0, fade, (cl.stats[STAT_ACTIVEWEAPON] == 1)); + + // flag icons + redflag = ((cl.stats[STAT_ITEMS]>>15) & 3); + blueflag = ((cl.stats[STAT_ITEMS]>>17) & 3); + x = sbar_flagstatus_right.integer ? vid_conwidth.integer - 10 - sbar_x - 64 : 10 - sbar_x; + if (redflag == 3 && blueflag == 3) + { + // The Impossible Combination[tm] + // Can only happen in Key Hunt mode... + Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 128)), sb_items[14]); + } + else + { + if (redflag) + Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 64)), sb_items[redflag+10]); + if (blueflag) + Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 128)), sb_items[blueflag+14]); + } + + // armor + if (cl.stats[STAT_ARMOR] > 0) + { + Sbar_DrawStretchPic (72, 0, sb_armor[0], sbar_alpha_fg.value, 24, 24); + if(cl.stats[STAT_ARMOR] > 200) + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0,1,0,1,0); + else if(cl.stats[STAT_ARMOR] > 100) + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.2,1,0.2,1,0); + else if(cl.stats[STAT_ARMOR] > 50) + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.6,0.7,0.8,1,0); + else if(cl.stats[STAT_ARMOR] > 25) + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,1,1,0.2,1,0); + else + Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.7,0,0,1,0); + } + + // health + if (cl.stats[STAT_HEALTH] != 0) + { + Sbar_DrawStretchPic (184, 0, sb_health, sbar_alpha_fg.value, 24, 24); + if(cl.stats[STAT_HEALTH] > 200) + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0,1,0,1,0); + else if(cl.stats[STAT_HEALTH] > 100) + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.2,1,0.2,1,0); + else if(cl.stats[STAT_HEALTH] > 50) + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.6,0.7,0.8,1,0); + else if(cl.stats[STAT_HEALTH] > 25) + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,1,1,0.2,1,0); + else + Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.7,0,0,1,0); + } + + // ammo + if ((cl.stats[STAT_ITEMS] & (NEX_IT_SHELLS | NEX_IT_BULLETS | NEX_IT_ROCKETS | NEX_IT_CELLS)) || cl.stats[STAT_AMMO] != 0) + { + if (cl.stats[STAT_ITEMS] & NEX_IT_SHELLS) + Sbar_DrawStretchPic (296, 0, sb_ammo[0], sbar_alpha_fg.value, 24, 24); + else if (cl.stats[STAT_ITEMS] & NEX_IT_BULLETS) + Sbar_DrawStretchPic (296, 0, sb_ammo[1], sbar_alpha_fg.value, 24, 24); + else if (cl.stats[STAT_ITEMS] & NEX_IT_ROCKETS) + Sbar_DrawStretchPic (296, 0, sb_ammo[2], sbar_alpha_fg.value, 24, 24); + else if (cl.stats[STAT_ITEMS] & NEX_IT_CELLS) + Sbar_DrawStretchPic (296, 0, sb_ammo[3], sbar_alpha_fg.value, 24, 24); + if(cl.stats[STAT_AMMO] > 10) + Sbar_DrawXNum(224, 0, cl.stats[STAT_AMMO], 3, 24, 0.6,0.7,0.8,1,0); + else + Sbar_DrawXNum(224, 0, cl.stats[STAT_AMMO], 3, 24, 0.7,0,0,1,0); + } + + if (sbar_x + 320 + 160 <= vid_conwidth.integer) + Sbar_MiniDeathmatchOverlay (sbar_x + 320, sbar_y); + if (sbar_x > 0) + Sbar_Score(16); + // The margin can be at most 8 to support 640x480 console size: + // 320 + 2 * (144 + 16) = 640 + } + else if (sb_lines) + { + int i; + float fade; + int redflag, blueflag; + float x; + + sbar_x = (vid_conwidth.integer - 640)/2 + Sbar_GetXOffset(); + sbar_y = vid_conheight.integer - (Sbar_GetYOffset() + 40); + + // calculate intensity to draw weapons bar at + fade = 3 - 2 * (cl.time - cl.weapontime); + if (fade > 0) + { + fade = min(fade, 1); + for (i = 0; i < 8;i++) + if (cl.stats[STAT_ITEMS] & (1 << i)) + Sbar_DrawWeapon(i + 1, fade, (i + 2 == cl.stats[STAT_ACTIVEWEAPON])); + + if((cl.stats[STAT_ITEMS] & (1<<12))) + Sbar_DrawWeapon(0, fade, (cl.stats[STAT_ACTIVEWEAPON] == 1)); + } + + //if (!cl.islocalgame) + // Sbar_DrawFrags (); + + if (sb_lines > 24) + Sbar_DrawAlphaPic (0, 0, sb_sbar, sbar_alpha_fg.value); + else + Sbar_DrawAlphaPic (0, 0, sb_sbar_minimal, sbar_alpha_fg.value); + + // flag icons + redflag = ((cl.stats[STAT_ITEMS]>>15) & 3); + blueflag = ((cl.stats[STAT_ITEMS]>>17) & 3); + x = sbar_flagstatus_right.integer ? vid_conwidth.integer - 10 - sbar_x - 64 : 10 - sbar_x; + if (redflag == 3 && blueflag == 3) + { + // The Impossible Combination[tm] + // Can only happen in Key Hunt mode... + Sbar_DrawPic ((int) x, -179, sb_items[14]); + } + else + { + if (redflag) + Sbar_DrawPic ((int) x, -117, sb_items[redflag+10]); + if (blueflag) + Sbar_DrawPic ((int) x, -177, sb_items[blueflag+14]); + } + + // armor + Sbar_DrawXNum ((340-3*24), 12, cl.stats[STAT_ARMOR], 3, 24, 0.6,0.7,0.8,1,0); + + // health + if(cl.stats[STAT_HEALTH] > 100) + Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,1,1,1,1,0); + else if(cl.stats[STAT_HEALTH] <= 25 && cl.time - (int)cl.time > 0.5) + Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,0.7,0,0,1,0); + else + Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,0.6,0.7,0.8,1,0); + + // AK dont draw ammo for the laser + if(cl.stats[STAT_ACTIVEWEAPON] != 12) + { + if (cl.stats[STAT_ITEMS] & NEX_IT_SHELLS) + Sbar_DrawPic (519, 0, sb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & NEX_IT_BULLETS) + Sbar_DrawPic (519, 0, sb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & NEX_IT_ROCKETS) + Sbar_DrawPic (519, 0, sb_ammo[2]); + else if (cl.stats[STAT_ITEMS] & NEX_IT_CELLS) + Sbar_DrawPic (519, 0, sb_ammo[3]); + + if(cl.stats[STAT_AMMO] <= 10) + Sbar_DrawXNum ((519-3*24), 12, cl.stats[STAT_AMMO], 3, 24, 0.7, 0,0,1,0); + else + Sbar_DrawXNum ((519-3*24), 12, cl.stats[STAT_AMMO], 3, 24, 0.6, 0.7,0.8,1,0); + + } + + if (sb_lines > 24) + DrawQ_Pic(sbar_x,sbar_y,sb_sbar_overlay,0,0,1,1,1,1,DRAWFLAG_MODULATE); + + if (sbar_x + 600 + 160 <= vid_conwidth.integer) + Sbar_MiniDeathmatchOverlay (sbar_x + 600, sbar_y); + + if (sbar_x > 0) + Sbar_Score(-16); + // Because: + // Mini scoreboard uses 12*4 per other team, that is, 144 + // pixels when there are four teams... + // Nexuiz by default sets vid_conwidth to 800... makes + // sbar_x == 80... + // so we need to shift it by 64 pixels to the right to fit + // BUT: then it overlaps with the image that gets drawn + // for viewsize 100! Therefore, just account for 3 teams, + // that is, 96 pixels mini scoreboard size, needing 16 pixels + // to the right! + } + } + else if (gamemode == GAME_ZYMOTIC) + { +#if 1 + float scale = 64.0f / 256.0f; + float kickoffset[3]; + VectorClear(kickoffset); + if (v_dmg_time > 0) + { + kickoffset[0] = (v_dmg_time/v_kicktime.value*v_dmg_roll) * 10 * scale; + kickoffset[1] = (v_dmg_time/v_kicktime.value*v_dmg_pitch) * 10 * scale; + } + sbar_x = (int)((vid_conwidth.integer - 256 * scale)/2 + kickoffset[0]); + sbar_y = (int)((vid_conheight.integer - 256 * scale)/2 + kickoffset[1]); + // left1 16, 48 : 126 -66 + // left2 16, 128 : 196 -66 + // right 176, 48 : 196 -136 + Sbar_DrawGauge(sbar_x + 16 * scale, sbar_y + 48 * scale, zymsb_crosshair_left1, 64*scale, 80*scale, 78*scale, -66*scale, cl.stats[STAT_AMMO] * (1.0 / 200.0), cl.stats[STAT_SHELLS] * (1.0 / 200.0), 0.8f,0.8f,0.0f,1.0f, 0.8f,0.5f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); + Sbar_DrawGauge(sbar_x + 16 * scale, sbar_y + 128 * scale, zymsb_crosshair_left2, 64*scale, 80*scale, 68*scale, -66*scale, cl.stats[STAT_NAILS] * (1.0 / 200.0), cl.stats[STAT_ROCKETS] * (1.0 / 200.0), 0.8f,0.8f,0.0f,1.0f, 0.8f,0.5f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); + Sbar_DrawGauge(sbar_x + 176 * scale, sbar_y + 48 * scale, zymsb_crosshair_right, 64*scale, 160*scale, 148*scale, -136*scale, cl.stats[STAT_ARMOR] * (1.0 / 300.0), cl.stats[STAT_HEALTH] * (1.0 / 300.0), 0.0f,0.5f,1.0f,1.0f, 1.0f,0.0f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); + DrawQ_Pic(sbar_x + 120 * scale, sbar_y + 120 * scale, zymsb_crosshair_center, 16 * scale, 16 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); +#else + float scale = 128.0f / 256.0f; + float healthstart, healthheight, healthstarttc, healthendtc; + float shieldstart, shieldheight, shieldstarttc, shieldendtc; + float ammostart, ammoheight, ammostarttc, ammoendtc; + float clipstart, clipheight, clipstarttc, clipendtc; + float kickoffset[3], offset; + VectorClear(kickoffset); + if (v_dmg_time > 0) + { + kickoffset[0] = (v_dmg_time/v_kicktime.value*v_dmg_roll) * 10 * scale; + kickoffset[1] = (v_dmg_time/v_kicktime.value*v_dmg_pitch) * 10 * scale; + } + sbar_x = (vid_conwidth.integer - 256 * scale)/2 + kickoffset[0]; + sbar_y = (vid_conheight.integer - 256 * scale)/2 + kickoffset[1]; + offset = 0; // TODO: offset should be controlled by recoil (question: how to detect firing?) + DrawQ_SuperPic(sbar_x + 120 * scale, sbar_y + ( 88 - offset) * scale, zymsb_crosshair_line, 16 * scale, 36 * scale, 0,0, 1,1,1,1, 1,0, 1,1,1,1, 0,1, 1,1,1,1, 1,1, 1,1,1,1, 0); + DrawQ_SuperPic(sbar_x + (132 + offset) * scale, sbar_y + 120 * scale, zymsb_crosshair_line, 36 * scale, 16 * scale, 0,1, 1,1,1,1, 0,0, 1,1,1,1, 1,1, 1,1,1,1, 1,0, 1,1,1,1, 0); + DrawQ_SuperPic(sbar_x + 120 * scale, sbar_y + (132 + offset) * scale, zymsb_crosshair_line, 16 * scale, 36 * scale, 1,1, 1,1,1,1, 0,1, 1,1,1,1, 1,0, 1,1,1,1, 0,0, 1,1,1,1, 0); + DrawQ_SuperPic(sbar_x + ( 88 - offset) * scale, sbar_y + 120 * scale, zymsb_crosshair_line, 36 * scale, 16 * scale, 1,0, 1,1,1,1, 1,1, 1,1,1,1, 0,0, 1,1,1,1, 0,1, 1,1,1,1, 0); + healthheight = cl.stats[STAT_HEALTH] * (152.0f / 300.0f); + shieldheight = cl.stats[STAT_ARMOR] * (152.0f / 300.0f); + healthstart = 204 - healthheight; + shieldstart = healthstart - shieldheight; + healthstarttc = healthstart * (1.0f / 256.0f); + healthendtc = (healthstart + healthheight) * (1.0f / 256.0f); + shieldstarttc = shieldstart * (1.0f / 256.0f); + shieldendtc = (shieldstart + shieldheight) * (1.0f / 256.0f); + ammoheight = cl.stats[STAT_SHELLS] * (62.0f / 200.0f); + ammostart = 114 - ammoheight; + ammostarttc = ammostart * (1.0f / 256.0f); + ammoendtc = (ammostart + ammoheight) * (1.0f / 256.0f); + clipheight = cl.stats[STAT_AMMO] * (122.0f / 200.0f); + clipstart = 190 - clipheight; + clipstarttc = clipstart * (1.0f / 256.0f); + clipendtc = (clipstart + clipheight) * (1.0f / 256.0f); + if (healthheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + healthstart * scale, zymsb_crosshair_health, 256 * scale, healthheight * scale, 0,healthstarttc, 1.0f,0.0f,0.0f,1.0f, 1,healthstarttc, 1.0f,0.0f,0.0f,1.0f, 0,healthendtc, 1.0f,0.0f,0.0f,1.0f, 1,healthendtc, 1.0f,0.0f,0.0f,1.0f, DRAWFLAG_NORMAL); + if (shieldheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + shieldstart * scale, zymsb_crosshair_health, 256 * scale, shieldheight * scale, 0,shieldstarttc, 0.0f,0.5f,1.0f,1.0f, 1,shieldstarttc, 0.0f,0.5f,1.0f,1.0f, 0,shieldendtc, 0.0f,0.5f,1.0f,1.0f, 1,shieldendtc, 0.0f,0.5f,1.0f,1.0f, DRAWFLAG_NORMAL); + if (ammoheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + ammostart * scale, zymsb_crosshair_ammo, 256 * scale, ammoheight * scale, 0,ammostarttc, 0.8f,0.8f,0.0f,1.0f, 1,ammostarttc, 0.8f,0.8f,0.0f,1.0f, 0,ammoendtc, 0.8f,0.8f,0.0f,1.0f, 1,ammoendtc, 0.8f,0.8f,0.0f,1.0f, DRAWFLAG_NORMAL); + if (clipheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + clipstart * scale, zymsb_crosshair_clip, 256 * scale, clipheight * scale, 0,clipstarttc, 1.0f,1.0f,0.0f,1.0f, 1,clipstarttc, 1.0f,1.0f,0.0f,1.0f, 0,clipendtc, 1.0f,1.0f,0.0f,1.0f, 1,clipendtc, 1.0f,1.0f,0.0f,1.0f, DRAWFLAG_NORMAL); + DrawQ_Pic(sbar_x + 0 * scale, sbar_y + 0 * scale, zymsb_crosshair_background, 256 * scale, 256 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); + DrawQ_Pic(sbar_x + 120 * scale, sbar_y + 120 * scale, zymsb_crosshair_center, 16 * scale, 16 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); +#endif + } + else // Quake and others + { + //Include stereo offset in x + sbar_x = (vid_conwidth.integer - 320)/2 + Sbar_GetXOffset(); + sbar_y = vid_conheight.integer - (Sbar_GetYOffset() + 40) - SBAR_HEIGHT; + // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode + //if (cl.gametype == GAME_DEATHMATCH && gamemode != GAME_TRANSFUSION) + + if (sb_lines > 24) + { + if (gamemode != GAME_GOODVSBAD2) + Sbar_DrawInventory (); + if (!cl.islocalgame && gamemode != GAME_TRANSFUSION) + Sbar_DrawFrags (); + } + + if (sb_showscores || (cl.stats[STAT_HEALTH] <= 0 && cl_deathscoreboard.integer)) + { + if (gamemode != GAME_GOODVSBAD2) + Sbar_DrawAlphaPic (0, 0, sb_scorebar, sbar_alpha_bg.value); + Sbar_DrawScoreboard (); + } + else if (sb_lines) + { + Sbar_DrawAlphaPic (0, 0, sb_sbar, sbar_alpha_bg.value); + + // keys (hipnotic only) + //MED 01/04/97 moved keys here so they would not be overwritten + if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) + { + if (cl.stats[STAT_ITEMS] & IT_KEY1) + Sbar_DrawPic (209, 3, sb_items[0]); + if (cl.stats[STAT_ITEMS] & IT_KEY2) + Sbar_DrawPic (209, 12, sb_items[1]); + } + // armor + if (gamemode != GAME_GOODVSBAD2) + { + if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) + { + Sbar_DrawNum (24, 0, 666, 3, 1); + Sbar_DrawPic (0, 0, sb_disc); + } + else + { + if (gamemode == GAME_ROGUE) + { + Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25); + if (cl.stats[STAT_ITEMS] & RIT_ARMOR3) + Sbar_DrawPic (0, 0, sb_armor[2]); + else if (cl.stats[STAT_ITEMS] & RIT_ARMOR2) + Sbar_DrawPic (0, 0, sb_armor[1]); + else if (cl.stats[STAT_ITEMS] & RIT_ARMOR1) + Sbar_DrawPic (0, 0, sb_armor[0]); + } + else + { + Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25); + if (cl.stats[STAT_ITEMS] & IT_ARMOR3) + Sbar_DrawPic (0, 0, sb_armor[2]); + else if (cl.stats[STAT_ITEMS] & IT_ARMOR2) + Sbar_DrawPic (0, 0, sb_armor[1]); + else if (cl.stats[STAT_ITEMS] & IT_ARMOR1) + Sbar_DrawPic (0, 0, sb_armor[0]); + } + } + } + + // face + Sbar_DrawFace (); + + // health + Sbar_DrawNum (136, 0, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25); + + // ammo icon + if (gamemode == GAME_ROGUE) + { + if (cl.stats[STAT_ITEMS] & RIT_SHELLS) + Sbar_DrawPic (224, 0, sb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & RIT_NAILS) + Sbar_DrawPic (224, 0, sb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & RIT_ROCKETS) + Sbar_DrawPic (224, 0, sb_ammo[2]); + else if (cl.stats[STAT_ITEMS] & RIT_CELLS) + Sbar_DrawPic (224, 0, sb_ammo[3]); + else if (cl.stats[STAT_ITEMS] & RIT_LAVA_NAILS) + Sbar_DrawPic (224, 0, rsb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & RIT_PLASMA_AMMO) + Sbar_DrawPic (224, 0, rsb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & RIT_MULTI_ROCKETS) + Sbar_DrawPic (224, 0, rsb_ammo[2]); + } + else + { + if (cl.stats[STAT_ITEMS] & IT_SHELLS) + Sbar_DrawPic (224, 0, sb_ammo[0]); + else if (cl.stats[STAT_ITEMS] & IT_NAILS) + Sbar_DrawPic (224, 0, sb_ammo[1]); + else if (cl.stats[STAT_ITEMS] & IT_ROCKETS) + Sbar_DrawPic (224, 0, sb_ammo[2]); + else if (cl.stats[STAT_ITEMS] & IT_CELLS) + Sbar_DrawPic (224, 0, sb_ammo[3]); + } + + Sbar_DrawNum (248, 0, cl.stats[STAT_AMMO], 3, cl.stats[STAT_AMMO] <= 10); + + // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode + if ((!cl.islocalgame || cl.gametype != GAME_COOP)) + { + if (gamemode == GAME_TRANSFUSION) + Sbar_MiniDeathmatchOverlay (0, 0); + else + Sbar_MiniDeathmatchOverlay (sbar_x + 324, vid_conheight.integer - 8*8); + Sbar_Score(24); + } + } + } + } + + if (cl.csqc_vidvars.drawcrosshair && crosshair.integer >= 1 && !cl.intermission && !r_letterbox.value) + { + pic = Draw_CachePic (va(vabuf, sizeof(vabuf), "gfx/crosshair%i", crosshair.integer)); + int stereoOffset = r_worldscale.value > 200.0f ? 12 : 5; + int yOffset = (r_worldscale.value > 200.0f ? 20 : 5); + DrawQ_Pic((vid_conwidth.integer - pic->width * crosshair_size.value) * 0.5f + (r_stereo_side ? -stereoOffset : stereoOffset), + (vid_conheight.integer - pic->height * crosshair_size.value) * 0.5f + yOffset, + pic, pic->width * crosshair_size.value, pic->height * crosshair_size.value, + crosshair_color_red.value, crosshair_color_green.value, crosshair_color_blue.value, crosshair_color_alpha.value, 0); + } + + if (cl_prydoncursor.integer > 0) + DrawQ_Pic((cl.cmd.cursor_screen[0] + 1) * 0.5 * vid_conwidth.integer, (cl.cmd.cursor_screen[1] + 1) * 0.5 * vid_conheight.integer, Draw_CachePic (va(vabuf, sizeof(vabuf), "gfx/prydoncursor%03i", cl_prydoncursor.integer)), 0, 0, 1, 1, 1, 1, 0); +} + +//============================================================================= + +/* +================== +Sbar_DeathmatchOverlay + +================== +*/ +static float Sbar_PrintScoreboardItem(scoreboard_t *s, float x, float y) +{ + int minutes; + qboolean myself = false; + unsigned char *c; + char vabuf[1024]; + minutes = (int)((cl.intermission ? cl.completed_time - s->qw_entertime : cl.time - s->qw_entertime) / 60.0); + + if((s - cl.scores) == cl.playerentity - 1) + myself = true; + if((s - teams) >= 0 && (s - teams) < MAX_SCOREBOARD) + if((s->colors & 15) == (cl.scores[cl.playerentity - 1].colors & 15)) + myself = true; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + if (s->qw_spectator) + { + if (s->qw_ping || s->qw_packetloss) + DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%4i %3i %4i spectator %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + else + DrawQ_String(x, y, va(vabuf, sizeof(vabuf), " %4i spectator %c%s", minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + else + { + // draw colors behind score + // + // + // + // + // + c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; + DrawQ_Fill(x + 14*8*FONT_SBAR->maxwidth, y+1, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + c = palette_rgb_shirtscoreboard[s->colors & 0xf]; + DrawQ_Fill(x + 14*8*FONT_SBAR->maxwidth, y+4, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + // print the text + //DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%c%4i %s", myself ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); + if (s->qw_ping || s->qw_packetloss) + DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%4i %3i %4i %5i %-4s %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + else + DrawQ_String(x, y, va(vabuf, sizeof(vabuf), " %4i %5i %-4s %c%s", minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + } + else + { + if (s->qw_spectator) + { + if (s->qw_ping || s->qw_packetloss) + DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%4i %3i spect %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + else + DrawQ_String(x, y, va(vabuf, sizeof(vabuf), " spect %c%s", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + else + { + // draw colors behind score + c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; + DrawQ_Fill(x + 9*8*FONT_SBAR->maxwidth, y+1, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + c = palette_rgb_shirtscoreboard[s->colors & 0xf]; + DrawQ_Fill(x + 9*8*FONT_SBAR->maxwidth, y+4, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); + // print the text + //DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%c%4i %s", myself ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); + if (s->qw_ping || s->qw_packetloss) + DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%4i %3i %5i %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + else + DrawQ_String(x, y, va(vabuf, sizeof(vabuf), " %5i %c%s", (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + } + return 8; +} + +void Sbar_DeathmatchOverlay (void) +{ + int i, y, xmin, xmax, ymin, ymax; + char vabuf[1024]; + + // request new ping times every two second + if (cl.last_ping_request < realtime - 2 && cls.netcon) + { + cl.last_ping_request = realtime; + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "pings"); + } + else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3 || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5 || cls.protocol == PROTOCOL_DARKPLACES6/* || cls.protocol == PROTOCOL_DARKPLACES7*/) + { + // these servers usually lack the pings command and so a less efficient "ping" command must be sent, which on modern DP servers will also reply with a pingplreport command after the ping listing + static int ping_anyway_counter = 0; + if(cl.parsingtextexpectingpingforscores == 1) + { + Con_DPrintf("want to send ping, but still waiting for other reply\n"); + if(++ping_anyway_counter >= 5) + cl.parsingtextexpectingpingforscores = 0; + } + if(cl.parsingtextexpectingpingforscores != 1) + { + ping_anyway_counter = 0; + cl.parsingtextexpectingpingforscores = 1; // hide the output of the next ping report + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "ping"); + } + } + else + { + // newer server definitely has pings command, so use it for more efficiency, avoids ping reports spamming the console if they are misparsed, and saves a little bandwidth + MSG_WriteByte(&cls.netcon->message, clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "pings"); + } + } + + // scores + Sbar_SortFrags (); + + ymin = 8; + ymax = 40 + 8 + (Sbar_IsTeammatch() ? (teamlines * 8 + 5): 0) + scoreboardlines * 8 - 1; + + if (cls.protocol == PROTOCOL_QUAKEWORLD) + xmin = (int) (vid_conwidth.integer - (26 + 15) * 8 * FONT_SBAR->maxwidth) / 2; // 26 characters until name, then we assume 15 character names (they can be longer but usually aren't) + else + xmin = (int) (vid_conwidth.integer - (16 + 25) * 8 * FONT_SBAR->maxwidth) / 2; // 16 characters until name, then we assume 25 character names (they can be longer but usually aren't) + xmax = vid_conwidth.integer - xmin; + + if(gamemode == GAME_NEXUIZ) + DrawQ_Pic (xmin - 8, ymin - 8, 0, xmax-xmin+1 + 2*8, ymax-ymin+1 + 2*8, 0, 0, 0, sbar_alpha_bg.value, 0); + + DrawQ_Pic ((vid_conwidth.integer - sb_ranking->width)/2, 8, sb_ranking, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); + + // draw the text + y = 40; + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + DrawQ_String(xmin, y, va(vabuf, sizeof(vabuf), "ping pl%% time frags team name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + else + { + DrawQ_String(xmin, y, va(vabuf, sizeof(vabuf), "ping pl%% frags name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); + } + y += 8; + + if (Sbar_IsTeammatch ()) + { + // show team scores first + for (i = 0;i < teamlines && y < vid_conheight.integer;i++) + y += (int)Sbar_PrintScoreboardItem((teams + teamsort[i]), xmin, y); + y += 5; + } + + for (i = 0;i < scoreboardlines && y < vid_conheight.integer;i++) + y += (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], xmin, y); +} + +/* +================== +Sbar_MiniDeathmatchOverlay + +================== +*/ +void Sbar_MiniDeathmatchOverlay (int x, int y) +{ + int i, j, numlines, range_begin, range_end, myteam, teamsep; + + // do not draw this if sbar_miniscoreboard_size is zero + if(sbar_miniscoreboard_size.value == 0) + return; + // adjust the given y if sbar_miniscoreboard_size doesn't indicate default (< 0) + if(sbar_miniscoreboard_size.value > 0) + y = (int) (vid_conheight.integer - sbar_miniscoreboard_size.value * 8); + + // scores + Sbar_SortFrags (); + + // decide where to print + if (gamemode == GAME_TRANSFUSION) + numlines = (vid_conwidth.integer - x + 127) / 128; + else + numlines = (vid_conheight.integer - y + 7) / 8; + + // give up if there isn't room + if (x >= vid_conwidth.integer || y >= vid_conheight.integer || numlines < 1) + return; + + //find us + for (i = 0; i < scoreboardlines; i++) + if (fragsort[i] == cl.playerentity - 1) + break; + + range_begin = 0; + range_end = scoreboardlines; + teamsep = 0; + + if (gamemode != GAME_TRANSFUSION) + if (Sbar_IsTeammatch ()) + { + // reserve space for the team scores + numlines -= teamlines; + + // find first and last player of my team (only draw the team totals and my own team) + range_begin = range_end = i; + myteam = cl.scores[fragsort[i]].colors & 15; + while(range_begin > 0 && (cl.scores[fragsort[range_begin-1]].colors & 15) == myteam) + --range_begin; + while(range_end < scoreboardlines && (cl.scores[fragsort[range_end]].colors & 15) == myteam) + ++range_end; + + // looks better than two players + if(numlines == 2) + { + teamsep = 8; + numlines = 1; + } + } + + // figure out start + i -= numlines/2; + i = min(i, range_end - numlines); + i = max(i, range_begin); + + if (gamemode == GAME_TRANSFUSION) + { + for (;i < range_end && x < vid_conwidth.integer;i++) + x += 128 + (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], x, y); + } + else + { + if(range_end - i < numlines) // won't draw to bottom? + y += 8 * (numlines - (range_end - i)); // bottom align + // show team scores first + for (j = 0;j < teamlines && y < vid_conheight.integer;j++) + y += (int)Sbar_PrintScoreboardItem((teams + teamsort[j]), x, y); + y += teamsep; + for (;i < range_end && y < vid_conheight.integer;i++) + y += (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], x, y); + } +} + +static int Sbar_TeamColorCompare(const void *t1_, const void *t2_) +{ + static int const sortorder[16] = + { + 1001, + 1002, + 1003, + 1004, + 1, // red + 1005, + 1006, + 1007, + 1008, + 4, // pink + 1009, + 1010, + 3, // yellow + 2, // blue + 1011, + 1012 + }; + const scoreboard_t *t1 = *(scoreboard_t **) t1_; + const scoreboard_t *t2 = *(scoreboard_t **) t2_; + int tc1 = sortorder[t1->colors & 15]; + int tc2 = sortorder[t2->colors & 15]; + return tc1 - tc2; +} + +void Sbar_Score (int margin) +{ + int i, me, score, otherleader, place, distribution, minutes, seconds; + double timeleft; + int sbar_x_save = sbar_x; + int sbar_y_save = sbar_y; + + + sbar_y = (int) (vid_conheight.value - (Sbar_GetYOffset() + 44)); + sbar_x -= margin; + + me = cl.playerentity - 1; + if (sbar_scorerank.integer && me >= 0 && me < cl.maxclients) + { + if(Sbar_IsTeammatch()) + { + // Layout: + // + // team1 team3 team4 + // + // TEAM2 + + scoreboard_t *teamcolorsort[16]; + + Sbar_SortFrags(); + for(i = 0; i < teamlines; ++i) + teamcolorsort[i] = &(teams[i]); + + // Now sort them by color + qsort(teamcolorsort, teamlines, sizeof(*teamcolorsort), Sbar_TeamColorCompare); + + // : margin + // -12*4: four digits space + place = (teamlines - 1) * (-12 * 4); + + for(i = 0; i < teamlines; ++i) + { + int cindex = teamcolorsort[i]->colors & 15; + unsigned char *c = palette_rgb_shirtscoreboard[cindex]; + float cm = max(max(c[0], c[1]), c[2]); + float cr = c[0] / cm; + float cg = c[1] / cm; + float cb = c[2] / cm; + if(cindex == (cl.scores[cl.playerentity - 1].colors & 15)) // my team + { + Sbar_DrawXNum(-32*4, 0, teamcolorsort[i]->frags, 4, 32, cr, cg, cb, 1, 0); + } + else // other team + { + Sbar_DrawXNum(place, -12, teamcolorsort[i]->frags, 4, 12, cr, cg, cb, 1, 0); + place += 4 * 12; + } + } + } + else + { + // Layout: + // + // leading place + // + // FRAGS + // + // find leading score other than ourselves, to calculate distribution + // find our place in the scoreboard + score = cl.scores[me].frags; + for (i = 0, otherleader = -1, place = 1;i < cl.maxclients;i++) + { + if (cl.scores[i].name[0] && i != me) + { + if (otherleader == -1 || cl.scores[i].frags > cl.scores[otherleader].frags) + otherleader = i; + if (score < cl.scores[i].frags || (score == cl.scores[i].frags && i < me)) + place++; + } + } + distribution = otherleader >= 0 ? score - cl.scores[otherleader].frags : 0; + if (place == 1) + Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 1, 1, 1, 0); + else if (place == 2) + Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 1, 0, 1, 0); + else + Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 0, 0, 1, 0); + if (otherleader < 0) + Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 1, 1, 0); + if (distribution >= 0) + { + Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 1, 1, 1, 0); + Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 1, 1, 0); + } + else if (distribution >= -5) + { + Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 1, 0, 1, 0); + Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 0, 1, 0); + } + else + { + Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 0, 0, 1, 0); + Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 0, 0, 1, 0); + } + } + } + + if (sbar_gametime.integer && cl.statsf[STAT_TIMELIMIT]) + { + timeleft = max(0, cl.statsf[STAT_TIMELIMIT] * 60 - cl.time); + minutes = (int)floor(timeleft / 60); + seconds = (int)(floor(timeleft) - minutes * 60); + if (minutes >= 5) + { + Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 1, 1, 0); + if(sb_colon && sb_colon->tex != r_texture_notexture) + DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 1, sbar_alpha_fg.value, 0); + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); + } + else if (minutes >= 1) + { + Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 0, 1, 0); + if(sb_colon && sb_colon->tex != r_texture_notexture) + DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 0, sbar_alpha_fg.value, 0); + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 0, 1, 0); + } + else if ((int)(timeleft * 4) & 1) + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); + else + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 0, 0, 1, 0); + } + else if (sbar_gametime.integer) + { + minutes = (int)floor(cl.time / 60); + seconds = (int)(floor(cl.time) - minutes * 60); + Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 1, 1, 0); + if(sb_colon && sb_colon->tex != r_texture_notexture) + DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 1, sbar_alpha_fg.value, 0); + Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); + } + + sbar_x = sbar_x_save; + sbar_y = sbar_y_save; +} + +/* +================== +Sbar_IntermissionOverlay + +================== +*/ +void Sbar_IntermissionOverlay (void) +{ + int dig; + int num; + + if (cl.gametype == GAME_DEATHMATCH) + { + Sbar_DeathmatchOverlay (); + return; + } + + sbar_x = ((vid_conwidth.integer - 320) >> 1) + Sbar_GetXOffset(); + sbar_y = (vid_conheight.integer - 200) >> 1; + + DrawQ_Pic (sbar_x + 64, sbar_y + 24, sb_complete, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); + DrawQ_Pic (sbar_x + 0, sbar_y + 56, sb_inter, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); + +// time + dig = (int)cl.completed_time / 60; + Sbar_DrawNum (160, 64, dig, 3, 0); + num = (int)cl.completed_time - dig*60; + Sbar_DrawPic (234,64,sb_colon); + Sbar_DrawPic (246,64,sb_nums[0][num/10]); + Sbar_DrawPic (266,64,sb_nums[0][num%10]); + +// LA: Display as "a" instead of "a/b" if b is 0 + if(cl.stats[STAT_TOTALSECRETS]) + { + Sbar_DrawNum (160, 104, cl.stats[STAT_SECRETS], 3, 0); + if (gamemode != GAME_NEXUIZ) + Sbar_DrawPic (232, 104, sb_slash); + Sbar_DrawNum (240, 104, cl.stats[STAT_TOTALSECRETS], 3, 0); + } + else + { + Sbar_DrawNum (240, 104, cl.stats[STAT_SECRETS], 3, 0); + } + + if(cl.stats[STAT_TOTALMONSTERS]) + { + Sbar_DrawNum (160, 144, cl.stats[STAT_MONSTERS], 3, 0); + if (gamemode != GAME_NEXUIZ) + Sbar_DrawPic (232, 144, sb_slash); + Sbar_DrawNum (240, 144, cl.stats[STAT_TOTALMONSTERS], 3, 0); + } + else + { + Sbar_DrawNum (240, 144, cl.stats[STAT_MONSTERS], 3, 0); + } +} + + +/* +================== +Sbar_FinaleOverlay + +================== +*/ +void Sbar_FinaleOverlay (void) +{ + DrawQ_Pic((vid_conwidth.integer - sb_finale->width)/2, 16, sb_finale, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); +} + diff --git a/app/jni/sbar.h b/app/jni/sbar.h new file mode 100644 index 0000000..0739756 --- /dev/null +++ b/app/jni/sbar.h @@ -0,0 +1,39 @@ +/* +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. + +*/ + +#ifndef SBAR_H +#define SBAR_H + +#define SBAR_HEIGHT 24 + +extern int sb_lines; ///< scan lines to draw +extern cvar_t sbar_alpha_bg; +extern cvar_t sbar_alpha_fg; + +void Sbar_Init (void); + +/// called every frame by screen +void Sbar_Draw (void); + +int Sbar_GetSortedPlayerIndex (int index); +void Sbar_SortFrags (void); + +#endif + diff --git a/app/jni/screen.h b/app/jni/screen.h new file mode 100644 index 0000000..cd9305d --- /dev/null +++ b/app/jni/screen.h @@ -0,0 +1,87 @@ +/* +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. + +*/ +// screen.h + +#ifndef SCREEN_H +#define SCREEN_H + +void CL_Screen_Init (void); +void CL_BeginUpdateScreen (); +void CL_EndUpdateScreen (); +void SCR_CenterPrint(const char *str); + +void SCR_BeginLoadingPlaque (qboolean startup); + +// invoke refresh of loading plaque (nothing else seen) +void SCR_UpdateLoadingScreen(qboolean clear, qboolean startup); +void SCR_UpdateLoadingScreenIfShown(void); + +// pushes an item on the loading screen +void SCR_PushLoadingScreen (qboolean redraw, const char *msg, float len_in_parent); +void SCR_PopLoadingScreen (qboolean redraw); +void SCR_ClearLoadingScreen (qboolean redraw); + +extern float scr_con_current; // current height of displayed console + +extern int sb_lines; + +extern cvar_t scr_viewsize; +extern cvar_t scr_fov; +extern cvar_t showfps; +extern cvar_t showtime; +extern cvar_t showdate; + +extern cvar_t crosshair; +extern cvar_t crosshair_size; + +extern cvar_t scr_conalpha; +extern cvar_t scr_conalphafactor; +extern cvar_t scr_conalpha2factor; +extern cvar_t scr_conalpha3factor; +extern cvar_t scr_conscroll_x; +extern cvar_t scr_conscroll_y; +extern cvar_t scr_conscroll2_x; +extern cvar_t scr_conscroll2_y; +extern cvar_t scr_conscroll3_x; +extern cvar_t scr_conscroll3_y; +extern cvar_t scr_conbrightness; +extern cvar_t r_letterbox; + +extern cvar_t scr_refresh; +extern cvar_t scr_stipple; + +extern cvar_t r_stereo_separation; +extern cvar_t r_stereo_angle; +qboolean R_Stereo_Active(void); +extern int r_stereo_side; + +typedef struct scr_touchscreenarea_s +{ + const char *pic; + float rect[4]; + float active; +} +scr_touchscreenarea_t; + +extern int scr_numtouchscreenareas; +extern scr_touchscreenarea_t scr_touchscreenareas[16]; + +#endif + diff --git a/app/jni/server.h b/app/jni/server.h new file mode 100644 index 0000000..669174b --- /dev/null +++ b/app/jni/server.h @@ -0,0 +1,615 @@ +/* +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. + +*/ +// server.h + +#ifndef SERVER_H +#define SERVER_H + +typedef struct server_static_s +{ + /// number of svs.clients slots (updated by maxplayers command) + int maxclients, maxclients_next; + /// client slots + struct client_s *clients; + /// episode completion information + int serverflags; + /// cleared when at SV_SpawnServer + qboolean changelevel_issued; + /// server infostring + char serverinfo[MAX_SERVERINFO_STRING]; + // performance data + float perf_cpuload; + float perf_lost; + float perf_offset_avg; + float perf_offset_max; + float perf_offset_sdev; + // temporary performance data accumulators + float perf_acc_realtime; + float perf_acc_sleeptime; + float perf_acc_lost; + float perf_acc_offset; + float perf_acc_offset_squared; + float perf_acc_offset_max; + int perf_acc_offset_samples; + + // csqc stuff + unsigned char *csqc_progdata; + size_t csqc_progsize_deflated; + unsigned char *csqc_progdata_deflated; + + // independent server thread (when running client) + qboolean threaded; // true if server is running on separate thread + qboolean volatile threadstop; + void *threadmutex; + void *thread; +} server_static_t; + +//============================================================================= + +typedef enum server_state_e {ss_loading, ss_active} server_state_t; + +#define MAX_CONNECTFLOODADDRESSES 16 +#define MAX_GETSTATUSFLOODADDRESSES 128 +typedef struct server_floodaddress_s +{ + double lasttime; + lhnetaddress_t address; +} +server_floodaddress_t; + +typedef struct server_s +{ + /// false if only a net client + qboolean active; + + qboolean paused; + double pausedstart; + /// handle connections specially + qboolean loadgame; + + /// one of the PROTOCOL_ values + protocolversion_t protocol; + + double time; + + double frametime; + + // used by PF_checkclient + int lastcheck; + double lastchecktime; + + // crc of clientside progs at time of level start + int csqc_progcrc; // -1 = no progs + int csqc_progsize; // -1 = no progs + char csqc_progname[MAX_QPATH]; // copied from csqc_progname at level start + + /// collision culling data + world_t world; + + /// map name + char name[64]; // %s followed by entrance name + // variants of map name + char worldmessage[40]; // map title (not related to filename) + char worldbasename[MAX_QPATH]; // %s + char worldname[MAX_QPATH]; // maps/%s.bsp + char worldnamenoextension[MAX_QPATH]; // maps/%s + struct model_s *worldmodel; + // NULL terminated + // LordHavoc: precaches are now MAX_QPATH rather than a pointer + // updated by SV_ModelIndex + char model_precache[MAX_MODELS][MAX_QPATH]; + struct model_s *models[MAX_MODELS]; + // NULL terminated + // LordHavoc: precaches are now MAX_QPATH rather than a pointer + // updated by SV_SoundIndex + char sound_precache[MAX_SOUNDS][MAX_QPATH]; + char lightstyles[MAX_LIGHTSTYLES][64]; + /// some actions are only valid during load + server_state_t state; + + sizebuf_t datagram; + unsigned char datagram_buf[NET_MAXMESSAGE]; + + // copied to all clients at end of frame + sizebuf_t reliable_datagram; + unsigned char reliable_datagram_buf[NET_MAXMESSAGE]; + + sizebuf_t signon; + /// LordHavoc: increased signon message buffer from 8192 + unsigned char signon_buf[NET_MAXMESSAGE]; + + /// connection flood blocking + /// note this is in server_t rather than server_static_t so that it is + /// reset on each map command (such as New Game in singleplayer) + server_floodaddress_t connectfloodaddresses[MAX_CONNECTFLOODADDRESSES]; + server_floodaddress_t getstatusfloodaddresses[MAX_GETSTATUSFLOODADDRESSES]; + +#define SV_MAX_PARTICLEEFFECTNAME 256 + qboolean particleeffectnamesloaded; + char particleeffectname[SV_MAX_PARTICLEEFFECTNAME][MAX_QPATH]; + + int writeentitiestoclient_stats_culled_pvs; + int writeentitiestoclient_stats_culled_trace; + int writeentitiestoclient_stats_visibleentities; + int writeentitiestoclient_stats_totalentities; + int writeentitiestoclient_cliententitynumber; + int writeentitiestoclient_clientnumber; + sizebuf_t *writeentitiestoclient_msg; + vec3_t writeentitiestoclient_eyes[MAX_CLIENTNETWORKEYES]; + int writeentitiestoclient_numeyes; + int writeentitiestoclient_pvsbytes; + unsigned char writeentitiestoclient_pvs[MAX_MAP_LEAFS/8]; + const entity_state_t *writeentitiestoclient_sendstates[MAX_EDICTS]; + unsigned short writeentitiestoclient_csqcsendstates[MAX_EDICTS]; + + int numsendentities; + entity_state_t sendentities[MAX_EDICTS]; + entity_state_t *sendentitiesindex[MAX_EDICTS]; + + int sententitiesmark; + int sententities[MAX_EDICTS]; + int sententitiesconsideration[MAX_EDICTS]; + + /// legacy support for self.Version based csqc entity networking + unsigned char csqcentityversion[MAX_EDICTS]; // legacy +} server_t; + +#define NUM_CSQCENTITIES_PER_FRAME 256 +typedef struct csqcentityframedb_s +{ + int framenum; + int num; + unsigned short entno[NUM_CSQCENTITIES_PER_FRAME]; + int sendflags[NUM_CSQCENTITIES_PER_FRAME]; +} csqcentityframedb_t; + +// if defined this does ping smoothing, otherwise it does not +//#define NUM_PING_TIMES 16 + +#define NUM_SPAWN_PARMS 16 + +typedef struct client_s +{ + /// false = empty client slot + qboolean active; + /// false = don't do ClientDisconnect on drop + qboolean clientconnectcalled; + /// false = don't allow spawn + qboolean prespawned; + /// false = don't allow begin + qboolean spawned; + /// false = don't send datagrams + qboolean begun; + /// 1 = send svc_serverinfo and advance to 2, 2 doesn't send, then advances to 0 (allowing unlimited sending) when prespawn is received + int sendsignon; + + /// requested rate in bytes per second + int rate; + + /// realtime this client connected + double connecttime; + + /// keepalive messages must be sent periodically during signon + double keepalivetime; + + /// communications handle + netconn_t *netconnection; + + int movesequence; + signed char movement_count[NETGRAPH_PACKETS]; + int movement_highestsequence_seen; // not the same as movesequence if prediction is off + /// movement + usercmd_t cmd; + /// intended motion calced from cmd + vec3_t wishdir; + + /// PRVM_EDICT_NUM(clientnum+1) + prvm_edict_t *edict; + +#ifdef NUM_PING_TIMES + float ping_times[NUM_PING_TIMES]; + /// ping_times[num_pings%NUM_PING_TIMES] + int num_pings; +#endif + /// LordHavoc: can be used for prediction or whatever... + float ping; + + /// this is used by sv_clmovement_minping code + double clmovement_disabletimeout; + /// this is used by sv_clmovement_inputtimeout code + float clmovement_inputtimeout; + +/// spawn parms are carried from level to level + prvm_vec_t spawn_parms[NUM_SPAWN_PARMS]; + + // properties that are sent across the network only when changed + char name[MAX_SCOREBOARDNAME], old_name[MAX_SCOREBOARDNAME]; + int colors, old_colors; + int frags, old_frags; + char playermodel[MAX_QPATH], old_model[MAX_QPATH]; + char playerskin[MAX_QPATH], old_skin[MAX_QPATH]; + + /// netaddress support + char netaddress[MAX_QPATH]; + + /// visibility state + float visibletime[MAX_EDICTS]; + + // scope is whether an entity is currently being networked to this client + // sendflags is what properties have changed on the entity since the last + // update that was sent + int csqcnumedicts; + unsigned char csqcentityscope[MAX_EDICTS]; + unsigned int csqcentitysendflags[MAX_EDICTS]; + +#define NUM_CSQCENTITYDB_FRAMES 256 + unsigned char csqcentityglobalhistory[MAX_EDICTS]; // set to 1 if the entity was ever csqc networked to the client, and never reset back to 0 + csqcentityframedb_t csqcentityframehistory[NUM_CSQCENTITYDB_FRAMES]; + int csqcentityframehistory_next; + int csqcentityframe_lastreset; + + /// prevent animated names + float nametime; + + /// latest received clc_ackframe (used to detect packet loss) + int latestframenum; + + /// cache weaponmodel name lookups + char weaponmodel[MAX_QPATH]; + int weaponmodelindex; + + /// clientcamera (entity to use as camera) + int clientcamera; + + entityframe_database_t *entitydatabase; + entityframe4_database_t *entitydatabase4; + entityframe5_database_t *entitydatabase5; + + // delta compression of stats + unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; + int stats[MAX_CL_STATS]; + + unsigned char unreliablemsg_data[NET_MAXMESSAGE]; + sizebuf_t unreliablemsg; + int unreliablemsg_splitpoints; + int unreliablemsg_splitpoint[NET_MAXMESSAGE/16]; + + // information on an active download if any + qfile_t *download_file; + int download_expectedposition; ///< next position the client should ack + qboolean download_started; + char download_name[MAX_QPATH]; + qboolean download_deflate; + + // fixangle data + qboolean fixangle_angles_set; + vec3_t fixangle_angles; + + /// demo recording + qfile_t *sv_demo_file; + + // number of skipped entity frames + // if it exceeds a limit, an empty entity frame is sent + int num_skippedentityframes; + + // last sent move sequence + // if the move sequence changed, an empty entity frame is sent + int lastmovesequence; +} client_t; + + +//============================================================================= + +// edict->movetype values +#define MOVETYPE_NONE 0 ///< never moves +#define MOVETYPE_ANGLENOCLIP 1 +#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 ///< gravity +#define MOVETYPE_STEP 4 ///< gravity, special edge handling +#define MOVETYPE_FLY 5 +#define MOVETYPE_TOSS 6 ///< gravity +#define MOVETYPE_PUSH 7 ///< no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 +#define MOVETYPE_FLYMISSILE 9 ///< extra size to monsters +#define MOVETYPE_BOUNCE 10 +#define MOVETYPE_BOUNCEMISSILE 11 ///< bounce w/o gravity +#define MOVETYPE_FOLLOW 12 ///< track movement of aiment +#define MOVETYPE_FAKEPUSH 13 ///< tenebrae's push that doesn't push +#define MOVETYPE_PHYSICS 32 ///< indicates this object is physics controlled +#define MOVETYPE_FLY_WORLDONLY 33 ///< like MOVETYPE_FLY, but uses MOVE_WORLDONLY for all its traces; objects of this movetype better be SOLID_NOT or SOLID_TRIGGER please, or else... + +// edict->solid values +#define SOLID_NOT 0 ///< no interaction with other objects +#define SOLID_TRIGGER 1 ///< touch on edge, but not blocking +#define SOLID_BBOX 2 ///< touch on edge, block +#define SOLID_SLIDEBOX 3 ///< touch on edge, but not an onground +#define SOLID_BSP 4 ///< bsp clip, touch on edge, block +// LordHavoc: corpse code +#define SOLID_CORPSE 5 ///< same as SOLID_BBOX, except it behaves as SOLID_NOT against SOLID_SLIDEBOX objects (players/monsters) +// LordHavoc: physics +// VorteX: now these fields are deprecated, as geomtype is more flexible +#define SOLID_PHYSICS_BOX 32 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) +#define SOLID_PHYSICS_SPHERE 33 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) +#define SOLID_PHYSICS_CAPSULE 34 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) +#define SOLID_PHYSICS_TRIMESH 35 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) +#define SOLID_PHYSICS_CYLINDER 36 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) + +// edict->deadflag values +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// edict->flags +#define FL_FLY 1 +#define FL_SWIM 2 +#define FL_CONVEYOR 4 +#define FL_CLIENT 8 +#define FL_INWATER 16 +#define FL_MONSTER 32 +#define FL_GODMODE 64 +#define FL_NOTARGET 128 +#define FL_ITEM 256 +#define FL_ONGROUND 512 +#define FL_PARTIALGROUND 1024 ///< not all corners are valid +#define FL_WATERJUMP 2048 ///< player jumping out of water +#define FL_JUMPRELEASED 4096 ///< for jump debouncing + +#define SPAWNFLAG_NOT_EASY 256 +#define SPAWNFLAG_NOT_MEDIUM 512 +#define SPAWNFLAG_NOT_HARD 1024 +#define SPAWNFLAG_NOT_DEATHMATCH 2048 + +//============================================================================ + +extern cvar_t coop; +extern cvar_t deathmatch; +extern cvar_t fraglimit; +extern cvar_t gamecfg; +extern cvar_t noexit; +extern cvar_t nomonsters; +extern cvar_t pausable; +extern cvar_t pr_checkextension; +extern cvar_t samelevel; +extern cvar_t saved1; +extern cvar_t saved2; +extern cvar_t saved3; +extern cvar_t saved4; +extern cvar_t savedgamecfg; +extern cvar_t scratch1; +extern cvar_t scratch2; +extern cvar_t scratch3; +extern cvar_t scratch4; +extern cvar_t skill; +extern cvar_t slowmo; +extern cvar_t sv_accelerate; +extern cvar_t sv_aim; +extern cvar_t sv_airaccel_qw; +extern cvar_t sv_airaccel_sideways_friction; +extern cvar_t sv_airaccelerate; +extern cvar_t sv_airstopaccelerate; +extern cvar_t sv_airstrafeaccelerate; +extern cvar_t sv_maxairstrafespeed; +extern cvar_t sv_airstrafeaccel_qw; +extern cvar_t sv_aircontrol; +extern cvar_t sv_aircontrol_power; +extern cvar_t sv_aircontrol_penalty; +extern cvar_t sv_airspeedlimit_nonqw; +extern cvar_t sv_allowdownloads; +extern cvar_t sv_allowdownloads_archive; +extern cvar_t sv_allowdownloads_config; +extern cvar_t sv_allowdownloads_dlcache; +extern cvar_t sv_allowdownloads_inarchive; +extern cvar_t sv_areagrid_mingridsize; +extern cvar_t sv_checkforpacketsduringsleep; +extern cvar_t sv_clmovement_enable; +extern cvar_t sv_clmovement_minping; +extern cvar_t sv_clmovement_minping_disabletime; +extern cvar_t sv_clmovement_inputtimeout; +extern cvar_t sv_clmovement_maxnetfps; +extern cvar_t sv_cullentities_nevercullbmodels; +extern cvar_t sv_cullentities_pvs; +extern cvar_t sv_cullentities_stats; +extern cvar_t sv_cullentities_trace; +extern cvar_t sv_cullentities_trace_delay; +extern cvar_t sv_cullentities_trace_enlarge; +extern cvar_t sv_cullentities_trace_prediction; +extern cvar_t sv_cullentities_trace_samples; +extern cvar_t sv_cullentities_trace_samples_extra; +extern cvar_t sv_debugmove; +extern cvar_t sv_echobprint; +extern cvar_t sv_edgefriction; +extern cvar_t sv_entpatch; +extern cvar_t sv_fixedframeratesingleplayer; +extern cvar_t sv_freezenonclients; +extern cvar_t sv_friction; +extern cvar_t sv_gameplayfix_blowupfallenzombies; +extern cvar_t sv_gameplayfix_consistentplayerprethink; +extern cvar_t sv_gameplayfix_delayprojectiles; +extern cvar_t sv_gameplayfix_droptofloorstartsolid; +extern cvar_t sv_gameplayfix_droptofloorstartsolid_nudgetocorrect; +extern cvar_t sv_gameplayfix_easierwaterjump; +extern cvar_t sv_gameplayfix_findradiusdistancetobox; +extern cvar_t sv_gameplayfix_gravityunaffectedbyticrate; +extern cvar_t sv_gameplayfix_grenadebouncedownslopes; +extern cvar_t sv_gameplayfix_multiplethinksperframe; +extern cvar_t sv_gameplayfix_noairborncorpse; +extern cvar_t sv_gameplayfix_noairborncorpse_allowsuspendeditems; +extern cvar_t sv_gameplayfix_nudgeoutofsolid; +extern cvar_t sv_gameplayfix_nudgeoutofsolid_separation; +extern cvar_t sv_gameplayfix_q2airaccelerate; +extern cvar_t sv_gameplayfix_nogravityonground; +extern cvar_t sv_gameplayfix_setmodelrealbox; +extern cvar_t sv_gameplayfix_slidemoveprojectiles; +extern cvar_t sv_gameplayfix_stepdown; +extern cvar_t sv_gameplayfix_stepmultipletimes; +extern cvar_t sv_gameplayfix_nostepmoveonsteepslopes; +extern cvar_t sv_gameplayfix_swiminbmodels; +extern cvar_t sv_gameplayfix_upwardvelocityclearsongroundflag; +extern cvar_t sv_gameplayfix_downtracesupportsongroundflag; +extern cvar_t sv_gameplayfix_q1bsptracelinereportstexture; +extern cvar_t sv_gameplayfix_unstickplayers; +extern cvar_t sv_gameplayfix_unstickentities; +extern cvar_t sv_gameplayfix_fixedcheckwatertransition; +extern cvar_t sv_gravity; +extern cvar_t sv_idealpitchscale; +extern cvar_t sv_jumpstep; +extern cvar_t sv_jumpvelocity; +extern cvar_t sv_maxairspeed; +extern cvar_t sv_maxrate; +extern cvar_t sv_maxspeed; +extern cvar_t sv_maxvelocity; +extern cvar_t sv_nostep; +extern cvar_t sv_playerphysicsqc; +extern cvar_t sv_progs; +extern cvar_t sv_protocolname; +extern cvar_t sv_random_seed; +extern cvar_t sv_ratelimitlocalplayer; +extern cvar_t sv_sound_land; +extern cvar_t sv_sound_watersplash; +extern cvar_t sv_stepheight; +extern cvar_t sv_stopspeed; +extern cvar_t sv_wallfriction; +extern cvar_t sv_wateraccelerate; +extern cvar_t sv_waterfriction; +extern cvar_t sv_areadebug; +extern cvar_t sys_ticrate; +extern cvar_t teamplay; +extern cvar_t temp1; +extern cvar_t timelimit; + +extern mempool_t *sv_mempool; + +/// persistant server info +extern server_static_t svs; +/// local server +extern server_t sv; + +extern client_t *host_client; + +//=========================================================== + +void SV_Init (void); + +void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count); +void SV_StartEffect (vec3_t org, int modelindex, int startframe, int framecount, int framerate); +void SV_StartSound (prvm_edict_t *entity, int channel, const char *sample, int volume, float attenuation, qboolean reliable, float speed); +void SV_StartPointSound (vec3_t origin, const char *sample, int volume, float attenuation, float speed); + +void SV_ConnectClient (int clientnum, netconn_t *netconnection); +void SV_DropClient (qboolean crash); + +void SV_SendClientMessages(void); + +void SV_ReadClientMessage(void); + +// precachemode values: +// 0 = fail if not precached, +// 1 = warn if not found and precache if possible +// 2 = precache +int SV_ModelIndex(const char *s, int precachemode); +int SV_SoundIndex(const char *s, int precachemode); + +int SV_ParticleEffectIndex(const char *name); + +dp_model_t *SV_GetModelByIndex(int modelindex); +dp_model_t *SV_GetModelFromEdict(prvm_edict_t *ed); + +void SV_SetIdealPitch (void); + +void SV_AddUpdates (void); + +void SV_ClientThink (void); + +void SV_ClientPrint(const char *msg); +void SV_ClientPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); +void SV_BroadcastPrint(const char *msg); +void SV_BroadcastPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); + +void SV_Physics (void); +void SV_Physics_ClientMove (void); +//void SV_Physics_ClientEntity (prvm_edict_t *ent); + +qboolean SV_PlayerCheckGround (prvm_edict_t *ent); +qboolean SV_CheckBottom (prvm_edict_t *ent); +qboolean SV_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace); + +/*! Needs to be called any time an entity changes origin, mins, maxs, or solid + * sets ent->v.absmin and ent->v.absmax + * call TouchAreaGrid as well to fire triggers that overlap the box + */ +void SV_LinkEdict(prvm_edict_t *ent); +void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent); +void SV_LinkEdict_TouchAreaGrid_Call(prvm_edict_t *touch, prvm_edict_t *ent); // if we detected a touch from another source + +/*! move an entity that is stuck by small amounts in various directions to try to nudge it back into the collision hull + * returns true if it found a better place + */ +qboolean SV_UnstickEntity (prvm_edict_t *ent); +/*! move an entity that is stuck out of the surface it is stuck in (can move large amounts) + * returns true if it found a better place + */ +qboolean SV_NudgeOutOfSolid(prvm_edict_t *ent); + +/// calculates hitsupercontentsmask for a generic qc entity +int SV_GenericHitSuperContentsMask(const prvm_edict_t *edict); +/// traces a box move against worldmodel and all entities in the specified area +trace_t SV_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); +trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask); +trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask); +int SV_EntitiesInBox(const vec3_t mins, const vec3_t maxs, int maxedicts, prvm_edict_t **resultedicts); + +qboolean SV_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs); + +int SV_PointSuperContents(const vec3_t point); + +void SV_FlushBroadcastMessages(void); +void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); + +void VM_SV_MoveToGoal(prvm_prog_t *prog); + +void SV_ApplyClientMove (void); +void SV_SaveSpawnparms (void); +void SV_SpawnServer (const char *server); + +void SV_CheckVelocity (prvm_edict_t *ent); + +void SV_SetupVM(void); + +const char *Host_TimingReport(char *buf, size_t buflen); ///< for output in Host_Status_f + +int SV_GetPitchSign(prvm_prog_t *prog, prvm_edict_t *ent); +void SV_GetEntityMatrix(prvm_prog_t *prog, prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix); + +void SV_StartThread(void); +void SV_StopThread(void); +#define SV_LockThreadMutex() (void)(svs.threaded ? Thread_LockMutex(svs.threadmutex) : 0) +#define SV_UnlockThreadMutex() (void)(svs.threaded ? Thread_UnlockMutex(svs.threadmutex) : 0) + +void VM_CustomStats_Clear(void); +void VM_SV_UpdateCustomStats(client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); +void Host_Savegame_to(prvm_prog_t *prog, const char *name); +void SV_SendServerinfo(client_t *client); + +#endif + diff --git a/app/jni/shader_glsl.h b/app/jni/shader_glsl.h new file mode 100644 index 0000000..487bd64 --- /dev/null +++ b/app/jni/shader_glsl.h @@ -0,0 +1,1699 @@ +"// ambient+diffuse+specular+normalmap+attenuation+cubemap+fog shader\n", +"// written by Forest 'LordHavoc' Hale\n", +"// shadowmapping enhancements by Lee 'eihrul' Salzman\n", +"\n", +"#ifdef USESKELETAL\n", +"# ifdef GL_ARB_uniform_buffer_object\n", +"# extension GL_ARB_uniform_buffer_object : enable\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USESHADOWMAP2D\n", +"# ifdef GL_EXT_gpu_shader4\n", +"# extension GL_EXT_gpu_shader4 : enable\n", +"# endif\n", +"# ifdef GL_ARB_texture_gather\n", +"# extension GL_ARB_texture_gather : enable\n", +"# else\n", +"# ifdef GL_AMD_texture_texture4\n", +"# extension GL_AMD_texture_texture4 : enable\n", +"# endif\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USECELSHADING\n", +"# define SHADEDIFFUSE myhalf diffuse = cast_myhalf(min(max(float(dot(surfacenormal, lightnormal)) * 2.0, 0.0), 1.0));\n", +"# ifdef USEEXACTSPECULARMATH\n", +"# define SHADESPECULAR(specpow) myhalf specular = pow(cast_myhalf(max(float(dot(reflect(lightnormal, surfacenormal), eyenormal))*-1.0, 0.0)), 1.0 + specpow);specular = max(0.0, specular * 10.0 - 9.0);\n", +"# else\n", +"# define SHADESPECULAR(specpow) myhalf3 specularnormal = normalize(lightnormal + eyenormal);myhalf specular = pow(cast_myhalf(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + specpow);specular = max(0.0, specular * 10.0 - 9.0);\n", +"# endif\n", +"#else\n", +"# define SHADEDIFFUSE myhalf diffuse = cast_myhalf(max(float(dot(surfacenormal, lightnormal)), 0.0));\n", +"# ifdef USEEXACTSPECULARMATH\n", +"# define SHADESPECULAR(specpow) myhalf specular = pow(cast_myhalf(max(float(dot(reflect(lightnormal, surfacenormal), eyenormal))*-1.0, 0.0)), 1.0 + specpow);\n", +"# else\n", +"# define SHADESPECULAR(specpow) myhalf3 specularnormal = normalize(lightnormal + eyenormal);myhalf specular = pow(cast_myhalf(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + specpow);\n", +"# endif\n", +"#endif\n", +"\n", +"#if defined(GLSL130) || defined(GLSL140)\n", +"precision highp float;\n", +"# ifdef VERTEX_SHADER\n", +"# define dp_varying out\n", +"# define dp_attribute in\n", +"# endif\n", +"# ifdef FRAGMENT_SHADER\n", +"out vec4 dp_FragColor;\n", +"# define dp_varying in\n", +"# define dp_attribute in\n", +"# endif\n", +"# define dp_offsetmapping_dFdx dFdx\n", +"# define dp_offsetmapping_dFdy dFdy\n", +"# define dp_textureGrad textureGrad\n", +"# define dp_textureOffset(a,b,c,d) textureOffset(a,b,ivec2(c,d))\n", +"# define dp_texture2D texture\n", +"# define dp_texture3D texture\n", +"# define dp_textureCube texture\n", +"# define dp_shadow2D(a,b) float(texture(a,b))\n", +"#else\n", +"# ifdef FRAGMENT_SHADER\n", +"# define dp_FragColor gl_FragColor\n", +"# endif\n", +"# define dp_varying varying\n", +"# define dp_attribute attribute\n", +"# define dp_offsetmapping_dFdx(a) vec2(0.0, 0.0)\n", +"# define dp_offsetmapping_dFdy(a) vec2(0.0, 0.0)\n", +"# define dp_textureGrad(a,b,c,d) texture2D(a,b)\n", +"# define dp_textureOffset(a,b,c,d) texture2DOffset(a,b,ivec2(c,d))\n", +"# define dp_texture2D texture2D\n", +"# define dp_texture3D texture3D\n", +"# define dp_textureCube textureCube\n", +"# define dp_shadow2D(a,b) float(shadow2D(a,b))\n", +"#endif\n", +"\n", +"// GL ES and GLSL130 shaders use precision modifiers, standard GL does not\n", +"// in GLSL130 we don't use them though because of syntax differences (can't use precision with inout)\n", +"#ifndef GL_ES\n", +"#define lowp\n", +"#define mediump\n", +"#define highp\n", +"#endif\n", +"\n", +"#ifdef USEDEPTHRGB\n", +" // for 565 RGB we'd need to use different multipliers\n", +"#define decodedepthmacro(d) dot((d).rgb, vec3(1.0, 255.0 / 65536.0, 255.0 / 16777215.0))\n", +"#define encodedepthmacro(d) (vec4(d, d*256.0, d*65536.0, 0.0) - floor(vec4(d, d*256.0, d*65536.0, 0.0)))\n", +"#endif\n", +"\n", +"#ifdef VERTEX_SHADER\n", +"dp_attribute vec4 Attrib_Position; // vertex\n", +"dp_attribute vec4 Attrib_Color; // color\n", +"dp_attribute vec4 Attrib_TexCoord0; // material texcoords\n", +"dp_attribute vec3 Attrib_TexCoord1; // svector\n", +"dp_attribute vec3 Attrib_TexCoord2; // tvector\n", +"dp_attribute vec3 Attrib_TexCoord3; // normal\n", +"dp_attribute vec4 Attrib_TexCoord4; // lightmap texcoords\n", +"#ifdef USESKELETAL\n", +"//uniform mat4 Skeletal_Transform[128];\n", +"// this is used with glBindBufferRange to bind a uniform block to the name\n", +"// Skeletal_Transform12_UniformBlock, the Skeletal_Transform12 variable is\n", +"// directly accessible without a namespace.\n", +"// explanation: http://www.opengl.org/wiki/Interface_Block_%28GLSL%29#Syntax\n", +"uniform Skeletal_Transform12_UniformBlock\n", +"{\n", +" vec4 Skeletal_Transform12[768];\n", +"};\n", +"dp_attribute vec4 Attrib_SkeletalIndex;\n", +"dp_attribute vec4 Attrib_SkeletalWeight;\n", +"#endif\n", +"#endif\n", +"dp_varying mediump vec4 VertexColor;\n", +"\n", +"#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE) || defined(USEFOGHEIGHTTEXTURE)\n", +"# define USEFOG\n", +"#endif\n", +"#if defined(MODE_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP)\n", +"# define USELIGHTMAP\n", +"#endif\n", +"#if defined(USESPECULAR) || defined(USEOFFSETMAPPING) || defined(USEREFLECTCUBE) || defined(MODE_FAKELIGHT) || defined(USEFOG)\n", +"# define USEEYEVECTOR\n", +"#endif\n", +"\n", +"//#ifdef __GLSL_CG_DATA_TYPES\n", +"//# define myhalf half\n", +"//# define myhalf2 half2\n", +"//# define myhalf3 half3\n", +"//# define myhalf4 half4\n", +"//# define cast_myhalf half\n", +"//# define cast_myhalf2 half2\n", +"//# define cast_myhalf3 half3\n", +"//# define cast_myhalf4 half4\n", +"//#else\n", +"# define myhalf mediump float\n", +"# define myhalf2 mediump vec2\n", +"# define myhalf3 mediump vec3\n", +"# define myhalf4 mediump vec4\n", +"# define cast_myhalf float\n", +"# define cast_myhalf2 vec2\n", +"# define cast_myhalf3 vec3\n", +"# define cast_myhalf4 vec4\n", +"//#endif\n", +"\n", +"#ifdef VERTEX_SHADER\n", +"uniform highp mat4 ModelViewProjectionMatrix;\n", +"#endif\n", +"\n", +"#ifdef VERTEX_SHADER\n", +"#ifdef USETRIPPY\n", +"// LordHavoc: based on shader code linked at: http://www.youtube.com/watch?v=JpksyojwqzE\n", +"// tweaked scale\n", +"uniform highp float ClientTime;\n", +"vec4 TrippyVertex(vec4 position)\n", +"{\n", +" float worldTime = ClientTime;\n", +" // tweaked for Quake\n", +" worldTime *= 10.0;\n", +" position *= 0.125;\n", +" //~tweaked for Quake\n", +" float distanceSquared = (position.x * position.x + position.z * position.z);\n", +" position.y += 5.0*sin(distanceSquared*sin(worldTime/143.0)/1000.0);\n", +" float y = position.y;\n", +" float x = position.x;\n", +" float om = sin(distanceSquared*sin(worldTime/256.0)/5000.0) * sin(worldTime/200.0);\n", +" position.y = x*sin(om)+y*cos(om);\n", +" position.x = x*cos(om)-y*sin(om);\n", +" return position;\n", +"}\n", +"#endif\n", +"#endif\n", +"\n", +"#ifdef MODE_DEPTH_OR_SHADOW\n", +"dp_varying highp float Depth;\n", +"#ifdef VERTEX_SHADER\n", +"void main(void)\n", +"{\n", +"#ifdef USESKELETAL\n", +" ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", +" ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", +" ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", +" vec4 sw = Attrib_SkeletalWeight;\n", +" vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", +" vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", +" vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", +" mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", +" vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", +"#define Attrib_Position SkeletalVertex\n", +"#endif\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +" Depth = gl_Position.z;\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main(void)\n", +"{\n", +"#ifdef USEDEPTHRGB\n", +" dp_FragColor = encodedepthmacro(Depth);\n", +"#else\n", +" dp_FragColor = vec4(1.0,1.0,1.0,1.0);\n", +"#endif\n", +"}\n", +"#endif\n", +"#else // !MODE_DEPTH_ORSHADOW\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_POSTPROCESS\n", +"dp_varying mediump vec2 TexCoord1;\n", +"dp_varying mediump vec2 TexCoord2;\n", +"\n", +"#ifdef VERTEX_SHADER\n", +"void main(void)\n", +"{\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +" TexCoord1 = Attrib_TexCoord0.xy;\n", +"#ifdef USEBLOOM\n", +" TexCoord2 = Attrib_TexCoord4.xy;\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"uniform sampler2D Texture_First;\n", +"#ifdef USEBLOOM\n", +"uniform sampler2D Texture_Second;\n", +"uniform mediump vec4 BloomColorSubtract;\n", +"#endif\n", +"#ifdef USEGAMMARAMPS\n", +"uniform sampler2D Texture_GammaRamps;\n", +"#endif\n", +"#ifdef USESATURATION\n", +"uniform mediump float Saturation;\n", +"#endif\n", +"#ifdef USEVIEWTINT\n", +"uniform mediump vec4 ViewTintColor;\n", +"#endif\n", +"//uncomment these if you want to use them:\n", +"uniform mediump vec4 UserVec1;\n", +"uniform mediump vec4 UserVec2;\n", +"// uniform mediump vec4 UserVec3;\n", +"// uniform mediump vec4 UserVec4;\n", +"// uniform highp float ClientTime;\n", +"uniform mediump vec2 PixelSize;\n", +"void main(void)\n", +"{\n", +" dp_FragColor = dp_texture2D(Texture_First, TexCoord1);\n", +"\n", +"#ifdef USEPOSTPROCESSING\n", +"// do r_glsl_dumpshader, edit glsl/default.glsl, and replace this by your own postprocessing if you want\n", +"// this code does a blur with the radius specified in the first component of r_glsl_postprocess_uservec1 and blends it using the second component\n", +" float sobel = 1.0;\n", +" // vec2 ts = textureSize(Texture_First, 0);\n", +" // vec2 px = vec2(1/ts.x, 1/ts.y);\n", +" vec2 px = PixelSize;\n", +" vec3 x1 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, px.y)).rgb;\n", +" vec3 x2 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, 0.0)).rgb;\n", +" vec3 x3 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x,-px.y)).rgb;\n", +" vec3 x4 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, px.y)).rgb;\n", +" vec3 x5 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, 0.0)).rgb;\n", +" vec3 x6 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x,-px.y)).rgb;\n", +" vec3 y1 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x,-px.y)).rgb;\n", +" vec3 y2 = dp_texture2D(Texture_First, TexCoord1 + vec2( 0.0,-px.y)).rgb;\n", +" vec3 y3 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x,-px.y)).rgb;\n", +" vec3 y4 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, px.y)).rgb;\n", +" vec3 y5 = dp_texture2D(Texture_First, TexCoord1 + vec2( 0.0, px.y)).rgb;\n", +" vec3 y6 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, px.y)).rgb;\n", +" float px1 = -1.0 * dot(vec3(0.3, 0.59, 0.11), x1);\n", +" float px2 = -2.0 * dot(vec3(0.3, 0.59, 0.11), x2);\n", +" float px3 = -1.0 * dot(vec3(0.3, 0.59, 0.11), x3);\n", +" float px4 = 1.0 * dot(vec3(0.3, 0.59, 0.11), x4);\n", +" float px5 = 2.0 * dot(vec3(0.3, 0.59, 0.11), x5);\n", +" float px6 = 1.0 * dot(vec3(0.3, 0.59, 0.11), x6);\n", +" float py1 = -1.0 * dot(vec3(0.3, 0.59, 0.11), y1);\n", +" float py2 = -2.0 * dot(vec3(0.3, 0.59, 0.11), y2);\n", +" float py3 = -1.0 * dot(vec3(0.3, 0.59, 0.11), y3);\n", +" float py4 = 1.0 * dot(vec3(0.3, 0.59, 0.11), y4);\n", +" float py5 = 2.0 * dot(vec3(0.3, 0.59, 0.11), y5);\n", +" float py6 = 1.0 * dot(vec3(0.3, 0.59, 0.11), y6);\n", +" sobel = 0.25 * abs(px1 + px2 + px3 + px4 + px5 + px6) + 0.25 * abs(py1 + py2 + py3 + py4 + py5 + py6);\n", +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.987688, -0.156434)) * UserVec1.y;\n", +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.156434, -0.891007)) * UserVec1.y;\n", +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2( 0.891007, -0.453990)) * UserVec1.y;\n", +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2( 0.707107, 0.707107)) * UserVec1.y;\n", +" dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.453990, 0.891007)) * UserVec1.y;\n", +" dp_FragColor /= (1.0 + 5.0 * UserVec1.y);\n", +" dp_FragColor.rgb = dp_FragColor.rgb * (1.0 + UserVec2.x) + vec3(max(0.0, sobel - UserVec2.z))*UserVec2.y;\n", +"#endif\n", +"\n", +"#ifdef USEBLOOM\n", +" dp_FragColor += max(vec4(0,0,0,0), dp_texture2D(Texture_Second, TexCoord2) - BloomColorSubtract);\n", +"#endif\n", +"\n", +"#ifdef USEVIEWTINT\n", +" dp_FragColor = mix(dp_FragColor, ViewTintColor, ViewTintColor.a);\n", +"#endif\n", +"\n", +"#ifdef USESATURATION\n", +" //apply saturation BEFORE gamma ramps, so v_glslgamma value does not matter\n", +" float y = dot(dp_FragColor.rgb, vec3(0.299, 0.587, 0.114));\n", +" // 'vampire sight' effect, wheres red is compensated\n", +" #ifdef SATURATION_REDCOMPENSATE\n", +" float rboost = max(0.0, (dp_FragColor.r - max(dp_FragColor.g, dp_FragColor.b))*(1.0 - Saturation));\n", +" dp_FragColor.rgb = mix(vec3(y), dp_FragColor.rgb, Saturation);\n", +" dp_FragColor.r += rboost;\n", +" #else\n", +" // normal desaturation\n", +" //dp_FragColor = vec3(y) + (dp_FragColor.rgb - vec3(y)) * Saturation;\n", +" dp_FragColor.rgb = mix(vec3(y), dp_FragColor.rgb, Saturation);\n", +" #endif\n", +"#endif\n", +"\n", +"#ifdef USEGAMMARAMPS\n", +" dp_FragColor.r = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.r, 0)).r;\n", +" dp_FragColor.g = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.g, 0)).g;\n", +" dp_FragColor.b = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.b, 0)).b;\n", +"#endif\n", +"}\n", +"#endif\n", +"#else // !MODE_POSTPROCESS\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_GENERIC\n", +"#ifdef USEDIFFUSE\n", +"dp_varying mediump vec2 TexCoord1;\n", +"#endif\n", +"#ifdef USESPECULAR\n", +"dp_varying mediump vec2 TexCoord2;\n", +"#endif\n", +"uniform myhalf Alpha;\n", +"#ifdef VERTEX_SHADER\n", +"void main(void)\n", +"{\n", +"#ifdef USESKELETAL\n", +" ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", +" ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", +" ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", +" vec4 sw = Attrib_SkeletalWeight;\n", +" vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", +" vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", +" vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", +" mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", +" vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", +"#define Attrib_Position SkeletalVertex\n", +"#endif\n", +" VertexColor = Attrib_Color;\n", +"#ifdef USEDIFFUSE\n", +" TexCoord1 = Attrib_TexCoord0.xy;\n", +"#endif\n", +"#ifdef USESPECULAR\n", +" TexCoord2 = Attrib_TexCoord1.xy;\n", +"#endif\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"#ifdef USEDIFFUSE\n", +"uniform sampler2D Texture_First;\n", +"#endif\n", +"#ifdef USESPECULAR\n", +"uniform sampler2D Texture_Second;\n", +"#endif\n", +"#ifdef USEGAMMARAMPS\n", +"uniform sampler2D Texture_GammaRamps;\n", +"#endif\n", +"\n", +"void main(void)\n", +"{\n", +"#ifdef USEVIEWTINT\n", +" dp_FragColor = VertexColor;\n", +"#else\n", +" dp_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n", +"#endif\n", +"#ifdef USEDIFFUSE\n", +"# ifdef USEREFLECTCUBE\n", +" // suppress texture alpha\n", +" dp_FragColor.rgb *= dp_texture2D(Texture_First, TexCoord1).rgb;\n", +"# else\n", +" dp_FragColor *= dp_texture2D(Texture_First, TexCoord1);\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USESPECULAR\n", +" vec4 tex2 = dp_texture2D(Texture_Second, TexCoord2);\n", +"# ifdef USECOLORMAPPING\n", +" dp_FragColor *= tex2;\n", +"# endif\n", +"# ifdef USEGLOW\n", +" dp_FragColor += tex2;\n", +"# endif\n", +"# ifdef USEVERTEXTEXTUREBLEND\n", +" dp_FragColor = mix(dp_FragColor, tex2, tex2.a);\n", +"# endif\n", +"#endif\n", +"#ifdef USEGAMMARAMPS\n", +" dp_FragColor.r = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.r, 0)).r;\n", +" dp_FragColor.g = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.g, 0)).g;\n", +" dp_FragColor.b = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.b, 0)).b;\n", +"#endif\n", +"#ifdef USEALPHAKILL\n", +" dp_FragColor.a *= Alpha;\n", +"#endif\n", +"}\n", +"#endif\n", +"#else // !MODE_GENERIC\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_BLOOMBLUR\n", +"dp_varying mediump vec2 TexCoord;\n", +"#ifdef VERTEX_SHADER\n", +"void main(void)\n", +"{\n", +" VertexColor = Attrib_Color;\n", +" TexCoord = Attrib_TexCoord0.xy;\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"uniform sampler2D Texture_First;\n", +"uniform mediump vec4 BloomBlur_Parameters;\n", +"\n", +"void main(void)\n", +"{\n", +" int i;\n", +" vec2 tc = TexCoord;\n", +" vec3 color = dp_texture2D(Texture_First, tc).rgb;\n", +" tc += BloomBlur_Parameters.xy;\n", +" for (i = 1;i < SAMPLES;i++)\n", +" {\n", +" color += dp_texture2D(Texture_First, tc).rgb;\n", +" tc += BloomBlur_Parameters.xy;\n", +" }\n", +" dp_FragColor = vec4(color * BloomBlur_Parameters.z + vec3(BloomBlur_Parameters.w), 1);\n", +"}\n", +"#endif\n", +"#else // !MODE_BLOOMBLUR\n", +"#ifdef MODE_REFRACTION\n", +"dp_varying mediump vec2 TexCoord;\n", +"dp_varying highp vec4 ModelViewProjectionPosition;\n", +"uniform highp mat4 TexMatrix;\n", +"#ifdef VERTEX_SHADER\n", +"\n", +"void main(void)\n", +"{\n", +"#ifdef USESKELETAL\n", +" ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", +" ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", +" ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", +" vec4 sw = Attrib_SkeletalWeight;\n", +" vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", +" vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", +" vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", +" mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", +" vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", +"#define Attrib_Position SkeletalVertex\n", +"#endif\n", +"#ifdef USEALPHAGENVERTEX\n", +" VertexColor = Attrib_Color;\n", +"#endif\n", +" TexCoord = vec2(TexMatrix * Attrib_TexCoord0);\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +" ModelViewProjectionPosition = gl_Position;\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"uniform sampler2D Texture_Normal;\n", +"uniform sampler2D Texture_Refraction;\n", +"\n", +"uniform mediump vec4 DistortScaleRefractReflect;\n", +"uniform mediump vec4 ScreenScaleRefractReflect;\n", +"uniform mediump vec4 ScreenCenterRefractReflect;\n", +"uniform mediump vec4 RefractColor;\n", +"uniform mediump vec4 ReflectColor;\n", +"uniform highp float ClientTime;\n", +"#ifdef USENORMALMAPSCROLLBLEND\n", +"uniform highp vec2 NormalmapScrollBlend;\n", +"#endif\n", +"\n", +"void main(void)\n", +"{\n", +" vec2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n", +" //vec2 ScreenTexCoord = (ModelViewProjectionPosition.xy + normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5)).xy * DistortScaleRefractReflect.xy * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n", +" vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n", +"#ifdef USEALPHAGENVERTEX\n", +" vec2 distort = DistortScaleRefractReflect.xy * VertexColor.a;\n", +" vec4 refractcolor = mix(RefractColor, vec4(1.0, 1.0, 1.0, 1.0), VertexColor.a);\n", +"#else\n", +" vec2 distort = DistortScaleRefractReflect.xy;\n", +" vec4 refractcolor = RefractColor;\n", +"#endif\n", +" #ifdef USENORMALMAPSCROLLBLEND\n", +" vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n", +" normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n", +" vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(cast_myhalf3(normal))).xy * distort;\n", +" #else\n", +" vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(cast_myhalf3(dp_texture2D(Texture_Normal, TexCoord)) - cast_myhalf3(0.5))).xy * distort;\n", +" #endif\n", +" // FIXME temporary hack to detect the case that the reflection\n", +" // gets blackened at edges due to leaving the area that contains actual\n", +" // content.\n", +" // Remove this 'ack once we have a better way to stop this thing from\n", +" // 'appening.\n", +" float f = min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(0.01, -0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(-0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(-0.01, -0.01)).rgb) / 0.05);\n", +" ScreenTexCoord = mix(SafeScreenTexCoord, ScreenTexCoord, f);\n", +" dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * refractcolor;\n", +"}\n", +"#endif\n", +"#else // !MODE_REFRACTION\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_WATER\n", +"dp_varying mediump vec2 TexCoord;\n", +"dp_varying highp vec3 EyeVector;\n", +"dp_varying highp vec4 ModelViewProjectionPosition;\n", +"#ifdef VERTEX_SHADER\n", +"uniform highp vec3 EyePosition;\n", +"uniform highp mat4 TexMatrix;\n", +"\n", +"void main(void)\n", +"{\n", +"#ifdef USESKELETAL\n", +" ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", +" ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", +" ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", +" vec4 sw = Attrib_SkeletalWeight;\n", +" vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", +" vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", +" vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", +" mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", +" mat3 SkeletalNormalMatrix = mat3(cross(SkeletalMatrix[1].xyz, SkeletalMatrix[2].xyz), cross(SkeletalMatrix[2].xyz, SkeletalMatrix[0].xyz), cross(SkeletalMatrix[0].xyz, SkeletalMatrix[1].xyz)); // is actually transpose(inverse(mat3(SkeletalMatrix))) * det(mat3(SkeletalMatrix))\n", +" vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", +" vec3 SkeletalSVector = normalize(Attrib_TexCoord1.xyz * SkeletalNormalMatrix);\n", +" vec3 SkeletalTVector = normalize(Attrib_TexCoord2.xyz * SkeletalNormalMatrix);\n", +" vec3 SkeletalNormal = normalize(Attrib_TexCoord3.xyz * SkeletalNormalMatrix);\n", +"#define Attrib_Position SkeletalVertex\n", +"#define Attrib_TexCoord1 SkeletalSVector\n", +"#define Attrib_TexCoord2 SkeletalTVector\n", +"#define Attrib_TexCoord3 SkeletalNormal\n", +"#endif\n", +"#ifdef USEALPHAGENVERTEX\n", +" VertexColor = Attrib_Color;\n", +"#endif\n", +" TexCoord = vec2(TexMatrix * Attrib_TexCoord0);\n", +" vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n", +" EyeVector.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n", +" EyeVector.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n", +" EyeVector.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +" ModelViewProjectionPosition = gl_Position;\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"uniform sampler2D Texture_Normal;\n", +"uniform sampler2D Texture_Refraction;\n", +"uniform sampler2D Texture_Reflection;\n", +"\n", +"uniform mediump vec4 DistortScaleRefractReflect;\n", +"uniform mediump vec4 ScreenScaleRefractReflect;\n", +"uniform mediump vec4 ScreenCenterRefractReflect;\n", +"uniform mediump vec4 RefractColor;\n", +"uniform mediump vec4 ReflectColor;\n", +"uniform mediump float ReflectFactor;\n", +"uniform mediump float ReflectOffset;\n", +"uniform highp float ClientTime;\n", +"#ifdef USENORMALMAPSCROLLBLEND\n", +"uniform highp vec2 NormalmapScrollBlend;\n", +"#endif\n", +"\n", +"void main(void)\n", +"{\n", +" vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n", +" //vec4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", +" vec4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", +" //SafeScreenTexCoord = gl_FragCoord.xyxy * vec4(1.0 / 1920.0, 1.0 / 1200.0, 1.0 / 1920.0, 1.0 / 1200.0);\n", +" // slight water animation via 2 layer scrolling (todo: tweak)\n", +"#ifdef USEALPHAGENVERTEX\n", +" vec4 distort = DistortScaleRefractReflect * VertexColor.a;\n", +" float reflectoffset = ReflectOffset * VertexColor.a;\n", +" float reflectfactor = ReflectFactor * VertexColor.a;\n", +" vec4 refractcolor = mix(RefractColor, vec4(1.0, 1.0, 1.0, 1.0), VertexColor.a);\n", +"#else\n", +" vec4 distort = DistortScaleRefractReflect;\n", +" float reflectoffset = ReflectOffset;\n", +" float reflectfactor = ReflectFactor;\n", +" vec4 refractcolor = RefractColor;\n", +"#endif\n", +" #ifdef USENORMALMAPSCROLLBLEND\n", +" vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n", +" normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n", +" vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(normal) + vec3(0.15)).xyxy * distort;\n", +" #else\n", +" vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5))).xyxy * distort;\n", +" #endif\n", +" // FIXME temporary hack to detect the case that the reflection\n", +" // gets blackened at edges due to leaving the area that contains actual\n", +" // content.\n", +" // Remove this 'ack once we have a better way to stop this thing from\n", +" // 'appening.\n", +" float f = min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(0.005, 0.01)).rgb) / 0.002);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(0.005, -0.01)).rgb) / 0.002);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(-0.005, 0.01)).rgb) / 0.002);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(-0.005, -0.01)).rgb) / 0.002);\n", +" ScreenTexCoord.xy = mix(SafeScreenTexCoord.xy, ScreenTexCoord.xy, f);\n", +" f = min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(0.005, 0.005)).rgb) / 0.002);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(0.005, -0.005)).rgb) / 0.002);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(-0.005, 0.005)).rgb) / 0.002);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(-0.005, -0.005)).rgb) / 0.002);\n", +" ScreenTexCoord.zw = mix(SafeScreenTexCoord.zw, ScreenTexCoord.zw, f);\n", +" float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * reflectfactor + reflectoffset;\n", +" dp_FragColor = mix(vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * refractcolor, vec4(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n", +"}\n", +"#endif\n", +"#else // !MODE_WATER\n", +"\n", +"\n", +"\n", +"\n", +"// common definitions between vertex shader and fragment shader:\n", +"\n", +"dp_varying mediump vec4 TexCoordSurfaceLightmap;\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"dp_varying mediump vec2 TexCoord2;\n", +"#endif\n", +"\n", +"#ifdef MODE_LIGHTSOURCE\n", +"dp_varying mediump vec3 CubeVector;\n", +"#endif\n", +"\n", +"#if (defined(MODE_LIGHTSOURCE) || defined(MODE_LIGHTDIRECTION)) && defined(USEDIFFUSE)\n", +"dp_varying mediump vec3 LightVector;\n", +"#endif\n", +"\n", +"#ifdef USEEYEVECTOR\n", +"dp_varying highp vec4 EyeVectorFogDepth;\n", +"#endif\n", +"\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE) || defined(USEBOUNCEGRIDDIRECTIONAL)\n", +"dp_varying highp vec4 VectorS; // direction of S texcoord (sometimes crudely called tangent)\n", +"dp_varying highp vec4 VectorT; // direction of T texcoord (sometimes crudely called binormal)\n", +"dp_varying highp vec4 VectorR; // direction of R texcoord (surface normal)\n", +"#else\n", +"# ifdef USEFOG\n", +"dp_varying highp vec3 EyeVectorModelSpace;\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USEREFLECTION\n", +"dp_varying highp vec4 ModelViewProjectionPosition;\n", +"#endif\n", +"#ifdef MODE_DEFERREDLIGHTSOURCE\n", +"uniform highp vec3 LightPosition;\n", +"dp_varying highp vec4 ModelViewPosition;\n", +"#endif\n", +"\n", +"#ifdef MODE_LIGHTSOURCE\n", +"uniform highp vec3 LightPosition;\n", +"#endif\n", +"uniform highp vec3 EyePosition;\n", +"#ifdef MODE_LIGHTDIRECTION\n", +"uniform highp vec3 LightDir;\n", +"#endif\n", +"uniform highp vec4 FogPlane;\n", +"\n", +"#ifdef USESHADOWMAPORTHO\n", +"dp_varying highp vec3 ShadowMapTC;\n", +"#endif\n", +"\n", +"#ifdef USEBOUNCEGRID\n", +"dp_varying highp vec3 BounceGridTexCoord;\n", +"#endif\n", +"\n", +"#ifdef MODE_DEFERREDGEOMETRY\n", +"dp_varying highp float Depth;\n", +"#endif\n", +"\n", +"\n", +"\n", +"\n", +"\n", +"\n", +"// TODO: get rid of tangentt (texcoord2) and use a crossproduct to regenerate it from tangents (texcoord1) and normal (texcoord3), this would require sending a 4 component texcoord1 with W as 1 or -1 according to which side the texcoord2 should be on\n", +"\n", +"// fragment shader specific:\n", +"#ifdef FRAGMENT_SHADER\n", +"\n", +"uniform sampler2D Texture_Normal;\n", +"uniform sampler2D Texture_Color;\n", +"uniform sampler2D Texture_Gloss;\n", +"#ifdef USEGLOW\n", +"uniform sampler2D Texture_Glow;\n", +"#endif\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"uniform sampler2D Texture_SecondaryNormal;\n", +"uniform sampler2D Texture_SecondaryColor;\n", +"uniform sampler2D Texture_SecondaryGloss;\n", +"#ifdef USEGLOW\n", +"uniform sampler2D Texture_SecondaryGlow;\n", +"#endif\n", +"#endif\n", +"#ifdef USECOLORMAPPING\n", +"uniform sampler2D Texture_Pants;\n", +"uniform sampler2D Texture_Shirt;\n", +"#endif\n", +"#ifdef USEFOG\n", +"#ifdef USEFOGHEIGHTTEXTURE\n", +"uniform sampler2D Texture_FogHeightTexture;\n", +"#endif\n", +"uniform sampler2D Texture_FogMask;\n", +"#endif\n", +"#ifdef USELIGHTMAP\n", +"uniform sampler2D Texture_Lightmap;\n", +"#endif\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n", +"uniform sampler2D Texture_Deluxemap;\n", +"#endif\n", +"#ifdef USEREFLECTION\n", +"uniform sampler2D Texture_Reflection;\n", +"#endif\n", +"\n", +"#ifdef MODE_DEFERREDLIGHTSOURCE\n", +"uniform sampler2D Texture_ScreenNormalMap;\n", +"#endif\n", +"#ifdef USEDEFERREDLIGHTMAP\n", +"#ifdef USECELOUTLINES\n", +"uniform sampler2D Texture_ScreenNormalMap;\n", +"#endif\n", +"uniform sampler2D Texture_ScreenDiffuse;\n", +"uniform sampler2D Texture_ScreenSpecular;\n", +"#endif\n", +"\n", +"uniform mediump vec3 Color_Pants;\n", +"uniform mediump vec3 Color_Shirt;\n", +"uniform mediump vec3 FogColor;\n", +"\n", +"#ifdef USEFOG\n", +"uniform highp float FogRangeRecip;\n", +"uniform highp float FogPlaneViewDist;\n", +"uniform highp float FogHeightFade;\n", +"vec3 FogVertex(vec4 surfacecolor)\n", +"{\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE) || defined(USEBOUNCEGRIDDIRECTIONAL)\n", +" vec3 EyeVectorModelSpace = vec3(VectorS.w, VectorT.w, VectorR.w);\n", +"#endif\n", +" float FogPlaneVertexDist = EyeVectorFogDepth.w;\n", +" float fogfrac;\n", +" vec3 fc = FogColor;\n", +"#ifdef USEFOGALPHAHACK\n", +" fc *= surfacecolor.a;\n", +"#endif\n", +"#ifdef USEFOGHEIGHTTEXTURE\n", +" vec4 fogheightpixel = dp_texture2D(Texture_FogHeightTexture, vec2(1,1) + vec2(FogPlaneVertexDist, FogPlaneViewDist) * (-2.0 * FogHeightFade));\n", +" fogfrac = fogheightpixel.a;\n", +" return mix(fogheightpixel.rgb * fc, surfacecolor.rgb, dp_texture2D(Texture_FogMask, cast_myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n", +"#else\n", +"# ifdef USEFOGOUTSIDE\n", +" fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n", +"# else\n", +" fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0, FogPlaneVertexDist)) * min(1.0, (min(0.0, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade);\n", +"# endif\n", +" return mix(fc, surfacecolor.rgb, dp_texture2D(Texture_FogMask, cast_myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef USEOFFSETMAPPING\n", +"uniform mediump vec4 OffsetMapping_ScaleSteps;\n", +"uniform mediump float OffsetMapping_Bias;\n", +"#ifdef USEOFFSETMAPPING_LOD\n", +"uniform mediump float OffsetMapping_LodDistance;\n", +"#endif\n", +"vec2 OffsetMapping(vec2 TexCoord, vec2 dPdx, vec2 dPdy)\n", +"{\n", +" float i;\n", +" // distance-based LOD\n", +"#ifdef USEOFFSETMAPPING_LOD\n", +" //mediump float LODFactor = min(1.0, OffsetMapping_LodDistance / EyeVectorFogDepth.z);\n", +" //mediump vec4 ScaleSteps = vec4(OffsetMapping_ScaleSteps.x, OffsetMapping_ScaleSteps.y * LODFactor, OffsetMapping_ScaleSteps.z / LODFactor, OffsetMapping_ScaleSteps.w * LODFactor);\n", +" mediump float GuessLODFactor = min(1.0, OffsetMapping_LodDistance / EyeVectorFogDepth.z);\n", +"#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n", +" // stupid workaround because 1-step and 2-step reliefmapping is void\n", +" mediump float LODSteps = max(3.0, ceil(GuessLODFactor * OffsetMapping_ScaleSteps.y));\n", +"#else\n", +" mediump float LODSteps = ceil(GuessLODFactor * OffsetMapping_ScaleSteps.y);\n", +"#endif\n", +" mediump float LODFactor = LODSteps / OffsetMapping_ScaleSteps.y;\n", +" mediump vec4 ScaleSteps = vec4(OffsetMapping_ScaleSteps.x, LODSteps, 1.0 / LODSteps, OffsetMapping_ScaleSteps.w * LODFactor);\n", +"#else\n", +" #define ScaleSteps OffsetMapping_ScaleSteps\n", +"#endif\n", +"#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n", +" float f;\n", +" // 14 sample relief mapping: linear search and then binary search\n", +" // this basically steps forward a small amount repeatedly until it finds\n", +" // itself inside solid, then jitters forward and back using decreasing\n", +" // amounts to find the impact\n", +" //vec3 OffsetVector = vec3(EyeVectorFogDepth.xy * ((1.0 / EyeVectorFogDepth.z) * ScaleSteps.x) * vec2(-1, 1), -1);\n", +" //vec3 OffsetVector = vec3(normalize(EyeVectorFogDepth.xy) * ScaleSteps.x * vec2(-1, 1), -1);\n", +" vec3 OffsetVector = vec3(normalize(EyeVectorFogDepth.xyz).xy * ScaleSteps.x * vec2(-1, 1), -1);\n", +" vec3 RT = vec3(vec2(TexCoord.xy - OffsetVector.xy*OffsetMapping_Bias), 1);\n", +" OffsetVector *= ScaleSteps.z;\n", +" for(i = 1.0; i < ScaleSteps.y; ++i)\n", +" RT += OffsetVector * step(dp_textureGrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z);\n", +" for(i = 0.0, f = 1.0; i < ScaleSteps.w; ++i, f *= 0.5)\n", +" RT += OffsetVector * (step(dp_textureGrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z) * f - 0.5 * f);\n", +" return RT.xy;\n", +"#else\n", +" // 2 sample offset mapping (only 2 samples because of ATI Radeon 9500-9800/X300 limits)\n", +" //vec2 OffsetVector = vec2(EyeVectorFogDepth.xy * ((1.0 / EyeVectorFogDepth.z) * ScaleSteps.x) * vec2(-1, 1));\n", +" //vec2 OffsetVector = vec2(normalize(EyeVectorFogDepth.xy) * ScaleSteps.x * vec2(-1, 1));\n", +" vec2 OffsetVector = vec2(normalize(EyeVectorFogDepth.xyz).xy * ScaleSteps.x * vec2(-1, 1));\n", +" OffsetVector *= ScaleSteps.z;\n", +" for(i = 0.0; i < ScaleSteps.y; ++i)\n", +" TexCoord += OffsetVector * ((1.0 - OffsetMapping_Bias) - dp_textureGrad(Texture_Normal, TexCoord, dPdx, dPdy).a);\n", +" return TexCoord;\n", +"#endif\n", +"}\n", +"#endif // USEOFFSETMAPPING\n", +"\n", +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE)\n", +"uniform sampler2D Texture_Attenuation;\n", +"uniform samplerCube Texture_Cube;\n", +"#endif\n", +"\n", +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n", +"\n", +"#ifdef USESHADOWMAP2D\n", +"# ifdef USESHADOWSAMPLER\n", +"uniform sampler2DShadow Texture_ShadowMap2D;\n", +"# else\n", +"uniform sampler2D Texture_ShadowMap2D;\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USESHADOWMAPVSDCT\n", +"uniform samplerCube Texture_CubeProjection;\n", +"#endif\n", +"\n", +"#if defined(USESHADOWMAP2D)\n", +"uniform mediump vec2 ShadowMap_TextureScale;\n", +"uniform mediump vec4 ShadowMap_Parameters;\n", +"#endif\n", +"\n", +"#if defined(USESHADOWMAP2D)\n", +"# ifdef USESHADOWMAPORTHO\n", +"# define GetShadowMapTC2D(dir) (min(dir, ShadowMap_Parameters.xyz))\n", +"# else\n", +"# ifdef USESHADOWMAPVSDCT\n", +"vec3 GetShadowMapTC2D(vec3 dir)\n", +"{\n", +" vec3 adir = abs(dir);\n", +" float m = max(max(adir.x, adir.y), adir.z);\n", +" vec4 proj = dp_textureCube(Texture_CubeProjection, dir);\n", +"#ifdef USEDEPTHRGB\n", +" return vec3(mix(dir.xy, dir.zz, proj.xy) * (ShadowMap_Parameters.x / m) + proj.zw * ShadowMap_Parameters.z, m + 64.0 * ShadowMap_Parameters.w);\n", +"#else\n", +" vec2 mparams = ShadowMap_Parameters.xy / m;\n", +" return vec3(mix(dir.xy, dir.zz, proj.xy) * mparams.x + proj.zw * ShadowMap_Parameters.z, mparams.y + ShadowMap_Parameters.w);\n", +"#endif\n", +"}\n", +"# else\n", +"vec3 GetShadowMapTC2D(vec3 dir)\n", +"{\n", +" vec3 adir = abs(dir);\n", +" float m; vec4 proj;\n", +" if (adir.x > adir.y) { m = adir.x; proj = vec4(dir.zyx, 0.5); } else { m = adir.y; proj = vec4(dir.xzy, 1.5); }\n", +" if (adir.z > m) { m = adir.z; proj = vec4(dir, 2.5); }\n", +"#ifdef USEDEPTHRGB\n", +" return vec3(proj.xy * (ShadowMap_Parameters.x / m) + vec2(0.5,0.5) + vec2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, m + 64.0 * ShadowMap_Parameters.w);\n", +"#else\n", +" vec2 mparams = ShadowMap_Parameters.xy / m;\n", +" return vec3(proj.xy * mparams.x + vec2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, mparams.y + ShadowMap_Parameters.w);\n", +"#endif\n", +"}\n", +"# endif\n", +"# endif\n", +"#endif // defined(USESHADOWMAP2D)\n", +"\n", +"# ifdef USESHADOWMAP2D\n", +"float ShadowMapCompare(vec3 dir)\n", +"{\n", +" vec3 shadowmaptc = GetShadowMapTC2D(dir);\n", +" float f;\n", +"\n", +"# ifdef USEDEPTHRGB\n", +"# ifdef USESHADOWMAPPCF\n", +"# define texval(x, y) decodedepthmacro(dp_texture2D(Texture_ShadowMap2D, center + vec2(x, y)*ShadowMap_TextureScale))\n", +"# if USESHADOWMAPPCF > 1\n", +" vec2 center = shadowmaptc.xy - 0.5, offset = fract(center);\n", +" center *= ShadowMap_TextureScale;\n", +" vec4 row1 = step(shadowmaptc.z, vec4(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0), texval( 2.0, -1.0)));\n", +" vec4 row2 = step(shadowmaptc.z, vec4(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0), texval( 2.0, 0.0)));\n", +" vec4 row3 = step(shadowmaptc.z, vec4(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0), texval( 2.0, 1.0)));\n", +" vec4 row4 = step(shadowmaptc.z, vec4(texval(-1.0, 2.0), texval( 0.0, 2.0), texval( 1.0, 2.0), texval( 2.0, 2.0)));\n", +" vec4 cols = row2 + row3 + mix(row1, row4, offset.y);\n", +" f = dot(mix(cols.xyz, cols.yzw, offset.x), vec3(1.0/9.0));\n", +"# else\n", +" vec2 center = shadowmaptc.xy*ShadowMap_TextureScale, offset = fract(shadowmaptc.xy);\n", +" vec3 row1 = step(shadowmaptc.z, vec3(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0)));\n", +" vec3 row2 = step(shadowmaptc.z, vec3(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0)));\n", +" vec3 row3 = step(shadowmaptc.z, vec3(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0)));\n", +" vec3 cols = row2 + mix(row1, row3, offset.y);\n", +" f = dot(mix(cols.xy, cols.yz, offset.x), vec2(0.25));\n", +"# endif\n", +"# else\n", +" f = step(shadowmaptc.z, decodedepthmacro(dp_texture2D(Texture_ShadowMap2D, shadowmaptc.xy*ShadowMap_TextureScale)));\n", +"# endif\n", +"# else\n", +"# ifdef USESHADOWSAMPLER\n", +"# ifdef USESHADOWMAPPCF\n", +"# define texval(off) dp_shadow2D(Texture_ShadowMap2D, vec3(off, shadowmaptc.z)) \n", +" vec2 offset = fract(shadowmaptc.xy - 0.5);\n", +" vec4 size = vec4(offset + 1.0, 2.0 - offset);\n", +"# if USESHADOWMAPPCF > 1\n", +" vec2 center = (shadowmaptc.xy - offset + 0.5)*ShadowMap_TextureScale;\n", +" vec4 weight = (vec4(-1.5, -1.5, 2.0, 2.0) + (shadowmaptc.xy - 0.5*offset).xyxy)*ShadowMap_TextureScale.xyxy;\n", +" f = (1.0/25.0)*dot(size.zxzx*size.wwyy, vec4(texval(weight.xy), texval(weight.zy), texval(weight.xw), texval(weight.zw))) +\n", +" (2.0/25.0)*dot(size, vec4(texval(vec2(weight.z, center.y)), texval(vec2(center.x, weight.w)), texval(vec2(weight.x, center.y)), texval(vec2(center.x, weight.y)))) +\n", +" (4.0/25.0)*texval(center);\n", +"# else\n", +" vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (shadowmaptc.xy - 0.5*offset).xyxy)*ShadowMap_TextureScale.xyxy;\n", +" f = (1.0/9.0)*dot(size.zxzx*size.wwyy, vec4(texval(weight.zw), texval(weight.xw), texval(weight.zy), texval(weight.xy)));\n", +"# endif \n", +"# else\n", +" f = dp_shadow2D(Texture_ShadowMap2D, vec3(shadowmaptc.xy*ShadowMap_TextureScale, shadowmaptc.z));\n", +"# endif\n", +"# else\n", +"# ifdef USESHADOWMAPPCF\n", +"# if defined(GL_ARB_texture_gather) || defined(GL_AMD_texture_texture4)\n", +"# ifdef GL_ARB_texture_gather\n", +"# define texval(x, y) textureGatherOffset(Texture_ShadowMap2D, center, ivec2(x, y))\n", +"# else\n", +"# define texval(x, y) texture4(Texture_ShadowMap2D, center + vec2(x, y)*ShadowMap_TextureScale)\n", +"# endif\n", +" vec2 offset = fract(shadowmaptc.xy - 0.5), center = (shadowmaptc.xy - offset)*ShadowMap_TextureScale;\n", +"# if USESHADOWMAPPCF > 1\n", +" vec4 group1 = step(shadowmaptc.z, texval(-2.0, -2.0));\n", +" vec4 group2 = step(shadowmaptc.z, texval( 0.0, -2.0));\n", +" vec4 group3 = step(shadowmaptc.z, texval( 2.0, -2.0));\n", +" vec4 group4 = step(shadowmaptc.z, texval(-2.0, 0.0));\n", +" vec4 group5 = step(shadowmaptc.z, texval( 0.0, 0.0));\n", +" vec4 group6 = step(shadowmaptc.z, texval( 2.0, 0.0));\n", +" vec4 group7 = step(shadowmaptc.z, texval(-2.0, 2.0));\n", +" vec4 group8 = step(shadowmaptc.z, texval( 0.0, 2.0));\n", +" vec4 group9 = step(shadowmaptc.z, texval( 2.0, 2.0));\n", +" vec4 locols = vec4(group1.ab, group3.ab);\n", +" vec4 hicols = vec4(group7.rg, group9.rg);\n", +" locols.yz += group2.ab;\n", +" hicols.yz += group8.rg;\n", +" vec4 midcols = vec4(group1.rg, group3.rg) + vec4(group7.ab, group9.ab) +\n", +" vec4(group4.rg, group6.rg) + vec4(group4.ab, group6.ab) +\n", +" mix(locols, hicols, offset.y);\n", +" vec4 cols = group5 + vec4(group2.rg, group8.ab);\n", +" cols.xyz += mix(midcols.xyz, midcols.yzw, offset.x);\n", +" f = dot(cols, vec4(1.0/25.0));\n", +"# else\n", +" vec4 group1 = step(shadowmaptc.z, texval(-1.0, -1.0));\n", +" vec4 group2 = step(shadowmaptc.z, texval( 1.0, -1.0));\n", +" vec4 group3 = step(shadowmaptc.z, texval(-1.0, 1.0));\n", +" vec4 group4 = step(shadowmaptc.z, texval( 1.0, 1.0));\n", +" vec4 cols = vec4(group1.rg, group2.rg) + vec4(group3.ab, group4.ab) +\n", +" mix(vec4(group1.ab, group2.ab), vec4(group3.rg, group4.rg), offset.y);\n", +" f = dot(mix(cols.xyz, cols.yzw, offset.x), vec3(1.0/9.0));\n", +"# endif\n", +"# else\n", +"# ifdef GL_EXT_gpu_shader4\n", +"# define texval(x, y) dp_textureOffset(Texture_ShadowMap2D, center, x, y).r\n", +"# else\n", +"# define texval(x, y) dp_texture2D(Texture_ShadowMap2D, center + vec2(x, y)*ShadowMap_TextureScale).r \n", +"# endif\n", +"# if USESHADOWMAPPCF > 1\n", +" vec2 center = shadowmaptc.xy - 0.5, offset = fract(center);\n", +" center *= ShadowMap_TextureScale;\n", +" vec4 row1 = step(shadowmaptc.z, vec4(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0), texval( 2.0, -1.0)));\n", +" vec4 row2 = step(shadowmaptc.z, vec4(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0), texval( 2.0, 0.0)));\n", +" vec4 row3 = step(shadowmaptc.z, vec4(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0), texval( 2.0, 1.0)));\n", +" vec4 row4 = step(shadowmaptc.z, vec4(texval(-1.0, 2.0), texval( 0.0, 2.0), texval( 1.0, 2.0), texval( 2.0, 2.0)));\n", +" vec4 cols = row2 + row3 + mix(row1, row4, offset.y);\n", +" f = dot(mix(cols.xyz, cols.yzw, offset.x), vec3(1.0/9.0));\n", +"# else\n", +" vec2 center = shadowmaptc.xy*ShadowMap_TextureScale, offset = fract(shadowmaptc.xy);\n", +" vec3 row1 = step(shadowmaptc.z, vec3(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0)));\n", +" vec3 row2 = step(shadowmaptc.z, vec3(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0)));\n", +" vec3 row3 = step(shadowmaptc.z, vec3(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0)));\n", +" vec3 cols = row2 + mix(row1, row3, offset.y);\n", +" f = dot(mix(cols.xy, cols.yz, offset.x), vec2(0.25));\n", +"# endif\n", +"# endif\n", +"# else\n", +" f = step(shadowmaptc.z, dp_texture2D(Texture_ShadowMap2D, shadowmaptc.xy*ShadowMap_TextureScale).r);\n", +"# endif\n", +"# endif\n", +"# endif\n", +"# ifdef USESHADOWMAPORTHO\n", +" return mix(ShadowMap_Parameters.w, 1.0, f);\n", +"# else\n", +" return f;\n", +"# endif\n", +"}\n", +"# endif\n", +"#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n", +"#endif // FRAGMENT_SHADER\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_DEFERREDGEOMETRY\n", +"#ifdef VERTEX_SHADER\n", +"uniform highp mat4 TexMatrix;\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"uniform highp mat4 BackgroundTexMatrix;\n", +"#endif\n", +"uniform highp mat4 ModelViewMatrix;\n", +"void main(void)\n", +"{\n", +"#ifdef USESKELETAL\n", +" ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", +" ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", +" ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", +" vec4 sw = Attrib_SkeletalWeight;\n", +" vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", +" vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", +" vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", +" mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", +" mat3 SkeletalNormalMatrix = mat3(cross(SkeletalMatrix[1].xyz, SkeletalMatrix[2].xyz), cross(SkeletalMatrix[2].xyz, SkeletalMatrix[0].xyz), cross(SkeletalMatrix[0].xyz, SkeletalMatrix[1].xyz)); // is actually transpose(inverse(mat3(SkeletalMatrix))) * det(mat3(SkeletalMatrix))\n", +" vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", +" vec3 SkeletalSVector = normalize(Attrib_TexCoord1.xyz * SkeletalNormalMatrix);\n", +" vec3 SkeletalTVector = normalize(Attrib_TexCoord2.xyz * SkeletalNormalMatrix);\n", +" vec3 SkeletalNormal = normalize(Attrib_TexCoord3.xyz * SkeletalNormalMatrix);\n", +"#define Attrib_Position SkeletalVertex\n", +"#define Attrib_TexCoord1 SkeletalSVector\n", +"#define Attrib_TexCoord2 SkeletalTVector\n", +"#define Attrib_TexCoord3 SkeletalNormal\n", +"#endif\n", +" TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, 0.0, 0.0);\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" VertexColor = Attrib_Color;\n", +" TexCoord2 = vec2(BackgroundTexMatrix * Attrib_TexCoord0);\n", +"#endif\n", +"\n", +" // transform unnormalized eye direction into tangent space\n", +"#ifdef USEOFFSETMAPPING\n", +" vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n", +" EyeVectorFogDepth.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n", +" EyeVectorFogDepth.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n", +" EyeVectorFogDepth.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n", +" EyeVectorFogDepth.w = 0.0;\n", +"#endif\n", +"\n", +" VectorS = (ModelViewMatrix * vec4(Attrib_TexCoord1.xyz, 0));\n", +" VectorT = (ModelViewMatrix * vec4(Attrib_TexCoord2.xyz, 0));\n", +" VectorR = (ModelViewMatrix * vec4(Attrib_TexCoord3.xyz, 0));\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +" Depth = (ModelViewMatrix * Attrib_Position).z;\n", +"}\n", +"#endif // VERTEX_SHADER\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main(void)\n", +"{\n", +"#ifdef USEOFFSETMAPPING\n", +" // apply offsetmapping\n", +" vec2 dPdx = dp_offsetmapping_dFdx(TexCoordSurfaceLightmap.xy);\n", +" vec2 dPdy = dp_offsetmapping_dFdy(TexCoordSurfaceLightmap.xy);\n", +" vec2 TexCoordOffset = OffsetMapping(TexCoordSurfaceLightmap.xy, dPdx, dPdy);\n", +"# define offsetMappedTexture2D(t) dp_textureGrad(t, TexCoordOffset, dPdx, dPdy)\n", +"#else\n", +"# define offsetMappedTexture2D(t) dp_texture2D(t, TexCoordSurfaceLightmap.xy)\n", +"#endif\n", +"\n", +"#ifdef USEALPHAKILL\n", +" if (offsetMappedTexture2D(Texture_Color).a < 0.5)\n", +" discard;\n", +"#endif\n", +"\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" float alpha = offsetMappedTexture2D(Texture_Color).a;\n", +" float terrainblend = clamp(float(VertexColor.a) * alpha * 2.0 - 0.5, float(0.0), float(1.0));\n", +" //float terrainblend = min(float(VertexColor.a) * alpha * 2.0, float(1.0));\n", +" //float terrainblend = float(VertexColor.a) * alpha > 0.5;\n", +"#endif\n", +"\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" vec3 surfacenormal = mix(vec3(dp_texture2D(Texture_SecondaryNormal, TexCoord2)), vec3(offsetMappedTexture2D(Texture_Normal)), terrainblend) - vec3(0.5, 0.5, 0.5);\n", +" float a = mix(dp_texture2D(Texture_SecondaryGloss, TexCoord2).a, offsetMappedTexture2D(Texture_Gloss).a, terrainblend);\n", +"#else\n", +" vec3 surfacenormal = vec3(offsetMappedTexture2D(Texture_Normal)) - vec3(0.5, 0.5, 0.5);\n", +" float a = offsetMappedTexture2D(Texture_Gloss).a;\n", +"#endif\n", +"\n", +" vec3 pixelnormal = normalize(surfacenormal.x * VectorS.xyz + surfacenormal.y * VectorT.xyz + surfacenormal.z * VectorR.xyz);\n", +" dp_FragColor = vec4(pixelnormal.x, pixelnormal.y, Depth, a);\n", +"}\n", +"#endif // FRAGMENT_SHADER\n", +"#else // !MODE_DEFERREDGEOMETRY\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_DEFERREDLIGHTSOURCE\n", +"#ifdef VERTEX_SHADER\n", +"uniform highp mat4 ModelViewMatrix;\n", +"void main(void)\n", +"{\n", +" ModelViewPosition = ModelViewMatrix * Attrib_Position;\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +"}\n", +"#endif // VERTEX_SHADER\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"uniform highp mat4 ViewToLight;\n", +"// ScreenToDepth = vec2(Far / (Far - Near), Far * Near / (Near - Far));\n", +"uniform highp vec2 ScreenToDepth;\n", +"uniform myhalf3 DeferredColor_Ambient;\n", +"uniform myhalf3 DeferredColor_Diffuse;\n", +"#ifdef USESPECULAR\n", +"uniform myhalf3 DeferredColor_Specular;\n", +"uniform myhalf SpecularPower;\n", +"#endif\n", +"uniform myhalf2 PixelToScreenTexCoord;\n", +"void main(void)\n", +"{\n", +" // calculate viewspace pixel position\n", +" vec2 ScreenTexCoord = gl_FragCoord.xy * PixelToScreenTexCoord;\n", +" vec3 position;\n", +" // get the geometry information (depth, normal, specular exponent)\n", +" myhalf4 normalmap = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord);\n", +" // decode viewspace pixel normal\n", +"// myhalf3 surfacenormal = normalize(normalmap.rgb - cast_myhalf3(0.5,0.5,0.5));\n", +" myhalf3 surfacenormal = myhalf3(normalmap.rg, sqrt(1.0-dot(normalmap.rg, normalmap.rg)));\n", +" // decode viewspace pixel position\n", +"// position.z = decodedepthmacro(dp_texture2D(Texture_ScreenDepth, ScreenTexCoord));\n", +" position.z = normalmap.b;\n", +"// position.z = ScreenToDepth.y / (dp_texture2D(Texture_ScreenDepth, ScreenTexCoord).r + ScreenToDepth.x);\n", +" position.xy = ModelViewPosition.xy * (position.z / ModelViewPosition.z);\n", +"\n", +" // now do the actual shading\n", +" // surfacenormal = pixel normal in viewspace\n", +" // LightVector = pixel to light in viewspace\n", +" // CubeVector = pixel in lightspace\n", +" // eyenormal = pixel to view direction in viewspace\n", +" vec3 CubeVector = vec3(ViewToLight * vec4(position,1));\n", +" myhalf fade = cast_myhalf(dp_texture2D(Texture_Attenuation, vec2(length(CubeVector), 0.0)));\n", +"#ifdef USEDIFFUSE\n", +" // calculate diffuse shading\n", +" myhalf3 lightnormal = cast_myhalf3(normalize(LightPosition - position));\n", +"SHADEDIFFUSE\n", +"#endif\n", +"#ifdef USESPECULAR\n", +" // calculate directional shading\n", +" myhalf3 eyenormal = -normalize(cast_myhalf3(position));\n", +"SHADESPECULAR(SpecularPower * normalmap.a)\n", +"#endif\n", +"\n", +"#if defined(USESHADOWMAP2D)\n", +" fade *= ShadowMapCompare(CubeVector);\n", +"#endif\n", +"\n", +"#ifdef USESPECULAR\n", +" gl_FragData[0] = vec4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n", +" gl_FragData[1] = vec4(DeferredColor_Specular * (specular * fade), 1.0);\n", +"# ifdef USECUBEFILTER\n", +" vec3 cubecolor = dp_textureCube(Texture_Cube, CubeVector).rgb;\n", +" gl_FragData[0].rgb *= cubecolor;\n", +" gl_FragData[1].rgb *= cubecolor;\n", +"# endif\n", +"#else\n", +"# ifdef USEDIFFUSE\n", +" gl_FragColor = vec4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n", +"# else\n", +" gl_FragColor = vec4(DeferredColor_Ambient * fade, 1.0);\n", +"# endif\n", +"# ifdef USECUBEFILTER\n", +" vec3 cubecolor = dp_textureCube(Texture_Cube, CubeVector).rgb;\n", +" gl_FragColor.rgb *= cubecolor;\n", +"# endif\n", +"#endif\n", +"}\n", +"#endif // FRAGMENT_SHADER\n", +"#else // !MODE_DEFERREDLIGHTSOURCE\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef VERTEX_SHADER\n", +"uniform highp mat4 TexMatrix;\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"uniform highp mat4 BackgroundTexMatrix;\n", +"#endif\n", +"#ifdef MODE_LIGHTSOURCE\n", +"uniform highp mat4 ModelToLight;\n", +"#endif\n", +"#ifdef USESHADOWMAPORTHO\n", +"uniform highp mat4 ShadowMapMatrix;\n", +"#endif\n", +"#ifdef USEBOUNCEGRID\n", +"uniform highp mat4 BounceGridMatrix;\n", +"#endif\n", +"void main(void)\n", +"{\n", +"#ifdef USESKELETAL\n", +" ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", +" ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", +" ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", +" vec4 sw = Attrib_SkeletalWeight;\n", +" vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", +" vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", +" vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", +" mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", +"// ivec4 si = ivec4(Attrib_SkeletalIndex);\n", +"// mat4 SkeletalMatrix = Skeletal_Transform[si.x] * Attrib_SkeletalWeight.x + Skeletal_Transform[si.y] * Attrib_SkeletalWeight.y + Skeletal_Transform[si.z] * Attrib_SkeletalWeight.z + Skeletal_Transform[si.w] * Attrib_SkeletalWeight.w;\n", +" mat3 SkeletalNormalMatrix = mat3(cross(SkeletalMatrix[1].xyz, SkeletalMatrix[2].xyz), cross(SkeletalMatrix[2].xyz, SkeletalMatrix[0].xyz), cross(SkeletalMatrix[0].xyz, SkeletalMatrix[1].xyz)); // is actually transpose(inverse(mat3(SkeletalMatrix))) * det(mat3(SkeletalMatrix))\n", +" vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", +" SkeletalVertex.w = 1.0;\n", +" vec3 SkeletalSVector = normalize(Attrib_TexCoord1.xyz * SkeletalNormalMatrix);\n", +" vec3 SkeletalTVector = normalize(Attrib_TexCoord2.xyz * SkeletalNormalMatrix);\n", +" vec3 SkeletalNormal = normalize(Attrib_TexCoord3.xyz * SkeletalNormalMatrix);\n", +"#define Attrib_Position SkeletalVertex\n", +"#define Attrib_TexCoord1 SkeletalSVector\n", +"#define Attrib_TexCoord2 SkeletalTVector\n", +"#define Attrib_TexCoord3 SkeletalNormal\n", +"#endif\n", +"\n", +"#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR) || defined(USEALPHAGENVERTEX)\n", +" VertexColor = Attrib_Color;\n", +"#endif\n", +" // copy the surface texcoord\n", +"#ifdef USELIGHTMAP\n", +" TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, Attrib_TexCoord4.xy);\n", +"#else\n", +" TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, 0.0, 0.0);\n", +"#endif\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" TexCoord2 = vec2(BackgroundTexMatrix * Attrib_TexCoord0);\n", +"#endif\n", +"\n", +"#ifdef USEBOUNCEGRID\n", +" BounceGridTexCoord = vec3(BounceGridMatrix * Attrib_Position);\n", +"#ifdef USEBOUNCEGRIDDIRECTIONAL\n", +" BounceGridTexCoord.z *= 0.125;\n", +"#endif\n", +"#endif\n", +"\n", +"#ifdef MODE_LIGHTSOURCE\n", +" // transform vertex position into light attenuation/cubemap space\n", +" // (-1 to +1 across the light box)\n", +" CubeVector = vec3(ModelToLight * Attrib_Position);\n", +"\n", +"# ifdef USEDIFFUSE\n", +" // transform unnormalized light direction into tangent space\n", +" // (we use unnormalized to ensure that it interpolates correctly and then\n", +" // normalize it per pixel)\n", +" vec3 lightminusvertex = LightPosition - Attrib_Position.xyz;\n", +" LightVector.x = dot(lightminusvertex, Attrib_TexCoord1.xyz);\n", +" LightVector.y = dot(lightminusvertex, Attrib_TexCoord2.xyz);\n", +" LightVector.z = dot(lightminusvertex, Attrib_TexCoord3.xyz);\n", +"# endif\n", +"#endif\n", +"\n", +"#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE)\n", +" LightVector.x = dot(LightDir, Attrib_TexCoord1.xyz);\n", +" LightVector.y = dot(LightDir, Attrib_TexCoord2.xyz);\n", +" LightVector.z = dot(LightDir, Attrib_TexCoord3.xyz);\n", +"#endif\n", +"\n", +" // transform unnormalized eye direction into tangent space\n", +"#ifdef USEEYEVECTOR\n", +" vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n", +" EyeVectorFogDepth.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n", +" EyeVectorFogDepth.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n", +" EyeVectorFogDepth.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n", +"#ifdef USEFOG\n", +" EyeVectorFogDepth.w = dot(FogPlane, Attrib_Position);\n", +"#else\n", +" EyeVectorFogDepth.w = 0.0;\n", +"#endif\n", +"#endif\n", +"\n", +"\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(USEREFLECTCUBE) || defined(USEBOUNCEGRIDDIRECTIONAL)\n", +"# ifdef USEFOG\n", +" VectorS = vec4(Attrib_TexCoord1.xyz, EyePosition.x - Attrib_Position.x);\n", +" VectorT = vec4(Attrib_TexCoord2.xyz, EyePosition.y - Attrib_Position.y);\n", +" VectorR = vec4(Attrib_TexCoord3.xyz, EyePosition.z - Attrib_Position.z);\n", +"# else\n", +" VectorS = vec4(Attrib_TexCoord1, 0);\n", +" VectorT = vec4(Attrib_TexCoord2, 0);\n", +" VectorR = vec4(Attrib_TexCoord3, 0);\n", +"# endif\n", +"#else\n", +"# ifdef USEFOG\n", +" EyeVectorModelSpace = EyePosition - Attrib_Position.xyz;\n", +"# endif\n", +"#endif\n", +"\n", +" // transform vertex to clipspace (post-projection, but before perspective divide by W occurs)\n", +" gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", +"\n", +"#ifdef USESHADOWMAPORTHO\n", +" ShadowMapTC = vec3(ShadowMapMatrix * gl_Position);\n", +"#endif\n", +"\n", +"#ifdef USEREFLECTION\n", +" ModelViewProjectionPosition = gl_Position;\n", +"#endif\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +"}\n", +"#endif // VERTEX_SHADER\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"#ifdef USEDEFERREDLIGHTMAP\n", +"uniform myhalf2 PixelToScreenTexCoord;\n", +"uniform myhalf3 DeferredMod_Diffuse;\n", +"uniform myhalf3 DeferredMod_Specular;\n", +"#endif\n", +"uniform myhalf3 Color_Ambient;\n", +"uniform myhalf3 Color_Diffuse;\n", +"uniform myhalf3 Color_Specular;\n", +"uniform myhalf SpecularPower;\n", +"#ifdef USEGLOW\n", +"uniform myhalf3 Color_Glow;\n", +"#endif\n", +"uniform myhalf Alpha;\n", +"#ifdef USEREFLECTION\n", +"uniform mediump vec4 DistortScaleRefractReflect;\n", +"uniform mediump vec4 ScreenScaleRefractReflect;\n", +"uniform mediump vec4 ScreenCenterRefractReflect;\n", +"uniform mediump vec4 ReflectColor;\n", +"#endif\n", +"#ifdef USEREFLECTCUBE\n", +"uniform highp mat4 ModelToReflectCube;\n", +"uniform sampler2D Texture_ReflectMask;\n", +"uniform samplerCube Texture_ReflectCube;\n", +"#endif\n", +"#ifdef MODE_LIGHTDIRECTION\n", +"uniform myhalf3 LightColor;\n", +"#endif\n", +"#ifdef MODE_LIGHTSOURCE\n", +"uniform myhalf3 LightColor;\n", +"#endif\n", +"#ifdef USEBOUNCEGRID\n", +"uniform sampler3D Texture_BounceGrid;\n", +"uniform float BounceGridIntensity;\n", +"uniform highp mat4 BounceGridMatrix;\n", +"#endif\n", +"uniform highp float ClientTime;\n", +"#ifdef USENORMALMAPSCROLLBLEND\n", +"uniform highp vec2 NormalmapScrollBlend;\n", +"#endif\n", +"void main(void)\n", +"{\n", +"#ifdef USEOFFSETMAPPING\n", +" // apply offsetmapping\n", +" vec2 dPdx = dp_offsetmapping_dFdx(TexCoordSurfaceLightmap.xy);\n", +" vec2 dPdy = dp_offsetmapping_dFdy(TexCoordSurfaceLightmap.xy);\n", +" vec2 TexCoordOffset = OffsetMapping(TexCoordSurfaceLightmap.xy, dPdx, dPdy);\n", +"# define offsetMappedTexture2D(t) dp_textureGrad(t, TexCoordOffset, dPdx, dPdy)\n", +"# define TexCoord TexCoordOffset\n", +"#else\n", +"# define offsetMappedTexture2D(t) dp_texture2D(t, TexCoordSurfaceLightmap.xy)\n", +"# define TexCoord TexCoordSurfaceLightmap.xy\n", +"#endif\n", +"\n", +" // combine the diffuse textures (base, pants, shirt)\n", +" myhalf4 color = cast_myhalf4(offsetMappedTexture2D(Texture_Color));\n", +"#ifdef USEALPHAKILL\n", +" if (color.a < 0.5)\n", +" discard;\n", +"#endif\n", +" color.a *= Alpha;\n", +"#ifdef USECOLORMAPPING\n", +" color.rgb += cast_myhalf3(offsetMappedTexture2D(Texture_Pants)) * Color_Pants + cast_myhalf3(offsetMappedTexture2D(Texture_Shirt)) * Color_Shirt;\n", +"#endif\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"#ifdef USEBOTHALPHAS\n", +" myhalf4 color2 = cast_myhalf4(dp_texture2D(Texture_SecondaryColor, TexCoord2));\n", +" myhalf terrainblend = clamp(cast_myhalf(VertexColor.a) * color.a, cast_myhalf(1.0 - color2.a), cast_myhalf(1.0));\n", +" color.rgb = mix(color2.rgb, color.rgb, terrainblend);\n", +"#else\n", +" myhalf terrainblend = clamp(cast_myhalf(VertexColor.a) * color.a * 2.0 - 0.5, cast_myhalf(0.0), cast_myhalf(1.0));\n", +" //myhalf terrainblend = min(cast_myhalf(VertexColor.a) * color.a * 2.0, cast_myhalf(1.0));\n", +" //myhalf terrainblend = cast_myhalf(VertexColor.a) * color.a > 0.5;\n", +" color.rgb = mix(cast_myhalf3(dp_texture2D(Texture_SecondaryColor, TexCoord2)), color.rgb, terrainblend);\n", +"#endif\n", +" color.a = 1.0;\n", +" //color = mix(cast_myhalf4(1, 0, 0, 1), color, terrainblend);\n", +"#endif\n", +"#ifdef USEALPHAGENVERTEX\n", +" color.a *= VertexColor.a;\n", +"#endif\n", +"\n", +" // get the surface normal\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" myhalf3 surfacenormal = normalize(mix(cast_myhalf3(dp_texture2D(Texture_SecondaryNormal, TexCoord2)), cast_myhalf3(offsetMappedTexture2D(Texture_Normal)), terrainblend) - cast_myhalf3(0.5, 0.5, 0.5));\n", +"#else\n", +" myhalf3 surfacenormal = normalize(cast_myhalf3(offsetMappedTexture2D(Texture_Normal)) - cast_myhalf3(0.5, 0.5, 0.5));\n", +"#endif\n", +"\n", +" // get the material colors\n", +" myhalf3 diffusetex = color.rgb;\n", +"#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n", +"# ifdef USEVERTEXTEXTUREBLEND\n", +" myhalf4 glosstex = mix(cast_myhalf4(dp_texture2D(Texture_SecondaryGloss, TexCoord2)), cast_myhalf4(offsetMappedTexture2D(Texture_Gloss)), terrainblend);\n", +"# else\n", +" myhalf4 glosstex = cast_myhalf4(offsetMappedTexture2D(Texture_Gloss));\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USEREFLECTCUBE\n", +" myhalf3 TangentReflectVector = reflect(-EyeVectorFogDepth.xyz, surfacenormal);\n", +" myhalf3 ModelReflectVector = TangentReflectVector.x * VectorS.xyz + TangentReflectVector.y * VectorT.xyz + TangentReflectVector.z * VectorR.xyz;\n", +" myhalf3 ReflectCubeTexCoord = cast_myhalf3(ModelToReflectCube * vec4(ModelReflectVector, 0));\n", +" diffusetex += cast_myhalf3(offsetMappedTexture2D(Texture_ReflectMask)) * cast_myhalf3(dp_textureCube(Texture_ReflectCube, ReflectCubeTexCoord));\n", +"#endif\n", +"\n", +"#ifdef USESPECULAR\n", +" myhalf3 eyenormal = normalize(cast_myhalf3(EyeVectorFogDepth.xyz));\n", +"#endif\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_LIGHTSOURCE\n", +" // light source\n", +"#ifdef USEDIFFUSE\n", +" myhalf3 lightnormal = cast_myhalf3(normalize(LightVector));\n", +"SHADEDIFFUSE\n", +" color.rgb = diffusetex * (Color_Ambient + diffuse * Color_Diffuse);\n", +"#ifdef USESPECULAR\n", +"SHADESPECULAR(SpecularPower * glosstex.a)\n", +" color.rgb += glosstex.rgb * (specular * Color_Specular);\n", +"#endif\n", +"#else\n", +" color.rgb = diffusetex * Color_Ambient;\n", +"#endif\n", +" color.rgb *= LightColor;\n", +" color.rgb *= cast_myhalf(dp_texture2D(Texture_Attenuation, vec2(length(CubeVector), 0.0)));\n", +"#if defined(USESHADOWMAP2D)\n", +" color.rgb *= ShadowMapCompare(CubeVector);\n", +"#endif\n", +"# ifdef USECUBEFILTER\n", +" color.rgb *= cast_myhalf3(dp_textureCube(Texture_Cube, CubeVector));\n", +"# endif\n", +"#endif // MODE_LIGHTSOURCE\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_LIGHTDIRECTION\n", +" #define SHADING\n", +" #ifdef USEDIFFUSE\n", +" myhalf3 lightnormal = cast_myhalf3(normalize(LightVector));\n", +" #endif\n", +" #define lightcolor LightColor\n", +"#endif // MODE_LIGHTDIRECTION\n", +"#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", +" #define SHADING\n", +" // deluxemap lightmapping using light vectors in modelspace (q3map2 -light -deluxe)\n", +" myhalf3 lightnormal_modelspace = cast_myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + cast_myhalf3(-1.0, -1.0, -1.0);\n", +" myhalf3 lightcolor = cast_myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n", +" // convert modelspace light vector to tangentspace\n", +" myhalf3 lightnormal;\n", +" lightnormal.x = dot(lightnormal_modelspace, cast_myhalf3(VectorS));\n", +" lightnormal.y = dot(lightnormal_modelspace, cast_myhalf3(VectorT));\n", +" lightnormal.z = dot(lightnormal_modelspace, cast_myhalf3(VectorR));\n", +" lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n", +" // calculate directional shading (and undoing the existing angle attenuation on the lightmap by the division)\n", +" // note that q3map2 is too stupid to calculate proper surface normals when q3map_nonplanar\n", +" // is used (the lightmap and deluxemap coords correspond to virtually random coordinates\n", +" // on that luxel, and NOT to its center, because recursive triangle subdivision is used\n", +" // to map the luxels to coordinates on the draw surfaces), which also causes\n", +" // deluxemaps to be wrong because light contributions from the wrong side of the surface\n", +" // are added up. To prevent divisions by zero or strong exaggerations, a max()\n", +" // nudge is done here at expense of some additional fps. This is ONLY needed for\n", +" // deluxemaps, tangentspace deluxemap avoid this problem by design.\n", +" lightcolor *= 1.0 / max(0.25, lightnormal.z);\n", +"#endif // MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", +"#ifdef MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", +" #define SHADING\n", +" // deluxemap lightmapping using light vectors in tangentspace (hmap2 -light)\n", +" myhalf3 lightnormal = cast_myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + cast_myhalf3(-1.0, -1.0, -1.0);\n", +" myhalf3 lightcolor = cast_myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n", +"#endif\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR)\n", +" #define SHADING\n", +" // forced deluxemap on lightmapped/vertexlit surfaces\n", +" myhalf3 lightnormal = cast_myhalf3(0.0, 0.0, 1.0);\n", +" #ifdef USELIGHTMAP\n", +" myhalf3 lightcolor = cast_myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n", +" #else\n", +" myhalf3 lightcolor = cast_myhalf3(VertexColor.rgb);\n", +" #endif\n", +"#endif\n", +"#ifdef MODE_FAKELIGHT\n", +" #define SHADING\n", +" myhalf3 lightnormal = cast_myhalf3(normalize(EyeVectorFogDepth.xyz));\n", +" myhalf3 lightcolor = cast_myhalf3(1.0);\n", +"#endif // MODE_FAKELIGHT\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_LIGHTMAP\n", +" color.rgb = diffusetex * (Color_Ambient + cast_myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw)) * Color_Diffuse);\n", +"#endif // MODE_LIGHTMAP\n", +"#ifdef MODE_VERTEXCOLOR\n", +" color.rgb = diffusetex * (Color_Ambient + cast_myhalf3(VertexColor.rgb) * Color_Diffuse);\n", +"#endif // MODE_VERTEXCOLOR\n", +"#ifdef MODE_FLATCOLOR\n", +" color.rgb = diffusetex * Color_Ambient;\n", +"#endif // MODE_FLATCOLOR\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef SHADING\n", +"# ifdef USEDIFFUSE\n", +"SHADEDIFFUSE\n", +"# ifdef USESPECULAR\n", +"SHADESPECULAR(SpecularPower * glosstex.a)\n", +" color.rgb = diffusetex * Color_Ambient + (diffusetex * Color_Diffuse * diffuse + glosstex.rgb * Color_Specular * specular) * lightcolor;\n", +"# else\n", +" color.rgb = diffusetex * (Color_Ambient + Color_Diffuse * diffuse * lightcolor);\n", +"# endif\n", +"# else\n", +" color.rgb = diffusetex * Color_Ambient;\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USESHADOWMAPORTHO\n", +" color.rgb *= ShadowMapCompare(ShadowMapTC);\n", +"#endif\n", +"\n", +"#ifdef USEDEFERREDLIGHTMAP\n", +" vec2 ScreenTexCoord = gl_FragCoord.xy * PixelToScreenTexCoord;\n", +" color.rgb += diffusetex * cast_myhalf3(dp_texture2D(Texture_ScreenDiffuse, ScreenTexCoord)) * DeferredMod_Diffuse;\n", +" color.rgb += glosstex.rgb * cast_myhalf3(dp_texture2D(Texture_ScreenSpecular, ScreenTexCoord)) * DeferredMod_Specular;\n", +"// color.rgb = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord).rgb * vec3(1.0, 1.0, 0.001);\n", +"#endif\n", +"\n", +"#ifdef USEBOUNCEGRID\n", +"#ifdef USEBOUNCEGRIDDIRECTIONAL\n", +"// myhalf4 bouncegrid_coeff1 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord ));\n", +"// myhalf4 bouncegrid_coeff2 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.125))) * 2.0 + cast_myhalf4(-1.0, -1.0, -1.0, -1.0);\n", +" myhalf4 bouncegrid_coeff3 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.250)));\n", +" myhalf4 bouncegrid_coeff4 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.375)));\n", +" myhalf4 bouncegrid_coeff5 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.500)));\n", +" myhalf4 bouncegrid_coeff6 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.625)));\n", +" myhalf4 bouncegrid_coeff7 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.750)));\n", +" myhalf4 bouncegrid_coeff8 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.875)));\n", +" myhalf3 bouncegrid_dir = normalize(mat3(BounceGridMatrix) * (surfacenormal.x * VectorS.xyz + surfacenormal.y * VectorT.xyz + surfacenormal.z * VectorR.xyz));\n", +" myhalf3 bouncegrid_dirp = max(cast_myhalf3(0.0, 0.0, 0.0), bouncegrid_dir);\n", +" myhalf3 bouncegrid_dirn = max(cast_myhalf3(0.0, 0.0, 0.0), -bouncegrid_dir);\n", +"// bouncegrid_dirp = bouncegrid_dirn = cast_myhalf3(1.0,1.0,1.0);\n", +" myhalf3 bouncegrid_light = cast_myhalf3(\n", +" dot(bouncegrid_coeff3.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff6.xyz, bouncegrid_dirn),\n", +" dot(bouncegrid_coeff4.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff7.xyz, bouncegrid_dirn),\n", +" dot(bouncegrid_coeff5.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff8.xyz, bouncegrid_dirn));\n", +" color.rgb += diffusetex * bouncegrid_light * BounceGridIntensity;\n", +"// color.rgb = bouncegrid_dir.rgb * 0.5 + vec3(0.5, 0.5, 0.5);\n", +"#else\n", +" color.rgb += diffusetex * cast_myhalf3(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord)) * BounceGridIntensity;\n", +"#endif\n", +"#endif\n", +"\n", +"#ifdef USEGLOW\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" color.rgb += mix(cast_myhalf3(dp_texture2D(Texture_SecondaryGlow, TexCoord2)), cast_myhalf3(offsetMappedTexture2D(Texture_Glow)), terrainblend) * Color_Glow;\n", +"#else\n", +" color.rgb += cast_myhalf3(offsetMappedTexture2D(Texture_Glow)) * Color_Glow;\n", +"#endif\n", +"#endif\n", +"\n", +"#ifdef USECELOUTLINES\n", +"# ifdef USEDEFERREDLIGHTMAP\n", +"// vec2 ScreenTexCoord = gl_FragCoord.xy * PixelToScreenTexCoord;\n", +" vec4 ScreenTexCoordStep = vec4(PixelToScreenTexCoord.x, 0.0, 0.0, PixelToScreenTexCoord.y);\n", +" vec4 DepthNeighbors;\n", +"\n", +" // enable to test ink on white geometry\n", +"// color.rgb = vec3(1.0, 1.0, 1.0);\n", +"\n", +" // note: this seems to be negative\n", +" float DepthCenter = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord).b;\n", +"\n", +" // edge detect method\n", +"// DepthNeighbors.x = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord - ScreenTexCoordStep.xy).b;\n", +"// DepthNeighbors.y = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + ScreenTexCoordStep.xy).b;\n", +"// DepthNeighbors.z = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + ScreenTexCoordStep.zw).b;\n", +"// DepthNeighbors.w = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord - ScreenTexCoordStep.zw).b;\n", +"// float DepthAverage = dot(DepthNeighbors, vec4(0.25, 0.25, 0.25, 0.25));\n", +"// float DepthDelta = abs(dot(DepthNeighbors.xy, vec2(-1.0, 1.0))) + abs(dot(DepthNeighbors.zw, vec2(-1.0, 1.0)));\n", +"// color.rgb *= max(0.5, 1.0 - max(0.0, abs(DepthCenter - DepthAverage) - 0.2 * DepthDelta) / (0.01 + 0.2 * DepthDelta));\n", +"// color.rgb *= step(abs(DepthCenter - DepthAverage), 0.2 * DepthDelta); \n", +"\n", +" // shadow method\n", +" float DepthScale1 = 4.0 / DepthCenter; // inner ink (shadow on object)\n", +"// float DepthScale1 = -4.0 / DepthCenter; // outer ink (shadow around object)\n", +"// float DepthScale1 = 0.003;\n", +" float DepthScale2 = DepthScale1 / 2.0;\n", +"// float DepthScale3 = DepthScale1 / 4.0;\n", +" float DepthBias1 = -DepthCenter * DepthScale1;\n", +" float DepthBias2 = -DepthCenter * DepthScale2;\n", +"// float DepthBias3 = -DepthCenter * DepthScale3;\n", +" float DepthShadow = max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2(-1.0, 0.0)).b * DepthScale1 + DepthBias1)\n", +" + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 1.0, 0.0)).b * DepthScale1 + DepthBias1)\n", +" + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, -1.0)).b * DepthScale1 + DepthBias1)\n", +" + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, 1.0)).b * DepthScale1 + DepthBias1)\n", +" + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2(-2.0, 0.0)).b * DepthScale2 + DepthBias2)\n", +" + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 2.0, 0.0)).b * DepthScale2 + DepthBias2)\n", +" + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, -2.0)).b * DepthScale2 + DepthBias2)\n", +" + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, 2.0)).b * DepthScale2 + DepthBias2)\n", +"// + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2(-3.0, 0.0)).b * DepthScale3 + DepthBias3)\n", +"// + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 3.0, 0.0)).b * DepthScale3 + DepthBias3)\n", +"// + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, -3.0)).b * DepthScale3 + DepthBias3)\n", +"// + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, 3.0)).b * DepthScale3 + DepthBias3)\n", +" - 0.0;\n", +" color.rgb *= 1.0 - max(0.0, min(DepthShadow, 1.0));\n", +"// color.r = DepthCenter / -1024.0;\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USEFOG\n", +" color.rgb = FogVertex(color);\n", +"#endif\n", +"\n", +" // reflection must come last because it already contains exactly the correct fog (the reflection render preserves camera distance from the plane, it only flips the side) and ContrastBoost/SceneBrightness\n", +"#ifdef USEREFLECTION\n", +" vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n", +" //vec4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(cast_myhalf3(offsetMappedTexture2D(Texture_Normal)) - cast_myhalf3(0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", +" vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW.zw + ScreenCenterRefractReflect.zw;\n", +" #ifdef USENORMALMAPSCROLLBLEND\n", +"# ifdef USEOFFSETMAPPING\n", +" vec3 normal = dp_textureGrad(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y, dPdx*NormalmapScrollBlend.y, dPdy*NormalmapScrollBlend.y).rgb - vec3(1.0);\n", +"# else\n", +" vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n", +"# endif\n", +" normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n", +" vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(cast_myhalf3(normal))).xy * DistortScaleRefractReflect.zw;\n", +" #else\n", +" vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(cast_myhalf3(offsetMappedTexture2D(Texture_Normal)) - cast_myhalf3(0.5))).xy * DistortScaleRefractReflect.zw;\n", +" #endif\n", +" // FIXME temporary hack to detect the case that the reflection\n", +" // gets blackened at edges due to leaving the area that contains actual\n", +" // content.\n", +" // Remove this 'ack once we have a better way to stop this thing from\n", +" // 'appening.\n", +" float f = min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(0.01, -0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(-0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(-0.01, -0.01)).rgb) / 0.05);\n", +" ScreenTexCoord = mix(SafeScreenTexCoord, ScreenTexCoord, f);\n", +" color.rgb = mix(color.rgb, cast_myhalf3(dp_texture2D(Texture_Reflection, ScreenTexCoord)) * ReflectColor.rgb, ReflectColor.a);\n", +"#endif\n", +"\n", +" dp_FragColor = vec4(color);\n", +"}\n", +"#endif // FRAGMENT_SHADER\n", +"\n", +"#endif // !MODE_DEFERREDLIGHTSOURCE\n", +"#endif // !MODE_DEFERREDGEOMETRY\n", +"#endif // !MODE_WATER\n", +"#endif // !MODE_REFRACTION\n", +"#endif // !MODE_BLOOMBLUR\n", +"#endif // !MODE_GENERIC\n", +"#endif // !MODE_POSTPROCESS\n", +"#endif // !MODE_DEPTH_OR_SHADOW\n", diff --git a/app/jni/shader_hlsl.h b/app/jni/shader_hlsl.h new file mode 100644 index 0000000..7881aa6 --- /dev/null +++ b/app/jni/shader_hlsl.h @@ -0,0 +1,1561 @@ +"// ambient+diffuse+specular+normalmap+attenuation+cubemap+fog shader\n", +"// written by Forest 'LordHavoc' Hale\n", +"// shadowmapping enhancements by Lee 'eihrul' Salzman\n", +"\n", +"// FIXME: we need to get rid of ModelViewProjectionPosition to make room for the texcoord for this\n", +"#if defined(USEREFLECTION)\n", +"#undef USESHADOWMAPORTHO\n", +"#endif\n", +"\n", +"#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE) || defined(USEFOGHEIGHTTEXTURE)\n", +"# define USEFOG\n", +"#endif\n", +"#if defined(MODE_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP)\n", +"#define USELIGHTMAP\n", +"#endif\n", +"#if defined(USESPECULAR) || defined(USEOFFSETMAPPING) || defined(USEREFLECTCUBE) || defined(MODE_FAKELIGHT)\n", +"#define USEEYEVECTOR\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"#ifdef HLSL\n", +"//#undef USESHADOWMAPPCF\n", +"//#define texDepth2D(tex,texcoord) tex2D(tex,texcoord).r\n", +"#define texDepth2D(tex,texcoord) dot(tex2D(tex,texcoord).rgb, float3(1.0, 255.0/65536.0, 255.0/16777216.0))\n", +"#else\n", +"#define texDepth2D(tex,texcoord) tex2D(tex,texcoord).r\n", +"#endif\n", +"#endif\n", +"\n", +"#ifdef VERTEX_SHADER\n", +"#ifdef USETRIPPY\n", +"// LordHavoc: based on shader code linked at: http://www.youtube.com/watch?v=JpksyojwqzE\n", +"// tweaked scale\n", +"float4 TrippyVertex(float4 position)\n", +"(\n", +"uniform float ClientTime : register(c2)\n", +")\n", +"{\n", +" float worldTime = ClientTime;\n", +" // tweaked for Quake\n", +" worldTime *= 10.0;\n", +" position *= 0.125;\n", +" //~tweaked for Quake\n", +" float distanceSquared = (position.x * position.x + position.z * position.z);\n", +" position.y += 5.0*sin(distanceSquared*sin(worldTime/143.0)/1000.0);\n", +" float y = position.y;\n", +" float x = position.x;\n", +" float om = sin(distanceSquared*sin(worldTime/256.0)/5000.0) * sin(worldTime/200.0);\n", +" position.y = x*sin(om)+y*cos(om);\n", +" position.x = x*cos(om)-y*sin(om);\n", +" return position;\n", +"}\n", +"#endif\n", +"#endif\n", +"\n", +"#ifdef MODE_DEPTH_OR_SHADOW\n", +"#ifdef VERTEX_SHADER\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"out float4 gl_Position : POSITION,\n", +"out float Depth : TEXCOORD0\n", +")\n", +"{\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +" Depth = gl_Position.z;\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main\n", +"(\n", +"float Depth : TEXCOORD0,\n", +"out float4 dp_FragColor : COLOR\n", +")\n", +"{\n", +"// float4 temp = float4(Depth,Depth*(65536.0/255.0),Depth*(16777216.0/255.0),0.0);\n", +" float4 temp = float4(Depth,Depth*256.0,Depth*65536.0,0.0);\n", +" temp.yz -= floor(temp.yz);\n", +" dp_FragColor = temp;\n", +"// dp_FragColor = float4(Depth,0,0,0);\n", +"}\n", +"#endif\n", +"#else // !MODE_DEPTH_OR_SHADOW\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_POSTPROCESS\n", +"\n", +"#ifdef VERTEX_SHADER\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n", +"float4 gl_MultiTexCoord4 : TEXCOORD4,\n", +"out float4 gl_Position : POSITION,\n", +"out float2 TexCoord1 : TEXCOORD0,\n", +"out float2 TexCoord2 : TEXCOORD1\n", +")\n", +"{\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +" TexCoord1 = gl_MultiTexCoord0.xy;\n", +"#ifdef USEBLOOM\n", +" TexCoord2 = gl_MultiTexCoord4.xy;\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main\n", +"(\n", +"float2 TexCoord1 : TEXCOORD0,\n", +"float2 TexCoord2 : TEXCOORD1,\n", +"uniform sampler Texture_First : register(s0),\n", +"#ifdef USEBLOOM\n", +"uniform sampler Texture_Second : register(s1),\n", +"#endif\n", +"#ifdef USEGAMMARAMPS\n", +"uniform sampler Texture_GammaRamps : register(s2),\n", +"#endif\n", +"#ifdef USESATURATION\n", +"uniform float Saturation : register(c30),\n", +"#endif\n", +"#ifdef USEVIEWTINT\n", +"uniform float4 ViewTintColor : register(c41),\n", +"#endif\n", +"uniform float4 UserVec1 : register(c37),\n", +"uniform float4 UserVec2 : register(c38),\n", +"uniform float4 UserVec3 : register(c39),\n", +"uniform float4 UserVec4 : register(c40),\n", +"uniform float ClientTime : register(c2),\n", +"uniform float2 PixelSize : register(c25),\n", +"uniform float4 BloomColorSubtract : register(c43),\n", +"out float4 dp_FragColor : COLOR\n", +")\n", +"{\n", +" dp_FragColor = tex2D(Texture_First, TexCoord1);\n", +"\n", +"#ifdef USEPOSTPROCESSING\n", +"// do r_glsl_dumpshader, edit glsl/default.glsl, and replace this by your own postprocessing if you want\n", +"// this code does a blur with the radius specified in the first component of r_glsl_postprocess_uservec1 and blends it using the second component\n", +" float sobel = 1.0;\n", +" // float2 ts = textureSize(Texture_First, 0);\n", +" // float2 px = float2(1/ts.x, 1/ts.y);\n", +" float2 px = PixelSize;\n", +" float3 x1 = tex2D(Texture_First, TexCoord1 + float2(-px.x, px.y)).rgb;\n", +" float3 x2 = tex2D(Texture_First, TexCoord1 + float2(-px.x, 0.0)).rgb;\n", +" float3 x3 = tex2D(Texture_First, TexCoord1 + float2(-px.x,-px.y)).rgb;\n", +" float3 x4 = tex2D(Texture_First, TexCoord1 + float2( px.x, px.y)).rgb;\n", +" float3 x5 = tex2D(Texture_First, TexCoord1 + float2( px.x, 0.0)).rgb;\n", +" float3 x6 = tex2D(Texture_First, TexCoord1 + float2( px.x,-px.y)).rgb;\n", +" float3 y1 = tex2D(Texture_First, TexCoord1 + float2( px.x,-px.y)).rgb;\n", +" float3 y2 = tex2D(Texture_First, TexCoord1 + float2( 0.0,-px.y)).rgb;\n", +" float3 y3 = tex2D(Texture_First, TexCoord1 + float2(-px.x,-px.y)).rgb;\n", +" float3 y4 = tex2D(Texture_First, TexCoord1 + float2( px.x, px.y)).rgb;\n", +" float3 y5 = tex2D(Texture_First, TexCoord1 + float2( 0.0, px.y)).rgb;\n", +" float3 y6 = tex2D(Texture_First, TexCoord1 + float2(-px.x, px.y)).rgb;\n", +" float px1 = -1.0 * dot(float3(0.3, 0.59, 0.11), x1);\n", +" float px2 = -2.0 * dot(float3(0.3, 0.59, 0.11), x2);\n", +" float px3 = -1.0 * dot(float3(0.3, 0.59, 0.11), x3);\n", +" float px4 = 1.0 * dot(float3(0.3, 0.59, 0.11), x4);\n", +" float px5 = 2.0 * dot(float3(0.3, 0.59, 0.11), x5);\n", +" float px6 = 1.0 * dot(float3(0.3, 0.59, 0.11), x6);\n", +" float py1 = -1.0 * dot(float3(0.3, 0.59, 0.11), y1);\n", +" float py2 = -2.0 * dot(float3(0.3, 0.59, 0.11), y2);\n", +" float py3 = -1.0 * dot(float3(0.3, 0.59, 0.11), y3);\n", +" float py4 = 1.0 * dot(float3(0.3, 0.59, 0.11), y4);\n", +" float py5 = 2.0 * dot(float3(0.3, 0.59, 0.11), y5);\n", +" float py6 = 1.0 * dot(float3(0.3, 0.59, 0.11), y6);\n", +" sobel = 0.25 * abs(px1 + px2 + px3 + px4 + px5 + px6) + 0.25 * abs(py1 + py2 + py3 + py4 + py5 + py6);\n", +" dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.987688, -0.156434)) * UserVec1.y;\n", +" dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.156434, -0.891007)) * UserVec1.y;\n", +" dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2( 0.891007, -0.453990)) * UserVec1.y;\n", +" dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2( 0.707107, 0.707107)) * UserVec1.y;\n", +" dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.453990, 0.891007)) * UserVec1.y;\n", +" dp_FragColor /= (1.0 + 5.0 * UserVec1.y);\n", +" dp_FragColor.rgb = dp_FragColor.rgb * (1.0 + UserVec2.x) + float3(1,1,1)*max(0.0, sobel - UserVec2.z)*UserVec2.y;\n", +"#endif\n", +"\n", +"#ifdef USEBLOOM\n", +" dp_FragColor += max(float4(0,0,0,0), tex2D(Texture_Second, TexCoord2) - BloomColorSubtract);\n", +"#endif\n", +"\n", +"#ifdef USEVIEWTINT\n", +" dp_FragColor = lerp(dp_FragColor, ViewTintColor, ViewTintColor.a);\n", +"#endif\n", +"\n", +"#ifdef USESATURATION\n", +" //apply saturation BEFORE gamma ramps, so v_glslgamma value does not matter\n", +" float y = dot(dp_FragColor.rgb, float3(0.299, 0.587, 0.114));\n", +" // 'vampire sight' effect, wheres red is compensated\n", +" #ifdef SATURATION_REDCOMPENSATE\n", +" float rboost = max(0.0, (dp_FragColor.r - max(dp_FragColor.g, dp_FragColor.b))*(1.0 - Saturation));\n", +" dp_FragColor.rgb = lerp(float3(y,y,y), dp_FragColor.rgb, Saturation);\n", +" dp_FragColor.r += r;\n", +" #else\n", +" // normal desaturation\n", +" //dp_FragColor = float3(y,y,y) + (dp_FragColor.rgb - float3(y)) * Saturation;\n", +" dp_FragColor.rgb = lerp(float3(y,y,y), dp_FragColor.rgb, Saturation);\n", +" #endif\n", +"#endif\n", +"\n", +"#ifdef USEGAMMARAMPS\n", +" dp_FragColor.r = tex2D(Texture_GammaRamps, float2(dp_FragColor.r, 0)).r;\n", +" dp_FragColor.g = tex2D(Texture_GammaRamps, float2(dp_FragColor.g, 0)).g;\n", +" dp_FragColor.b = tex2D(Texture_GammaRamps, float2(dp_FragColor.b, 0)).b;\n", +"#endif\n", +"}\n", +"#endif\n", +"#else // !MODE_POSTPROCESS\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_GENERIC\n", +"#ifdef VERTEX_SHADER\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"float4 gl_Color : COLOR0,\n", +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n", +"float4 gl_MultiTexCoord1 : TEXCOORD1,\n", +"out float4 gl_Position : POSITION,\n", +"#ifdef USEDIFFUSE\n", +"out float2 TexCoord1 : TEXCOORD0,\n", +"#endif\n", +"#ifdef USESPECULAR\n", +"out float2 TexCoord2 : TEXCOORD1,\n", +"#endif\n", +"out float4 gl_FrontColor : COLOR\n", +")\n", +"{\n", +" gl_FrontColor = gl_Color;\n", +"#ifdef USEDIFFUSE\n", +" TexCoord1 = gl_MultiTexCoord0.xy;\n", +"#endif\n", +"#ifdef USESPECULAR\n", +" TexCoord2 = gl_MultiTexCoord1.xy;\n", +"#endif\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"\n", +"void main\n", +"(\n", +"float4 gl_FrontColor : COLOR0,\n", +"float2 TexCoord1 : TEXCOORD0,\n", +"float2 TexCoord2 : TEXCOORD1,\n", +"#ifdef USEDIFFUSE\n", +"uniform sampler Texture_First : register(s0),\n", +"#endif\n", +"#ifdef USESPECULAR\n", +"uniform sampler Texture_Second : register(s1),\n", +"#endif\n", +"#ifdef USEGAMMARAMPS\n", +"uniform sampler Texture_GammaRamps : register(s2),\n", +"#endif\n", +"uniform half Alpha : register(c0),\n", +"out float4 dp_FragColor : COLOR\n", +")\n", +"{\n", +"#ifdef USEVIEWTINT\n", +" dp_FragColor = gl_FrontColor;\n", +"#else\n", +" dp_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n", +"#endif\n", +"#ifdef USEDIFFUSE\n", +"# ifdef USEREFLECTCUBE\n", +" // suppress texture alpha\n", +" dp_FragColor.rgb *= tex2D(Texture_First, TexCoord1).rgb;\n", +"# else\n", +" dp_FragColor *= tex2D(Texture_First, TexCoord1);\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USESPECULAR\n", +" float4 tex2 = tex2D(Texture_Second, TexCoord2);\n", +"# ifdef USECOLORMAPPING\n", +" dp_FragColor *= tex2;\n", +"# endif\n", +"# ifdef USEGLOW\n", +" dp_FragColor += tex2;\n", +"# endif\n", +"# ifdef USEVERTEXTEXTUREBLEND\n", +" dp_FragColor = lerp(dp_FragColor, tex2, tex2.a);\n", +"# endif\n", +"#endif\n", +"#ifdef USEGAMMARAMPS\n", +" dp_FragColor.r = tex2D(Texture_GammaRamps, vec2(dp_FragColor.r, 0)).r;\n", +" dp_FragColor.g = tex2D(Texture_GammaRamps, vec2(dp_FragColor.g, 0)).g;\n", +" dp_FragColor.b = tex2D(Texture_GammaRamps, vec2(dp_FragColor.b, 0)).b;\n", +"#endif\n", +"#ifdef USEALPHAKILL\n", +" dp_FragColor.a *= Alpha;\n", +"#endif\n", +"}\n", +"#endif\n", +"#else // !MODE_GENERIC\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_BLOOMBLUR\n", +"#ifdef VERTEX_SHADER\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n", +"out float4 gl_Position : POSITION,\n", +"out float2 TexCoord : TEXCOORD0\n", +")\n", +"{\n", +" TexCoord = gl_MultiTexCoord0.xy;\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"\n", +"void main\n", +"(\n", +"float2 TexCoord : TEXCOORD0,\n", +"uniform sampler Texture_First : register(s0),\n", +"uniform float4 BloomBlur_Parameters : register(c1),\n", +"out float4 dp_FragColor : COLOR\n", +")\n", +"{\n", +" int i;\n", +" float2 tc = TexCoord;\n", +" float3 color = tex2D(Texture_First, tc).rgb;\n", +" tc += BloomBlur_Parameters.xy;\n", +" for (i = 1;i < SAMPLES;i++)\n", +" {\n", +" color += tex2D(Texture_First, tc).rgb;\n", +" tc += BloomBlur_Parameters.xy;\n", +" }\n", +" dp_FragColor = float4(color * BloomBlur_Parameters.z + float3(BloomBlur_Parameters.w), 1);\n", +"}\n", +"#endif\n", +"#else // !MODE_BLOOMBLUR\n", +"#ifdef MODE_REFRACTION\n", +"#ifdef VERTEX_SHADER\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"#ifdef USEALPHAGENVERTEX\n", +"float4 gl_Color : COLOR0,\n", +"#endif\n", +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n", +"uniform float4x4 TexMatrix : register(c0),\n", +"uniform float3 EyePosition : register(c24),\n", +"#ifdef USEALPHAGENVERTEX\n", +"out float4 gl_FrontColor : COLOR,\n", +"#endif\n", +"out float4 gl_Position : POSITION,\n", +"out float2 TexCoord : TEXCOORD0,\n", +"out float3 EyeVector : TEXCOORD1,\n", +"out float4 ModelViewProjectionPosition : TEXCOORD2\n", +")\n", +"{\n", +"#ifdef USEALPHAGENVERTEX\n", +" gl_FrontColor = gl_Color;\n", +"#endif\n", +" TexCoord = mul(TexMatrix, gl_MultiTexCoord0).xy;\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +" ModelViewProjectionPosition = gl_Position;\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main\n", +"(\n", +"#ifdef USEALPHAGENVERTEX\n", +"float4 gl_FrontColor : COLOR,\n", +"#endif\n", +"float2 TexCoord : TEXCOORD0,\n", +"float3 EyeVector : TEXCOORD1,\n", +"float4 ModelViewProjectionPosition : TEXCOORD2,\n", +"uniform sampler Texture_Normal : register(s0),\n", +"uniform sampler Texture_Refraction : register(s3),\n", +"uniform sampler Texture_Reflection : register(s7),\n", +"uniform float4 DistortScaleRefractReflect : register(c14),\n", +"uniform float4 ScreenScaleRefractReflect : register(c32),\n", +"uniform float4 ScreenCenterRefractReflect : register(c31),\n", +"uniform float4 RefractColor : register(c29),\n", +"out float4 dp_FragColor : COLOR\n", +")\n", +"{\n", +" float2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n", +" //float2 ScreenTexCoord = (ModelViewProjectionPosition.xy + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy * DistortScaleRefractReflect.xy * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n", +" float2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n", +"#ifdef USEALPHAGENVERTEX\n", +" float2 distort = DistortScaleRefractReflect.xy * gl_FrontColor.a;\n", +" float4 refractcolor = mix(RefractColor, vec4(1.0, 1.0, 1.0, 1.0), gl_FrontColor.a);\n", +"#else\n", +" float2 distort = DistortScaleRefractReflect.xy;\n", +" float4 refractcolor = RefractColor;\n", +"#endif\n", +" float2 ScreenTexCoord = SafeScreenTexCoord + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy * distort;\n", +" // FIXME temporary hack to detect the case that the reflection\n", +" // gets blackened at edges due to leaving the area that contains actual\n", +" // content.\n", +" // Remove this 'ack once we have a better way to stop this thing from\n", +" // 'appening.\n", +" float f = min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(0.01, -0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(-0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(-0.01, -0.01)).rgb) / 0.05);\n", +" ScreenTexCoord = lerp(SafeScreenTexCoord, ScreenTexCoord, f);\n", +" dp_FragColor = float4(tex2D(Texture_Refraction, ScreenTexCoord).rgb, 1) * refractcolor;\n", +"}\n", +"#endif\n", +"#else // !MODE_REFRACTION\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_WATER\n", +"#ifdef VERTEX_SHADER\n", +"\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"#ifdef USEALPHAGENVERTEX\n", +"float4 gl_Color : COLOR0,\n", +"#endif\n", +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n", +"float4 gl_MultiTexCoord1 : TEXCOORD1,\n", +"float4 gl_MultiTexCoord2 : TEXCOORD2,\n", +"float4 gl_MultiTexCoord3 : TEXCOORD3,\n", +"uniform float4x4 TexMatrix : register(c0),\n", +"uniform float3 EyePosition : register(c24),\n", +"#ifdef USEALPHAGENVERTEX\n", +"out float4 gl_FrontColor : COLOR,\n", +"#endif\n", +"out float4 gl_Position : POSITION,\n", +"out float2 TexCoord : TEXCOORD0,\n", +"out float3 EyeVector : TEXCOORD1,\n", +"out float4 ModelViewProjectionPosition : TEXCOORD2\n", +")\n", +"{\n", +"#ifdef USEALPHAGENVERTEX\n", +" gl_FrontColor = gl_Color;\n", +"#endif\n", +" TexCoord = mul(TexMatrix, gl_MultiTexCoord0).xy;\n", +" float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n", +" EyeVector = float3(dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz));\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +" ModelViewProjectionPosition = gl_Position;\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main\n", +"(\n", +"#ifdef USEALPHAGENVERTEX\n", +"float4 gl_FrontColor : COLOR,\n", +"#endif\n", +"float2 TexCoord : TEXCOORD0,\n", +"float3 EyeVector : TEXCOORD1,\n", +"float4 ModelViewProjectionPosition : TEXCOORD2,\n", +"uniform sampler Texture_Normal : register(s0),\n", +"uniform sampler Texture_Refraction : register(s3),\n", +"uniform sampler Texture_Reflection : register(s7),\n", +"uniform float4 DistortScaleRefractReflect : register(c14),\n", +"uniform float4 ScreenScaleRefractReflect : register(c32),\n", +"uniform float4 ScreenCenterRefractReflect : register(c31),\n", +"uniform float4 RefractColor : register(c29),\n", +"uniform float4 ReflectColor : register(c26),\n", +"uniform float ReflectFactor : register(c27),\n", +"uniform float ReflectOffset : register(c28),\n", +"out float4 dp_FragColor : COLOR\n", +")\n", +"{\n", +" float4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n", +" //float4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", +" float4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", +" //SafeScreenTexCoord = gl_FragCoord.xyxy * float4(1.0 / 1920.0, 1.0 / 1200.0, 1.0 / 1920.0, 1.0 / 1200.0);\n", +"#ifdef USEALPHAGENVERTEX\n", +" float4 distort = DistortScaleRefractReflect * gl_FrontColor.a;\n", +" float reflectoffset = ReflectOffset * gl_FrontColor.a;\n", +" float reflectfactor = ReflectFactor * gl_FrontColor.a;\n", +" float4 refractcolor = mix(RefractColor, vec4(1.0, 1.0, 1.0, 1.0), gl_FrontColor.a);\n", +"#else\n", +" float4 distort = DistortScaleRefractReflect;\n", +" float reflectoffset = ReflectOffset;\n", +" float reflectfactor = ReflectFactor;\n", +" float4 refractcolor = RefractColor;\n", +"#endif\n", +" float4 ScreenTexCoord = SafeScreenTexCoord + float2(normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy).xyxy * distort;\n", +" // FIXME temporary hack to detect the case that the reflection\n", +" // gets blackened at edges due to leaving the area that contains actual\n", +" // content.\n", +" // Remove this 'ack once we have a better way to stop this thing from\n", +" // 'appening.\n", +" float f = min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(0.01, -0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(-0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(-0.01, -0.01)).rgb) / 0.05);\n", +" ScreenTexCoord.xy = lerp(SafeScreenTexCoord.xy, ScreenTexCoord.xy, f);\n", +" f = min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(0.01, -0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(-0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(-0.01, -0.01)).rgb) / 0.05);\n", +" ScreenTexCoord.zw = lerp(SafeScreenTexCoord.zw, ScreenTexCoord.zw, f);\n", +" float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * reflectfactor + reflectoffset;\n", +" dp_FragColor = lerp(float4(tex2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * refractcolor, float4(tex2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n", +"}\n", +"#endif\n", +"#else // !MODE_WATER\n", +"\n", +"\n", +"\n", +"\n", +"// TODO: get rid of tangentt (texcoord2) and use a crossproduct to regenerate it from tangents (texcoord1) and normal (texcoord3), this would require sending a 4 component texcoord1 with W as 1 or -1 according to which side the texcoord2 should be on\n", +"\n", +"// fragment shader specific:\n", +"#ifdef FRAGMENT_SHADER\n", +"\n", +"#ifdef USEFOG\n", +"float3 FogVertex(float4 surfacecolor, float3 FogColor, float3 EyeVectorModelSpace, float FogPlaneVertexDist, float FogRangeRecip, float FogPlaneViewDist, float FogHeightFade, sampler Texture_FogMask, sampler Texture_FogHeightTexture)\n", +"{\n", +" float fogfrac;\n", +" float3 fc = FogColor;\n", +"#ifdef USEFOGALPHAHACK\n", +" fc *= surfacecolor.a;\n", +"#endif\n", +"#ifdef USEFOGHEIGHTTEXTURE\n", +" float4 fogheightpixel = tex2D(Texture_FogHeightTexture, float2(1,1) + float2(FogPlaneVertexDist, FogPlaneViewDist) * (-2.0 * FogHeightFade));\n", +" fogfrac = fogheightpixel.a;\n", +" return lerp(fogheightpixel.rgb * fc, surfacecolor.rgb, tex2D(Texture_FogMask, float2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n", +"#else\n", +"# ifdef USEFOGOUTSIDE\n", +" fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n", +"# else\n", +" fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0, FogPlaneVertexDist)) * min(1.0, (min(0.0, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade);\n", +"# endif\n", +" return lerp(fc, surfacecolor.rgb, tex2D(Texture_FogMask, float2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n", +"#endif\n", +"}\n", +"#endif\n", +"\n", +"#ifdef USEOFFSETMAPPING\n", +"float2 OffsetMapping(float2 TexCoord, float4 OffsetMapping_ScaleSteps, float OffsetMapping_Bias, float OffsetMapping_LodDistance, float3 EyeVector, sampler Texture_Normal, float2 dPdx, float2 dPdy)\n", +"{\n", +" float i;\n", +" // distance-based LOD\n", +"#ifdef USEOFFSETMAPPING_LOD\n", +" //float LODFactor = min(1.0, OffsetMapping_LodDistance / EyeVectorFogDepth.z);\n", +" //float4 ScaleSteps = float4(OffsetMapping_ScaleSteps.x, OffsetMapping_ScaleSteps.y * LODFactor, OffsetMapping_ScaleSteps.z / LODFactor, OffsetMapping_ScaleSteps.w * LODFactor);\n", +" float GuessLODFactor = min(1.0, OffsetMapping_LodDistance / EyeVectorFogDepth.z);\n", +"#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n", +" // stupid workaround because 1-step and 2-step reliefmapping is void\n", +" float LODSteps = max(3.0, ceil(GuessLODFactor * OffsetMapping_ScaleSteps.y));\n", +"#else\n", +" float LODSteps = ceil(GuessLODFactor * OffsetMapping_ScaleSteps.y);\n", +"#endif\n", +" float LODFactor = LODSteps / OffsetMapping_ScaleSteps.y;\n", +" float4 ScaleSteps = float4(OffsetMapping_ScaleSteps.x, LODSteps, 1.0 / LODSteps, OffsetMapping_ScaleSteps.w * LODFactor);\n", +"#else\n", +" #define ScaleSteps OffsetMapping_ScaleSteps\n", +"#endif\n", +"#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n", +" // 14 sample relief mapping: linear search and then binary search\n", +" // this basically steps forward a small amount repeatedly until it finds\n", +" // itself inside solid, then jitters forward and back using decreasing\n", +" // amounts to find the impact\n", +" //float3 OffsetVector = float3(EyeVector.xy * ((1.0 / EyeVector.z) * ScaleSteps.x) * float2(-1, 1), -1);\n", +" //float3 OffsetVector = float3(normalize(EyeVector.xy) * ScaleSteps.x * float2(-1, 1), -1);\n", +" float3 OffsetVector = float3(normalize(EyeVector).xy * ScaleSteps.x * float2(-1, 1), -1);\n", +" float3 RT = float3(float2(TexCoord.xy - OffsetVector.xy*OffsetMapping_Bias), 1);\n", +" OffsetVector *= ScaleSteps.z;\n", +" for(i = 1.0; i < ScaleSteps.y; ++i)\n", +" RT += OffsetVector * step(tex2Dgrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z);\n", +" for(i = 0.0, f = 1.0; i < ScaleSteps.w; ++i, f *= 0.5)\n", +" RT += OffsetVector * (step(tex2Dgrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z) * f - 0.5 * f);\n", +" return RT.xy;\n", +"#else\n", +" // 2 sample offset mapping (only 2 samples because of ATI Radeon 9500-9800/X300 limits)\n", +" //float2 OffsetVector = float2(EyeVector.xy * ((1.0 / EyeVector.z) * ScaleSteps.x) * float2(-1, 1));\n", +" //float2 OffsetVector = float2(normalize(EyeVector.xy) * ScaleSteps.x * float2(-1, 1));\n", +" float2 OffsetVector = float2(normalize(EyeVector).xy * ScaleSteps.x * float2(-1, 1));\n", +" OffsetVector *= ScaleSteps.z;\n", +" for(i = 0.0; i < ScaleSteps.y; ++i)\n", +" TexCoord += OffsetVector * ((1.0 - OffsetMapping_Bias) - tex2Dgrad(Texture_Normal, TexCoord, dPdx, dPdy).a);\n", +" return TexCoord;\n", +"#endif\n", +"}\n", +"#endif // USEOFFSETMAPPING\n", +"\n", +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n", +"#if defined(USESHADOWMAP2D)\n", +"# ifdef USESHADOWMAPORTHO\n", +"# define GetShadowMapTC2D(dir, ShadowMap_Parameters) (min(dir, ShadowMap_Parameters.xyz))\n", +"# else\n", +"# ifdef USESHADOWMAPVSDCT\n", +"float3 GetShadowMapTC2D(float3 dir, float4 ShadowMap_Parameters, samplerCUBE Texture_CubeProjection)\n", +"{\n", +" float3 adir = abs(dir);\n", +" float2 mparams = ShadowMap_Parameters.xy / max(max(adir.x, adir.y), adir.z);\n", +" float4 proj = texCUBE(Texture_CubeProjection, dir);\n", +" return float3(lerp(dir.xy, dir.zz, proj.xy) * mparams.x + proj.zw * ShadowMap_Parameters.z, mparams.y + ShadowMap_Parameters.w);\n", +"}\n", +"# else\n", +"float3 GetShadowMapTC2D(float3 dir, float4 ShadowMap_Parameters)\n", +"{\n", +" float3 adir = abs(dir);\n", +" float m; float4 proj;\n", +" if (adir.x > adir.y) { m = adir.x; proj = float4(dir.zyx, 0.5); } else { m = adir.y; proj = float4(dir.xzy, 1.5); }\n", +" if (adir.z > m) { m = adir.z; proj = float4(dir, 2.5); }\n", +"#ifdef HLSL\n", +" return float3(proj.xy * ShadowMap_Parameters.x / m + float2(0.5,0.5) + float2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, m + 64 * ShadowMap_Parameters.w);\n", +"#else\n", +" float2 mparams = ShadowMap_Parameters.xy / m;\n", +" return float3(proj.xy * mparams.x + float2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, mparams.y + ShadowMap_Parameters.w);\n", +"#endif\n", +"}\n", +"# endif\n", +"# endif\n", +"#endif // defined(USESHADOWMAP2D)\n", +"\n", +"# ifdef USESHADOWMAP2D\n", +"#ifdef USESHADOWMAPVSDCT\n", +"float ShadowMapCompare(float3 dir, sampler Texture_ShadowMap2D, float4 ShadowMap_Parameters, float2 ShadowMap_TextureScale, samplerCUBE Texture_CubeProjection)\n", +"#else\n", +"float ShadowMapCompare(float3 dir, sampler Texture_ShadowMap2D, float4 ShadowMap_Parameters, float2 ShadowMap_TextureScale)\n", +"#endif\n", +"{\n", +"#ifdef USESHADOWMAPVSDCT\n", +" float3 shadowmaptc = GetShadowMapTC2D(dir, ShadowMap_Parameters, Texture_CubeProjection);\n", +"#else\n", +" float3 shadowmaptc = GetShadowMapTC2D(dir, ShadowMap_Parameters);\n", +"#endif\n", +" float f;\n", +"\n", +"# ifdef USESHADOWSAMPLER\n", +"# ifdef USESHADOWMAPPCF\n", +"# define texval(x, y) tex2Dproj(Texture_ShadowMap2D, float4(center + float2(x, y)*ShadowMap_TextureScale, shadowmaptc.z, 1.0)).r \n", +" float2 center = shadowmaptc.xy*ShadowMap_TextureScale;\n", +" f = dot(float4(0.25,0.25,0.25,0.25), float4(texval(-0.4, 1.0), texval(-1.0, -0.4), texval(0.4, -1.0), texval(1.0, 0.4)));\n", +"# else\n", +" f = tex2Dproj(Texture_ShadowMap2D, float4(shadowmaptc.xy*ShadowMap_TextureScale, shadowmaptc.z, 1.0)).r;\n", +"# endif\n", +"# else\n", +"# ifdef USESHADOWMAPPCF\n", +"# if defined(GL_ARB_texture_gather) || defined(GL_AMD_texture_texture4)\n", +"# ifdef GL_ARB_texture_gather\n", +"# define texval(x, y) textureGatherOffset(Texture_ShadowMap2D, center, int2(x, y))\n", +"# else\n", +"# define texval(x, y) texture4(Texture_ShadowMap2D, center + float2(x, y)*ShadowMap_TextureScale)\n", +"# endif\n", +" float2 offset = frac(shadowmaptc.xy - 0.5), center = (shadowmaptc.xy - offset)*ShadowMap_TextureScale;\n", +"# if USESHADOWMAPPCF > 1\n", +" float4 group1 = step(shadowmaptc.z, texval(-2.0, -2.0));\n", +" float4 group2 = step(shadowmaptc.z, texval( 0.0, -2.0));\n", +" float4 group3 = step(shadowmaptc.z, texval( 2.0, -2.0));\n", +" float4 group4 = step(shadowmaptc.z, texval(-2.0, 0.0));\n", +" float4 group5 = step(shadowmaptc.z, texval( 0.0, 0.0));\n", +" float4 group6 = step(shadowmaptc.z, texval( 2.0, 0.0));\n", +" float4 group7 = step(shadowmaptc.z, texval(-2.0, 2.0));\n", +" float4 group8 = step(shadowmaptc.z, texval( 0.0, 2.0));\n", +" float4 group9 = step(shadowmaptc.z, texval( 2.0, 2.0));\n", +" float4 locols = float4(group1.ab, group3.ab);\n", +" float4 hicols = float4(group7.rg, group9.rg);\n", +" locols.yz += group2.ab;\n", +" hicols.yz += group8.rg;\n", +" float4 midcols = float4(group1.rg, group3.rg) + float4(group7.ab, group9.ab) +\n", +" float4(group4.rg, group6.rg) + float4(group4.ab, group6.ab) +\n", +" lerp(locols, hicols, offset.y);\n", +" float4 cols = group5 + float4(group2.rg, group8.ab);\n", +" cols.xyz += lerp(midcols.xyz, midcols.yzw, offset.x);\n", +" f = dot(cols, float4(1.0/25.0));\n", +"# else\n", +" float4 group1 = step(shadowmaptc.z, texval(-1.0, -1.0));\n", +" float4 group2 = step(shadowmaptc.z, texval( 1.0, -1.0));\n", +" float4 group3 = step(shadowmaptc.z, texval(-1.0, 1.0));\n", +" float4 group4 = step(shadowmaptc.z, texval( 1.0, 1.0));\n", +" float4 cols = float4(group1.rg, group2.rg) + float4(group3.ab, group4.ab) +\n", +" lerp(float4(group1.ab, group2.ab), float4(group3.rg, group4.rg), offset.y);\n", +" f = dot(lerp(cols.xyz, cols.yzw, offset.x), float3(1.0/9.0));\n", +"# endif\n", +"# else\n", +"# ifdef GL_EXT_gpu_shader4\n", +"# define texval(x, y) tex2DOffset(Texture_ShadowMap2D, center, int2(x, y)).r\n", +"# else\n", +"# define texval(x, y) texDepth2D(Texture_ShadowMap2D, center + float2(x, y)*ShadowMap_TextureScale).r \n", +"# endif\n", +"# if USESHADOWMAPPCF > 1\n", +" float2 center = shadowmaptc.xy - 0.5, offset = frac(center);\n", +" center *= ShadowMap_TextureScale;\n", +" float4 row1 = step(shadowmaptc.z, float4(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0), texval( 2.0, -1.0)));\n", +" float4 row2 = step(shadowmaptc.z, float4(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0), texval( 2.0, 0.0)));\n", +" float4 row3 = step(shadowmaptc.z, float4(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0), texval( 2.0, 1.0)));\n", +" float4 row4 = step(shadowmaptc.z, float4(texval(-1.0, 2.0), texval( 0.0, 2.0), texval( 1.0, 2.0), texval( 2.0, 2.0)));\n", +" float4 cols = row2 + row3 + lerp(row1, row4, offset.y);\n", +" f = dot(lerp(cols.xyz, cols.yzw, offset.x), float3(1.0/9.0));\n", +"# else\n", +" float2 center = shadowmaptc.xy*ShadowMap_TextureScale, offset = frac(shadowmaptc.xy);\n", +" float3 row1 = step(shadowmaptc.z, float3(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0)));\n", +" float3 row2 = step(shadowmaptc.z, float3(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0)));\n", +" float3 row3 = step(shadowmaptc.z, float3(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0)));\n", +" float3 cols = row2 + lerp(row1, row3, offset.y);\n", +" f = dot(lerp(cols.xy, cols.yz, offset.x), float2(0.25,0.25));\n", +"# endif\n", +"# endif\n", +"# else\n", +" f = step(shadowmaptc.z, tex2D(Texture_ShadowMap2D, shadowmaptc.xy*ShadowMap_TextureScale).r);\n", +"# endif\n", +"# endif\n", +"# ifdef USESHADOWMAPORTHO\n", +" return lerp(ShadowMap_Parameters.w, 1.0, f);\n", +"# else\n", +" return f;\n", +"# endif\n", +"}\n", +"# endif\n", +"#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n", +"#endif // FRAGMENT_SHADER\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_DEFERREDGEOMETRY\n", +"#ifdef VERTEX_SHADER\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"float4 gl_Color : COLOR0,\n", +"#endif\n", +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n", +"float4 gl_MultiTexCoord1 : TEXCOORD1,\n", +"float4 gl_MultiTexCoord2 : TEXCOORD2,\n", +"float4 gl_MultiTexCoord3 : TEXCOORD3,\n", +"uniform float4x4 TexMatrix : register(c0),\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"uniform float4x4 BackgroundTexMatrix : register(c4),\n", +"#endif\n", +"uniform float4x4 ModelViewMatrix : register(c12),\n", +"#ifdef USEOFFSETMAPPING\n", +"uniform float3 EyePosition : register(c24),\n", +"#endif\n", +"out float4 gl_Position : POSITION,\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"out float4 gl_FrontColor : COLOR,\n", +"#endif\n", +"out float4 TexCoordBoth : TEXCOORD0,\n", +"#ifdef USEOFFSETMAPPING\n", +"out float3 EyeVector : TEXCOORD2,\n", +"#endif\n", +"out float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n", +"out float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n", +"out float4 VectorR : TEXCOORD7 // direction of R texcoord (surface normal), Depth value\n", +")\n", +"{\n", +" TexCoordBoth = mul(TexMatrix, gl_MultiTexCoord0);\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" gl_FrontColor = gl_Color;\n", +" TexCoordBoth.zw = float2(Backgroundmul(TexMatrix, gl_MultiTexCoord0));\n", +"#endif\n", +"\n", +" // transform unnormalized eye direction into tangent space\n", +"#ifdef USEOFFSETMAPPING\n", +" float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n", +" EyeVector = float3(dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz));\n", +"#endif\n", +"\n", +" VectorS = mul(ModelViewMatrix, float4(gl_MultiTexCoord1.xyz, 0)).xyz;\n", +" VectorT = mul(ModelViewMatrix, float4(gl_MultiTexCoord2.xyz, 0)).xyz;\n", +" VectorR.xyz = mul(ModelViewMatrix, float4(gl_MultiTexCoord3.xyz, 0)).xyz;\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +" VectorR.w = mul(ModelViewMatrix, gl_Vertex).z;\n", +"}\n", +"#endif // VERTEX_SHADER\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main\n", +"(\n", +"float4 TexCoordBoth : TEXCOORD0,\n", +"float3 EyeVector : TEXCOORD2,\n", +"float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n", +"float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n", +"float4 VectorR : TEXCOORD7, // direction of R texcoord (surface normal), Depth value\n", +"uniform sampler Texture_Normal : register(s0),\n", +"#ifdef USEALPHAKILL\n", +"uniform sampler Texture_Color : register(s1),\n", +"#endif\n", +"uniform sampler Texture_Gloss : register(s2),\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"uniform sampler Texture_SecondaryNormal : register(s4),\n", +"uniform sampler Texture_SecondaryGloss : register(s6),\n", +"#endif\n", +"#ifdef USEOFFSETMAPPING\n", +"uniform float4 OffsetMapping_ScaleSteps : register(c24),\n", +"uniform float4 OffsetMapping_LodDistance : register(c53),\n", +"uniform float OffsetMapping_Bias : register(c54),\n", +"#endif\n", +"uniform half SpecularPower : register(c36),\n", +"out float4 dp_FragColor : COLOR\n", +")\n", +"{\n", +" float2 TexCoord = TexCoordBoth.xy;\n", +"#ifdef USEOFFSETMAPPING\n", +" // apply offsetmapping\n", +" float2 dPdx = ddx(TexCoord);\n", +" float2 dPdy = ddy(TexCoord);\n", +" float2 TexCoordOffset = OffsetMapping(TexCoord, OffsetMapping_ScaleSteps, OffsetMapping_Bias, OffsetMapping_LodDistance, EyeVector, Texture_Normal, dPdx, dPdy);\n", +"# define offsetMappedTexture2D(t) tex2Dgrad(t, TexCoordOffset, dPdx, dPdy)\n", +"#else\n", +"# define offsetMappedTexture2D(t) tex2D(t, TexCoord)\n", +"#endif\n", +"\n", +"#ifdef USEALPHAKILL\n", +" if (offsetMappedTexture2D(Texture_Color).a < 0.5)\n", +" discard;\n", +"#endif\n", +"\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" float alpha = offsetMappedTexture2D(Texture_Color).a;\n", +" float terrainblend = clamp(float(gl_FrontColor.a) * alpha * 2.0 - 0.5, float(0.0), float(1.0));\n", +" //float terrainblend = min(float(gl_FrontColor.a) * alpha * 2.0, float(1.0));\n", +" //float terrainblend = float(gl_FrontColor.a) * alpha > 0.5;\n", +"#endif\n", +"\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" float3 surfacenormal = lerp(tex2D(Texture_SecondaryNormal, TexCoord2).rgb, offsetMappedTexture2D(Texture_Normal).rgb, terrainblend) - float3(0.5, 0.5, 0.5);\n", +" float a = lerp(tex2D(Texture_SecondaryGloss, TexCoord2).a, offsetMappedTexture2D(Texture_Gloss).a, terrainblend);\n", +"#else\n", +" float3 surfacenormal = offsetMappedTexture2D(Texture_Normal).rgb - float3(0.5, 0.5, 0.5);\n", +" float a = offsetMappedTexture2D(Texture_Gloss).a;\n", +"#endif\n", +"\n", +" float3 pixelnormal = normalize(surfacenormal.x * VectorS.xyz + surfacenormal.y * VectorT.xyz + surfacenormal.z * VectorR.xyz);\n", +" dp_FragColor = float4(pixelnormal.x, pixelnormal.y, VectorR.w, a);\n", +"}\n", +"#endif // FRAGMENT_SHADER\n", +"#else // !MODE_DEFERREDGEOMETRY\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_DEFERREDLIGHTSOURCE\n", +"#ifdef VERTEX_SHADER\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"uniform float4x4 ModelViewMatrix : register(c12),\n", +"out float4 gl_Position : POSITION,\n", +"out float4 ModelViewPosition : TEXCOORD0\n", +")\n", +"{\n", +" ModelViewPosition = mul(ModelViewMatrix, gl_Vertex);\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +"}\n", +"#endif // VERTEX_SHADER\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main\n", +"(\n", +"#ifdef HLSL\n", +"float2 Pixel : VPOS,\n", +"#else\n", +"float2 Pixel : WPOS,\n", +"#endif\n", +"float4 ModelViewPosition : TEXCOORD0,\n", +"uniform float4x4 ViewToLight : register(c44),\n", +"uniform float2 ScreenToDepth : register(c33), // ScreenToDepth = float2(Far / (Far - Near), Far * Near / (Near - Far));\n", +"uniform float3 LightPosition : register(c23),\n", +"uniform half2 PixelToScreenTexCoord : register(c42),\n", +"uniform half3 DeferredColor_Ambient : register(c9),\n", +"uniform half3 DeferredColor_Diffuse : register(c10),\n", +"#ifdef USESPECULAR\n", +"uniform half3 DeferredColor_Specular : register(c11),\n", +"uniform half SpecularPower : register(c36),\n", +"#endif\n", +"uniform sampler Texture_Attenuation : register(s9),\n", +"uniform sampler Texture_ScreenDepth : register(s13),\n", +"uniform sampler Texture_ScreenNormalMap : register(s14),\n", +"\n", +"#ifdef USECUBEFILTER\n", +"uniform samplerCUBE Texture_Cube : register(s10),\n", +"#endif\n", +"\n", +"#ifdef USESHADOWMAP2D\n", +"# ifdef USESHADOWSAMPLER\n", +"uniform sampler Texture_ShadowMap2D : register(s15),\n", +"# else\n", +"uniform sampler Texture_ShadowMap2D : register(s15),\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USESHADOWMAPVSDCT\n", +"uniform samplerCUBE Texture_CubeProjection : register(s12),\n", +"#endif\n", +"\n", +"#if defined(USESHADOWMAP2D)\n", +"uniform float2 ShadowMap_TextureScale : register(c35),\n", +"uniform float4 ShadowMap_Parameters : register(c34),\n", +"#endif\n", +"\n", +"out float4 gl_FragData0 : COLOR0,\n", +"out float4 gl_FragData1 : COLOR1\n", +")\n", +"{\n", +" // calculate viewspace pixel position\n", +" float2 ScreenTexCoord = Pixel * PixelToScreenTexCoord;\n", +" float3 position;\n", +" // get the geometry information (depth, normal, specular exponent)\n", +" half4 normalmap = half4(tex2D(Texture_ScreenNormalMap, ScreenTexCoord));\n", +" // decode viewspace pixel normal\n", +"// float3 surfacenormal = normalize(normalmap.rgb - cast_myhalf3(0.5,0.5,0.5));\n", +" float3 surfacenormal = half3(normalmap.rg, sqrt(1.0-dot(normalmap.rg, normalmap.rg)));\n", +" // decode viewspace pixel position\n", +"// position.z = decodedepthmacro(dp_texture2D(Texture_ScreenDepth, ScreenTexCoord));\n", +" position.z = normalmap.b;\n", +"// position.z = ScreenToDepth.y / (dp_texture2D(Texture_ScreenDepth, ScreenTexCoord).r + ScreenToDepth.x);\n", +" position.xy = ModelViewPosition.xy * (position.z / ModelViewPosition.z);\n", +"\n", +" // now do the actual shading\n", +" // surfacenormal = pixel normal in viewspace\n", +" // LightVector = pixel to light in viewspace\n", +" // CubeVector = position in lightspace\n", +" // eyevector = pixel to view in viewspace\n", +" float3 CubeVector = mul(ViewToLight, float4(position,1)).xyz;\n", +" half fade = half(tex2D(Texture_Attenuation, float2(length(CubeVector), 0.0)).r);\n", +"#ifdef USEDIFFUSE\n", +" // calculate diffuse shading\n", +" half3 lightnormal = half3(normalize(LightPosition - position));\n", +" half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n", +"#endif\n", +"#ifdef USESPECULAR\n", +" // calculate directional shading\n", +" float3 eyevector = position * -1.0;\n", +"# ifdef USEEXACTSPECULARMATH\n", +" half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(eyevector)))*-1.0, 0.0)), 1.0 + SpecularPower * normalmap.a));\n", +"# else\n", +" half3 specularnormal = half3(normalize(lightnormal + half3(normalize(eyevector))));\n", +" half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + SpecularPower * normalmap.a));\n", +"# endif\n", +"#endif\n", +"\n", +"#if defined(USESHADOWMAP2D)\n", +" fade *= half(ShadowMapCompare(CubeVector, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale\n", +"#ifdef USESHADOWMAPVSDCT\n", +", Texture_CubeProjection\n", +"#endif\n", +" ));\n", +"#endif\n", +"\n", +"#ifdef USEDIFFUSE\n", +" gl_FragData0 = float4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n", +"#else\n", +" gl_FragData0 = float4(DeferredColor_Ambient * fade, 1.0);\n", +"#endif\n", +"#ifdef USESPECULAR\n", +" gl_FragData1 = float4(DeferredColor_Specular * (specular * fade), 1.0);\n", +"#else\n", +" gl_FragData1 = float4(0.0, 0.0, 0.0, 1.0);\n", +"#endif\n", +"\n", +"# ifdef USECUBEFILTER\n", +" float3 cubecolor = texCUBE(Texture_Cube, CubeVector).rgb;\n", +" gl_FragData0.rgb *= cubecolor;\n", +" gl_FragData1.rgb *= cubecolor;\n", +"# endif\n", +"}\n", +"#endif // FRAGMENT_SHADER\n", +"#else // !MODE_DEFERREDLIGHTSOURCE\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef VERTEX_SHADER\n", +"void main\n", +"(\n", +"float4 gl_Vertex : POSITION,\n", +"uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", +"#if defined(USEVERTEXTEXTUREBLEND) || defined(MODE_VERTEXCOLOR) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR)\n", +"float4 gl_Color : COLOR0,\n", +"#endif\n", +"float4 gl_MultiTexCoord0 : TEXCOORD0,\n", +"float4 gl_MultiTexCoord1 : TEXCOORD1,\n", +"float4 gl_MultiTexCoord2 : TEXCOORD2,\n", +"float4 gl_MultiTexCoord3 : TEXCOORD3,\n", +"float4 gl_MultiTexCoord4 : TEXCOORD4,\n", +"\n", +"uniform float3 EyePosition : register(c24),\n", +"uniform float4x4 TexMatrix : register(c0),\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"uniform float4x4 BackgroundTexMatrix : register(c4),\n", +"#endif\n", +"#ifdef MODE_LIGHTSOURCE\n", +"uniform float4x4 ModelToLight : register(c20),\n", +"#endif\n", +"#ifdef MODE_LIGHTSOURCE\n", +"uniform float3 LightPosition : register(c27),\n", +"#endif\n", +"#ifdef MODE_LIGHTDIRECTION\n", +"uniform float3 LightDir : register(c26),\n", +"#endif\n", +"uniform float4 FogPlane : register(c25),\n", +"#ifdef MODE_DEFERREDLIGHTSOURCE\n", +"uniform float3 LightPosition : register(c27),\n", +"#endif\n", +"#ifdef USESHADOWMAPORTHO\n", +"uniform float4x4 ShadowMapMatrix : register(c16),\n", +"#endif\n", +"#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR) || defined(USEALPHAGENVERTEX)\n", +"out float4 gl_FrontColor : COLOR,\n", +"#endif\n", +"out float4 TexCoordBoth : TEXCOORD0,\n", +"#ifdef USELIGHTMAP\n", +"out float2 TexCoordLightmap : TEXCOORD1,\n", +"#endif\n", +"#ifdef USEEYEVECTOR\n", +"out float3 EyeVector : TEXCOORD2,\n", +"#endif\n", +"#ifdef USEREFLECTION\n", +"out float4 ModelViewProjectionPosition : TEXCOORD3,\n", +"#endif\n", +"#ifdef USEFOG\n", +"out float4 EyeVectorModelSpaceFogPlaneVertexDist : TEXCOORD4,\n", +"#endif\n", +"#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE) || defined(USEDIFFUSE)\n", +"out float3 LightVector : TEXCOORD1,\n", +"#endif\n", +"#ifdef MODE_LIGHTSOURCE\n", +"out float3 CubeVector : TEXCOORD3,\n", +"#endif\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE)\n", +"out float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n", +"out float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n", +"out float3 VectorR : TEXCOORD7, // direction of R texcoord (surface normal)\n", +"#endif\n", +"#ifdef USESHADOWMAPORTHO\n", +"out float3 ShadowMapTC : TEXCOORD3, // CONFLICTS WITH USEREFLECTION!\n", +"#endif\n", +"out float4 gl_Position : POSITION\n", +")\n", +"{\n", +"#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR) || defined(USEALPHAGENVERTEX)\n", +" gl_FrontColor = gl_Color;\n", +"#endif\n", +" // copy the surface texcoord\n", +" TexCoordBoth = mul(TexMatrix, gl_MultiTexCoord0);\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" TexCoordBoth.zw = mul(BackgroundTexMatrix, gl_MultiTexCoord0).xy;\n", +"#endif\n", +"#ifdef USELIGHTMAP\n", +" TexCoordLightmap = gl_MultiTexCoord4.xy;\n", +"#endif\n", +"\n", +"#ifdef MODE_LIGHTSOURCE\n", +" // transform vertex position into light attenuation/cubemap space\n", +" // (-1 to +1 across the light box)\n", +" CubeVector = mul(ModelToLight, gl_Vertex).xyz;\n", +"\n", +"# ifdef USEDIFFUSE\n", +" // transform unnormalized light direction into tangent space\n", +" // (we use unnormalized to ensure that it interpolates correctly and then\n", +" // normalize it per pixel)\n", +" float3 lightminusvertex = LightPosition - gl_Vertex.xyz;\n", +" LightVector = float3(dot(lightminusvertex, gl_MultiTexCoord1.xyz), dot(lightminusvertex, gl_MultiTexCoord2.xyz), dot(lightminusvertex, gl_MultiTexCoord3.xyz));\n", +"# endif\n", +"#endif\n", +"\n", +"#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE)\n", +" LightVector = float3(dot(LightDir, gl_MultiTexCoord1.xyz), dot(LightDir, gl_MultiTexCoord2.xyz), dot(LightDir, gl_MultiTexCoord3.xyz));\n", +"#endif\n", +"\n", +" // transform unnormalized eye direction into tangent space\n", +"#ifdef USEEYEVECTOR\n", +" float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n", +" EyeVector = float3(dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz));\n", +"#endif\n", +"\n", +"#ifdef USEFOG\n", +" EyeVectorModelSpaceFogPlaneVertexDist.xyz = EyePosition - gl_Vertex.xyz;\n", +" EyeVectorModelSpaceFogPlaneVertexDist.w = dot(FogPlane, gl_Vertex);\n", +"#endif\n", +"\n", +"#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", +" VectorS = gl_MultiTexCoord1.xyz;\n", +" VectorT = gl_MultiTexCoord2.xyz;\n", +" VectorR = gl_MultiTexCoord3.xyz;\n", +"#endif\n", +"\n", +" // transform vertex to camera space, using ftransform to match non-VS rendering\n", +" gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", +"\n", +"#ifdef USESHADOWMAPORTHO\n", +" ShadowMapTC = mul(ShadowMapMatrix, gl_Position).xyz;\n", +"#endif\n", +"\n", +"#ifdef USEREFLECTION\n", +" ModelViewProjectionPosition = gl_Position;\n", +"#endif\n", +"#ifdef USETRIPPY\n", +" gl_Position = TrippyVertex(gl_Position);\n", +"#endif\n", +"}\n", +"#endif // VERTEX_SHADER\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef FRAGMENT_SHADER\n", +"void main\n", +"(\n", +"#ifdef USEDEFERREDLIGHTMAP\n", +"#ifdef HLSL\n", +"float2 Pixel : VPOS,\n", +"#else\n", +"float2 Pixel : WPOS,\n", +"#endif\n", +"#endif\n", +"float4 gl_FrontColor : COLOR,\n", +"float4 TexCoordBoth : TEXCOORD0,\n", +"#ifdef USELIGHTMAP\n", +"float2 TexCoordLightmap : TEXCOORD1,\n", +"#endif\n", +"#ifdef USEEYEVECTOR\n", +"float3 EyeVector : TEXCOORD2,\n", +"#endif\n", +"#ifdef USEREFLECTION\n", +"float4 ModelViewProjectionPosition : TEXCOORD3,\n", +"#endif\n", +"#ifdef USEFOG\n", +"float4 EyeVectorModelSpaceFogPlaneVertexDist : TEXCOORD4,\n", +"#endif\n", +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_LIGHTDIRECTION)\n", +"float3 LightVector : TEXCOORD1,\n", +"#endif\n", +"#ifdef MODE_LIGHTSOURCE\n", +"float3 CubeVector : TEXCOORD3,\n", +"#endif\n", +"#ifdef MODE_DEFERREDLIGHTSOURCE\n", +"float4 ModelViewPosition : TEXCOORD0,\n", +"#endif\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE)\n", +"float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n", +"float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n", +"float3 VectorR : TEXCOORD7, // direction of R texcoord (surface normal)\n", +"#endif\n", +"#ifdef USESHADOWMAPORTHO\n", +"float3 ShadowMapTC : TEXCOORD3, // CONFLICTS WITH USEREFLECTION!\n", +"#endif\n", +"\n", +"uniform sampler Texture_Normal : register(s0),\n", +"uniform sampler Texture_Color : register(s1),\n", +"#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n", +"uniform sampler Texture_Gloss : register(s2),\n", +"#endif\n", +"#ifdef USEGLOW\n", +"uniform sampler Texture_Glow : register(s3),\n", +"#endif\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"uniform sampler Texture_SecondaryNormal : register(s4),\n", +"uniform sampler Texture_SecondaryColor : register(s5),\n", +"#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n", +"uniform sampler Texture_SecondaryGloss : register(s6),\n", +"#endif\n", +"#ifdef USEGLOW\n", +"uniform sampler Texture_SecondaryGlow : register(s7),\n", +"#endif\n", +"#endif\n", +"#ifdef USECOLORMAPPING\n", +"uniform sampler Texture_Pants : register(s4),\n", +"uniform sampler Texture_Shirt : register(s7),\n", +"#endif\n", +"#ifdef USEFOG\n", +"uniform sampler Texture_FogHeightTexture : register(s14),\n", +"uniform sampler Texture_FogMask : register(s8),\n", +"#endif\n", +"#ifdef USELIGHTMAP\n", +"uniform sampler Texture_Lightmap : register(s9),\n", +"#endif\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n", +"uniform sampler Texture_Deluxemap : register(s10),\n", +"#endif\n", +"#ifdef USEREFLECTION\n", +"uniform sampler Texture_Reflection : register(s7),\n", +"#endif\n", +"\n", +"#ifdef MODE_DEFERREDLIGHTSOURCE\n", +"uniform sampler Texture_ScreenDepth : register(s13),\n", +"uniform sampler Texture_ScreenNormalMap : register(s14),\n", +"#endif\n", +"#ifdef USEDEFERREDLIGHTMAP\n", +"uniform sampler Texture_ScreenDepth : register(s13),\n", +"uniform sampler Texture_ScreenNormalMap : register(s14),\n", +"uniform sampler Texture_ScreenDiffuse : register(s11),\n", +"uniform sampler Texture_ScreenSpecular : register(s12),\n", +"#endif\n", +"\n", +"#ifdef USECOLORMAPPING\n", +"uniform half3 Color_Pants : register(c7),\n", +"uniform half3 Color_Shirt : register(c8),\n", +"#endif\n", +"#ifdef USEFOG\n", +"uniform float3 FogColor : register(c16),\n", +"uniform float FogRangeRecip : register(c20),\n", +"uniform float FogPlaneViewDist : register(c19),\n", +"uniform float FogHeightFade : register(c17),\n", +"#endif\n", +"\n", +"#ifdef USEOFFSETMAPPING\n", +"uniform float4 OffsetMapping_ScaleSteps : register(c24),\n", +"uniform float OffsetMapping_Bias : register(c54),\n", +"#endif\n", +"\n", +"#ifdef USEDEFERREDLIGHTMAP\n", +"uniform half2 PixelToScreenTexCoord : register(c42),\n", +"uniform half3 DeferredMod_Diffuse : register(c12),\n", +"uniform half3 DeferredMod_Specular : register(c13),\n", +"#endif\n", +"uniform half3 Color_Ambient : register(c3),\n", +"uniform half3 Color_Diffuse : register(c4),\n", +"uniform half3 Color_Specular : register(c5),\n", +"uniform half SpecularPower : register(c36),\n", +"#ifdef USEGLOW\n", +"uniform half3 Color_Glow : register(c6),\n", +"#endif\n", +"uniform half Alpha : register(c0),\n", +"#ifdef USEREFLECTION\n", +"uniform float4 DistortScaleRefractReflect : register(c14),\n", +"uniform float4 ScreenScaleRefractReflect : register(c32),\n", +"uniform float4 ScreenCenterRefractReflect : register(c31),\n", +"uniform half4 ReflectColor : register(c26),\n", +"#endif\n", +"#ifdef USEREFLECTCUBE\n", +"uniform float4x4 ModelToReflectCube : register(c48),\n", +"uniform sampler Texture_ReflectMask : register(s5),\n", +"uniform samplerCUBE Texture_ReflectCube : register(s6),\n", +"#endif\n", +"#ifdef MODE_LIGHTDIRECTION\n", +"uniform half3 LightColor : register(c21),\n", +"#endif\n", +"#ifdef MODE_LIGHTSOURCE\n", +"uniform half3 LightColor : register(c21),\n", +"#endif\n", +"\n", +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE)\n", +"uniform sampler Texture_Attenuation : register(s9),\n", +"uniform samplerCUBE Texture_Cube : register(s10),\n", +"#endif\n", +"\n", +"#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n", +"\n", +"#ifdef USESHADOWMAP2D\n", +"# ifdef USESHADOWSAMPLER\n", +"uniform sampler Texture_ShadowMap2D : register(s15),\n", +"# else\n", +"uniform sampler Texture_ShadowMap2D : register(s15),\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USESHADOWMAPVSDCT\n", +"uniform samplerCUBE Texture_CubeProjection : register(s12),\n", +"#endif\n", +"\n", +"#if defined(USESHADOWMAP2D)\n", +"uniform float2 ShadowMap_TextureScale : register(c35),\n", +"uniform float4 ShadowMap_Parameters : register(c34),\n", +"#endif\n", +"#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n", +"\n", +"out float4 dp_FragColor : COLOR\n", +")\n", +"{\n", +" float2 TexCoord = TexCoordBoth.xy;\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" float2 TexCoord2 = TexCoordBoth.zw;\n", +"#endif\n", +"#ifdef USEOFFSETMAPPING\n", +" // apply offsetmapping\n", +" float2 dPdx = ddx(TexCoord);\n", +" float2 dPdy = ddy(TexCoord);\n", +" float2 TexCoordOffset = OffsetMapping(TexCoord, OffsetMapping_ScaleSteps, OffsetMapping_Bias, OffsetMapping_LodDistance, EyeVector, Texture_Normal, dPdx, dPdy);\n", +"# define offsetMappedTexture2D(t) tex2Dgrad(t, TexCoordOffset, dPdx, dPdy)\n", +"#else\n", +"# define offsetMappedTexture2D(t) tex2D(t, TexCoord)\n", +"#endif\n", +"\n", +" // combine the diffuse textures (base, pants, shirt)\n", +" half4 color = half4(offsetMappedTexture2D(Texture_Color));\n", +"#ifdef USEALPHAKILL\n", +" if (color.a < 0.5)\n", +" discard;\n", +"#endif\n", +" color.a *= Alpha;\n", +"#ifdef USECOLORMAPPING\n", +" color.rgb += half3(offsetMappedTexture2D(Texture_Pants).rgb) * Color_Pants + half3(offsetMappedTexture2D(Texture_Shirt).rgb) * Color_Shirt;\n", +"#endif\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +"#ifdef USEBOTHALPHAS\n", +" half4 color2 = half4(tex2D(Texture_SecondaryColor, TexCoord2));\n", +" half terrainblend = clamp(half(gl_FrontColor.a) * color.a, half(1.0 - color2.a), half(1.0));\n", +" color.rgb = lerp(color2.rgb, color.rgb, terrainblend);\n", +"#else\n", +" half terrainblend = clamp(half(gl_FrontColor.a) * color.a * 2.0 - 0.5, half(0.0), half(1.0));\n", +" //half terrainblend = min(half(gl_FrontColor.a) * color.a * 2.0, half(1.0));\n", +" //half terrainblend = half(gl_FrontColor.a) * color.a > 0.5;\n", +" color.rgb = half3(lerp(tex2D(Texture_SecondaryColor, TexCoord2).rgb, float3(color.rgb), terrainblend));\n", +" color.a = 1.0;\n", +" //color = half4(lerp(float4(1, 0, 0, 1), color, terrainblend));\n", +"#endif\n", +"#endif\n", +"#ifdef USEALPHAGENVERTEX\n", +" color.a *= gl_FrontColor.a;\n", +"#endif\n", +"\n", +" // get the surface normal\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" half3 surfacenormal = normalize(half3(lerp(tex2D(Texture_SecondaryNormal, TexCoord2).rgb, offsetMappedTexture2D(Texture_Normal).rgb, terrainblend)) - half3(0.5, 0.5, 0.5));\n", +"#else\n", +" half3 surfacenormal = half3(normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5, 0.5, 0.5)));\n", +"#endif\n", +"\n", +" // get the material colors\n", +" half3 diffusetex = color.rgb;\n", +"#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n", +"# ifdef USEVERTEXTEXTUREBLEND\n", +" half4 glosstex = half4(lerp(tex2D(Texture_SecondaryGloss, TexCoord2), offsetMappedTexture2D(Texture_Gloss), terrainblend));\n", +"# else\n", +" half4 glosstex = half4(offsetMappedTexture2D(Texture_Gloss));\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USEREFLECTCUBE\n", +" float3 TangentReflectVector = reflect(-EyeVector, surfacenormal);\n", +" float3 ModelReflectVector = TangentReflectVector.x * VectorS + TangentReflectVector.y * VectorT + TangentReflectVector.z * VectorR;\n", +" float3 ReflectCubeTexCoord = mul(ModelToReflectCube, float4(ModelReflectVector, 0)).xyz;\n", +" diffusetex += half3(offsetMappedTexture2D(Texture_ReflectMask).rgb) * half3(texCUBE(Texture_ReflectCube, ReflectCubeTexCoord).rgb);\n", +"#endif\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_LIGHTSOURCE\n", +" // light source\n", +"#ifdef USEDIFFUSE\n", +" half3 lightnormal = half3(normalize(LightVector));\n", +" half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n", +" color.rgb = diffusetex * (Color_Ambient + diffuse * Color_Diffuse);\n", +"#ifdef USESPECULAR\n", +"#ifdef USEEXACTSPECULARMATH\n", +" half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(EyeVector)))*-1.0, 0.0)), 1.0 + SpecularPower * glosstex.a));\n", +"#else\n", +" half3 specularnormal = half3(normalize(lightnormal + half3(normalize(EyeVector))));\n", +" half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + SpecularPower * glosstex.a));\n", +"#endif\n", +" color.rgb += glosstex.rgb * (specular * Color_Specular);\n", +"#endif\n", +"#else\n", +" color.rgb = diffusetex * Color_Ambient;\n", +"#endif\n", +" color.rgb *= LightColor;\n", +" color.rgb *= half(tex2D(Texture_Attenuation, float2(length(CubeVector), 0.0)).r);\n", +"#if defined(USESHADOWMAP2D)\n", +" color.rgb *= half(ShadowMapCompare(CubeVector, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale\n", +"#ifdef USESHADOWMAPVSDCT\n", +", Texture_CubeProjection\n", +"#endif\n", +" ));\n", +"\n", +"#endif\n", +"# ifdef USECUBEFILTER\n", +" color.rgb *= half3(texCUBE(Texture_Cube, CubeVector).rgb);\n", +"# endif\n", +"\n", +"#ifdef USESHADOWMAP2D\n", +"#ifdef USESHADOWMAPVSDCT\n", +"// float3 shadowmaptc = GetShadowMapTC2D(CubeVector, ShadowMap_Parameters, Texture_CubeProjection);\n", +"#else\n", +"// float3 shadowmaptc = GetShadowMapTC2D(CubeVector, ShadowMap_Parameters);\n", +"#endif\n", +"// color.rgb = half3(tex2D(Texture_ShadowMap2D, float2(0.1,0.1)).rgb);\n", +"// color.rgb = half3(tex2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale).rgb);\n", +"// color.rgb = half3(shadowmaptc.xyz * float3(ShadowMap_TextureScale,1.0));\n", +"// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n", +"// color.rgb = half3(tex2D(Texture_ShadowMap2D, float2(0.1,0.1)).rgb);\n", +"// color.rgb = half3(tex2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale).rgb);\n", +"// color.rgb = half3(shadowmaptc.xyz * float3(ShadowMap_TextureScale,1.0));\n", +"// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n", +"// color.r = half(shadowmaptc.z - texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n", +"// color.r = half(shadowmaptc.z);\n", +"// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n", +"// color.r = half(shadowmaptc.z);\n", +"// color.r = 1;\n", +"// color.rgb = abs(CubeVector);\n", +"#endif\n", +"// color.rgb = half3(1,1,1);\n", +"#endif // MODE_LIGHTSOURCE\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_LIGHTDIRECTION\n", +" #define SHADING\n", +" #ifdef USEDIFFUSE\n", +" half3 lightnormal = half3(normalize(LightVector));\n", +" #endif\n", +" #define lightcolor LightColor\n", +"#endif // MODE_LIGHTDIRECTION\n", +"#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", +" #define SHADING\n", +" // deluxemap lightmapping using light vectors in modelspace (q3map2 -light -deluxe)\n", +" half3 lightnormal_modelspace = half3(tex2D(Texture_Deluxemap, TexCoordLightmap).rgb) * 2.0 + half3(-1.0, -1.0, -1.0);\n", +" half3 lightcolor = half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb);\n", +" // convert modelspace light vector to tangentspace\n", +" half3 lightnormal = half3(dot(lightnormal_modelspace, half3(VectorS)), dot(lightnormal_modelspace, half3(VectorT)), dot(lightnormal_modelspace, half3(VectorR)));\n", +" // calculate directional shading (and undoing the existing angle attenuation on the lightmap by the division)\n", +" // note that q3map2 is too stupid to calculate proper surface normals when q3map_nonplanar\n", +" // is used (the lightmap and deluxemap coords correspond to virtually random coordinates\n", +" // on that luxel, and NOT to its center, because recursive triangle subdivision is used\n", +" // to map the luxels to coordinates on the draw surfaces), which also causes\n", +" // deluxemaps to be wrong because light contributions from the wrong side of the surface\n", +" // are added up. To prevent divisions by zero or strong exaggerations, a max()\n", +" // nudge is done here at expense of some additional fps. This is ONLY needed for\n", +" // deluxemaps, tangentspace deluxemap avoid this problem by design.\n", +" lightcolor *= 1.0 / max(0.25, lightnormal.z);\n", +"#endif // MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", +"#ifdef MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", +" #define SHADING\n", +" // deluxemap lightmapping using light vectors in tangentspace (hmap2 -light)\n", +" half3 lightnormal = half3(tex2D(Texture_Deluxemap, TexCoordLightmap).rgb) * 2.0 + half3(-1.0, -1.0, -1.0);\n", +" half3 lightcolor = half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb);\n", +"#endif\n", +"#if defined(MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR)\n", +" #define SHADING\n", +" // forced deluxemap on lightmapped/vertexlit surfaces\n", +" half3 lightnormal = half3(0.0, 0.0, 1.0);\n", +" #ifdef USELIGHTMAP\n", +" half3 lightcolor = half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb);\n", +" #else\n", +" half3 lightcolor = half3(gl_FrontColor.rgb);\n", +" #endif\n", +"#endif\n", +"#ifdef MODE_FAKELIGHT\n", +" #define SHADING\n", +" half3 lightnormal = half3(normalize(EyeVector));\n", +" half3 lightcolor = half3(1.0,1.0,1.0);\n", +"#endif // MODE_FAKELIGHT\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef MODE_LIGHTMAP\n", +" color.rgb = diffusetex * (Color_Ambient + half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb) * Color_Diffuse);\n", +"#endif // MODE_LIGHTMAP\n", +"#ifdef MODE_VERTEXCOLOR\n", +" color.rgb = diffusetex * (Color_Ambient + half3(gl_FrontColor.rgb) * Color_Diffuse);\n", +"#endif // MODE_VERTEXCOLOR\n", +"#ifdef MODE_FLATCOLOR\n", +" color.rgb = diffusetex * Color_Ambient;\n", +"#endif // MODE_FLATCOLOR\n", +"\n", +"\n", +"\n", +"\n", +"#ifdef SHADING\n", +"# ifdef USEDIFFUSE\n", +" half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n", +"# ifdef USESPECULAR\n", +"# ifdef USEEXACTSPECULARMATH\n", +" half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(EyeVector)))*-1.0, 0.0)), 1.0 + SpecularPower * glosstex.a));\n", +"# else\n", +" half3 specularnormal = half3(normalize(lightnormal + half3(normalize(EyeVector))));\n", +" half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + SpecularPower * glosstex.a));\n", +"# endif\n", +" color.rgb = diffusetex * Color_Ambient + (diffusetex * Color_Diffuse * diffuse + glosstex.rgb * Color_Specular * specular) * lightcolor;\n", +"# else\n", +" color.rgb = diffusetex * (Color_Ambient + Color_Diffuse * diffuse * lightcolor);\n", +"# endif\n", +"# else\n", +" color.rgb = diffusetex * Color_Ambient;\n", +"# endif\n", +"#endif\n", +"\n", +"#ifdef USESHADOWMAPORTHO\n", +" color.rgb *= half(ShadowMapCompare(ShadowMapTC, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale));\n", +"#endif\n", +"\n", +"#ifdef USEDEFERREDLIGHTMAP\n", +" float2 ScreenTexCoord = Pixel * PixelToScreenTexCoord;\n", +" color.rgb += diffusetex * half3(tex2D(Texture_ScreenDiffuse, ScreenTexCoord).rgb) * DeferredMod_Diffuse;\n", +" color.rgb += glosstex.rgb * half3(tex2D(Texture_ScreenSpecular, ScreenTexCoord).rgb) * DeferredMod_Specular;\n", +"// color.rgb = half3(tex2D(Texture_ScreenDepth, ScreenTexCoord).rgb);\n", +"// color.r = half(texDepth2D(Texture_ScreenDepth, ScreenTexCoord)) * 1.0;\n", +"#endif\n", +"\n", +"#ifdef USEGLOW\n", +"#ifdef USEVERTEXTEXTUREBLEND\n", +" color.rgb += half3(lerp(tex2D(Texture_SecondaryGlow, TexCoord2).rgb, offsetMappedTexture2D(Texture_Glow).rgb, terrainblend)) * Color_Glow;\n", +"#else\n", +" color.rgb += half3(offsetMappedTexture2D(Texture_Glow).rgb) * Color_Glow;\n", +"#endif\n", +"#endif\n", +"\n", +"#ifdef USEFOG\n", +" color.rgb = half3(FogVertex(color, FogColor, EyeVectorModelSpaceFogPlaneVertexDist.xyz, EyeVectorModelSpaceFogPlaneVertexDist.w, FogRangeRecip, FogPlaneViewDist, FogHeightFade, Texture_FogMask, Texture_FogHeightTexture));\n", +"#endif\n", +"\n", +" // reflection must come last because it already contains exactly the correct fog (the reflection render preserves camera distance from the plane, it only flips the side) and ContrastBoost/SceneBrightness\n", +"#ifdef USEREFLECTION\n", +" float4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n", +" //float4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5,0.5,0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", +" float2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW.zw + ScreenCenterRefractReflect.zw;\n", +" float2 ScreenTexCoord = SafeScreenTexCoord + float3(normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5,0.5,0.5))).xy * DistortScaleRefractReflect.zw;\n", +" // FIXME temporary hack to detect the case that the reflection\n", +" // gets blackened at edges due to leaving the area that contains actual\n", +" // content.\n", +" // Remove this 'ack once we have a better way to stop this thing from\n", +" // 'appening.\n", +" float f = min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(0.01, -0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(-0.01, 0.01)).rgb) / 0.05);\n", +" f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(-0.01, -0.01)).rgb) / 0.05);\n", +" ScreenTexCoord = lerp(SafeScreenTexCoord, ScreenTexCoord, f);\n", +" color.rgb = lerp(color.rgb, half3(tex2D(Texture_Reflection, ScreenTexCoord).rgb) * ReflectColor.rgb, ReflectColor.a);\n", +"#endif\n", +"\n", +" dp_FragColor = float4(color);\n", +"}\n", +"#endif // FRAGMENT_SHADER\n", +"\n", +"#endif // !MODE_DEFERREDLIGHTSOURCE\n", +"#endif // !MODE_DEFERREDGEOMETRY\n", +"#endif // !MODE_WATER\n", +"#endif // !MODE_REFRACTION\n", +"#endif // !MODE_BLOOMBLUR\n", +"#endif // !MODE_GENERIC\n", +"#endif // !MODE_POSTPROCESS\n", +"#endif // !MODE_DEPTH_OR_SHADOW\n", diff --git a/app/jni/snd_android.c b/app/jni/snd_android.c new file mode 100644 index 0000000..64f3e1c --- /dev/null +++ b/app/jni/snd_android.c @@ -0,0 +1,185 @@ +/* +Copyright (C) 2013 n0n3m4 +Copyright (C) 2004 Andreas Kirsch + +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 + +#include "snd_main.h" + + +static unsigned int audiopos = 0; +static unsigned int buffersize; +__attribute__((weak)) __dso_handle=0; + +extern void jni_initAudio(void *buffer, int size); +extern void jni_writeAudio(int offset, int length); + +void QC_GetAudio() +{ + int offset = (audiopos*4) & (buffersize - 1); + if (snd_renderbuffer!=NULL) + jni_writeAudio(offset, 2048*4); + audiopos+=2048; +} + +/* +==================== +SndSys_Init + +Create "snd_renderbuffer" with the proper sound format if the call is successful +May return a suggested format if the requested format isn't available +==================== +*/ +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) +{ + + snd_threaded = false; + + Con_DPrint ("SndSys_Init: using the ANDROID module\n"); + + int wantspecfreq = requested->speed; + int wantspecformat = (requested->width); + int wantspecchannels = requested->channels; + + Con_Printf("Wanted audio Specification:\n" + "\tChannels : %i\n" + "\tFormat : 0x%X\n" + "\tFrequency : %i\n", + wantspecchannels, wantspecformat, wantspecfreq); + + int obtainspecchannels=2; + int obtainspecfreq=44100; + int obtainspecformat=2; + + Con_Printf("Obtained audio specification:\n" + "\tChannels : %i\n" + "\tFormat : 0x%X\n" + "\tFrequency : %i\n", + obtainspecchannels, obtainspecformat, obtainspecfreq); + + // If we haven't obtained what we wanted + if (wantspecfreq != obtainspecfreq || + wantspecformat != obtainspecformat || + wantspecchannels != obtainspecchannels) + { + // Pass the obtained format as a suggested format + if (suggested != NULL) + { + suggested->speed = obtainspecfreq; + suggested->width = obtainspecformat; + suggested->channels = obtainspecchannels; + } + + return false; + } + + snd_threaded = false; //TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + snd_renderbuffer = Snd_CreateRingBuffer(requested, 16384, 0); + if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) + Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD); + + audiopos = 0; + buffersize=16384*2*2; + jni_initAudio(snd_renderbuffer->ring, buffersize); + + return true; +} + + +/* +==================== +SndSys_Shutdown + +Stop the sound card, delete "snd_renderbuffer" and free its other resources +==================== +*/ +void SndSys_Shutdown(void) +{ + if (snd_renderbuffer != NULL) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } +} + + +/* +==================== +SndSys_Submit + +Submit the contents of "snd_renderbuffer" to the sound card +==================== +*/ +void SndSys_Submit (void) +{ + // Nothing to do here (this sound module is callback-based) +} + + +/* +==================== +SndSys_GetSoundTime + +Returns the number of sample frames consumed since the sound started +==================== +*/ +unsigned int SndSys_GetSoundTime (void) +{ + return audiopos; +} + + +/* +==================== +SndSys_LockRenderBuffer + +Get the exclusive lock on "snd_renderbuffer" +==================== +*/ +qboolean SndSys_LockRenderBuffer (void) +{ + return true; +} + + +/* +==================== +SndSys_UnlockRenderBuffer + +Release the exclusive lock on "snd_renderbuffer" +==================== +*/ +void SndSys_UnlockRenderBuffer (void) +{ +} + +/* +==================== +SndSys_SendKeyEvents + +Send keyboard events originating from the sound system (e.g. MIDI) +==================== +*/ +void SndSys_SendKeyEvents(void) +{ + // not supported +} diff --git a/app/jni/snd_main.c b/app/jni/snd_main.c new file mode 100644 index 0000000..ab3ff1a --- /dev/null +++ b/app/jni/snd_main.c @@ -0,0 +1,2342 @@ +/* +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. + +*/ +// snd_main.c -- main control for any streaming sound output device + +#include "quakedef.h" + +#include "snd_main.h" +#include "snd_ogg.h" +#include "snd_modplug.h" +#include "csprogs.h" +#include "cl_collision.h" +#include "cdaudio.h" + + +#define SND_MIN_SPEED 8000 +#define SND_MAX_SPEED 192000 +#define SND_MIN_WIDTH 1 +#define SND_MAX_WIDTH 2 +#define SND_MIN_CHANNELS 1 +#define SND_MAX_CHANNELS 8 +#if SND_LISTENERS != 8 +# error this data only supports up to 8 channel, update it! +#endif + +speakerlayout_t snd_speakerlayout; + +// Our speaker layouts are based on ALSA. They differ from those +// Win32 and Mac OS X APIs use when there's more than 4 channels. +// (rear left + rear right, and front center + LFE are swapped). +#define SND_SPEAKERLAYOUTS (sizeof(snd_speakerlayouts) / sizeof(snd_speakerlayouts[0])) +static const speakerlayout_t snd_speakerlayouts[] = +{ + { + "surround71", 8, + { + {0, 45, 0.2, 0.2, 0.5}, // front left + {1, 315, 0.2, 0.2, 0.5}, // front right + {2, 135, 0.2, 0.2, 0.5}, // rear left + {3, 225, 0.2, 0.2, 0.5}, // rear right + {4, 0, 0.2, 0.2, 0.5}, // front center + {5, 0, 0, 0, 0}, // lfe (we don't have any good lfe sound sources and it would take some filtering work to generate them (and they'd probably still be wrong), so... no lfe) + {6, 90, 0.2, 0.2, 0.5}, // side left + {7, 180, 0.2, 0.2, 0.5}, // side right + } + }, + { + "surround51", 6, + { + {0, 45, 0.2, 0.2, 0.5}, // front left + {1, 315, 0.2, 0.2, 0.5}, // front right + {2, 135, 0.2, 0.2, 0.5}, // rear left + {3, 225, 0.2, 0.2, 0.5}, // rear right + {4, 0, 0.2, 0.2, 0.5}, // front center + {5, 0, 0, 0, 0}, // lfe (we don't have any good lfe sound sources and it would take some filtering work to generate them (and they'd probably still be wrong), so... no lfe) + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + } + }, + { + // these systems sometimes have a subwoofer as well, but it has no + // channel of its own + "surround40", 4, + { + {0, 45, 0.3, 0.3, 0.8}, // front left + {1, 315, 0.3, 0.3, 0.8}, // front right + {2, 135, 0.3, 0.3, 0.8}, // rear left + {3, 225, 0.3, 0.3, 0.8}, // rear right + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + } + }, + { + // these systems sometimes have a subwoofer as well, but it has no + // channel of its own + "stereo", 2, + { + {0, 90, 0.5, 0.5, 1}, // side left + {1, 270, 0.5, 0.5, 1}, // side right + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + } + }, + { + "mono", 1, + { + {0, 0, 0, 1, 1}, // center + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + } + } +}; + + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +channel_t channels[MAX_CHANNELS]; +unsigned int total_channels; + +snd_ringbuffer_t *snd_renderbuffer = NULL; +static unsigned int soundtime = 0; +static unsigned int oldpaintedtime = 0; +static unsigned int extrasoundtime = 0; +static double snd_starttime = 0.0; +qboolean snd_threaded = false; +qboolean snd_usethreadedmixing = false; + +vec3_t listener_origin; +matrix4x4_t listener_basematrix; +static unsigned char *listener_pvs = NULL; +static int listener_pvsbytes = 0; +matrix4x4_t listener_matrix[SND_LISTENERS]; +mempool_t *snd_mempool; + +// Linked list of known sfx +static sfx_t *known_sfx = NULL; + +static qboolean sound_spatialized = false; + +qboolean simsound = false; + +static qboolean recording_sound = false; + +int snd_blocked = 0; +static int current_swapstereo = false; +static int current_channellayout = SND_CHANNELLAYOUT_AUTO; +static int current_channellayout_used = SND_CHANNELLAYOUT_AUTO; + +static float spatialpower, spatialmin, spatialdiff, spatialoffset, spatialfactor; +typedef enum { SPATIAL_NONE, SPATIAL_LOG, SPATIAL_POW, SPATIAL_THRESH } spatialmethod_t; +spatialmethod_t spatialmethod; + +// Cvars declared in sound.h (part of the sound API) +cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg)"}; +cvar_t mastervolume = {CVAR_SAVE, "mastervolume", "0.7", "master volume"}; +cvar_t volume = {CVAR_SAVE, "volume", "0.7", "volume of sound effects"}; +cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0", "indicates the sound subsystem is active"}; +cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1", "volume of ambient sound effects (such as swampy sounds at the start of e1m2)"}; +cvar_t snd_soundradius = {CVAR_SAVE, "snd_soundradius", "1200", "radius of weapon sounds and other standard sound effects (monster idle noises are half this radius and flickering light noises are one third of this radius)"}; +cvar_t snd_attenuation_exponent = {CVAR_SAVE, "snd_attenuation_exponent", "1", "Exponent of (1-radius) in sound attenuation formula"}; +cvar_t snd_attenuation_decibel = {CVAR_SAVE, "snd_attenuation_decibel", "0", "Decibel sound attenuation per sound radius distance"}; +cvar_t snd_spatialization_min_radius = {CVAR_SAVE, "snd_spatialization_min_radius", "10000", "use minimum spatialization above to this radius"}; +cvar_t snd_spatialization_max_radius = {CVAR_SAVE, "snd_spatialization_max_radius", "100", "use maximum spatialization below this radius"}; +cvar_t snd_spatialization_min = {CVAR_SAVE, "snd_spatialization_min", "0.70", "minimum spatializazion of sounds"}; +cvar_t snd_spatialization_max = {CVAR_SAVE, "snd_spatialization_max", "0.95", "maximum spatialization of sounds"}; +cvar_t snd_spatialization_power = {CVAR_SAVE, "snd_spatialization_power", "0", "exponent of the spatialization falloff curve (0: logarithmic)"}; +cvar_t snd_spatialization_control = {CVAR_SAVE, "snd_spatialization_control", "0", "enable spatialization control (headphone friendly mode)"}; +cvar_t snd_spatialization_prologic = {CVAR_SAVE, "snd_spatialization_prologic", "0", "use dolby prologic (I, II or IIx) encoding (snd_channels must be 2)"}; +cvar_t snd_spatialization_prologic_frontangle = {CVAR_SAVE, "snd_spatialization_prologic_frontangle", "30", "the angle between the front speakers and the center speaker"}; +cvar_t snd_spatialization_occlusion = {CVAR_SAVE, "snd_spatialization_occlusion", "1", "enable occlusion testing on spatialized sounds, which simply quiets sounds that are blocked by the world; 1 enables PVS method, 2 enables LineOfSight method, 3 enables both"}; + +// Cvars declared in snd_main.h (shared with other snd_*.c files) +cvar_t _snd_mixahead = {CVAR_SAVE, "_snd_mixahead", "0.15", "how much sound to mix ahead of time"}; +cvar_t snd_streaming = { CVAR_SAVE, "snd_streaming", "1", "enables keeping compressed ogg sound files compressed, decompressing them only as needed, otherwise they will be decompressed completely at load (may use a lot of memory); when set to 2, streaming is performed even if this would waste memory"}; +cvar_t snd_streaming_length = { CVAR_SAVE, "snd_streaming_length", "1", "decompress sounds completely if they are less than this play time when snd_streaming is 1"}; +cvar_t snd_swapstereo = {CVAR_SAVE, "snd_swapstereo", "0", "swaps left/right speakers for old ISA soundblaster cards"}; +extern cvar_t v_flipped; +cvar_t snd_channellayout = {0, "snd_channellayout", "0", "channel layout. Can be 0 (auto - snd_restart needed), 1 (standard layout), or 2 (ALSA layout)"}; +cvar_t snd_mutewhenidle = {CVAR_SAVE, "snd_mutewhenidle", "1", "whether to disable sound output when game window is inactive"}; +cvar_t snd_maxchannelvolume = {CVAR_SAVE, "snd_maxchannelvolume", "10", "maximum volume of a single sound"}; +cvar_t snd_softclip = {CVAR_SAVE, "snd_softclip", "0", "Use soft-clipping. Soft-clipping can make the sound more smooth if very high volume levels are used. Enable this option if the dynamic range of the loudspeakers is very low. WARNING: This feature creates distortion and should be considered a last resort."}; +//cvar_t snd_softclip = {CVAR_SAVE, "snd_softclip", "0", "Use soft-clipping (when set to 2, use it even if output is floating point). Soft-clipping can make the sound more smooth if very high volume levels are used. Enable this option if the dynamic range of the loudspeakers is very low. WARNING: This feature creates distortion and should be considered a last resort."}; +cvar_t snd_entchannel0volume = {CVAR_SAVE, "snd_entchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel1volume = {CVAR_SAVE, "snd_entchannel1volume", "1", "volume multiplier of the 1st entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel2volume = {CVAR_SAVE, "snd_entchannel2volume", "1", "volume multiplier of the 2nd entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel3volume = {CVAR_SAVE, "snd_entchannel3volume", "1", "volume multiplier of the 3rd entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel4volume = {CVAR_SAVE, "snd_entchannel4volume", "1", "volume multiplier of the 4th entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel5volume = {CVAR_SAVE, "snd_entchannel5volume", "1", "volume multiplier of the 5th entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel6volume = {CVAR_SAVE, "snd_entchannel6volume", "1", "volume multiplier of the 6th entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_entchannel7volume = {CVAR_SAVE, "snd_entchannel7volume", "1", "volume multiplier of the 7th entity channel of regular entities (DEPRECATED)"}; +cvar_t snd_playerchannel0volume = {CVAR_SAVE, "snd_playerchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel1volume = {CVAR_SAVE, "snd_playerchannel1volume", "1", "volume multiplier of the 1st entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel2volume = {CVAR_SAVE, "snd_playerchannel2volume", "1", "volume multiplier of the 2nd entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel3volume = {CVAR_SAVE, "snd_playerchannel3volume", "1", "volume multiplier of the 3rd entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel4volume = {CVAR_SAVE, "snd_playerchannel4volume", "1", "volume multiplier of the 4th entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel5volume = {CVAR_SAVE, "snd_playerchannel5volume", "1", "volume multiplier of the 5th entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel6volume = {CVAR_SAVE, "snd_playerchannel6volume", "1", "volume multiplier of the 6th entity channel of player entities (DEPRECATED)"}; +cvar_t snd_playerchannel7volume = {CVAR_SAVE, "snd_playerchannel7volume", "1", "volume multiplier of the 7th entity channel of player entities (DEPRECATED)"}; +cvar_t snd_worldchannel0volume = {CVAR_SAVE, "snd_worldchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel1volume = {CVAR_SAVE, "snd_worldchannel1volume", "1", "volume multiplier of the 1st entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel2volume = {CVAR_SAVE, "snd_worldchannel2volume", "1", "volume multiplier of the 2nd entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel3volume = {CVAR_SAVE, "snd_worldchannel3volume", "1", "volume multiplier of the 3rd entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel4volume = {CVAR_SAVE, "snd_worldchannel4volume", "1", "volume multiplier of the 4th entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel5volume = {CVAR_SAVE, "snd_worldchannel5volume", "1", "volume multiplier of the 5th entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel6volume = {CVAR_SAVE, "snd_worldchannel6volume", "1", "volume multiplier of the 6th entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_worldchannel7volume = {CVAR_SAVE, "snd_worldchannel7volume", "1", "volume multiplier of the 7th entity channel of the world entity (DEPRECATED)"}; +cvar_t snd_csqcchannel0volume = {CVAR_SAVE, "snd_csqcchannel0volume", "1", "volume multiplier of the auto-allocate entity channel CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel1volume = {CVAR_SAVE, "snd_csqcchannel1volume", "1", "volume multiplier of the 1st entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel2volume = {CVAR_SAVE, "snd_csqcchannel2volume", "1", "volume multiplier of the 2nd entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel3volume = {CVAR_SAVE, "snd_csqcchannel3volume", "1", "volume multiplier of the 3rd entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel4volume = {CVAR_SAVE, "snd_csqcchannel4volume", "1", "volume multiplier of the 4th entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel5volume = {CVAR_SAVE, "snd_csqcchannel5volume", "1", "volume multiplier of the 5th entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel6volume = {CVAR_SAVE, "snd_csqcchannel6volume", "1", "volume multiplier of the 6th entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_csqcchannel7volume = {CVAR_SAVE, "snd_csqcchannel7volume", "1", "volume multiplier of the 7th entity channel of CSQC entities (DEPRECATED)"}; +cvar_t snd_channel0volume = {CVAR_SAVE, "snd_channel0volume", "1", "volume multiplier of the auto-allocate entity channel"}; +cvar_t snd_channel1volume = {CVAR_SAVE, "snd_channel1volume", "1", "volume multiplier of the 1st entity channel"}; +cvar_t snd_channel2volume = {CVAR_SAVE, "snd_channel2volume", "1", "volume multiplier of the 2nd entity channel"}; +cvar_t snd_channel3volume = {CVAR_SAVE, "snd_channel3volume", "1", "volume multiplier of the 3rd entity channel"}; +cvar_t snd_channel4volume = {CVAR_SAVE, "snd_channel4volume", "1", "volume multiplier of the 4th entity channel"}; +cvar_t snd_channel5volume = {CVAR_SAVE, "snd_channel5volume", "1", "volume multiplier of the 5th entity channel"}; +cvar_t snd_channel6volume = {CVAR_SAVE, "snd_channel6volume", "1", "volume multiplier of the 6th entity channel"}; +cvar_t snd_channel7volume = {CVAR_SAVE, "snd_channel7volume", "1", "volume multiplier of the 7th entity channel"}; + +// Local cvars +static cvar_t nosound = {0, "nosound", "0", "disables sound"}; +static cvar_t snd_precache = {0, "snd_precache", "1", "loads sounds before they are used"}; +static cvar_t ambient_level = {0, "ambient_level", "0.3", "volume of environment noises (water and wind)"}; +static cvar_t ambient_fade = {0, "ambient_fade", "100", "rate of volume fading when moving from one environment to another"}; +static cvar_t snd_noextraupdate = {0, "snd_noextraupdate", "0", "disables extra sound mixer calls that are meant to reduce the chance of sound breakup at very low framerates"}; +static cvar_t snd_show = {0, "snd_show", "0", "shows some statistics about sound mixing"}; + +// Default sound format is 48KHz, 16-bit, stereo +// (48KHz because a lot of onboard sound cards sucks at any other speed) +static cvar_t snd_speed = {CVAR_SAVE, "snd_speed", "48000", "sound output frequency, in hertz"}; +static cvar_t snd_width = {CVAR_SAVE, "snd_width", "2", "sound output precision, in bytes (1 and 2 supported)"}; +static cvar_t snd_channels = {CVAR_SAVE, "snd_channels", "2", "number of channels for the sound output (2 for stereo; up to 8 supported for 3D sound)"}; + +static cvar_t snd_startloopingsounds = {0, "snd_startloopingsounds", "1", "whether to start sounds that would loop (you want this to be 1); existing sounds are not affected"}; +static cvar_t snd_startnonloopingsounds = {0, "snd_startnonloopingsounds", "1", "whether to start sounds that would not loop (you want this to be 1); existing sounds are not affected"}; + +// randomization +static cvar_t snd_identicalsoundrandomization_time = {0, "snd_identicalsoundrandomization_time", "0.1", "how much seconds to randomly skip (positive) or delay (negative) sounds when multiple identical sounds are started on the same frame"}; +static cvar_t snd_identicalsoundrandomization_tics = {0, "snd_identicalsoundrandomization_tics", "0", "if nonzero, how many tics to limit sound randomization as defined by snd_identicalsoundrandomization_time"}; + +// Ambient sounds +static sfx_t* ambient_sfxs [2] = { NULL, NULL }; +static const char* ambient_names [2] = { "sound/ambience/water1.wav", "sound/ambience/wind2.wav" }; + + +// ==================================================================== +// Functions +// ==================================================================== + +void S_FreeSfx (sfx_t *sfx, qboolean force); + +static void S_Play_Common (float fvol, float attenuation) +{ + int i, ch_ind; + char name [MAX_QPATH]; + sfx_t *sfx; + + i = 1; + while (i < Cmd_Argc ()) + { + // Get the name, and appends ".wav" as an extension if there's none + strlcpy (name, Cmd_Argv (i), sizeof (name)); + if (!strrchr (name, '.')) + strlcat (name, ".wav", sizeof (name)); + i++; + + // If we need to get the volume from the command line + if (fvol == -1.0f) + { + fvol = atof (Cmd_Argv (i)); + i++; + } + + sfx = S_PrecacheSound (name, true, true); + if (sfx) + { + ch_ind = S_StartSound (-1, 0, sfx, listener_origin, fvol, attenuation); + + // Free the sfx if the file didn't exist + if (!sfx->fetcher) + S_FreeSfx (sfx, false); + else + channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; + } + } +} + +static void S_Play_f(void) +{ + S_Play_Common (1.0f, 1.0f); +} + +static void S_Play2_f(void) +{ + S_Play_Common (1.0f, 0.0f); +} + +static void S_PlayVol_f(void) +{ + S_Play_Common (-1.0f, 0.0f); +} + +static void S_SoundList_f (void) +{ + unsigned int i; + sfx_t *sfx; + unsigned int total; + + total = 0; + for (sfx = known_sfx, i = 0; sfx != NULL; sfx = sfx->next, i++) + { + if (sfx->fetcher != NULL) + { + unsigned int size; + + size = sfx->memsize; + Con_Printf ("%c%c%c(%5iHz %2db %6s) %8i : %s\n", + (sfx->loopstart < sfx->total_length) ? 'L' : ' ', + (sfx->flags & SFXFLAG_STREAMED) ? 'S' : ' ', + (sfx->flags & SFXFLAG_MENUSOUND) ? 'P' : ' ', + sfx->format.speed, + sfx->format.width * 8, + (sfx->format.channels == 1) ? "mono" : "stereo", + size, + sfx->name); + total += size; + } + else + Con_Printf (" ( unknown ) unloaded : %s\n", sfx->name); + } + Con_Printf("Total resident: %i\n", total); +} + + +static void S_SoundInfo_f(void) +{ + if (snd_renderbuffer == NULL) + { + Con_Print("sound system not started\n"); + return; + } + + Con_Printf("%5d speakers\n", snd_renderbuffer->format.channels); + Con_Printf("%5d frames\n", snd_renderbuffer->maxframes); + Con_Printf("%5d samplebits\n", snd_renderbuffer->format.width * 8); + Con_Printf("%5d speed\n", snd_renderbuffer->format.speed); + Con_Printf("%5u total_channels\n", total_channels); +} + + +int S_GetSoundRate(void) +{ + return snd_renderbuffer ? snd_renderbuffer->format.speed : 0; +} + +int S_GetSoundChannels(void) +{ + return snd_renderbuffer ? snd_renderbuffer->format.channels : 0; +} + + +static qboolean S_ChooseCheaperFormat (snd_format_t* format, qboolean fixed_speed, qboolean fixed_width, qboolean fixed_channels) +{ + static const snd_format_t thresholds [] = + { + // speed width channels + { SND_MIN_SPEED, SND_MIN_WIDTH, SND_MIN_CHANNELS }, + { 11025, 1, 2 }, + { 22050, 2, 2 }, + { 44100, 2, 2 }, + { 48000, 2, 6 }, + { 96000, 2, 6 }, + { SND_MAX_SPEED, SND_MAX_WIDTH, SND_MAX_CHANNELS }, + }; + const unsigned int nb_thresholds = sizeof(thresholds) / sizeof(thresholds[0]); + unsigned int speed_level, width_level, channels_level; + + // If we have reached the minimum values, there's nothing more we can do + if ((format->speed == thresholds[0].speed || fixed_speed) && + (format->width == thresholds[0].width || fixed_width) && + (format->channels == thresholds[0].channels || fixed_channels)) + return false; + + // Check the min and max values + #define CHECK_BOUNDARIES(param) \ + if (format->param < thresholds[0].param) \ + { \ + format->param = thresholds[0].param; \ + return true; \ + } \ + if (format->param > thresholds[nb_thresholds - 1].param) \ + { \ + format->param = thresholds[nb_thresholds - 1].param; \ + return true; \ + } + CHECK_BOUNDARIES(speed); + CHECK_BOUNDARIES(width); + CHECK_BOUNDARIES(channels); + #undef CHECK_BOUNDARIES + + // Find the level of each parameter + #define FIND_LEVEL(param) \ + param##_level = 0; \ + while (param##_level < nb_thresholds - 1) \ + { \ + if (format->param <= thresholds[param##_level].param) \ + break; \ + \ + param##_level++; \ + } + FIND_LEVEL(speed); + FIND_LEVEL(width); + FIND_LEVEL(channels); + #undef FIND_LEVEL + + // Decrease the parameter with the highest level to the previous level + if (channels_level >= speed_level && channels_level >= width_level && !fixed_channels) + { + format->channels = thresholds[channels_level - 1].channels; + return true; + } + if (speed_level >= width_level && !fixed_speed) + { + format->speed = thresholds[speed_level - 1].speed; + return true; + } + + format->width = thresholds[width_level - 1].width; + return true; +} + + +#define SWAP_LISTENERS(l1, l2, tmpl) { tmpl = (l1); (l1) = (l2); (l2) = tmpl; } + +static void S_SetChannelLayout (void) +{ + unsigned int i; + listener_t swaplistener; + listener_t *listeners; + int layout; + + for (i = 0; i < SND_SPEAKERLAYOUTS; i++) + if (snd_speakerlayouts[i].channels == snd_renderbuffer->format.channels) + break; + if (i >= SND_SPEAKERLAYOUTS) + { + Con_Printf("S_SetChannelLayout: can't find the speaker layout for %hu channels. Defaulting to mono output\n", + snd_renderbuffer->format.channels); + i = SND_SPEAKERLAYOUTS - 1; + } + + snd_speakerlayout = snd_speakerlayouts[i]; + listeners = snd_speakerlayout.listeners; + + // Swap the left and right channels if snd_swapstereo is set + if (boolxor(snd_swapstereo.integer, v_flipped.integer)) + { + switch (snd_speakerlayout.channels) + { + case 8: + SWAP_LISTENERS(listeners[6], listeners[7], swaplistener); + // no break + case 4: + case 6: + SWAP_LISTENERS(listeners[2], listeners[3], swaplistener); + // no break + case 2: + SWAP_LISTENERS(listeners[0], listeners[1], swaplistener); + break; + + default: + case 1: + // Nothing to do + break; + } + } + + // Sanity check + if (snd_channellayout.integer < SND_CHANNELLAYOUT_AUTO || + snd_channellayout.integer > SND_CHANNELLAYOUT_ALSA) + Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD); + + if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) + { + // If we're in the sound engine initialization + if (current_channellayout_used == SND_CHANNELLAYOUT_AUTO) + { + layout = SND_CHANNELLAYOUT_STANDARD; + Cvar_SetValueQuick (&snd_channellayout, layout); + } + else + layout = current_channellayout_used; + } + else + layout = snd_channellayout.integer; + + // Convert our layout (= ALSA) to the standard layout if necessary + if (snd_speakerlayout.channels == 6 || snd_speakerlayout.channels == 8) + { + if (layout == SND_CHANNELLAYOUT_STANDARD) + { + SWAP_LISTENERS(listeners[2], listeners[4], swaplistener); + SWAP_LISTENERS(listeners[3], listeners[5], swaplistener); + } + + Con_Printf("S_SetChannelLayout: using %s speaker layout for 3D sound\n", + (layout == SND_CHANNELLAYOUT_ALSA) ? "ALSA" : "standard"); + } + + current_swapstereo = boolxor(snd_swapstereo.integer, v_flipped.integer); + current_channellayout = snd_channellayout.integer; + current_channellayout_used = layout; +} + + +void S_Startup (void) +{ + qboolean fixed_speed, fixed_width, fixed_channels; + snd_format_t chosen_fmt; + static snd_format_t prev_render_format = {0, 0, 0}; + char* env; +#if _MSC_VER >= 1400 + size_t envlen; +#endif + int i; + + if (!snd_initialized.integer) + return; + + fixed_speed = false; + fixed_width = false; + fixed_channels = false; + + // Get the starting sound format from the cvars + chosen_fmt.speed = snd_speed.integer; + chosen_fmt.width = snd_width.integer; + chosen_fmt.channels = snd_channels.integer; + + // Check the environment variables to see if the player wants a particular sound format +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_CHANNELS"); +#else + env = getenv("QUAKE_SOUND_CHANNELS"); +#endif + if (env != NULL) + { + chosen_fmt.channels = atoi (env); +#if _MSC_VER >= 1400 + free(env); +#endif + fixed_channels = true; + } +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_SPEED"); +#else + env = getenv("QUAKE_SOUND_SPEED"); +#endif + if (env != NULL) + { + chosen_fmt.speed = atoi (env); +#if _MSC_VER >= 1400 + free(env); +#endif + fixed_speed = true; + } +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_SAMPLEBITS"); +#else + env = getenv("QUAKE_SOUND_SAMPLEBITS"); +#endif + if (env != NULL) + { + chosen_fmt.width = atoi (env) / 8; +#if _MSC_VER >= 1400 + free(env); +#endif + fixed_width = true; + } + + // Parse the command line to see if the player wants a particular sound format +// COMMANDLINEOPTION: Sound: -sndquad sets sound output to 4 channel surround + if (COM_CheckParm ("-sndquad") != 0) + { + chosen_fmt.channels = 4; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndstereo sets sound output to stereo + else if (COM_CheckParm ("-sndstereo") != 0) + { + chosen_fmt.channels = 2; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndmono sets sound output to mono + else if (COM_CheckParm ("-sndmono") != 0) + { + chosen_fmt.channels = 1; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndspeed chooses sound output rate (supported values are 48000, 44100, 32000, 24000, 22050, 16000, 11025 (quake), 8000) + i = COM_CheckParm ("-sndspeed"); + if (0 < i && i < com_argc - 1) + { + chosen_fmt.speed = atoi (com_argv[i + 1]); + fixed_speed = true; + } +// COMMANDLINEOPTION: Sound: -sndbits chooses 8 bit or 16 bit sound output + i = COM_CheckParm ("-sndbits"); + if (0 < i && i < com_argc - 1) + { + chosen_fmt.width = atoi (com_argv[i + 1]) / 8; + fixed_width = true; + } + +#if 0 + // LordHavoc: now you can with the resampler... + // You can't change sound speed after start time (not yet supported) + if (prev_render_format.speed != 0) + { + fixed_speed = true; + if (chosen_fmt.speed != prev_render_format.speed) + { + Con_Printf("S_Startup: sound speed has changed! This is NOT supported yet. Falling back to previous speed (%u Hz)\n", + prev_render_format.speed); + chosen_fmt.speed = prev_render_format.speed; + } + } +#endif + + // Sanity checks + if (chosen_fmt.speed < SND_MIN_SPEED) + { + chosen_fmt.speed = SND_MIN_SPEED; + fixed_speed = false; + } + else if (chosen_fmt.speed > SND_MAX_SPEED) + { + chosen_fmt.speed = SND_MAX_SPEED; + fixed_speed = false; + } + + if (chosen_fmt.width < SND_MIN_WIDTH) + { + chosen_fmt.width = SND_MIN_WIDTH; + fixed_width = false; + } + else if (chosen_fmt.width > SND_MAX_WIDTH) + { + chosen_fmt.width = SND_MAX_WIDTH; + fixed_width = false; + } + + if (chosen_fmt.channels < SND_MIN_CHANNELS) + { + chosen_fmt.channels = SND_MIN_CHANNELS; + fixed_channels = false; + } + else if (chosen_fmt.channels > SND_MAX_CHANNELS) + { + chosen_fmt.channels = SND_MAX_CHANNELS; + fixed_channels = false; + } + + // create the sound buffer used for sumitting the samples to the plaform-dependent module + if (!simsound) + { + snd_format_t suggest_fmt; + qboolean accepted; + + accepted = false; + do + { + Con_Printf("S_Startup: initializing sound output format: %dHz, %d bit, %d channels...\n", + chosen_fmt.speed, chosen_fmt.width * 8, + chosen_fmt.channels); + + memset(&suggest_fmt, 0, sizeof(suggest_fmt)); + accepted = SndSys_Init(&chosen_fmt, &suggest_fmt); + + if (!accepted) + { + Con_Printf("S_Startup: sound output initialization FAILED\n"); + + // If the module is suggesting another one + if (suggest_fmt.speed != 0) + { + memcpy(&chosen_fmt, &suggest_fmt, sizeof(chosen_fmt)); + Con_Printf (" Driver has suggested %dHz, %d bit, %d channels. Retrying...\n", + suggest_fmt.speed, suggest_fmt.width * 8, + suggest_fmt.channels); + } + // Else, try to find a less resource-demanding format + else if (!S_ChooseCheaperFormat (&chosen_fmt, fixed_speed, fixed_width, fixed_channels)) + break; + } + } while (!accepted); + + // If we haven't found a suitable format + if (!accepted) + { + Con_Print("S_Startup: SndSys_Init failed.\n"); + sound_spatialized = false; + return; + } + } + else + { + snd_renderbuffer = Snd_CreateRingBuffer(&chosen_fmt, 0, NULL); + Con_Print ("S_Startup: simulating sound output\n"); + } + + memcpy(&prev_render_format, &snd_renderbuffer->format, sizeof(prev_render_format)); + Con_Printf("Sound format: %dHz, %d channels, %d bits per sample\n", + chosen_fmt.speed, chosen_fmt.channels, chosen_fmt.width * 8); + + // Update the cvars + if (snd_speed.integer != (int)chosen_fmt.speed) + Cvar_SetValueQuick(&snd_speed, chosen_fmt.speed); + if (snd_width.integer != chosen_fmt.width) + Cvar_SetValueQuick(&snd_width, chosen_fmt.width); + if (snd_channels.integer != chosen_fmt.channels) + Cvar_SetValueQuick(&snd_channels, chosen_fmt.channels); + + current_channellayout_used = SND_CHANNELLAYOUT_AUTO; + S_SetChannelLayout(); + + snd_starttime = realtime; + + // If the sound module has already run, add an extra time to make sure + // the sound time doesn't decrease, to not confuse playing SFXs + if (oldpaintedtime != 0) + { + // The extra time must be a multiple of the render buffer size + // to avoid modifying the current position in the buffer, + // some modules write directly to a shared (DMA) buffer + extrasoundtime = oldpaintedtime + snd_renderbuffer->maxframes - 1; + extrasoundtime -= extrasoundtime % snd_renderbuffer->maxframes; + Con_Printf("S_Startup: extra sound time = %u\n", extrasoundtime); + + soundtime = extrasoundtime; + } + else + extrasoundtime = 0; + snd_renderbuffer->startframe = soundtime; + snd_renderbuffer->endframe = soundtime; + recording_sound = false; +} + +void S_Shutdown(void) +{ + if (snd_renderbuffer == NULL) + return; + + oldpaintedtime = snd_renderbuffer->endframe; + + if (simsound) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } + else + SndSys_Shutdown(); + + sound_spatialized = false; +} + +static void S_Restart_f(void) +{ + // NOTE: we can't free all sounds if we are running a map (this frees sfx_t that are still referenced by precaches) + // So, refuse to do this if we are connected. + if(cls.state == ca_connected) + { + Con_Printf("snd_restart would wreak havoc if you do that while connected!\n"); + return; + } + + S_Shutdown(); + S_Startup(); +} + +/* +================ +S_Init +================ +*/ +void S_Init(void) +{ + Cvar_RegisterVariable(&volume); + Cvar_RegisterVariable(&bgmvolume); + Cvar_RegisterVariable(&mastervolume); + Cvar_RegisterVariable(&snd_staticvolume); + Cvar_RegisterVariable(&snd_entchannel0volume); + Cvar_RegisterVariable(&snd_entchannel1volume); + Cvar_RegisterVariable(&snd_entchannel2volume); + Cvar_RegisterVariable(&snd_entchannel3volume); + Cvar_RegisterVariable(&snd_entchannel4volume); + Cvar_RegisterVariable(&snd_entchannel5volume); + Cvar_RegisterVariable(&snd_entchannel6volume); + Cvar_RegisterVariable(&snd_entchannel7volume); + Cvar_RegisterVariable(&snd_worldchannel0volume); + Cvar_RegisterVariable(&snd_worldchannel1volume); + Cvar_RegisterVariable(&snd_worldchannel2volume); + Cvar_RegisterVariable(&snd_worldchannel3volume); + Cvar_RegisterVariable(&snd_worldchannel4volume); + Cvar_RegisterVariable(&snd_worldchannel5volume); + Cvar_RegisterVariable(&snd_worldchannel6volume); + Cvar_RegisterVariable(&snd_worldchannel7volume); + Cvar_RegisterVariable(&snd_playerchannel0volume); + Cvar_RegisterVariable(&snd_playerchannel1volume); + Cvar_RegisterVariable(&snd_playerchannel2volume); + Cvar_RegisterVariable(&snd_playerchannel3volume); + Cvar_RegisterVariable(&snd_playerchannel4volume); + Cvar_RegisterVariable(&snd_playerchannel5volume); + Cvar_RegisterVariable(&snd_playerchannel6volume); + Cvar_RegisterVariable(&snd_playerchannel7volume); + Cvar_RegisterVariable(&snd_csqcchannel0volume); + Cvar_RegisterVariable(&snd_csqcchannel1volume); + Cvar_RegisterVariable(&snd_csqcchannel2volume); + Cvar_RegisterVariable(&snd_csqcchannel3volume); + Cvar_RegisterVariable(&snd_csqcchannel4volume); + Cvar_RegisterVariable(&snd_csqcchannel5volume); + Cvar_RegisterVariable(&snd_csqcchannel6volume); + Cvar_RegisterVariable(&snd_csqcchannel7volume); + Cvar_RegisterVariable(&snd_channel0volume); + Cvar_RegisterVariable(&snd_channel1volume); + Cvar_RegisterVariable(&snd_channel2volume); + Cvar_RegisterVariable(&snd_channel3volume); + Cvar_RegisterVariable(&snd_channel4volume); + Cvar_RegisterVariable(&snd_channel5volume); + Cvar_RegisterVariable(&snd_channel6volume); + Cvar_RegisterVariable(&snd_channel7volume); + + Cvar_RegisterVariable(&snd_attenuation_exponent); + Cvar_RegisterVariable(&snd_attenuation_decibel); + + Cvar_RegisterVariable(&snd_spatialization_min_radius); + Cvar_RegisterVariable(&snd_spatialization_max_radius); + Cvar_RegisterVariable(&snd_spatialization_min); + Cvar_RegisterVariable(&snd_spatialization_max); + Cvar_RegisterVariable(&snd_spatialization_power); + Cvar_RegisterVariable(&snd_spatialization_control); + Cvar_RegisterVariable(&snd_spatialization_occlusion); + Cvar_RegisterVariable(&snd_spatialization_prologic); + Cvar_RegisterVariable(&snd_spatialization_prologic_frontangle); + + Cvar_RegisterVariable(&snd_speed); + Cvar_RegisterVariable(&snd_width); + Cvar_RegisterVariable(&snd_channels); + Cvar_RegisterVariable(&snd_mutewhenidle); + Cvar_RegisterVariable(&snd_maxchannelvolume); + Cvar_RegisterVariable(&snd_softclip); + + Cvar_RegisterVariable(&snd_startloopingsounds); + Cvar_RegisterVariable(&snd_startnonloopingsounds); + + Cvar_RegisterVariable(&snd_identicalsoundrandomization_time); + Cvar_RegisterVariable(&snd_identicalsoundrandomization_tics); + +// COMMANDLINEOPTION: Sound: -nosound disables sound (including CD audio) + if (COM_CheckParm("-nosound")) + { + // dummy out Play and Play2 because mods stuffcmd that + Cmd_AddCommand("play", Host_NoOperation_f, "does nothing because -nosound was specified"); + Cmd_AddCommand("play2", Host_NoOperation_f, "does nothing because -nosound was specified"); + return; + } + + snd_mempool = Mem_AllocPool("sound", 0, NULL); + +// COMMANDLINEOPTION: Sound: -simsound runs sound mixing but with no output + if (COM_CheckParm("-simsound")) + simsound = true; + + Cmd_AddCommand("play", S_Play_f, "play a sound at your current location (not heard by anyone else)"); + Cmd_AddCommand("play2", S_Play2_f, "play a sound globally throughout the level (not heard by anyone else)"); + Cmd_AddCommand("playvol", S_PlayVol_f, "play a sound at the specified volume level at your current location (not heard by anyone else)"); + Cmd_AddCommand("stopsound", S_StopAllSounds, "silence"); + Cmd_AddCommand("soundlist", S_SoundList_f, "list loaded sounds"); + Cmd_AddCommand("soundinfo", S_SoundInfo_f, "print sound system information (such as channels and speed)"); + Cmd_AddCommand("snd_restart", S_Restart_f, "restart sound system"); + Cmd_AddCommand("snd_unloadallsounds", S_UnloadAllSounds_f, "unload all sound files"); + + Cvar_RegisterVariable(&nosound); + Cvar_RegisterVariable(&snd_precache); + Cvar_RegisterVariable(&snd_initialized); + Cvar_RegisterVariable(&snd_streaming); + Cvar_RegisterVariable(&snd_streaming_length); + Cvar_RegisterVariable(&ambient_level); + Cvar_RegisterVariable(&ambient_fade); + Cvar_RegisterVariable(&snd_noextraupdate); + Cvar_RegisterVariable(&snd_show); + Cvar_RegisterVariable(&_snd_mixahead); + Cvar_RegisterVariable(&snd_swapstereo); // for people with backwards sound wiring + Cvar_RegisterVariable(&snd_channellayout); + Cvar_RegisterVariable(&snd_soundradius); + + Cvar_SetValueQuick(&snd_initialized, true); + + known_sfx = NULL; + + total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics + memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); + + OGG_OpenLibrary (); + ModPlug_OpenLibrary (); +} + + +/* +================ +S_Terminate + +Shutdown and free all resources +================ +*/ +void S_Terminate (void) +{ + S_Shutdown (); + ModPlug_CloseLibrary (); + OGG_CloseLibrary (); + + // Free all SFXs + while (known_sfx != NULL) + S_FreeSfx (known_sfx, true); + + Cvar_SetValueQuick (&snd_initialized, false); + Mem_FreePool (&snd_mempool); +} + + +/* +================== +S_UnloadAllSounds_f +================== +*/ +void S_UnloadAllSounds_f (void) +{ + int i; + + // NOTE: we can't free all sounds if we are running a map (this frees sfx_t that are still referenced by precaches) + // So, refuse to do this if we are connected. + if(cls.state == ca_connected) + { + Con_Printf("snd_unloadallsounds would wreak havoc if you do that while connected!\n"); + return; + } + + // stop any active sounds + S_StopAllSounds(); + + // because the ambient sounds will be freed, clear the pointers + for (i = 0;i < (int)sizeof (ambient_sfxs) / (int)sizeof (ambient_sfxs[0]);i++) + ambient_sfxs[i] = NULL; + + // now free all sounds + while (known_sfx != NULL) + S_FreeSfx (known_sfx, true); +} + + +/* +================== +S_FindName +================== +*/ +sfx_t changevolume_sfx = {""}; +sfx_t *S_FindName (const char *name) +{ + sfx_t *sfx; + + if (!snd_initialized.integer) + return NULL; + + if(!strcmp(name, changevolume_sfx.name)) + return &changevolume_sfx; + + if (strlen (name) >= sizeof (sfx->name)) + { + Con_Printf ("S_FindName: sound name too long (%s)\n", name); + return NULL; + } + + // Look for this sound in the list of known sfx + // TODO: hash table search? + for (sfx = known_sfx; sfx != NULL; sfx = sfx->next) + if(!strcmp (sfx->name, name)) + return sfx; + + // check for # in the beginning, try lookup by soundindex + if (name[0] == '#' && name[1]) + { + int soundindex = atoi(name + 1); + if (soundindex > 0 && soundindex < MAX_SOUNDS) + if (cl.sound_precache[soundindex]->name[0]) + return cl.sound_precache[soundindex]; + } + + // Add a sfx_t struct for this sound + sfx = (sfx_t *)Mem_Alloc (snd_mempool, sizeof (*sfx)); + memset (sfx, 0, sizeof(*sfx)); + strlcpy (sfx->name, name, sizeof (sfx->name)); + sfx->memsize = sizeof(*sfx); + sfx->next = known_sfx; + known_sfx = sfx; + + return sfx; +} + + +/* +================== +S_FreeSfx +================== +*/ +void S_FreeSfx (sfx_t *sfx, qboolean force) +{ + unsigned int i; + + // Do not free a precached sound during purge + if (!force && (sfx->flags & (SFXFLAG_LEVELSOUND | SFXFLAG_MENUSOUND))) + return; + + if (developer_loading.integer) + Con_Printf ("unloading sound %s\n", sfx->name); + + // Remove it from the list of known sfx + if (sfx == known_sfx) + known_sfx = known_sfx->next; + else + { + sfx_t *prev_sfx; + + for (prev_sfx = known_sfx; prev_sfx != NULL; prev_sfx = prev_sfx->next) + if (prev_sfx->next == sfx) + { + prev_sfx->next = sfx->next; + break; + } + if (prev_sfx == NULL) + { + Con_Printf ("S_FreeSfx: Can't find SFX %s in the list!\n", sfx->name); + return; + } + } + + // Stop all channels using this sfx + for (i = 0; i < total_channels; i++) + { + if (channels[i].sfx == sfx) + { + Con_Printf("S_FreeSfx: stopping channel %i for sfx \"%s\"\n", i, sfx->name); + S_StopChannel (i, true, false); + } + } + + // Free it + if (sfx->fetcher != NULL && sfx->fetcher->freesfx != NULL) + sfx->fetcher->freesfx(sfx); + Mem_Free (sfx); +} + + +/* +================== +S_ClearUsed +================== +*/ +void S_ClearUsed (void) +{ + sfx_t *sfx; +// sfx_t *sfxnext; + unsigned int i; + + // Start the ambient sounds and make them loop + for (i = 0; i < sizeof (ambient_sfxs) / sizeof (ambient_sfxs[0]); i++) + { + // Precache it if it's not done (and pass false for levelsound because these are permanent) + if (ambient_sfxs[i] == NULL) + ambient_sfxs[i] = S_PrecacheSound (ambient_names[i], false, false); + if (ambient_sfxs[i] != NULL) + { + channels[i].sfx = ambient_sfxs[i]; + channels[i].sfx->flags |= SFXFLAG_MENUSOUND; + channels[i].flags |= CHANNELFLAG_FORCELOOP; + channels[i].basevolume = 0.0f; + channels[i].basespeed = channels[i].mixspeed = 1.0f; + } + } + + // Clear SFXFLAG_LEVELSOUND flag so that sounds not precached this level will be purged + for (sfx = known_sfx; sfx != NULL; sfx = sfx->next) + sfx->flags &= ~SFXFLAG_LEVELSOUND; +} + +/* +================== +S_PurgeUnused +================== +*/ +void S_PurgeUnused(void) +{ + sfx_t *sfx; + sfx_t *sfxnext; + + // Free all not-precached per-level sfx + for (sfx = known_sfx;sfx;sfx = sfxnext) + { + sfxnext = sfx->next; + if (!(sfx->flags & (SFXFLAG_LEVELSOUND | SFXFLAG_MENUSOUND))) + S_FreeSfx (sfx, false); + } +} + + +/* +================== +S_PrecacheSound +================== +*/ +sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean levelsound) +{ + sfx_t *sfx; + + if (!snd_initialized.integer) + return NULL; + + if (name == NULL || name[0] == 0) + return NULL; + + sfx = S_FindName (name); + + if (sfx == NULL) + return NULL; + + // clear the FILEMISSING flag so that S_LoadSound will try again on a + // previously missing file + sfx->flags &= ~ SFXFLAG_FILEMISSING; + + // set a flag to indicate this has been precached for this level or permanently + if (levelsound) + sfx->flags |= SFXFLAG_LEVELSOUND; + else + sfx->flags |= SFXFLAG_MENUSOUND; + + if (!nosound.integer && snd_precache.integer) + S_LoadSound(sfx, complain); + + return sfx; +} + +/* +================== +S_SoundLength +================== +*/ + +float S_SoundLength(const char *name) +{ + sfx_t *sfx; + + if (!snd_initialized.integer) + return -1; + if (name == NULL || name[0] == 0) + return -1; + + sfx = S_FindName(name); + if (sfx == NULL) + return -1; + return sfx->total_length / (float) S_GetSoundRate(); +} + +/* +================== +S_IsSoundPrecached +================== +*/ +qboolean S_IsSoundPrecached (const sfx_t *sfx) +{ + return (sfx != NULL && sfx->fetcher != NULL) || (sfx == &changevolume_sfx); +} + +/* +================== +S_BlockSound +================== +*/ +void S_BlockSound (void) +{ + snd_blocked++; +} + + +/* +================== +S_UnblockSound +================== +*/ +void S_UnblockSound (void) +{ + snd_blocked--; +} + + +/* +================= +SND_PickChannel + +Picks a channel based on priorities, empty slots, number of channels +================= +*/ +static channel_t *SND_PickChannel(int entnum, int entchannel) +{ + int ch_idx; + int first_to_die; + int first_life_left, life_left; + channel_t* ch; + sfx_t *sfx; // use this instead of ch->sfx->, because that is volatile. + +// Check for replacement sound, or find the best one to replace + first_to_die = -1; + first_life_left = 0x7fffffff; + + // entity channels try to replace the existing sound on the channel + // channels <= 0 are autochannels + if (IS_CHAN_SINGLE(entchannel)) + { + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) + { + ch = &channels[ch_idx]; + if (ch->entnum == entnum && ch->entchannel == entchannel) + { + // always override sound from same entity + S_StopChannel (ch_idx, true, false); + return &channels[ch_idx]; + } + } + } + + // there was no channel to override, so look for the first empty one + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) + { + ch = &channels[ch_idx]; + sfx = ch->sfx; // fetch the volatile variable + if (!sfx) + { + // no sound on this channel + first_to_die = ch_idx; + goto emptychan_found; + } + + // don't let monster sounds override player sounds + if ((ch->entnum == cl.viewentity || ch->entnum == CL_VM_GetViewEntity()) && !(entnum == cl.viewentity || entnum == CL_VM_GetViewEntity())) + continue; + + // don't override looped sounds + if ((ch->flags & CHANNELFLAG_FORCELOOP) || sfx->loopstart < sfx->total_length) + continue; + life_left = (int)((double)sfx->total_length - ch->position); + + if (life_left < first_life_left) + { + first_life_left = life_left; + first_to_die = ch_idx; + } + } + + if (first_to_die == -1) + return NULL; + + S_StopChannel (first_to_die, true, false); + +emptychan_found: + return &channels[first_to_die]; +} + +/* +================= +SND_Spatialize + +Spatializes a channel +================= +*/ +extern cvar_t cl_gameplayfix_soundsmovewithentities; +static void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) +{ + int i; + double f; + float angle_side, angle_front, angle_factor, mixspeed; + vec_t dist, mastervol, intensity; + vec3_t source_vec; + char vabuf[1024]; + + // update sound origin if we know about the entity + if (ch->entnum > 0 && cls.state == ca_connected && cl_gameplayfix_soundsmovewithentities.integer) + { + if (ch->entnum >= MAX_EDICTS) + { + //Con_Printf("-- entnum %i origin %f %f %f neworigin %f %f %f\n", ch->entnum, ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); + + if (ch->entnum > MAX_EDICTS) + if (!CL_VM_GetEntitySoundOrigin(ch->entnum, ch->origin)) + ch->entnum = MAX_EDICTS; // entity was removed, disown sound + } + else if (cl.entities[ch->entnum].state_current.active) + { + dp_model_t *model; + //Con_Printf("-- entnum %i origin %f %f %f neworigin %f %f %f\n", ch->entnum, ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); + model = CL_GetModelByIndex(cl.entities[ch->entnum].state_current.modelindex); + if (model && model->soundfromcenter) + VectorMAM(0.5f, cl.entities[ch->entnum].render.mins, 0.5f, cl.entities[ch->entnum].render.maxs, ch->origin); + else + Matrix4x4_OriginFromMatrix(&cl.entities[ch->entnum].render.matrix, ch->origin); + } + else if (cl.csqc_server2csqcentitynumber[ch->entnum]) + { + //Con_Printf("-- entnum %i (client %i) origin %f %f %f neworigin %f %f %f\n", ch->entnum, cl.csqc_server2csqcentitynumber[ch->entnum], ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); + + if (!CL_VM_GetEntitySoundOrigin(cl.csqc_server2csqcentitynumber[ch->entnum] + MAX_EDICTS, ch->origin)) + ch->entnum = MAX_EDICTS; // entity was removed, disown sound + } + } + + mastervol = ch->basevolume; + mixspeed = ch->basespeed; + + // TODO: implement doppler based on origin change relative to viewer and time of recent origin changes + + // Adjust volume of static sounds + if (isstatic) + mastervol *= snd_staticvolume.value; + else if(!(ch->flags & CHANNELFLAG_FULLVOLUME)) // same as SND_PaintChannel uses + { + // old legacy separated cvars + if(ch->entnum >= MAX_EDICTS) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_csqcchannel0volume.value; break; + case 1: mastervol *= snd_csqcchannel1volume.value; break; + case 2: mastervol *= snd_csqcchannel2volume.value; break; + case 3: mastervol *= snd_csqcchannel3volume.value; break; + case 4: mastervol *= snd_csqcchannel4volume.value; break; + case 5: mastervol *= snd_csqcchannel5volume.value; break; + case 6: mastervol *= snd_csqcchannel6volume.value; break; + case 7: mastervol *= snd_csqcchannel7volume.value; break; + default: break; + } + } + else if(ch->entnum == 0) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_worldchannel0volume.value; break; + case 1: mastervol *= snd_worldchannel1volume.value; break; + case 2: mastervol *= snd_worldchannel2volume.value; break; + case 3: mastervol *= snd_worldchannel3volume.value; break; + case 4: mastervol *= snd_worldchannel4volume.value; break; + case 5: mastervol *= snd_worldchannel5volume.value; break; + case 6: mastervol *= snd_worldchannel6volume.value; break; + case 7: mastervol *= snd_worldchannel7volume.value; break; + default: break; + } + } + else if(ch->entnum > 0 && ch->entnum <= cl.maxclients) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_playerchannel0volume.value; break; + case 1: mastervol *= snd_playerchannel1volume.value; break; + case 2: mastervol *= snd_playerchannel2volume.value; break; + case 3: mastervol *= snd_playerchannel3volume.value; break; + case 4: mastervol *= snd_playerchannel4volume.value; break; + case 5: mastervol *= snd_playerchannel5volume.value; break; + case 6: mastervol *= snd_playerchannel6volume.value; break; + case 7: mastervol *= snd_playerchannel7volume.value; break; + default: break; + } + } + else + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_entchannel0volume.value; break; + case 1: mastervol *= snd_entchannel1volume.value; break; + case 2: mastervol *= snd_entchannel2volume.value; break; + case 3: mastervol *= snd_entchannel3volume.value; break; + case 4: mastervol *= snd_entchannel4volume.value; break; + case 5: mastervol *= snd_entchannel5volume.value; break; + case 6: mastervol *= snd_entchannel6volume.value; break; + case 7: mastervol *= snd_entchannel7volume.value; break; + default: break; + } + } + + switch(ch->entchannel) + { + case 0: mastervol *= snd_channel0volume.value; break; + case 1: mastervol *= snd_channel1volume.value; break; + case 2: mastervol *= snd_channel2volume.value; break; + case 3: mastervol *= snd_channel3volume.value; break; + case 4: mastervol *= snd_channel4volume.value; break; + case 5: mastervol *= snd_channel5volume.value; break; + case 6: mastervol *= snd_channel6volume.value; break; + case 7: mastervol *= snd_channel7volume.value; break; + default: mastervol *= Cvar_VariableValueOr(va(vabuf, sizeof(vabuf), "snd_channel%dvolume", CHAN_ENGINE2CVAR(ch->entchannel)), 1.0); break; + } + } + + // If this channel does not manage its own volume (like CD tracks) + if (!(ch->flags & CHANNELFLAG_FULLVOLUME)) + mastervol *= volume.value; + + if(snd_maxchannelvolume.value > 0) + { + // clamp HERE to allow to go at most 10dB past mastervolume (before clamping), when mastervolume < -10dB (so relative volumes don't get too messy) + mastervol = bound(0.0f, mastervol, 10.0f * snd_maxchannelvolume.value); + } + + // always apply "master" + mastervol *= mastervolume.value; + + // add in ReplayGain very late; prevent clipping when close + if(sfx) + if(sfx->volume_peak > 0) + { + // Replaygain support + // Con_DPrintf("Setting volume on ReplayGain-enabled track... %f -> ", fvol); + mastervol *= sfx->volume_mult; + if(snd_maxchannelvolume.value > 0) + { + if(mastervol * sfx->volume_peak > snd_maxchannelvolume.value) + mastervol = snd_maxchannelvolume.value / sfx->volume_peak; + } + // Con_DPrintf("%f\n", fvol); + } + + if(snd_maxchannelvolume.value > 0) + { + // clamp HERE to keep relative volumes of the channels correct + mastervol = min(mastervol, snd_maxchannelvolume.value); + } + + mastervol = max(0.0f, mastervol); + + ch->mixspeed = mixspeed; + + // anything coming from the view entity will always be full volume + // LordHavoc: make sounds with ATTN_NONE have no spatialization + if (ch->entnum == cl.viewentity || ch->entnum == CL_VM_GetViewEntity() || ch->distfade == 0) + { + ch->prologic_invert = 1; + if (snd_spatialization_prologic.integer != 0) + { + ch->volume[0] = mastervol * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5); + ch->volume[1] = mastervol * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5); + for (i = 2;i < SND_LISTENERS;i++) + ch->volume[i] = 0; + } + else + { + for (i = 0;i < SND_LISTENERS;i++) + ch->volume[i] = mastervol * snd_speakerlayout.listeners[i].ambientvolume; + } + } + else + { + // calculate stereo seperation and distance attenuation + VectorSubtract(listener_origin, ch->origin, source_vec); + dist = VectorLength(source_vec); + f = dist * ch->distfade; + + f = + ((snd_attenuation_exponent.value == 0) ? 1.0 : pow(1.0 - min(1.0, f), (double)snd_attenuation_exponent.value)) + * + ((snd_attenuation_decibel.value == 0) ? 1.0 : pow(0.1, 0.1 * snd_attenuation_decibel.value * f)); + + intensity = mastervol * f; + if (intensity > 0) + { + qboolean occluded = false; + if (snd_spatialization_occlusion.integer) + { + if(snd_spatialization_occlusion.integer & 1) + if(listener_pvs) + { + int cluster = cl.worldmodel->brush.PointInLeaf(cl.worldmodel, ch->origin)->clusterindex; + if(cluster >= 0 && cluster < 8 * listener_pvsbytes && !CHECKPVSBIT(listener_pvs, cluster)) + occluded = true; + } + + if(snd_spatialization_occlusion.integer & 2) + if(!occluded) + if(cl.worldmodel && cl.worldmodel->brush.TraceLineOfSight && !cl.worldmodel->brush.TraceLineOfSight(cl.worldmodel, listener_origin, ch->origin)) + occluded = true; + } + if(occluded) + intensity *= 0.5f; + + ch->prologic_invert = 1; + if (snd_spatialization_prologic.integer != 0) + { + if (dist == 0) + angle_factor = 0.5f; + else + { + Matrix4x4_Transform(&listener_basematrix, ch->origin, source_vec); + VectorNormalize(source_vec); + + switch(spatialmethod) + { + case SPATIAL_LOG: + if(dist == 0) + f = spatialmin + spatialdiff * (spatialfactor < 0); // avoid log(0), but do the right thing + else + f = spatialmin + spatialdiff * bound(0, (log(dist) - spatialoffset) * spatialfactor, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_POW: + f = (pow(dist, spatialpower) - spatialoffset) * spatialfactor; + f = spatialmin + spatialdiff * bound(0, f, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_THRESH: + f = spatialmin + spatialdiff * (dist < spatialoffset); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_NONE: + default: + break; + } + + // the z axis needs to be removed and normalized because otherwise the volume would get lower as the sound source goes higher or lower then normal + source_vec[2] = 0; + VectorNormalize(source_vec); + angle_side = acos(source_vec[0]) / M_PI * 180; // angle between 0 and 180 degrees + angle_front = asin(source_vec[1]) / M_PI * 180; // angle between -90 and 90 degrees + if (angle_side > snd_spatialization_prologic_frontangle.value) + { + ch->prologic_invert = -1; // this will cause the right channel to do a 180 degrees phase shift (turning the sound wave upside down), + // but the best would be 90 degrees phase shift left and a -90 degrees phase shift right. + angle_factor = (angle_side - snd_spatialization_prologic_frontangle.value) / (360 - 2 * snd_spatialization_prologic_frontangle.value); + // angle_factor is between 0 and 1 and represents the angle range from the front left, to all the surround speakers (amount may vary, + // 1 in prologic I 2 in prologic II and 3 or 4 in prologic IIx) to the front right speaker. + if (angle_front > 0) + angle_factor = 1 - angle_factor; + } + else + angle_factor = angle_front / snd_spatialization_prologic_frontangle.value / 2.0 + 0.5; + //angle_factor is between 0 and 1 and represents the angle range from the front left to the center to the front right speaker + } + + ch->volume[0] = intensity * sqrt(angle_factor); + ch->volume[1] = intensity * sqrt(1 - angle_factor); + for (i = 2;i < SND_LISTENERS;i++) + ch->volume[i] = 0; + } + else + { + for (i = 0;i < SND_LISTENERS;i++) + { + Matrix4x4_Transform(&listener_matrix[i], ch->origin, source_vec); + VectorNormalize(source_vec); + + switch(spatialmethod) + { + case SPATIAL_LOG: + if(dist == 0) + f = spatialmin + spatialdiff * (spatialfactor < 0); // avoid log(0), but do the right thing + else + f = spatialmin + spatialdiff * bound(0, (log(dist) - spatialoffset) * spatialfactor, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_POW: + f = (pow(dist, spatialpower) - spatialoffset) * spatialfactor; + f = spatialmin + spatialdiff * bound(0, f, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_THRESH: + f = spatialmin + spatialdiff * (dist < spatialoffset); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_NONE: + default: + break; + } + + ch->volume[i] = intensity * max(0, source_vec[0] * snd_speakerlayout.listeners[i].dotscale + snd_speakerlayout.listeners[i].dotbias); + } + } + } + else + for (i = 0;i < SND_LISTENERS;i++) + ch->volume[i] = 0; + } +} +static void SND_Spatialize(channel_t *ch, qboolean isstatic) +{ + sfx_t *sfx = ch->sfx; + SND_Spatialize_WithSfx(ch, isstatic, sfx); +} + + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +static void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, vec3_t origin, float fvol, float attenuation, qboolean isstatic, int entnum, int entchannel, int startpos, float fspeed) +{ + if (!sfx) + { + Con_Printf("S_PlaySfxOnChannel called with NULL??\n"); + return; + } + + if ((sfx->loopstart < sfx->total_length) || (flags & CHANNELFLAG_FORCELOOP)) + { + if(!snd_startloopingsounds.integer) + return; + } + else + { + if(!snd_startnonloopingsounds.integer) + return; + } + + // Initialize the channel + // a crash was reported on an in-use channel, so check here... + if (target_chan->sfx) + { + int channelindex = (int)(target_chan - channels); + Con_Printf("S_PlaySfxOnChannel(%s): channel %i already in use?? Clearing.\n", sfx->name, channelindex); + S_StopChannel (channelindex, true, false); + } + // We MUST set sfx LAST because otherwise we could crash a threaded mixer + // (otherwise we'd have to call SndSys_LockRenderBuffer here) + memset (target_chan, 0, sizeof (*target_chan)); + VectorCopy (origin, target_chan->origin); + target_chan->flags = flags; + target_chan->position = startpos; // start of the sound + target_chan->entnum = entnum; + target_chan->entchannel = entchannel; + + // If it's a static sound + if (isstatic) + { + if (sfx->loopstart >= sfx->total_length && (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEWORLD)) + Con_DPrintf("Quake compatibility warning: Static sound \"%s\" is not looped\n", sfx->name); + target_chan->distfade = attenuation / (64.0f * snd_soundradius.value); + } + else + target_chan->distfade = attenuation / snd_soundradius.value; + + // set the listener volumes + S_SetChannelVolume(target_chan - channels, fvol); + S_SetChannelSpeed(target_chan - channels, fspeed); + SND_Spatialize_WithSfx (target_chan, isstatic, sfx); + + // finally, set the sfx pointer, so the channel becomes valid for playback + // and will be noticed by the mixer + target_chan->sfx = sfx; +} + + +int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags, float fspeed) +{ + channel_t *target_chan, *check, *ch; + int ch_idx, startpos, i; + + if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) + return -1; + + if(sfx == &changevolume_sfx) + { + if (!IS_CHAN_SINGLE(entchannel)) + return -1; + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) + { + ch = &channels[ch_idx]; + if (ch->entnum == entnum && ch->entchannel == entchannel) + { + S_SetChannelVolume(ch_idx, fvol); + S_SetChannelSpeed(ch_idx, fspeed); + for(i = 1; i > 0 && (i <= flags || i <= (int) channels[ch_idx].flags); i <<= 1) + if((flags ^ channels[ch_idx].flags) & i) + S_SetChannelFlag(ch_idx, i, (flags & i) != 0); + ch->distfade = attenuation / snd_soundradius.value; + SND_Spatialize(ch, false); + return ch_idx; + } + } + return -1; + } + + if (sfx->fetcher == NULL) + return -1; + + // Pick a channel to play on + target_chan = SND_PickChannel(entnum, entchannel); + if (!target_chan) + return -1; + + // if an identical sound has also been started this frame, offset the pos + // a bit to keep it from just making the first one louder + check = &channels[NUM_AMBIENTS]; + startpos = (int)(startposition * sfx->format.speed); + if (startpos == 0) + { + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++, check++) + { + if (check == target_chan) + continue; + if (check->sfx == sfx && check->position == 0 && check->basespeed == fspeed) + { + // calculate max offset + float maxtime = snd_identicalsoundrandomization_time.value; + float maxtics = snd_identicalsoundrandomization_tics.value; + float maxticsdelta = ((cls.state == ca_connected) ? (maxtics * (cl.mtime[0] - cl.mtime[1])) : 0); + float maxdelta = 0; + if(maxticsdelta == 0 || fabs(maxticsdelta) > fabs(maxtime)) + maxdelta = maxtime; + else + maxdelta = fabs(maxticsdelta) * ((maxtime > 0) ? 1 : -1); + + // use negative pos offset to delay this sound effect + startpos = lhrandom(0, maxdelta * sfx->format.speed); + break; + } + } + } + + S_PlaySfxOnChannel (sfx, target_chan, flags, origin, fvol, attenuation, false, entnum, entchannel, startpos, fspeed); + + return (target_chan - channels); +} + +int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation) +{ + return S_StartSound_StartPosition_Flags(entnum, entchannel, sfx, origin, fvol, attenuation, 0, CHANNELFLAG_NONE, 1.0f); +} + +void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx) +{ + channel_t *ch; + sfx_t *sfx; + + if (channel_ind >= total_channels) + return; + + // we have to lock an audio mutex to prevent crashes if an audio mixer + // thread is currently mixing this channel + // the SndSys_LockRenderBuffer function uses such a mutex in + // threaded sound backends + if (lockmutex && !simsound) + SndSys_LockRenderBuffer(); + + ch = &channels[channel_ind]; + sfx = ch->sfx; + if (sfx != NULL) + { + if (sfx->fetcher != NULL && sfx->fetcher->stopchannel != NULL) + sfx->fetcher->stopchannel(ch); + ch->fetcher_data = NULL; + ch->sfx = NULL; + if (freesfx) + S_FreeSfx(sfx, true); + } + if (lockmutex && !simsound) + SndSys_UnlockRenderBuffer(); +} + + +qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value) +{ + if (ch_ind >= total_channels) + return false; + + if (flag != CHANNELFLAG_FORCELOOP && + flag != CHANNELFLAG_PAUSED && + flag != CHANNELFLAG_FULLVOLUME && + flag != CHANNELFLAG_LOCALSOUND) + return false; + + if (value) + channels[ch_ind].flags |= flag; + else + channels[ch_ind].flags &= ~flag; + + return true; +} + +void S_StopSound(int entnum, int entchannel) +{ + unsigned int i; + + for (i = 0; i < MAX_DYNAMIC_CHANNELS; i++) + if (channels[i].entnum == entnum && channels[i].entchannel == entchannel) + { + S_StopChannel (i, true, false); + return; + } +} + +void S_StopAllSounds (void) +{ + unsigned int i; + + // TOCHECK: is this test necessary? + if (snd_renderbuffer == NULL) + return; + + // stop CD audio because it may be using a faketrack + CDAudio_Stop(); + + if (simsound || SndSys_LockRenderBuffer ()) + { + int clear; + size_t memsize; + + for (i = 0; i < total_channels; i++) + if (channels[i].sfx) + S_StopChannel (i, false, false); + + total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics + memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); + + // Mute the contents of the submittion buffer + clear = (snd_renderbuffer->format.width == 1) ? 0x80 : 0; + memsize = snd_renderbuffer->maxframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + memset(snd_renderbuffer->ring, clear, memsize); + + if (!simsound) + SndSys_UnlockRenderBuffer (); + } +} + +void S_PauseGameSounds (qboolean toggle) +{ + unsigned int i; + + for (i = 0; i < total_channels; i++) + { + channel_t *ch; + + ch = &channels[i]; + if (ch->sfx != NULL && ! (ch->flags & CHANNELFLAG_LOCALSOUND)) + S_SetChannelFlag (i, CHANNELFLAG_PAUSED, toggle); + } +} + +void S_SetChannelVolume(unsigned int ch_ind, float fvol) +{ + channels[ch_ind].basevolume = fvol; +} + +void S_SetChannelSpeed(unsigned int ch_ind, float fspeed) +{ + channels[ch_ind].basespeed = fspeed; +} + +float S_GetChannelPosition (unsigned int ch_ind) +{ + // note: this is NOT accurate yet + double s; + channel_t *ch = &channels[ch_ind]; + sfx_t *sfx = ch->sfx; + if (!sfx) + return -1; + + s = ch->position / sfx->format.speed; + /* + if(!snd_usethreadedmixing) + s += _snd_mixahead.value; + */ + return (float)s; +} + +float S_GetEntChannelPosition(int entnum, int entchannel) +{ + channel_t *ch; + unsigned int i; + + for (i = 0; i < total_channels; i++) + { + ch = &channels[i]; + if (ch->entnum == entnum && ch->entchannel == entchannel) + return S_GetChannelPosition(i); + } + return -1; // no playing sound in this channel +} + +/* +================= +S_StaticSound +================= +*/ +void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) +{ + channel_t *target_chan; + + if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) + return; + if (!sfx->fetcher) + { + Con_Printf ("S_StaticSound: \"%s\" hasn't been precached\n", sfx->name); + return; + } + + if (total_channels == MAX_CHANNELS) + { + Con_Print("S_StaticSound: total_channels == MAX_CHANNELS\n"); + return; + } + + target_chan = &channels[total_channels++]; + S_PlaySfxOnChannel (sfx, target_chan, CHANNELFLAG_FORCELOOP, origin, fvol, attenuation, true, 0, 0, 0, 1.0f); +} + + +/* +=================== +S_UpdateAmbientSounds +=================== +*/ +static void S_UpdateAmbientSounds (void) +{ + int i; + float vol; + float fade = (float)max(0.0, cl.time - cl.oldtime) * ambient_fade.value / 256.0f; + int ambient_channel; + channel_t *chan; + unsigned char ambientlevels[NUM_AMBIENTS]; + sfx_t *sfx; + + memset(ambientlevels, 0, sizeof(ambientlevels)); + if (cl.worldmodel && cl.worldmodel->brush.AmbientSoundLevelsForPoint) + cl.worldmodel->brush.AmbientSoundLevelsForPoint(cl.worldmodel, listener_origin, ambientlevels, sizeof(ambientlevels)); + + // Calc ambient sound levels + for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++) + { + chan = &channels[ambient_channel]; + sfx = chan->sfx; // fetch the volatile variable + if (sfx == NULL || sfx->fetcher == NULL) + continue; + + i = ambientlevels[ambient_channel]; + if (i < 8) + i = 0; + vol = i * (1.0f / 256.0f); + + // Don't adjust volume too fast + if (chan->basevolume < vol) + { + chan->basevolume += fade; + if (chan->basevolume > vol) + chan->basevolume = vol; + } + else if (chan->basevolume > vol) + { + chan->basevolume -= fade; + if (chan->basevolume < vol) + chan->basevolume = vol; + } + + if (snd_spatialization_prologic.integer != 0) + { + chan->volume[0] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5); + chan->volume[1] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5); + for (i = 2;i < SND_LISTENERS;i++) + chan->volume[i] = 0.0f; + } + else + { + for (i = 0;i < SND_LISTENERS;i++) + chan->volume[i] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[i].ambientvolume; + } + } +} + +static void S_PaintAndSubmit (void) +{ + unsigned int newsoundtime, paintedtime, endtime, maxtime, usedframes; + int usesoundtimehack; + static int soundtimehack = -1; + static int oldsoundtime = 0; + + if (snd_renderbuffer == NULL || nosound.integer) + return; + + // Update sound time + snd_usethreadedmixing = false; + usesoundtimehack = true; + if (cls.timedemo) // SUPER NASTY HACK to mix non-realtime sound for more reliable benchmarking + { + usesoundtimehack = 1; + newsoundtime = (unsigned int)((double)cl.mtime[0] * (double)snd_renderbuffer->format.speed); + } + else if (cls.capturevideo.soundrate && !cls.capturevideo.realtime) // SUPER NASTY HACK to record non-realtime sound + { + usesoundtimehack = 2; + newsoundtime = (unsigned int)((double)cls.capturevideo.frame * (double)snd_renderbuffer->format.speed / (double)cls.capturevideo.framerate); + } + else if (simsound) + { + usesoundtimehack = 3; + newsoundtime = (unsigned int)((realtime - snd_starttime) * (double)snd_renderbuffer->format.speed); + } + else + { + snd_usethreadedmixing = snd_threaded && !cls.capturevideo.soundrate; + usesoundtimehack = 0; + newsoundtime = SndSys_GetSoundTime(); + } + // if the soundtimehack state changes we need to reset the soundtime + if (soundtimehack != usesoundtimehack) + { + snd_renderbuffer->startframe = snd_renderbuffer->endframe = soundtime = newsoundtime; + + // Mute the contents of the submission buffer + if (simsound || SndSys_LockRenderBuffer ()) + { + int clear; + size_t memsize; + + clear = (snd_renderbuffer->format.width == 1) ? 0x80 : 0; + memsize = snd_renderbuffer->maxframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + memset(snd_renderbuffer->ring, clear, memsize); + + if (!simsound) + SndSys_UnlockRenderBuffer (); + } + } + soundtimehack = usesoundtimehack; + + if (!soundtimehack && snd_blocked > 0) + return; + + if (snd_usethreadedmixing) + return; // the audio thread will mix its own data + + newsoundtime += extrasoundtime; + if (newsoundtime < soundtime) + { + if ((cls.capturevideo.soundrate != 0) != recording_sound) + { + unsigned int additionaltime; + + // add some time to extrasoundtime make newsoundtime higher + + // The extra time must be a multiple of the render buffer size + // to avoid modifying the current position in the buffer, + // some modules write directly to a shared (DMA) buffer + additionaltime = (soundtime - newsoundtime) + snd_renderbuffer->maxframes - 1; + additionaltime -= additionaltime % snd_renderbuffer->maxframes; + + extrasoundtime += additionaltime; + newsoundtime += additionaltime; + Con_DPrintf("S_PaintAndSubmit: new extra sound time = %u\n", + extrasoundtime); + } + else if (!soundtimehack) + Con_Printf("S_PaintAndSubmit: WARNING: newsoundtime < soundtime (%u < %u)\n", + newsoundtime, soundtime); + } + soundtime = newsoundtime; + recording_sound = (cls.capturevideo.soundrate != 0); + + // Lock submitbuffer + if (!simsound && !SndSys_LockRenderBuffer()) + { + // If the lock failed, stop here + Con_DPrint(">> S_PaintAndSubmit: SndSys_LockRenderBuffer() failed\n"); + return; + } + + // Check to make sure that we haven't overshot + paintedtime = snd_renderbuffer->endframe; + if (paintedtime < soundtime) + paintedtime = soundtime; + + // mix ahead of current position + if (soundtimehack) + endtime = soundtime + (unsigned int)(_snd_mixahead.value * (float)snd_renderbuffer->format.speed); + else + endtime = soundtime + (unsigned int)(max(_snd_mixahead.value * (float)snd_renderbuffer->format.speed, min(3 * (soundtime - oldsoundtime), 0.3 * (float)snd_renderbuffer->format.speed))); + usedframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; + maxtime = paintedtime + snd_renderbuffer->maxframes - usedframes; + endtime = min(endtime, maxtime); + + while (paintedtime < endtime) + { + unsigned int startoffset; + unsigned int nbframes; + + // see how much we can fit in the paint buffer + nbframes = endtime - paintedtime; + // limit to the end of the ring buffer (in case of wrapping) + startoffset = paintedtime % snd_renderbuffer->maxframes; + nbframes = min(nbframes, snd_renderbuffer->maxframes - startoffset); + + // mix into the buffer + S_MixToBuffer(&snd_renderbuffer->ring[startoffset * snd_renderbuffer->format.width * snd_renderbuffer->format.channels], nbframes); + + paintedtime += nbframes; + snd_renderbuffer->endframe = paintedtime; + } + if (!simsound) + SndSys_UnlockRenderBuffer(); + + // Remove outdated samples from the ring buffer, if any + if (snd_renderbuffer->startframe < soundtime) + snd_renderbuffer->startframe = soundtime; + + if (simsound) + snd_renderbuffer->startframe = snd_renderbuffer->endframe; + else + SndSys_Submit(); + + oldsoundtime = soundtime; + + cls.soundstats.latency_milliseconds = (snd_renderbuffer->endframe - snd_renderbuffer->startframe) * 1000 / snd_renderbuffer->format.speed; + R_TimeReport("audiomix"); +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Update(const matrix4x4_t *listenermatrix) +{ + unsigned int i, j, k; + channel_t *ch, *combine; + matrix4x4_t rotatematrix; + + if (snd_renderbuffer == NULL || nosound.integer) + return; + + { + double mindist_trans, maxdist_trans; + + spatialmin = snd_spatialization_min.value; + spatialdiff = snd_spatialization_max.value - spatialmin; + + if(snd_spatialization_control.value) + { + spatialpower = snd_spatialization_power.value; + + if(spatialpower == 0) + { + spatialmethod = SPATIAL_LOG; + mindist_trans = log(max(1, snd_spatialization_min_radius.value)); + maxdist_trans = log(max(1, snd_spatialization_max_radius.value)); + } + else + { + spatialmethod = SPATIAL_POW; + mindist_trans = pow(snd_spatialization_min_radius.value, spatialpower); + maxdist_trans = pow(snd_spatialization_max_radius.value, spatialpower); + } + + if(mindist_trans - maxdist_trans == 0) + { + spatialmethod = SPATIAL_THRESH; + mindist_trans = snd_spatialization_min_radius.value; + } + else + { + spatialoffset = mindist_trans; + spatialfactor = 1 / (maxdist_trans - mindist_trans); + } + } + else + spatialmethod = SPATIAL_NONE; + + } + + // If snd_swapstereo or snd_channellayout has changed, recompute the channel layout + if (current_swapstereo != boolxor(snd_swapstereo.integer, v_flipped.integer) || + current_channellayout != snd_channellayout.integer) + S_SetChannelLayout(); + + Matrix4x4_Invert_Simple(&listener_basematrix, listenermatrix); + Matrix4x4_OriginFromMatrix(listenermatrix, listener_origin); + if (cl.worldmodel && cl.worldmodel->brush.FatPVS && cl.worldmodel->brush.num_pvsclusterbytes && cl.worldmodel->brush.PointInLeaf) + { + if(cl.worldmodel->brush.num_pvsclusterbytes != listener_pvsbytes) + { + if(listener_pvs) + Mem_Free(listener_pvs); + listener_pvsbytes = cl.worldmodel->brush.num_pvsclusterbytes; + listener_pvs = (unsigned char *) Mem_Alloc(snd_mempool, listener_pvsbytes); + } + cl.worldmodel->brush.FatPVS(cl.worldmodel, listener_origin, 2, listener_pvs, listener_pvsbytes, 0); + } + else + { + if(listener_pvs) + { + Mem_Free(listener_pvs); + listener_pvs = NULL; + } + listener_pvsbytes = 0; + } + + // calculate the current matrices + for (j = 0;j < SND_LISTENERS;j++) + { + Matrix4x4_CreateFromQuakeEntity(&rotatematrix, 0, 0, 0, 0, -snd_speakerlayout.listeners[j].yawangle, 0, 1); + Matrix4x4_Concat(&listener_matrix[j], &rotatematrix, &listener_basematrix); + // I think this should now do this: + // 1. create a rotation matrix for rotating by e.g. -90 degrees CCW + // (note: the matrix will rotate the OBJECT, not the VIEWER, so its + // angle has to be taken negative) + // 2. create a transform which first rotates and moves its argument + // into the player's view coordinates (using basematrix which is + // an inverted "absolute" listener matrix), then applies the + // rotation matrix for the ear + // Isn't Matrix4x4_CreateFromQuakeEntity a bit misleading because this + // does not actually refer to an entity? + } + + // update general area ambient sound sources + S_UpdateAmbientSounds (); + + combine = NULL; + R_TimeReport("audioprep"); + + // update spatialization for static and dynamic sounds + cls.soundstats.totalsounds = 0; + cls.soundstats.mixedsounds = 0; + ch = channels+NUM_AMBIENTS; + for (i=NUM_AMBIENTS ; isfx) + continue; + cls.soundstats.totalsounds++; + + // respatialize channel + SND_Spatialize(ch, i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS); + + // try to combine static sounds with a previous channel of the same + // sound effect so we don't mix five torches every frame + if (i > MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS) + { + // no need to merge silent channels + for (j = 0;j < SND_LISTENERS;j++) + if (ch->volume[j]) + break; + if (j == SND_LISTENERS) + continue; + // if the last combine chosen isn't suitable, find a new one + if (!(combine && combine != ch && combine->sfx == ch->sfx)) + { + // search for one + combine = NULL; + for (j = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS;j < i;j++) + { + if (channels[j].sfx == ch->sfx) + { + combine = channels + j; + break; + } + } + } + if (combine && combine != ch && combine->sfx == ch->sfx) + { + for (j = 0;j < SND_LISTENERS;j++) + { + combine->volume[j] += ch->volume[j]; + ch->volume[j] = 0; + } + } + } + for (k = 0;k < SND_LISTENERS;k++) + if (ch->volume[k]) + break; + if (k < SND_LISTENERS) + cls.soundstats.mixedsounds++; + } + R_TimeReport("audiospatialize"); + + sound_spatialized = true; + + // debugging output + if (snd_show.integer) + Con_Printf("----(%u)----\n", cls.soundstats.mixedsounds); + + S_PaintAndSubmit(); +} + +void S_ExtraUpdate (void) +{ + if (snd_noextraupdate.integer || !sound_spatialized) + return; + + S_PaintAndSubmit(); +} + +qboolean S_LocalSound (const char *sound) +{ + sfx_t *sfx; + int ch_ind; + + if (!snd_initialized.integer || nosound.integer) + return true; + + sfx = S_PrecacheSound (sound, true, false); + if (!sfx) + { + Con_Printf("S_LocalSound: can't precache %s\n", sound); + return false; + } + + // menu sounds must not be freed on level change + sfx->flags |= SFXFLAG_MENUSOUND; + + // fun fact: in Quake 1, this used -1 "replace any entity channel", + // which we no longer support anyway + // changed by Black in r4297 "Changed S_LocalSound to play multiple sounds at a time." + ch_ind = S_StartSound (cl.viewentity, 0, sfx, vec3_origin, 1, 0); + if (ch_ind < 0) + return false; + + channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; + return true; +} diff --git a/app/jni/snd_main.h b/app/jni/snd_main.h new file mode 100644 index 0000000..527ce49 --- /dev/null +++ b/app/jni/snd_main.h @@ -0,0 +1,213 @@ +/* +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. + +*/ + +#ifndef SND_MAIN_H +#define SND_MAIN_H + +#include "sound.h" + + +typedef struct snd_format_s +{ + unsigned int speed; + unsigned short width; + unsigned short channels; +} snd_format_t; + +typedef struct snd_buffer_s +{ + snd_format_t format; + unsigned int nbframes; // current size, in sample frames + unsigned int maxframes; // max size (buffer size), in sample frames + unsigned char samples[4]; // variable sized +} snd_buffer_t; + +typedef struct snd_ringbuffer_s +{ + snd_format_t format; + unsigned char* ring; + unsigned int maxframes; // max size (buffer size), in sample frames + unsigned int startframe; // index of the first frame in the buffer + // if startframe == endframe, the bufffer is empty + unsigned int endframe; // index of the first EMPTY frame in the "ring" buffer + // may be smaller than startframe if the "ring" buffer has wrapped +} snd_ringbuffer_t; + +// sfx_t flags +#define SFXFLAG_NONE 0 +#define SFXFLAG_FILEMISSING (1 << 0) // wasn't able to load the associated sound file +#define SFXFLAG_LEVELSOUND (1 << 1) // the sfx is part of the server or client precache list for this level +#define SFXFLAG_STREAMED (1 << 2) // informative only. You shouldn't need to know that +#define SFXFLAG_MENUSOUND (1 << 3) // not freed during level change (menu sounds, music, etc) + +typedef struct snd_fetcher_s snd_fetcher_t; +struct sfx_s +{ + char name[MAX_QPATH]; + sfx_t *next; + size_t memsize; // total memory used (including sfx_t and fetcher data) + + snd_format_t format; // format describing the audio data that fetcher->getsamplesfloat will return + unsigned int flags; // cf SFXFLAG_* defines + unsigned int loopstart; // in sample frames. equals total_length if not looped + unsigned int total_length; // in sample frames + const snd_fetcher_t *fetcher; + void *fetcher_data; // Per-sfx data for the sound fetching functions + + float volume_mult; // for replay gain (multiplier to apply) + float volume_peak; // for replay gain (highest peak); if set to 0, ReplayGain isn't supported +}; + +// maximum supported speakers constant +#define SND_LISTENERS 8 + +typedef struct channel_s +{ + // provided sound information + sfx_t *sfx; // pointer to sound sample being used + float basevolume; // 0-1 master volume + unsigned int flags; // cf CHANNELFLAG_* defines + int entnum; // makes sound follow entity origin (allows replacing interrupting existing sound on same id) + int entchannel; // which channel id on the entity + vec3_t origin; // origin of sound effect + vec_t distfade; // distance multiplier (attenuation/clipK) + void *fetcher_data; // Per-channel data for the sound fetching function + int prologic_invert;// whether a sound is played on the surround channels in prologic + float basespeed; // playback rate multiplier for pitch variation + + // these are often updated while mixer is running, glitching should be minimized (mismatched channel volumes from spatialization is okay) + // spatialized playback speed (speed * doppler ratio) + float mixspeed; + // spatialized volume per speaker (mastervol * distanceattenuation * channelvolume cvars) + float volume[SND_LISTENERS]; + + // updated ONLY by mixer + // position in sfx, starts at 0, loops or stops at sfx->total_length + double position; +} channel_t; + +// Sound fetching functions +// "start" is both an input and output parameter: it returns the actual start time of the sound buffer +typedef void (*snd_fetcher_getsamplesfloat_t) (channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat); +typedef void (*snd_fetcher_stopchannel_t) (channel_t *ch); +typedef void (*snd_fetcher_freesfx_t) (sfx_t *sfx); +struct snd_fetcher_s +{ + snd_fetcher_getsamplesfloat_t getsamplesfloat; + snd_fetcher_stopchannel_t stopchannel; + snd_fetcher_freesfx_t freesfx; +}; + +extern unsigned int total_channels; +extern channel_t channels[MAX_CHANNELS]; + +extern snd_ringbuffer_t *snd_renderbuffer; +extern qboolean snd_threaded; // enables use of snd_usethreadedmixing, provided that no sound hacks are in effect (like timedemo) +extern qboolean snd_usethreadedmixing; // if true, the main thread does not mix sound, soundtime does not advance, and neither does snd_renderbuffer->endframe, instead the audio thread will call S_MixToBuffer as needed + +extern cvar_t _snd_mixahead; +extern cvar_t snd_swapstereo; +extern cvar_t snd_streaming; +extern cvar_t snd_streaming_length; + +#define SND_CHANNELLAYOUT_AUTO 0 +#define SND_CHANNELLAYOUT_STANDARD 1 +#define SND_CHANNELLAYOUT_ALSA 2 +extern cvar_t snd_channellayout; + +extern int snd_blocked; // counter. When > 0, we stop submitting sound to the audio device + +extern mempool_t *snd_mempool; + +// If simsound is true, the sound card is not initialized and no sound is submitted to it. +// More generally, all arch-dependent operations are skipped or emulated. +// Used for isolating performance in the renderer. +extern qboolean simsound; + + +#define STREAM_BUFFERSIZE 16384 // in sampleframes + + +// ==================================================================== +// Architecture-independent functions +// ==================================================================== + +void S_MixToBuffer(void *stream, unsigned int frames); + +qboolean S_LoadSound (sfx_t *sfx, qboolean complain); + +snd_buffer_t *Snd_CreateSndBuffer (const unsigned char *samples, unsigned int sampleframes, const snd_format_t* in_format, unsigned int sb_speed); +qboolean Snd_AppendToSndBuffer (snd_buffer_t* sb, const unsigned char *samples, unsigned int sampleframes, const snd_format_t* format); + +// If "buffer" is NULL, the function allocates one buffer of "sampleframes" sample frames itself +// (if "sampleframes" is 0, the function chooses the size). +snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int sampleframes, void* buffer); + + +// ==================================================================== +// Architecture-dependent functions +// ==================================================================== + +// Create "snd_renderbuffer" with the proper sound format if the call is successful +// May return a suggested format if the requested format isn't available +qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested); + +// Stop the sound card, delete "snd_renderbuffer" and free its other resources +void SndSys_Shutdown (void); + +// Submit the contents of "snd_renderbuffer" to the sound card +void SndSys_Submit (void); + +// Returns the number of sample frames consumed since the sound started +unsigned int SndSys_GetSoundTime (void); + +// Get the exclusive lock on "snd_renderbuffer" +qboolean SndSys_LockRenderBuffer (void); + +// Release the exclusive lock on "snd_renderbuffer" +void SndSys_UnlockRenderBuffer (void); + +// if the sound system can generate events, send them +void SndSys_SendKeyEvents(void); + +// exported for capturevideo so ogg can see all channels +typedef struct portable_samplepair_s +{ + float sample[SND_LISTENERS]; +} portable_sampleframe_t; + +typedef struct listener_s +{ + int channel_unswapped; // for un-swapping + float yawangle; + float dotscale; + float dotbias; + float ambientvolume; +} +listener_t; +typedef struct speakerlayout_s +{ + const char *name; + unsigned int channels; + listener_t listeners[SND_LISTENERS]; +} +speakerlayout_t; + +#endif diff --git a/app/jni/snd_mem.c b/app/jni/snd_mem.c new file mode 100644 index 0000000..635bdd3 --- /dev/null +++ b/app/jni/snd_mem.c @@ -0,0 +1,388 @@ +/* +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 "snd_main.h" +#include "snd_ogg.h" +#include "snd_wav.h" +#include "snd_modplug.h" + + +/* +==================== +Snd_CreateRingBuffer + +If "buffer" is NULL, the function allocates one buffer of "sampleframes" sample frames itself +(if "sampleframes" is 0, the function chooses the size). +==================== +*/ +snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int sampleframes, void* buffer) +{ + snd_ringbuffer_t *ringbuffer; + + // If the caller provides a buffer, it must give us its size + if (sampleframes == 0 && buffer != NULL) + return NULL; + + ringbuffer = (snd_ringbuffer_t*)Mem_Alloc(snd_mempool, sizeof (*ringbuffer)); + memset(ringbuffer, 0, sizeof(*ringbuffer)); + memcpy(&ringbuffer->format, format, sizeof(ringbuffer->format)); + + // If we haven't been given a buffer + if (buffer == NULL) + { + unsigned int maxframes; + size_t memsize; + + if (sampleframes == 0) + maxframes = (format->speed + 1) / 2; // Make the sound buffer large enough for containing 0.5 sec of sound + else + maxframes = sampleframes; + + memsize = maxframes * format->width * format->channels; + ringbuffer->ring = (unsigned char *) Mem_Alloc(snd_mempool, memsize); + ringbuffer->maxframes = maxframes; + } + else + { + ringbuffer->ring = (unsigned char *) buffer; + ringbuffer->maxframes = sampleframes; + } + + return ringbuffer; +} + + +/* +==================== +Snd_CreateSndBuffer +==================== +*/ +snd_buffer_t *Snd_CreateSndBuffer (const unsigned char *samples, unsigned int sampleframes, const snd_format_t* in_format, unsigned int sb_speed) +{ + size_t newsampleframes, memsize; + snd_buffer_t* sb; + + newsampleframes = (size_t) ceil((double)sampleframes * (double)sb_speed / (double)in_format->speed); + + memsize = newsampleframes * in_format->channels * in_format->width; + memsize += sizeof (*sb) - sizeof (sb->samples); + + sb = (snd_buffer_t*)Mem_Alloc (snd_mempool, memsize); + sb->format.channels = in_format->channels; + sb->format.width = in_format->width; + sb->format.speed = sb_speed; + sb->maxframes = newsampleframes; + sb->nbframes = 0; + + if (!Snd_AppendToSndBuffer (sb, samples, sampleframes, in_format)) + { + Mem_Free (sb); + return NULL; + } + + return sb; +} + + +/* +==================== +Snd_AppendToSndBuffer +==================== +*/ +qboolean Snd_AppendToSndBuffer (snd_buffer_t* sb, const unsigned char *samples, unsigned int sampleframes, const snd_format_t* format) +{ + size_t srclength, outcount; + unsigned char *out_data; + + //Con_DPrintf("ResampleSfx: %d samples @ %dHz -> %d samples @ %dHz\n", + // sampleframes, format->speed, outcount, sb->format.speed); + + // If the formats are incompatible + if (sb->format.channels != format->channels || sb->format.width != format->width) + { + Con_Print("AppendToSndBuffer: incompatible sound formats!\n"); + return false; + } + + outcount = (size_t) ((double)sampleframes * (double)sb->format.speed / (double)format->speed); + + // If the sound buffer is too short + if (outcount > sb->maxframes - sb->nbframes) + { + Con_Print("AppendToSndBuffer: sound buffer too short!\n"); + return false; + } + + out_data = &sb->samples[sb->nbframes * sb->format.width * sb->format.channels]; + srclength = sampleframes * format->channels; + + // Trivial case (direct transfer) + if (format->speed == sb->format.speed) + { + if (format->width == 1) + { + size_t i; + + for (i = 0; i < srclength; i++) + ((signed char*)out_data)[i] = samples[i] - 128; + } + else // if (format->width == 2) + memcpy (out_data, samples, srclength * format->width); + } + + // General case (linear interpolation with a fixed-point fractional + // step, 18-bit integer part and 14-bit fractional part) + // Can handle up to 2^18 (262144) samples per second (> 96KHz stereo) +# define FRACTIONAL_BITS 14 +# define FRACTIONAL_MASK ((1 << FRACTIONAL_BITS) - 1) +# define INTEGER_BITS (sizeof(samplefrac)*8 - FRACTIONAL_BITS) + else + { + const unsigned int fracstep = (unsigned int)((double)format->speed / sb->format.speed * (1 << FRACTIONAL_BITS)); + size_t remain_in = srclength, total_out = 0; + unsigned int samplefrac; + const unsigned char *in_ptr = samples; + unsigned char *out_ptr = out_data; + + // Check that we can handle one second of that sound + if (format->speed * format->channels > (1 << INTEGER_BITS)) + { + Con_Printf ("ResampleSfx: sound quality too high for resampling (%uHz, %u channel(s))\n", + format->speed, format->channels); + return 0; + } + + // We work 1 sec at a time to make sure we don't accumulate any + // significant error when adding "fracstep" over several seconds, and + // also to be able to handle very long sounds. + while (total_out < outcount) + { + size_t tmpcount, interpolation_limit, i, j; + unsigned int srcsample; + + samplefrac = 0; + + // If more than 1 sec of sound remains to be converted + if (outcount - total_out > sb->format.speed) + { + tmpcount = sb->format.speed; + interpolation_limit = tmpcount; // all samples can be interpolated + } + else + { + tmpcount = outcount - total_out; + interpolation_limit = (int)ceil((double)(((remain_in / format->channels) - 1) << FRACTIONAL_BITS) / fracstep); + if (interpolation_limit > tmpcount) + interpolation_limit = tmpcount; + } + + // 16 bit samples + if (format->width == 2) + { + const short* in_ptr_short; + + // Interpolated part + for (i = 0; i < interpolation_limit; i++) + { + srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; + in_ptr_short = &((const short*)in_ptr)[srcsample]; + + for (j = 0; j < format->channels; j++) + { + int a, b; + + a = *in_ptr_short; + b = *(in_ptr_short + format->channels); + *((short*)out_ptr) = (((b - a) * (samplefrac & FRACTIONAL_MASK)) >> FRACTIONAL_BITS) + a; + + in_ptr_short++; + out_ptr += sizeof (short); + } + + samplefrac += fracstep; + } + + // Non-interpolated part + for (/* nothing */; i < tmpcount; i++) + { + srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; + in_ptr_short = &((const short*)in_ptr)[srcsample]; + + for (j = 0; j < format->channels; j++) + { + *((short*)out_ptr) = *in_ptr_short; + + in_ptr_short++; + out_ptr += sizeof (short); + } + + samplefrac += fracstep; + } + } + // 8 bit samples + else // if (format->width == 1) + { + const unsigned char* in_ptr_byte; + + // Convert up to 1 sec of sound + for (i = 0; i < interpolation_limit; i++) + { + srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; + in_ptr_byte = &((const unsigned char*)in_ptr)[srcsample]; + + for (j = 0; j < format->channels; j++) + { + int a, b; + + a = *in_ptr_byte - 128; + b = *(in_ptr_byte + format->channels) - 128; + *((signed char*)out_ptr) = (((b - a) * (samplefrac & FRACTIONAL_MASK)) >> FRACTIONAL_BITS) + a; + + in_ptr_byte++; + out_ptr += sizeof (signed char); + } + + samplefrac += fracstep; + } + + // Non-interpolated part + for (/* nothing */; i < tmpcount; i++) + { + srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; + in_ptr_byte = &((const unsigned char*)in_ptr)[srcsample]; + + for (j = 0; j < format->channels; j++) + { + *((signed char*)out_ptr) = *in_ptr_byte - 128; + + in_ptr_byte++; + out_ptr += sizeof (signed char); + } + + samplefrac += fracstep; + } + } + + // Update the counters and the buffer position + remain_in -= format->speed * format->channels; + in_ptr += format->speed * format->channels * format->width; + total_out += tmpcount; + } + } + + sb->nbframes += outcount; + return true; +} + + +//============================================================================= + +/* +============== +S_LoadSound +============== +*/ +qboolean S_LoadSound (sfx_t *sfx, qboolean complain) +{ + char namebuffer[MAX_QPATH + 16]; + size_t len; + + // See if already loaded + if (sfx->fetcher != NULL) + return true; + + // If we weren't able to load it previously, no need to retry + // Note: S_PrecacheSound clears this flag to cause a retry + if (sfx->flags & SFXFLAG_FILEMISSING) + return false; + + // No sound? + if (snd_renderbuffer == NULL) + return false; + + // Initialize volume peak to 0; if ReplayGain is supported, the loader will change this away + sfx->volume_peak = 0.0; + + if (developer_loading.integer) + Con_Printf("loading sound %s\n", sfx->name); + + SCR_PushLoadingScreen(true, sfx->name, 1); + + // LordHavoc: if the sound filename does not begin with sound/, try adding it + if (strncasecmp(sfx->name, "sound/", 6)) + { + dpsnprintf (namebuffer, sizeof(namebuffer), "sound/%s", sfx->name); + len = strlen(namebuffer); + if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".wav")) + { + if (S_LoadWavFile (namebuffer, sfx)) + goto loaded; + memcpy (namebuffer + len - 3, "ogg", 4); + } + if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".ogg")) + { + if (OGG_LoadVorbisFile (namebuffer, sfx)) + goto loaded; + } + else + { + if (ModPlug_LoadModPlugFile (namebuffer, sfx)) + goto loaded; + } + } + + // LordHavoc: then try without the added sound/ as wav and ogg + dpsnprintf (namebuffer, sizeof(namebuffer), "%s", sfx->name); + len = strlen(namebuffer); + // request foo.wav: tries foo.wav, then foo.ogg + // request foo.ogg: tries foo.ogg only + // request foo.mod: tries foo.mod only + if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".wav")) + { + if (S_LoadWavFile (namebuffer, sfx)) + goto loaded; + memcpy (namebuffer + len - 3, "ogg", 4); + } + if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".ogg")) + { + if (OGG_LoadVorbisFile (namebuffer, sfx)) + goto loaded; + } + else + { + if (ModPlug_LoadModPlugFile (namebuffer, sfx)) + goto loaded; + } + + // Can't load the sound! + sfx->flags |= SFXFLAG_FILEMISSING; + if (complain) + Con_DPrintf("failed to load sound \"%s\"\n", sfx->name); + + SCR_PopLoadingScreen(false); + return false; + +loaded: + SCR_PopLoadingScreen(false); + return true; +} diff --git a/app/jni/snd_mix.c b/app/jni/snd_mix.c new file mode 100644 index 0000000..2274f35 --- /dev/null +++ b/app/jni/snd_mix.c @@ -0,0 +1,532 @@ +/* +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 "snd_main.h" + +extern cvar_t snd_softclip; + +static portable_sampleframe_t paintbuffer[PAINTBUFFER_SIZE]; +static portable_sampleframe_t paintbuffer_unswapped[PAINTBUFFER_SIZE]; + +extern speakerlayout_t snd_speakerlayout; // for querying the listeners + +static void S_CaptureAVISound(const portable_sampleframe_t *paintbuffer, size_t length) +{ + size_t i; + unsigned int j; + + if (!cls.capturevideo.active) + return; + + // undo whatever swapping the channel layout (swapstereo, ALSA) did + for(j = 0; j < snd_speakerlayout.channels; ++j) + { + unsigned int j0 = snd_speakerlayout.listeners[j].channel_unswapped; + for(i = 0; i < length; ++i) + paintbuffer_unswapped[i].sample[j0] = paintbuffer[i].sample[j]; + } + + SCR_CaptureVideo_SoundFrame(paintbuffer_unswapped, length); +} + +extern cvar_t snd_softclip; + +static void S_SoftClipPaintBuffer(portable_sampleframe_t *painted_ptr, int nbframes, int width, int channels) +{ + int i; + + if((snd_softclip.integer == 1 && width <= 2) || snd_softclip.integer > 1) + { + portable_sampleframe_t *p = painted_ptr; + +#if 0 +/* Soft clipping, the sound of a dream, thanks to Jon Wattes + post to Musicdsp.org */ +#define SOFTCLIP(x) (x) = sin(bound(-M_PI/2, (x), M_PI/2)) * 0.25 +#endif + + // let's do a simple limiter instead, seems to sound better + static float maxvol = 0; + maxvol = max(1.0f, maxvol * (1.0f - nbframes / (0.4f * snd_renderbuffer->format.speed))); +#define SOFTCLIP(x) if(fabs(x)>maxvol) maxvol=fabs(x); (x) /= maxvol; + + if (channels == 8) // 7.1 surround + { + for (i = 0;i < nbframes;i++, p++) + { + SOFTCLIP(p->sample[0]); + SOFTCLIP(p->sample[1]); + SOFTCLIP(p->sample[2]); + SOFTCLIP(p->sample[3]); + SOFTCLIP(p->sample[4]); + SOFTCLIP(p->sample[5]); + SOFTCLIP(p->sample[6]); + SOFTCLIP(p->sample[7]); + } + } + else if (channels == 6) // 5.1 surround + { + for (i = 0; i < nbframes; i++, p++) + { + SOFTCLIP(p->sample[0]); + SOFTCLIP(p->sample[1]); + SOFTCLIP(p->sample[2]); + SOFTCLIP(p->sample[3]); + SOFTCLIP(p->sample[4]); + SOFTCLIP(p->sample[5]); + } + } + else if (channels == 4) // 4.0 surround + { + for (i = 0; i < nbframes; i++, p++) + { + SOFTCLIP(p->sample[0]); + SOFTCLIP(p->sample[1]); + SOFTCLIP(p->sample[2]); + SOFTCLIP(p->sample[3]); + } + } + else if (channels == 2) // 2.0 stereo + { + for (i = 0; i < nbframes; i++, p++) + { + SOFTCLIP(p->sample[0]); + SOFTCLIP(p->sample[1]); + } + } + else if (channels == 1) // 1.0 mono + { + for (i = 0; i < nbframes; i++, p++) + { + SOFTCLIP(p->sample[0]); + } + } +#undef SOFTCLIP + } +} + +static void S_ConvertPaintBuffer(portable_sampleframe_t *painted_ptr, void *rb_ptr, int nbframes, int width, int channels) +{ + int i, val; + + // FIXME: add 24bit and 32bit float formats + // FIXME: optimize with SSE intrinsics? + if (width == 2) // 16bit + { + short *snd_out = (short*)rb_ptr; + if (channels == 8) // 7.1 surround + { + for (i = 0;i < nbframes;i++, painted_ptr++) + { + val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[4] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[5] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[6] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[7] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + } + } + else if (channels == 6) // 5.1 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[4] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[5] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + } + } + else if (channels == 4) // 4.0 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + } + } + else if (channels == 2) // 2.0 stereo + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); + } + } + else if (channels == 1) // 1.0 mono + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (int)((painted_ptr->sample[0] + painted_ptr->sample[1]) * 16384.0f);*snd_out++ = bound(-32768, val, 32767); + } + } + + // noise is really really annoying + if (cls.timedemo) + memset(rb_ptr, 0, nbframes * channels * width); + } + else // 8bit + { + unsigned char *snd_out = (unsigned char*)rb_ptr; + if (channels == 8) // 7.1 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[4] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[5] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[6] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[7] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + } + } + else if (channels == 6) // 5.1 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[4] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[5] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + } + } + else if (channels == 4) // 4.0 surround + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + } + } + else if (channels == 2) // 2.0 stereo + { + for (i = 0; i < nbframes; i++, painted_ptr++) + { + val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); + } + } + else if (channels == 1) // 1.0 mono + { + for (i = 0;i < nbframes;i++, painted_ptr++) + { + val = (int)((painted_ptr->sample[0] + painted_ptr->sample[1]) * 64.0f) + 128; *snd_out++ = bound(0, val, 255); + } + } + + // noise is really really annoying + if (cls.timedemo) + memset(rb_ptr, 128, nbframes * channels); + } +} + + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +void S_MixToBuffer(void *stream, unsigned int bufferframes) +{ + int channelindex; + channel_t *ch; + int totalmixframes; + unsigned char *outbytes = (unsigned char *) stream; + sfx_t *sfx; + portable_sampleframe_t *paint; + int wantframes; + int i; + int count; + int fetched; + int fetch; + int istartframe; + int iendframe; + int ilengthframes; + int totallength; + int loopstart; + int indexfrac; + int indexfracstep; +#define S_FETCHBUFFERSIZE 4096 + float fetchsampleframes[S_FETCHBUFFERSIZE*2]; + const float *fetchsampleframe; + float vol[SND_LISTENERS]; + float lerp[2]; + float sample[3]; + double posd; + double speedd; + float maxvol; + qboolean looping; + qboolean silent; + + // mix as many times as needed to fill the requested buffer + while (bufferframes) + { + // limit to the size of the paint buffer + totalmixframes = min(bufferframes, PAINTBUFFER_SIZE); + + // clear the paint buffer + memset(paintbuffer, 0, totalmixframes * sizeof(paintbuffer[0])); + + // paint in the channels. + // channels with zero volumes still advance in time but don't paint. + ch = channels; // cppcheck complains here but it is wrong, channels is a channel_t[MAX_CHANNELS] and not an int + for (channelindex = 0;channelindex < (int)total_channels;channelindex++, ch++) + { + sfx = ch->sfx; + if (sfx == NULL) + continue; + if (!S_LoadSound (sfx, true)) + continue; + if (ch->flags & CHANNELFLAG_PAUSED) + continue; + if (!sfx->total_length) + continue; + + // copy the channel information to the stack for reference, otherwise the + // values might change during a mix if the spatializer is updating them + // (note: this still may get some old and some new values!) + posd = ch->position; + speedd = ch->mixspeed * sfx->format.speed / snd_renderbuffer->format.speed; + for (i = 0;i < SND_LISTENERS;i++) + vol[i] = ch->volume[i]; + + // check total volume level, because we can skip some code on silent sounds but other code must still run (position updates mainly) + maxvol = 0; + for (i = 0;i < SND_LISTENERS;i++) + if(vol[i] > maxvol) + maxvol = vol[i]; + switch(snd_renderbuffer->format.width) + { + case 1: // 8bpp + silent = maxvol < (1.0f / (256.0f)); + // so silent it has zero effect + break; + case 2: // 16bpp + silent = maxvol < (1.0f / (65536.0f)); + // so silent it has zero effect + break; + default: // floating point + silent = maxvol < 1.0e-13f; + // 130 dB is difference between hearing + // threshold and a jackhammer from + // working distance. + // therefore, anyone who turns up + // volume so much they notice this + // cutoff, likely already has their + // ear-drums blown out anyway. + break; + } + + // when doing prologic mixing, some channels invert one side + if (ch->prologic_invert == -1) + vol[1] *= -1.0f; + + // get some sfx info in a consistent form + totallength = sfx->total_length; + loopstart = (int)sfx->loopstart < totallength ? (int)sfx->loopstart : ((ch->flags & CHANNELFLAG_FORCELOOP) ? 0 : totallength); + looping = loopstart < totallength; + + // do the actual paint now (may skip work if silent) + paint = paintbuffer; + istartframe = 0; + for (wantframes = totalmixframes;wantframes > 0;posd += count * speedd, wantframes -= count) + { + // check if this is a delayed sound + if (posd < 0) + { + // for a delayed sound we have to eat into the delay first + count = (int)floor(-posd / speedd) + 1; + count = bound(1, count, wantframes); + // let the for loop iterator apply the skip + continue; + } + + // compute a fetch size that won't overflow our buffer + count = wantframes; + for (;;) + { + istartframe = (int)floor(posd); + iendframe = (int)floor(posd + (count-1) * speedd); + ilengthframes = count > 1 ? (iendframe - istartframe + 2) : 2; + if (ilengthframes <= S_FETCHBUFFERSIZE) + break; + // reduce count by 25% and try again + count -= count >> 2; + } + + // zero whole fetch buffer for safety + // (floating point noise from uninitialized memory = HORRIBLE) + // otherwise we would only need to clear the excess + if (!silent) + memset(fetchsampleframes, 0, ilengthframes*sfx->format.channels*sizeof(fetchsampleframes[0])); + + // if looping, do multiple fetches + fetched = 0; + for (;;) + { + fetch = min(ilengthframes - fetched, totallength - istartframe); + if (fetch > 0) + { + if (!silent) + sfx->fetcher->getsamplesfloat(ch, sfx, istartframe, fetch, fetchsampleframes + fetched*sfx->format.channels); + istartframe += fetch; + fetched += fetch; + } + if (istartframe == totallength && looping && fetched < ilengthframes) + { + // loop and fetch some more + posd += loopstart - totallength; + istartframe = loopstart; + } + else + { + break; + } + } + + // set up our fixedpoint resampling variables (float to int conversions are expensive so do not do one per sampleframe) + fetchsampleframe = fetchsampleframes; + indexfrac = (int)floor((posd - floor(posd)) * 65536.0); + indexfracstep = (int)floor(speedd * 65536.0); + if (!silent) + { + if (sfx->format.channels == 2) + { + // music is stereo +#if SND_LISTENERS != 8 +#error the following code only supports up to 8 channels, update it +#endif + if (snd_speakerlayout.channels > 2) + { + // surround mixing + for (i = 0;i < count;i++, paint++) + { + lerp[1] = indexfrac * (1.0f / 65536.0f); + lerp[0] = 1.0f - lerp[1]; + sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[2] * lerp[1]; + sample[1] = fetchsampleframe[1] * lerp[0] + fetchsampleframe[3] * lerp[1]; + sample[2] = (sample[0] + sample[1]) * 0.5f; + paint->sample[0] += sample[0] * vol[0]; + paint->sample[1] += sample[1] * vol[1]; + paint->sample[2] += sample[0] * vol[2]; + paint->sample[3] += sample[1] * vol[3]; + paint->sample[4] += sample[2] * vol[4]; + paint->sample[5] += sample[2] * vol[5]; + paint->sample[6] += sample[0] * vol[6]; + paint->sample[7] += sample[1] * vol[7]; + indexfrac += indexfracstep; + fetchsampleframe += 2 * (indexfrac >> 16); + indexfrac &= 0xFFFF; + } + } + else + { + // stereo mixing + for (i = 0;i < count;i++, paint++) + { + lerp[1] = indexfrac * (1.0f / 65536.0f); + lerp[0] = 1.0f - lerp[1]; + sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[2] * lerp[1]; + sample[1] = fetchsampleframe[1] * lerp[0] + fetchsampleframe[3] * lerp[1]; + paint->sample[0] += sample[0] * vol[0]; + paint->sample[1] += sample[1] * vol[1]; + indexfrac += indexfracstep; + fetchsampleframe += 2 * (indexfrac >> 16); + indexfrac &= 0xFFFF; + } + } + } + else if (sfx->format.channels == 1) + { + // most sounds are mono +#if SND_LISTENERS != 8 +#error the following code only supports up to 8 channels, update it +#endif + if (snd_speakerlayout.channels > 2) + { + // surround mixing + for (i = 0;i < count;i++, paint++) + { + lerp[1] = indexfrac * (1.0f / 65536.0f); + lerp[0] = 1.0f - lerp[1]; + sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[1] * lerp[1]; + paint->sample[0] += sample[0] * vol[0]; + paint->sample[1] += sample[0] * vol[1]; + paint->sample[2] += sample[0] * vol[2]; + paint->sample[3] += sample[0] * vol[3]; + paint->sample[4] += sample[0] * vol[4]; + paint->sample[5] += sample[0] * vol[5]; + paint->sample[6] += sample[0] * vol[6]; + paint->sample[7] += sample[0] * vol[7]; + indexfrac += indexfracstep; + fetchsampleframe += (indexfrac >> 16); + indexfrac &= 0xFFFF; + } + } + else + { + // stereo mixing + for (i = 0;i < count;i++, paint++) + { + lerp[1] = indexfrac * (1.0f / 65536.0f); + lerp[0] = 1.0f - lerp[1]; + sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[1] * lerp[1]; + paint->sample[0] += sample[0] * vol[0]; + paint->sample[1] += sample[0] * vol[1]; + indexfrac += indexfracstep; + fetchsampleframe += (indexfrac >> 16); + indexfrac &= 0xFFFF; + } + } + } + } + } + ch->position = posd; + if (!looping && istartframe == totallength) + S_StopChannel(ch - channels, false, false); + } + + S_SoftClipPaintBuffer(paintbuffer, totalmixframes, snd_renderbuffer->format.width, snd_renderbuffer->format.channels); + + if (!snd_usethreadedmixing) + S_CaptureAVISound(paintbuffer, totalmixframes); + + S_ConvertPaintBuffer(paintbuffer, outbytes, totalmixframes, snd_renderbuffer->format.width, snd_renderbuffer->format.channels); + + // advance the output pointer + outbytes += totalmixframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + bufferframes -= totalmixframes; + } +} diff --git a/app/jni/snd_modplug.c b/app/jni/snd_modplug.c new file mode 100644 index 0000000..6dc3fce --- /dev/null +++ b/app/jni/snd_modplug.c @@ -0,0 +1,432 @@ +/* + Copyright (C) 2003-2005 Mathieu Olivier + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#include "quakedef.h" +#include "snd_main.h" +#include "snd_modplug.h" + +#ifdef SND_MODPLUG_STATIC + +#include +qboolean ModPlug_OpenLibrary (void) +{ + return true; // statically linked +} +void ModPlug_CloseLibrary (void) +{ +} +#define modplug_dll 1 +#define qModPlug_Load ModPlug_Load +#define qModPlug_Unload ModPlug_Unload +#define qModPlug_Read ModPlug_Read +#define qModPlug_Seek ModPlug_Seek +#define qModPlug_GetSettings ModPlug_GetSettings +#define qModPlug_SetSettings ModPlug_SetSettings +#define qModPlug_SetMasterVolume ModPlug_SetMasterVolume + +#else +// BEGIN SECTION FROM modplug.h + + /* + * This source code is public domain. + * + * Authors: Kenton Varda (C interface wrapper) + */ + + enum _ModPlug_Flags + { + MODPLUG_ENABLE_OVERSAMPLING = 1 << 0, /* Enable oversampling (*highly* recommended) */ + MODPLUG_ENABLE_NOISE_REDUCTION = 1 << 1, /* Enable noise reduction */ + MODPLUG_ENABLE_REVERB = 1 << 2, /* Enable reverb */ + MODPLUG_ENABLE_MEGABASS = 1 << 3, /* Enable megabass */ + MODPLUG_ENABLE_SURROUND = 1 << 4 /* Enable surround sound. */ + }; + + enum _ModPlug_ResamplingMode + { + MODPLUG_RESAMPLE_NEAREST = 0, /* No interpolation (very fast, extremely bad sound quality) */ + MODPLUG_RESAMPLE_LINEAR = 1, /* Linear interpolation (fast, good quality) */ + MODPLUG_RESAMPLE_SPLINE = 2, /* Cubic spline interpolation (high quality) */ + MODPLUG_RESAMPLE_FIR = 3 /* 8-tap fir filter (extremely high quality) */ + }; + + typedef struct _ModPlug_Settings + { + int mFlags; /* One or more of the MODPLUG_ENABLE_* flags above, bitwise-OR'ed */ + + /* Note that ModPlug always decodes sound at 44100kHz, 32 bit, stereo and then + * down-mixes to the settings you choose. */ + int mChannels; /* Number of channels - 1 for mono or 2 for stereo */ + int mBits; /* Bits per sample - 8, 16, or 32 */ + int mFrequency; /* Sampling rate - 11025, 22050, or 44100 */ + int mResamplingMode; /* One of MODPLUG_RESAMPLE_*, above */ + + int mStereoSeparation; /* Stereo separation, 1 - 256 */ + int mMaxMixChannels; /* Maximum number of mixing channels (polyphony), 32 - 256 */ + + int mReverbDepth; /* Reverb level 0(quiet)-100(loud) */ + int mReverbDelay; /* Reverb delay in ms, usually 40-200ms */ + int mBassAmount; /* XBass level 0(quiet)-100(loud) */ + int mBassRange; /* XBass cutoff in Hz 10-100 */ + int mSurroundDepth; /* Surround level 0(quiet)-100(heavy) */ + int mSurroundDelay; /* Surround delay in ms, usually 5-40ms */ + int mLoopCount; /* Number of times to loop. Zero prevents looping. + -1 loops forever. */ + } ModPlug_Settings; + + struct _ModPlugFile; + typedef struct _ModPlugFile ModPlugFile; + +// END SECTION FROM modplug.h + +static ModPlugFile* (*qModPlug_Load) (const void* data, int size); +static void (*qModPlug_Unload) (ModPlugFile* file); +static int (*qModPlug_Read) (ModPlugFile* file, void* buffer, int size); +static void (*qModPlug_Seek) (ModPlugFile* file, int millisecond); +static void (*qModPlug_GetSettings) (ModPlug_Settings* settings); +static void (*qModPlug_SetSettings) (const ModPlug_Settings* settings); +typedef void (ModPlug_SetMasterVolume_t) (ModPlugFile* file,unsigned int cvol) ; +ModPlug_SetMasterVolume_t *qModPlug_SetMasterVolume; + + +static dllfunction_t modplugfuncs[] = +{ + {"ModPlug_Load", (void **) &qModPlug_Load}, + {"ModPlug_Unload", (void **) &qModPlug_Unload}, + {"ModPlug_Read", (void **) &qModPlug_Read}, + {"ModPlug_Seek", (void **) &qModPlug_Seek}, + {"ModPlug_GetSettings", (void **) &qModPlug_GetSettings}, + {"ModPlug_SetSettings", (void **) &qModPlug_SetSettings}, + {NULL, NULL} +}; + +// Handles for the modplug and modplugfile DLLs +static dllhandle_t modplug_dll = NULL; + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +ModPlug_OpenLibrary + +Try to load the modplugFile DLL +==================== +*/ +qboolean ModPlug_OpenLibrary (void) +{ + const char* dllnames_modplug [] = + { +#if defined(WIN32) + "libmodplug-1.dll", + "modplug.dll", +#elif defined(MACOSX) + "libmodplug.dylib", +#else + "libmodplug.so.1", + "libmodplug.so", +#endif + NULL + }; + + // Already loaded? + if (modplug_dll) + return true; + +// COMMANDLINEOPTION: Sound: -nomodplug disables modplug sound support + if (COM_CheckParm("-nomodplug")) + return false; + + // Load the DLLs + // We need to load both by hand because some OSes seem to not load + // the modplug DLL automatically when loading the modplugFile DLL + if(Sys_LoadLibrary (dllnames_modplug, &modplug_dll, modplugfuncs)) + { + qModPlug_SetMasterVolume = (ModPlug_SetMasterVolume_t *) Sys_GetProcAddress(modplug_dll, "ModPlug_SetMasterVolume"); + if(!qModPlug_SetMasterVolume) + Con_Print("Warning: modplug volume control not supported. Try getting a newer version of libmodplug.\n"); + return true; + } + else + return false; +} + + +/* +==================== +ModPlug_CloseLibrary + +Unload the modplugFile DLL +==================== +*/ +void ModPlug_CloseLibrary (void) +{ + Sys_UnloadLibrary (&modplug_dll); +} +#endif + + +/* +================================================================= + + modplug decoding + +================================================================= +*/ + +// Per-sfx data structure +typedef struct +{ + unsigned char *file; + size_t filesize; +} modplug_stream_persfx_t; + +// Per-channel data structure +typedef struct +{ + ModPlugFile *mf; + int bs; + int buffer_firstframe; + int buffer_numframes; + unsigned char buffer[STREAM_BUFFERSIZE*4]; +} modplug_stream_perchannel_t; + + +/* +==================== +ModPlug_GetSamplesFloat +==================== +*/ +static void ModPlug_GetSamplesFloat(channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) +{ + modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)ch->fetcher_data; + modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data; + int newlength, done, ret; + int f = sfx->format.width * sfx->format.channels; // bytes per frame + short *buf; + int i, len; + + // If there's no fetcher structure attached to the channel yet + if (per_ch == NULL) + { + per_ch = (modplug_stream_perchannel_t *)Mem_Alloc(snd_mempool, sizeof(*per_ch)); + + // Open it with the modplugFile API + per_ch->mf = qModPlug_Load(per_sfx->file, per_sfx->filesize); + if (!per_ch->mf) + { + // we can't call Con_Printf here, not thread safe +// Con_Printf("error while reading ModPlug stream \"%s\"\n", per_sfx->name); + Mem_Free(per_ch); + return; + } + +#ifndef SND_MODPLUG_STATIC + if(qModPlug_SetMasterVolume) +#endif + qModPlug_SetMasterVolume(per_ch->mf, 512); // max volume, DP scales down! + + per_ch->bs = 0; + + per_ch->buffer_firstframe = 0; + per_ch->buffer_numframes = 0; + ch->fetcher_data = per_ch; + } + + // if the request is too large for our buffer, loop... + while (numsampleframes * f > (int)sizeof(per_ch->buffer)) + { + done = sizeof(per_ch->buffer) / f; + ModPlug_GetSamplesFloat(ch, sfx, firstsampleframe, done, outsamplesfloat); + firstsampleframe += done; + numsampleframes -= done; + outsamplesfloat += done * sfx->format.channels; + } + + // seek if the request is before the current buffer (loop back) + // seek if the request starts beyond the current buffer by at least one frame (channel was zero volume for a while) + // do not seek if the request overlaps the buffer end at all (expected behavior) + if (per_ch->buffer_firstframe > firstsampleframe || per_ch->buffer_firstframe + per_ch->buffer_numframes < firstsampleframe) + { + // we expect to decode forward from here so this will be our new buffer start + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes = 0; + // we don't actually seek - we don't care much about timing on silent mod music streams and looping never happens + //qModPlug_Seek(per_ch->mf, firstsampleframe * 1000.0 / sfx->format.speed); + } + + // decompress the file as needed + if (firstsampleframe + numsampleframes > per_ch->buffer_firstframe + per_ch->buffer_numframes) + { + // first slide the buffer back, discarding any data preceding the range we care about + int offset = firstsampleframe - per_ch->buffer_firstframe; + int keeplength = per_ch->buffer_numframes - offset; + if (keeplength > 0) + memmove(per_ch->buffer, per_ch->buffer + offset * sfx->format.width * sfx->format.channels, keeplength * sfx->format.width * sfx->format.channels); + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes -= offset; + // decompress as much as we can fit in the buffer + newlength = sizeof(per_ch->buffer) - per_ch->buffer_numframes * f; + done = 0; + while (newlength > done && (ret = qModPlug_Read(per_ch->mf, (void *)((unsigned char *)per_ch->buffer + done), (int)(newlength - done))) > 0) + done += ret; + // clear the missing space if any + if (done < newlength) + { + memset(per_ch->buffer + done, 0, newlength - done); + // Argh. We didn't get as many samples as we wanted. Probably + // libmodplug forgot what mLoopCount==-1 means... basically, this means + // we can't loop like this. Try to let DP fix it later... + sfx->total_length = firstsampleframe + done / f; + sfx->loopstart = 0; + // can't Con_Printf from this thread + //if (newlength != done) + // Con_DPrintf("ModPlug_Fetch: wanted: %d, got: %d\n", newlength, done); + } + // we now have more data in the buffer + per_ch->buffer_numframes += done / f; + } + + // convert the sample format for the caller + buf = (short *)(per_ch->buffer + (firstsampleframe - per_ch->buffer_firstframe) * f); + len = numsampleframes * sfx->format.channels; + for (i = 0;i < len;i++) + outsamplesfloat[i] = buf[i] * (1.0f / 32768.0f); +} + + +/* +==================== +ModPlug_StopChannel +==================== +*/ +static void ModPlug_StopChannel(channel_t *ch) +{ + modplug_stream_perchannel_t *per_ch = (modplug_stream_perchannel_t *)ch->fetcher_data; + + if (per_ch != NULL) + { + // Free the modplug decoder + qModPlug_Unload(per_ch->mf); + + Mem_Free(per_ch); + } +} + + +/* +==================== +ModPlug_FreeSfx +==================== +*/ +static void ModPlug_FreeSfx (sfx_t *sfx) +{ + modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data; + + // Free the modplug file + Mem_Free(per_sfx->file); + + // Free the stream structure + Mem_Free(per_sfx); +} + + +static const snd_fetcher_t modplug_fetcher = { ModPlug_GetSamplesFloat, ModPlug_StopChannel, ModPlug_FreeSfx }; + + +/* +==================== +ModPlug_LoadmodplugFile + +Load an modplug file into memory +==================== +*/ +qboolean ModPlug_LoadModPlugFile (const char *filename, sfx_t *sfx) +{ + unsigned char *data; + fs_offset_t filesize; + ModPlugFile *mf; + modplug_stream_persfx_t* per_sfx; + ModPlug_Settings s; + + if (!modplug_dll) + return false; + + // Already loaded? + if (sfx->fetcher != NULL) + return true; + + // Load the file + data = FS_LoadFile (filename, snd_mempool, false, &filesize); + if (data == NULL) + return false; + + if (developer_loading.integer >= 2) + Con_Printf ("Loading ModPlug file \"%s\"\n", filename); + + qModPlug_GetSettings(&s); + s.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION | MODPLUG_ENABLE_REVERB; + s.mChannels = 2; + s.mBits = 16; + s.mFrequency = 44100; + s.mResamplingMode = MODPLUG_RESAMPLE_SPLINE; + s.mLoopCount = -1; + qModPlug_SetSettings(&s); + + // Open it with the modplugFile API + if (!(mf = qModPlug_Load (data, filesize))) + { + Con_Printf ("error while opening ModPlug file \"%s\"\n", filename); + Mem_Free(data); + return false; + } + +#ifndef SND_MODPLUG_STATIC + if(qModPlug_SetMasterVolume) +#endif + qModPlug_SetMasterVolume(mf, 512); // max volume, DP scales down! + + if (developer_loading.integer >= 2) + Con_Printf ("\"%s\" will be streamed\n", filename); + per_sfx = (modplug_stream_persfx_t *)Mem_Alloc (snd_mempool, sizeof (*per_sfx)); + per_sfx->file = data; + per_sfx->filesize = filesize; + sfx->memsize += sizeof(*per_sfx); + sfx->memsize += filesize; + sfx->format.speed = 44100; // modplug always works at that rate + sfx->format.width = 2; // We always work with 16 bits samples + sfx->format.channels = 2; // stereo rulez ;) (MAYBE default to mono because Amiga MODs sound better then?) + sfx->fetcher_data = per_sfx; + sfx->fetcher = &modplug_fetcher; + sfx->flags |= SFXFLAG_STREAMED; + sfx->total_length = 1<<30; // 2147384647; // they always loop (FIXME this breaks after 6 hours, we need support for a real "infinite" value!) + sfx->loopstart = sfx->total_length; // modplug does it + + return true; +} diff --git a/app/jni/snd_modplug.h b/app/jni/snd_modplug.h new file mode 100644 index 0000000..0811080 --- /dev/null +++ b/app/jni/snd_modplug.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2003 Mathieu Olivier + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef SND_ModPlug_H +#define SND_ModPlug_H + + +qboolean ModPlug_OpenLibrary (void); +void ModPlug_CloseLibrary (void); +qboolean ModPlug_LoadModPlugFile (const char *filename, sfx_t *sfx); + + +#endif diff --git a/app/jni/snd_ogg.c b/app/jni/snd_ogg.c new file mode 100644 index 0000000..683d421 --- /dev/null +++ b/app/jni/snd_ogg.c @@ -0,0 +1,718 @@ +/* + Copyright (C) 2003-2005 Mathieu Olivier + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#include "quakedef.h" +#include "snd_main.h" +#include "snd_ogg.h" +#include "snd_wav.h" + +#ifdef LINK_TO_LIBVORBIS +#define OV_EXCLUDE_STATIC_CALLBACKS +#include +#include + +#define qov_clear ov_clear +#define qov_info ov_info +#define qov_comment ov_comment +#define qov_open_callbacks ov_open_callbacks +#define qov_pcm_seek ov_pcm_seek +#define qov_pcm_total ov_pcm_total +#define qov_read ov_read +#define qvorbis_comment_query vorbis_comment_query + +qboolean OGG_OpenLibrary (void) {return true;} +void OGG_CloseLibrary (void) {} +#else + +/* +================================================================= + + Minimal set of definitions from the Ogg Vorbis lib + (C) COPYRIGHT 1994-2001 by the XIPHOPHORUS Company + http://www.xiph.org/ + + WARNING: for a matter of simplicity, several pointer types are + casted to "void*", and most enumerated values are not included + +================================================================= +*/ + +#ifdef _MSC_VER +typedef __int64 ogg_int64_t; +#else +typedef long long ogg_int64_t; +#endif + +typedef struct +{ + size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource); + int (*seek_func) (void *datasource, ogg_int64_t offset, int whence); + int (*close_func) (void *datasource); + long (*tell_func) (void *datasource); +} ov_callbacks; + +typedef struct +{ + unsigned char *data; + int storage; + int fill; + int returned; + int unsynced; + int headerbytes; + int bodybytes; +} ogg_sync_state; + +typedef struct +{ + int version; + int channels; + long rate; + long bitrate_upper; + long bitrate_nominal; + long bitrate_lower; + long bitrate_window; + void *codec_setup; +} vorbis_info; + +typedef struct +{ + unsigned char *body_data; + long body_storage; + long body_fill; + long body_returned; + int *lacing_vals; + ogg_int64_t *granule_vals; + long lacing_storage; + long lacing_fill; + long lacing_packet; + long lacing_returned; + unsigned char header[282]; + int header_fill; + int e_o_s; + int b_o_s; + long serialno; + long pageno; + ogg_int64_t packetno; + ogg_int64_t granulepos; +} ogg_stream_state; + +typedef struct +{ + 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 +{ + long endbyte; + int endbit; + unsigned char *buffer; + unsigned char *ptr; + long storage; +} oggpack_buffer; + +typedef struct +{ + float **pcm; + 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; + void *localstore; + long localtop; + long localalloc; + long totaluse; + void *reap; // VOIDED POINTER + long glue_bits; + long time_bits; + long floor_bits; + long res_bits; + void *internal; +} vorbis_block; + +typedef struct +{ + char **user_comments; + int *comment_lengths; + int comments; + char *vendor; +} vorbis_comment; + +typedef struct +{ + void *datasource; + int seekable; + ogg_int64_t offset; + ogg_int64_t end; + ogg_sync_state oy; + int links; + ogg_int64_t *offsets; + ogg_int64_t *dataoffsets; + long *serialnos; + ogg_int64_t *pcmlengths; + vorbis_info *vi; + vorbis_comment *vc; + ogg_int64_t pcm_offset; + int ready_state; + long current_serialno; + int current_link; + double bittrack; + double samptrack; + ogg_stream_state os; + vorbis_dsp_state vd; + vorbis_block vb; + ov_callbacks callbacks; +} OggVorbis_File; + + +/* +================================================================= + + DarkPlaces definitions + +================================================================= +*/ + +// Functions exported from the vorbisfile library +static int (*qov_clear) (OggVorbis_File *vf); +static vorbis_info* (*qov_info) (OggVorbis_File *vf,int link); +static vorbis_comment* (*qov_comment) (OggVorbis_File *vf,int link); +static char * (*qvorbis_comment_query) (vorbis_comment *vc, const char *tag, int count); +static int (*qov_open_callbacks) (void *datasource, OggVorbis_File *vf, + char *initial, long ibytes, + ov_callbacks callbacks); +static int (*qov_pcm_seek) (OggVorbis_File *vf,ogg_int64_t pos); +static ogg_int64_t (*qov_pcm_total) (OggVorbis_File *vf,int i); +static long (*qov_read) (OggVorbis_File *vf,char *buffer,int length, + int bigendianp,int word,int sgned,int *bitstream); + +static dllfunction_t vorbisfilefuncs[] = +{ + {"ov_clear", (void **) &qov_clear}, + {"ov_info", (void **) &qov_info}, + {"ov_comment", (void **) &qov_comment}, + {"ov_open_callbacks", (void **) &qov_open_callbacks}, + {"ov_pcm_seek", (void **) &qov_pcm_seek}, + {"ov_pcm_total", (void **) &qov_pcm_total}, + {"ov_read", (void **) &qov_read}, + {NULL, NULL} +}; + +static dllfunction_t vorbisfuncs[] = +{ + {"vorbis_comment_query", (void **) &qvorbis_comment_query}, + {NULL, NULL} +}; + +// Handles for the Vorbis and Vorbisfile DLLs +static dllhandle_t vo_dll = NULL; +static dllhandle_t vf_dll = NULL; + + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +OGG_OpenLibrary + +Try to load the VorbisFile DLL +==================== +*/ +qboolean OGG_OpenLibrary (void) +{ + 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_vf [] = + { +#if defined(WIN32) + "libvorbisfile-3.dll", + "libvorbisfile.dll", + "vorbisfile.dll", +#elif defined(MACOSX) + "libvorbisfile.dylib", +#else + "libvorbisfile.so.3", + "libvorbisfile.so", +#endif + NULL + }; + + // Already loaded? + if (vf_dll) + return true; + +// COMMANDLINEOPTION: Sound: -novorbis disables ogg vorbis sound support + if (COM_CheckParm("-novorbis")) + return false; + + // Load the DLLs + // We need to load both by hand because some OSes seem to not load + // the vorbis DLL automatically when loading the VorbisFile DLL + return Sys_LoadLibrary (dllnames_vo, &vo_dll, vorbisfuncs) && Sys_LoadLibrary (dllnames_vf, &vf_dll, vorbisfilefuncs); +} + + +/* +==================== +OGG_CloseLibrary + +Unload the VorbisFile DLL +==================== +*/ +void OGG_CloseLibrary (void) +{ + Sys_UnloadLibrary (&vf_dll); + Sys_UnloadLibrary (&vo_dll); +} + +#endif + +/* +================================================================= + + Ogg Vorbis decoding + +================================================================= +*/ + +typedef struct +{ + unsigned char *buffer; + ogg_int64_t ind, buffsize; +} ov_decode_t; + +static size_t ovcb_read (void *ptr, size_t size, size_t nb, void *datasource) +{ + ov_decode_t *ov_decode = (ov_decode_t*)datasource; + size_t remain, len; + + remain = ov_decode->buffsize - ov_decode->ind; + len = size * nb; + if (remain < len) + len = remain - remain % size; + + memcpy (ptr, ov_decode->buffer + ov_decode->ind, len); + ov_decode->ind += len; + + return len / size; +} + +static int ovcb_seek (void *datasource, ogg_int64_t offset, int whence) +{ + ov_decode_t *ov_decode = (ov_decode_t*)datasource; + + switch (whence) + { + case SEEK_SET: + break; + case SEEK_CUR: + offset += ov_decode->ind; + break; + case SEEK_END: + offset += ov_decode->buffsize; + break; + default: + return -1; + } + if (offset < 0 || offset > ov_decode->buffsize) + return -1; + + ov_decode->ind = offset; + return 0; +} + +static int ovcb_close (void *ov_decode) +{ + return 0; +} + +static long ovcb_tell (void *ov_decode) +{ + return ((ov_decode_t*)ov_decode)->ind; +} + +// Per-sfx data structure +typedef struct +{ + unsigned char *file; + size_t filesize; +} ogg_stream_persfx_t; + +// Per-channel data structure +typedef struct +{ + OggVorbis_File vf; + ov_decode_t ov_decode; + int bs; + int buffer_firstframe; + int buffer_numframes; + unsigned char buffer[STREAM_BUFFERSIZE*4]; +} ogg_stream_perchannel_t; + + +static const ov_callbacks callbacks = {ovcb_read, ovcb_seek, ovcb_close, ovcb_tell}; + +/* +==================== +OGG_GetSamplesFloat +==================== +*/ +static void OGG_GetSamplesFloat (channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) +{ + ogg_stream_perchannel_t *per_ch = (ogg_stream_perchannel_t *)ch->fetcher_data; + ogg_stream_persfx_t *per_sfx = (ogg_stream_persfx_t *)sfx->fetcher_data; + int f = sfx->format.width * sfx->format.channels; // bytes per frame in the buffer + short *buf; + int i, len; + int newlength, done, ret; + + // if this channel does not yet have a channel fetcher, make one + if (per_ch == NULL) + { + // allocate a struct to keep track of our file position and buffer + per_ch = (ogg_stream_perchannel_t *)Mem_Alloc(snd_mempool, sizeof(*per_ch)); + // begin decoding the file + per_ch->ov_decode.buffer = per_sfx->file; + per_ch->ov_decode.ind = 0; + per_ch->ov_decode.buffsize = per_sfx->filesize; + if (qov_open_callbacks(&per_ch->ov_decode, &per_ch->vf, NULL, 0, callbacks) < 0) + { + // this never happens - this function succeeded earlier on the same data + Mem_Free(per_ch); + return; + } + per_ch->bs = 0; + per_ch->buffer_firstframe = 0; + per_ch->buffer_numframes = 0; + // attach the struct to our channel + ch->fetcher_data = (void *)per_ch; + } + + // if the request is too large for our buffer, loop... + while (numsampleframes * f > (int)sizeof(per_ch->buffer)) + { + done = sizeof(per_ch->buffer) / f; + OGG_GetSamplesFloat(ch, sfx, firstsampleframe, done, outsamplesfloat); + firstsampleframe += done; + numsampleframes -= done; + outsamplesfloat += done * sfx->format.channels; + } + + // seek if the request is before the current buffer (loop back) + // seek if the request starts beyond the current buffer by at least one frame (channel was zero volume for a while) + // do not seek if the request overlaps the buffer end at all (expected behavior) + if (per_ch->buffer_firstframe > firstsampleframe || per_ch->buffer_firstframe + per_ch->buffer_numframes < firstsampleframe) + { + // we expect to decode forward from here so this will be our new buffer start + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes = 0; + ret = qov_pcm_seek(&per_ch->vf, (ogg_int64_t)firstsampleframe); + if (ret != 0) + { + // LordHavoc: we can't Con_Printf here, not thread safe... + //Con_Printf("OGG_FetchSound: qov_pcm_seek(..., %d) returned %d\n", firstsampleframe, ret); + return; + } + } + + // decompress the file as needed + if (firstsampleframe + numsampleframes > per_ch->buffer_firstframe + per_ch->buffer_numframes) + { + // first slide the buffer back, discarding any data preceding the range we care about + int offset = firstsampleframe - per_ch->buffer_firstframe; + int keeplength = per_ch->buffer_numframes - offset; + if (keeplength > 0) + memmove(per_ch->buffer, per_ch->buffer + offset * sfx->format.width * sfx->format.channels, keeplength * sfx->format.width * sfx->format.channels); + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes -= offset; + // decompress as much as we can fit in the buffer + newlength = sizeof(per_ch->buffer) - per_ch->buffer_numframes * f; + done = 0; + while (newlength > done && (ret = qov_read(&per_ch->vf, (char *)per_ch->buffer + per_ch->buffer_numframes * f + done, (int)(newlength - done), mem_bigendian, 2, 1, &per_ch->bs)) > 0) + done += ret; + // clear the missing space if any + if (done < newlength) + memset(per_ch->buffer + done, 0, newlength - done); + // we now have more data in the buffer + per_ch->buffer_numframes += done / f; + } + + // convert the sample format for the caller + buf = (short *)((char *)per_ch->buffer + (firstsampleframe - per_ch->buffer_firstframe) * f); + len = numsampleframes * sfx->format.channels; + for (i = 0;i < len;i++) + outsamplesfloat[i] = buf[i] * (1.0f / 32768.0f); +} + + +/* +==================== +OGG_StopChannel +==================== +*/ +static void OGG_StopChannel(channel_t *ch) +{ + ogg_stream_perchannel_t *per_ch = (ogg_stream_perchannel_t *)ch->fetcher_data; + if (per_ch != NULL) + { + // release the vorbis decompressor + qov_clear(&per_ch->vf); + Mem_Free(per_ch); + } +} + + +/* +==================== +OGG_FreeSfx +==================== +*/ +static void OGG_FreeSfx(sfx_t *sfx) +{ + ogg_stream_persfx_t *per_sfx = (ogg_stream_persfx_t *)sfx->fetcher_data; + // free the complete file we were keeping around + Mem_Free(per_sfx->file); + // free the file information structure + Mem_Free(per_sfx); +} + + +static const snd_fetcher_t ogg_fetcher = {OGG_GetSamplesFloat, OGG_StopChannel, OGG_FreeSfx}; + +static void OGG_DecodeTags(vorbis_comment *vc, unsigned int *start, unsigned int *length, unsigned int numsamples, double *peak, double *gaindb) +{ + const char *startcomment = NULL, *lengthcomment = NULL, *endcomment = NULL, *thiscomment = NULL; + + *start = numsamples; + *length = numsamples; + *peak = 0.0; + *gaindb = 0.0; + + if(!vc) + return; + + thiscomment = qvorbis_comment_query(vc, "REPLAYGAIN_TRACK_PEAK", 0); + if(thiscomment) + *peak = atof(thiscomment); + thiscomment = qvorbis_comment_query(vc, "REPLAYGAIN_TRACK_GAIN", 0); + if(thiscomment) + *gaindb = atof(thiscomment); + + startcomment = qvorbis_comment_query(vc, "LOOP_START", 0); // DarkPlaces, and some Japanese app + if(startcomment) + { + endcomment = qvorbis_comment_query(vc, "LOOP_END", 0); + if(!endcomment) + lengthcomment = qvorbis_comment_query(vc, "LOOP_LENGTH", 0); + } + else + { + startcomment = qvorbis_comment_query(vc, "LOOPSTART", 0); // RPG Maker VX + if(startcomment) + { + lengthcomment = qvorbis_comment_query(vc, "LOOPLENGTH", 0); + if(!lengthcomment) + endcomment = qvorbis_comment_query(vc, "LOOPEND", 0); + } + else + { + startcomment = qvorbis_comment_query(vc, "LOOPPOINT", 0); // Sonic Robo Blast 2 + } + } + + if(startcomment) + { + *start = (unsigned int) bound(0, atof(startcomment), numsamples); + if(endcomment) + *length = (unsigned int) bound(0, atof(endcomment), numsamples); + else if(lengthcomment) + *length = (unsigned int) bound(0, *start + atof(lengthcomment), numsamples); + } +} + +/* +==================== +OGG_LoadVorbisFile + +Load an Ogg Vorbis file into memory +==================== +*/ +qboolean OGG_LoadVorbisFile(const char *filename, sfx_t *sfx) +{ + unsigned char *data; + fs_offset_t filesize; + ov_decode_t ov_decode; + OggVorbis_File vf; + vorbis_info *vi; + vorbis_comment *vc; + double peak, gaindb; + +#ifndef LINK_TO_LIBVORBIS + if (!vf_dll) + return false; +#endif + + // Return if already loaded + if (sfx->fetcher != NULL) + return true; + + // Load the file completely + data = FS_LoadFile(filename, snd_mempool, false, &filesize); + if (data == NULL) + return false; + + if (developer_loading.integer >= 2) + Con_Printf("Loading Ogg Vorbis file \"%s\"\n", filename); + + // Open it with the VorbisFile API + ov_decode.buffer = data; + ov_decode.ind = 0; + ov_decode.buffsize = filesize; + if (qov_open_callbacks(&ov_decode, &vf, NULL, 0, callbacks) < 0) + { + Con_Printf("error while opening Ogg Vorbis file \"%s\"\n", filename); + Mem_Free(data); + return false; + } + + // Get the stream information + vi = qov_info(&vf, -1); + if (vi->channels < 1 || vi->channels > 2) + { + Con_Printf("%s has an unsupported number of channels (%i)\n", + sfx->name, vi->channels); + qov_clear (&vf); + Mem_Free(data); + return false; + } + + sfx->format.speed = vi->rate; + sfx->format.channels = vi->channels; + sfx->format.width = 2; // We always work with 16 bits samples + + sfx->total_length = qov_pcm_total(&vf, -1); + + if (snd_streaming.integer && (snd_streaming.integer >= 2 || sfx->total_length > max(sizeof(ogg_stream_perchannel_t), snd_streaming_length.value * sfx->format.speed))) + { + // large sounds use the OGG fetcher to decode the file on demand (but the entire file is held in memory) + ogg_stream_persfx_t* per_sfx; + if (developer_loading.integer >= 2) + Con_Printf("Ogg sound file \"%s\" will be streamed\n", filename); + per_sfx = (ogg_stream_persfx_t *)Mem_Alloc(snd_mempool, sizeof(*per_sfx)); + sfx->memsize += sizeof (*per_sfx); + per_sfx->file = data; + per_sfx->filesize = filesize; + sfx->memsize += filesize; + sfx->fetcher_data = per_sfx; + sfx->fetcher = &ogg_fetcher; + sfx->flags |= SFXFLAG_STREAMED; + vc = qov_comment(&vf, -1); + OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, sfx->total_length, &peak, &gaindb); + qov_clear(&vf); + } + else + { + // small sounds are entirely loaded and use the PCM fetcher + char *buff; + ogg_int64_t len; + ogg_int64_t done; + int bs; + long ret; + if (developer_loading.integer >= 2) + Con_Printf ("Ogg sound file \"%s\" will be cached\n", filename); + len = sfx->total_length * sfx->format.channels * sfx->format.width; + sfx->flags &= ~SFXFLAG_STREAMED; + sfx->memsize += len; + sfx->fetcher = &wav_fetcher; + sfx->fetcher_data = Mem_Alloc(snd_mempool, (size_t)len); + buff = (char *)sfx->fetcher_data; + done = 0; + bs = 0; + while ((ret = qov_read(&vf, &buff[done], (int)(len - done), mem_bigendian, 2, 1, &bs)) > 0) + done += ret; + vc = qov_comment(&vf, -1); + OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, sfx->total_length, &peak, &gaindb); + qov_clear(&vf); + Mem_Free(data); + } + + if(peak) + { + sfx->volume_mult = min(1.0f / peak, exp(gaindb * 0.05f * log(10.0f))); + sfx->volume_peak = peak; + if (developer_loading.integer >= 2) + Con_Printf ("Ogg sound file \"%s\" uses ReplayGain (gain %f, peak %f)\n", filename, sfx->volume_mult, sfx->volume_peak); + } + else if(gaindb != 0) + { + sfx->volume_mult = min(1.0f / peak, exp(gaindb * 0.05f * log(10.0f))); + sfx->volume_peak = 1.0; // if peak is not defined, we won't trust it + if (developer_loading.integer >= 2) + Con_Printf ("Ogg sound file \"%s\" uses ReplayGain (gain %f, peak not defined and assumed to be %f)\n", filename, sfx->volume_mult, sfx->volume_peak); + } + + return true; +} diff --git a/app/jni/snd_ogg.h b/app/jni/snd_ogg.h new file mode 100644 index 0000000..f8c5fe7 --- /dev/null +++ b/app/jni/snd_ogg.h @@ -0,0 +1,33 @@ +/* + Copyright (C) 2003 Mathieu Olivier + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#ifndef SND_OGG_H +#define SND_OGG_H + + +qboolean OGG_OpenLibrary (void); +void OGG_CloseLibrary (void); +qboolean OGG_LoadVorbisFile (const char *filename, sfx_t *sfx); + + +#endif diff --git a/app/jni/snd_wav.c b/app/jni/snd_wav.c new file mode 100644 index 0000000..6f86191 --- /dev/null +++ b/app/jni/snd_wav.c @@ -0,0 +1,348 @@ +/* + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#include "quakedef.h" +#include "snd_main.h" +#include "snd_wav.h" + + +typedef struct wavinfo_s +{ + int rate; + int width; + int channels; + int loopstart; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + + +static unsigned char *data_p; +static unsigned char *iff_end; +static unsigned char *last_chunk; +static unsigned char *iff_data; +static int iff_chunk_len; + + +static short GetLittleShort(void) +{ + short val; + + val = BuffLittleShort (data_p); + data_p += 2; + + return val; +} + +static int GetLittleLong(void) +{ + int val = 0; + + val = BuffLittleLong (data_p); + data_p += 4; + + return val; +} + +static void FindNextChunk(const char *name) +{ + while (1) + { + data_p=last_chunk; + + if (data_p >= iff_end) + { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0) + { + data_p = NULL; + return; + } + if (data_p + iff_chunk_len > iff_end) + { + // truncated chunk! + data_p = NULL; + return; + } + data_p -= 8; + last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); + if (!strncmp((const char *)data_p, name, 4)) + return; + } +} + +static void FindChunk(const char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + + +/* +static void DumpChunks(void) +{ + char str[5]; + + str[4] = 0; + data_p=iff_data; + do + { + memcpy (str, data_p, 4); + data_p += 4; + iff_chunk_len = GetLittleLong(); + Con_Printf("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); + data_p += (iff_chunk_len + 1) & ~1; + } while (data_p < iff_end); +} +*/ + + +/* +============ +GetWavinfo +============ +*/ +static wavinfo_t GetWavinfo (char *name, unsigned char *wav, int wavlength) +{ + wavinfo_t info; + int i; + int format; + int samples; + + memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + + // find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp((const char *)data_p+8, "WAVE", 4))) + { + Con_Print("Missing RIFF/WAVE chunks\n"); + return info; + } + + // get "fmt " chunk + iff_data = data_p + 12; + //DumpChunks (); + + FindChunk("fmt "); + if (!data_p) + { + Con_Print("Missing fmt chunk\n"); + return info; + } + data_p += 8; + format = GetLittleShort(); + if (format != 1) + { + Con_Print("Microsoft PCM format only\n"); + return info; + } + + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4+2; + info.width = GetLittleShort() / 8; + + // get cue chunk + FindChunk("cue "); + if (data_p) + { + data_p += 32; + info.loopstart = GetLittleLong(); + + // if the next chunk is a LIST chunk, look for a cue length marker + FindNextChunk ("LIST"); + if (data_p) + { + if (!strncmp ((const char *)data_p + 28, "mark", 4)) + { // this is not a proper parse, but it works with cooledit... + data_p += 24; + i = GetLittleLong (); // samples in loop + info.samples = info.loopstart + i; + } + } + } + else + info.loopstart = -1; + + // find data chunk + FindChunk("data"); + if (!data_p) + { + Con_Print("Missing data chunk\n"); + return info; + } + + data_p += 4; + samples = GetLittleLong () / info.width / info.channels; + + if (info.samples) + { + if (samples < info.samples) + { + Con_Printf ("Sound %s has a bad loop length\n", name); + info.samples = samples; + } + } + else + info.samples = samples; + + info.dataofs = data_p - wav; + + return info; +} + + +/* +==================== +WAV_GetSamplesFloat +==================== +*/ +static void WAV_GetSamplesFloat(channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) +{ + int i, len = numsampleframes * sfx->format.channels; + if (sfx->format.width == 2) + { + const short *bufs = (const short *)sfx->fetcher_data + firstsampleframe * sfx->format.channels; + for (i = 0;i < len;i++) + outsamplesfloat[i] = bufs[i] * (1.0f / 32768.0f); + } + else + { + const signed char *bufb = (const signed char *)sfx->fetcher_data + firstsampleframe * sfx->format.channels; + for (i = 0;i < len;i++) + outsamplesfloat[i] = bufb[i] * (1.0f / 128.0f); + } +} + +/* +==================== +WAV_FreeSfx +==================== +*/ +static void WAV_FreeSfx(sfx_t *sfx) +{ + // free the loaded sound data + Mem_Free(sfx->fetcher_data); +} + +const snd_fetcher_t wav_fetcher = { WAV_GetSamplesFloat, NULL, WAV_FreeSfx }; + + +/* +============== +S_LoadWavFile +============== +*/ +qboolean S_LoadWavFile (const char *filename, sfx_t *sfx) +{ + fs_offset_t filesize; + unsigned char *data; + wavinfo_t info; + int i, len; + const unsigned char *inb; + unsigned char *outb; + + // Already loaded? + if (sfx->fetcher != NULL) + return true; + + // Load the file + data = FS_LoadFile(filename, snd_mempool, false, &filesize); + if (!data) + return false; + + // Don't try to load it if it's not a WAV file + if (memcmp (data, "RIFF", 4) || memcmp (data + 8, "WAVE", 4)) + { + Mem_Free(data); + return false; + } + + if (developer_loading.integer >= 2) + Con_Printf ("Loading WAV file \"%s\"\n", filename); + + info = GetWavinfo (sfx->name, data, (int)filesize); + if (info.channels < 1 || info.channels > 2) // Stereo sounds are allowed (intended for music) + { + Con_Printf("%s has an unsupported number of channels (%i)\n",sfx->name, info.channels); + Mem_Free(data); + return false; + } + //if (info.channels == 2) + // Log_Printf("stereosounds.log", "%s\n", sfx->name); + + sfx->format.speed = info.rate; + sfx->format.width = info.width; + sfx->format.channels = info.channels; + sfx->fetcher = &wav_fetcher; + sfx->fetcher_data = Mem_Alloc(snd_mempool, info.samples * sfx->format.width * sfx->format.channels); + sfx->total_length = info.samples; + sfx->memsize += filesize; + len = info.samples * sfx->format.channels * sfx->format.width; + inb = data + info.dataofs; + outb = (unsigned char *)sfx->fetcher_data; + if (info.width == 2) + { + if (mem_bigendian) + { + // we have to byteswap the data at load (better than doing it while mixing) + for (i = 0;i < len;i += 2) + { + outb[i] = inb[i+1]; + outb[i+1] = inb[i]; + } + } + else + { + // we can just copy it straight + memcpy(outb, inb, len); + } + } + else + { + // convert unsigned byte sound data to signed bytes for quicker mixing + for (i = 0;i < len;i++) + outb[i] = inb[i] - 0x80; + } + + if (info.loopstart < 0) + sfx->loopstart = sfx->total_length; + else + sfx->loopstart = info.loopstart; + sfx->loopstart = min(sfx->loopstart, sfx->total_length); + sfx->flags &= ~SFXFLAG_STREAMED; + + return true; +} diff --git a/app/jni/snd_wav.h b/app/jni/snd_wav.h new file mode 100644 index 0000000..89c7ee5 --- /dev/null +++ b/app/jni/snd_wav.h @@ -0,0 +1,34 @@ +/* + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + + +#ifndef SND_WAV_H +#define SND_WAV_H + + +extern const snd_fetcher_t wav_fetcher; + +qboolean S_LoadWavFile (const char *filename, sfx_t *sfx); + + +#endif diff --git a/app/jni/snprintf.c b/app/jni/snprintf.c new file mode 100644 index 0000000..810cccd --- /dev/null +++ b/app/jni/snprintf.c @@ -0,0 +1,1025 @@ +/* + * snprintf.c - a portable implementation of snprintf + * + * AUTHOR + * Mark Martinec , April 1999. + * + * Copyright 1999, Mark Martinec. All rights reserved. + * + * TERMS AND CONDITIONS + * This program is free software; you can redistribute it and/or modify + * it under the terms of the "Frontier Artistic License" which comes + * with this Kit. + * + * 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 Frontier Artistic License for more details. + * + * You should have received a copy of the Frontier Artistic License + * with this Kit in the file named LICENSE.txt . + * If not, I'll be glad to provide one. + * + * FEATURES + * - careful adherence to specs regarding flags, field width and precision; + * - good performance for large string handling (large format, large + * argument or large paddings). Performance is similar to system's sprintf + * and in several cases significantly better (make sure you compile with + * optimizations turned on, tell the compiler the code is strict ANSI + * if necessary to give it more freedom for optimizations); + * - return value semantics per ISO/IEC 9899:1999 ("ISO C99"); + * - written in standard ISO/ANSI C - requires an ANSI C compiler. + * + * SUPPORTED CONVERSION SPECIFIERS AND DATA TYPES + * + * This snprintf only supports the following conversion specifiers: + * s, c, d, u, o, x, X, p (and synonyms: i, D, U, O - see below) + * with flags: '-', '+', ' ', '0' and '#'. + * An asterisk is supported for field width as well as precision. + * + * Length modifiers 'h' (short int), 'l' (long int), + * and 'll' (long long int) are supported. + * NOTE: + * If macro SNPRINTF_LONGLONG_SUPPORT is not defined (default) the + * length modifier 'll' is recognized but treated the same as 'l', + * which may cause argument value truncation! Defining + * SNPRINTF_LONGLONG_SUPPORT requires that your system's sprintf also + * handles length modifier 'll'. long long int is a language extension + * which may not be portable. + * + * Conversion of numeric data (conversion specifiers d, u, o, x, X, p) + * with length modifiers (none or h, l, ll) is left to the system routine + * sprintf, but all handling of flags, field width and precision as well as + * c and s conversions is done very carefully by this portable routine. + * If a string precision (truncation) is specified (e.g. %.8s) it is + * guaranteed the string beyond the specified precision will not be referenced. + * + * Length modifiers h, l and ll are ignored for c and s conversions (data + * types wint_t and wchar_t are not supported). + * + * The following common synonyms for conversion characters are supported: + * - i is a synonym for d + * - D is a synonym for ld, explicit length modifiers are ignored + * - U is a synonym for lu, explicit length modifiers are ignored + * - O is a synonym for lo, explicit length modifiers are ignored + * The D, O and U conversion characters are nonstandard, they are supported + * for backward compatibility only, and should not be used for new code. + * + * The following is specifically NOT supported: + * - flag ' (thousands' grouping character) is recognized but ignored + * - numeric conversion specifiers: f, e, E, g, G and synonym F, + * as well as the new a and A conversion specifiers + * - length modifier 'L' (long double) and 'q' (quad - use 'll' instead) + * - wide character/string conversions: lc, ls, and nonstandard + * synonyms C and S + * - writeback of converted string length: conversion character n + * - the n$ specification for direct reference to n-th argument + * - locales + * + * It is permitted for str_m to be zero, and it is permitted to specify NULL + * pointer for resulting string argument if str_m is zero (as per ISO C99). + * + * The return value is the number of characters which would be generated + * for the given input, excluding the trailing null. If this value + * is greater or equal to str_m, not all characters from the result + * have been stored in str, output bytes beyond the (str_m-1) -th character + * are discarded. If str_m is greater than zero it is guaranteed + * the resulting string will be null-terminated. + * + * NOTE that this matches the ISO C99, OpenBSD, and GNU C library 2.1, + * but is different from some older and vendor implementations, + * and is also different from XPG, XSH5, SUSv2 specifications. + * For historical discussion on changes in the semantics and standards + * of snprintf see printf(3) man page in the Linux programmers manual. + * + * Routines asprintf and vasprintf return a pointer (in the ptr argument) + * to a buffer sufficiently large to hold the resulting string. This pointer + * should be passed to free(3) to release the allocated storage when it is + * no longer needed. If sufficient space cannot be allocated, these functions + * will return -1 and set ptr to be a NULL pointer. These two routines are a + * GNU C library extensions (glibc). + * + * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf, + * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1 + * characters into the allocated output string, the last character in the + * allocated buffer then gets the terminating null. If the formatted string + * length (the return value) is greater than or equal to the str_m argument, + * the resulting string was truncated and some of the formatted characters + * were discarded. These routines present a handy way to limit the amount + * of allocated memory to some sane value. + * + * AVAILABILITY + * http://www.ijs.si/software/snprintf/ + * + * REVISION HISTORY + * 1999-04 V0.9 Mark Martinec + * - initial version, some modifications after comparing printf + * man pages for Digital Unix 4.0, Solaris 2.6 and HPUX 10, + * and checking how Perl handles sprintf (differently!); + * 1999-04-09 V1.0 Mark Martinec + * - added main test program, fixed remaining inconsistencies, + * added optional (long long int) support; + * 1999-04-12 V1.1 Mark Martinec + * - support the 'p' conversion (pointer to void); + * - if a string precision is specified + * make sure the string beyond the specified precision + * will not be referenced (e.g. by strlen); + * 1999-04-13 V1.2 Mark Martinec + * - support synonyms %D=%ld, %U=%lu, %O=%lo; + * - speed up the case of long format string with few conversions; + * 1999-06-30 V1.3 Mark Martinec + * - fixed runaway loop (eventually crashing when str_l wraps + * beyond 2^31) while copying format string without + * conversion specifiers to a buffer that is too short + * (thanks to Edwin Young for + * spotting the problem); + * - added macros PORTABLE_SNPRINTF_VERSION_(MAJOR|MINOR) + * to snprintf.h + * 2000-02-14 V2.0 (never released) Mark Martinec + * - relaxed license terms: The Artistic License now applies. + * You may still apply the GNU GENERAL PUBLIC LICENSE + * as was distributed with previous versions, if you prefer; + * - changed REVISION HISTORY dates to use ISO 8601 date format; + * - added vsnprintf (patch also independently proposed by + * Caolan McNamara 2000-05-04, and Keith M Willenson 2000-06-01) + * 2000-06-27 V2.1 Mark Martinec + * - removed POSIX check for str_m<1; value 0 for str_m is + * allowed by ISO C99 (and GNU C library 2.1) - (pointed out + * on 2000-05-04 by Caolan McNamara, caolan@ csn dot ul dot ie). + * Besides relaxed license this change in standards adherence + * is the main reason to bump up the major version number; + * - added nonstandard routines asnprintf, vasnprintf, asprintf, + * vasprintf that dynamically allocate storage for the + * resulting string; these routines are not compiled by default, + * see comments where NEED_V?ASN?PRINTF macros are defined; + * - autoconf contributed by Caolan McNamara + * 2000-10-06 V2.2 Mark Martinec + * - BUG FIX: the %c conversion used a temporary variable + * that was no longer in scope when referenced, + * possibly causing incorrect resulting character; + * - BUG FIX: make precision and minimal field width unsigned + * to handle huge values (2^31 <= n < 2^32) correctly; + * also be more careful in the use of signed/unsigned/size_t + * internal variables - probably more careful than many + * vendor implementations, but there may still be a case + * where huge values of str_m, precision or minimal field + * could cause incorrect behaviour; + * - use separate variables for signed/unsigned arguments, + * and for short/int, long, and long long argument lengths + * to avoid possible incompatibilities on certain + * computer architectures. Also use separate variable + * arg_sign to hold sign of a numeric argument, + * to make code more transparent; + * - some fiddling with zero padding and "0x" to make it + * Linux compatible; + * - systematically use macros fast_memcpy and fast_memset + * instead of case-by-case hand optimization; determine some + * breakeven string lengths for different architectures; + * - terminology change: 'format' -> 'conversion specifier', + * 'C9x' -> 'ISO/IEC 9899:1999 ("ISO C99")', + * 'alternative form' -> 'alternate form', + * 'data type modifier' -> 'length modifier'; + * - several comments rephrased and new ones added; + * - make compiler not complain about 'credits' defined but + * not used; + */ + + +/* Define HAVE_SNPRINTF if your system already has snprintf and vsnprintf. + * + * If HAVE_SNPRINTF is defined this module will not produce code for + * snprintf and vsnprintf, unless PREFER_PORTABLE_SNPRINTF is defined as well, + * causing this portable version of snprintf to be called portable_snprintf + * (and portable_vsnprintf). + */ +#define HAVE_SNPRINTF + +/* Define PREFER_PORTABLE_SNPRINTF if your system does have snprintf and + * vsnprintf but you would prefer to use the portable routine(s) instead. + * In this case the portable routine is declared as portable_snprintf + * (and portable_vsnprintf) and a macro 'snprintf' (and 'vsnprintf') + * is defined to expand to 'portable_v?snprintf' - see file snprintf.h . + * Defining this macro is only useful if HAVE_SNPRINTF is also defined, + * but does does no harm if defined nevertheless. + */ +#define PREFER_PORTABLE_SNPRINTF + +/* Define SNPRINTF_LONGLONG_SUPPORT if you want to support + * data type (long long int) and length modifier 'll' (e.g. %lld). + * If undefined, 'll' is recognized but treated as a single 'l'. + * + * If the system's sprintf does not handle 'll' + * the SNPRINTF_LONGLONG_SUPPORT must not be defined! + * + * This is off by default as (long long int) is a language extension. + */ +/* #define SNPRINTF_LONGLONG_SUPPORT */ + +/* Define NEED_SNPRINTF_ONLY if you only need snprintf, and not vsnprintf. + * If NEED_SNPRINTF_ONLY is defined, the snprintf will be defined directly, + * otherwise both snprintf and vsnprintf routines will be defined + * and snprintf will be a simple wrapper around vsnprintf, at the expense + * of an extra procedure call. + */ +/* #define NEED_SNPRINTF_ONLY */ + +/* Define NEED_V?ASN?PRINTF macros if you need library extension + * routines asprintf, vasprintf, asnprintf, vasnprintf respectively, + * and your system library does not provide them. They are all small + * wrapper routines around portable_vsnprintf. Defining any of the four + * NEED_V?ASN?PRINTF macros automatically turns off NEED_SNPRINTF_ONLY + * and turns on PREFER_PORTABLE_SNPRINTF. + * + * Watch for name conflicts with the system library if these routines + * are already present there. + * + * NOTE: vasprintf and vasnprintf routines need va_copy() from stdarg.h, as + * specified by C99, to be able to traverse the same list of arguments twice. + * I don't know of any other standard and portable way of achieving the same. + * With some versions of gcc you may use __va_copy(). You might even get away + * with "ap2 = ap", in this case you must not call va_end(ap2) ! + * #define va_copy(ap2,ap) ap2 = ap + */ +/* #define NEED_ASPRINTF */ +/* #define NEED_ASNPRINTF */ +/* #define NEED_VASPRINTF */ +/* #define NEED_VASNPRINTF */ + + +/* Define the following macros if desired: + * SOLARIS_COMPATIBLE, SOLARIS_BUG_COMPATIBLE, + * HPUX_COMPATIBLE, HPUX_BUG_COMPATIBLE, LINUX_COMPATIBLE, + * DIGITAL_UNIX_COMPATIBLE, DIGITAL_UNIX_BUG_COMPATIBLE, + * PERL_COMPATIBLE, PERL_BUG_COMPATIBLE, + * + * - For portable applications it is best not to rely on peculiarities + * of a given implementation so it may be best not to define any + * of the macros that select compatibility and to avoid features + * that vary among the systems. + * + * - Selecting compatibility with more than one operating system + * is not strictly forbidden but is not recommended. + * + * - 'x'_BUG_COMPATIBLE implies 'x'_COMPATIBLE . + * + * - 'x'_COMPATIBLE refers to (and enables) a behaviour that is + * documented in a sprintf man page on a given operating system + * and actually adhered to by the system's sprintf (but not on + * most other operating systems). It may also refer to and enable + * a behaviour that is declared 'undefined' or 'implementation specific' + * in the man page but a given implementation behaves predictably + * in a certain way. + * + * - 'x'_BUG_COMPATIBLE refers to (and enables) a behaviour of system's sprintf + * that contradicts the sprintf man page on the same operating system. + * + * - I do not claim that the 'x'_COMPATIBLE and 'x'_BUG_COMPATIBLE + * conditionals take into account all idiosyncrasies of a particular + * implementation, there may be other incompatibilities. + */ + + + +/* ============================================= */ +/* NO USER SERVICABLE PARTS FOLLOWING THIS POINT */ +/* ============================================= */ + +#define PORTABLE_SNPRINTF_VERSION_MAJOR 2 +#define PORTABLE_SNPRINTF_VERSION_MINOR 2 + +#if defined(NEED_ASPRINTF) || defined(NEED_ASNPRINTF) || defined(NEED_VASPRINTF) || defined(NEED_VASNPRINTF) +# if defined(NEED_SNPRINTF_ONLY) +# undef NEED_SNPRINTF_ONLY +# endif +# if !defined(PREFER_PORTABLE_SNPRINTF) +# define PREFER_PORTABLE_SNPRINTF +# endif +#endif + +#if defined(SOLARIS_BUG_COMPATIBLE) && !defined(SOLARIS_COMPATIBLE) +#define SOLARIS_COMPATIBLE +#endif + +#if defined(HPUX_BUG_COMPATIBLE) && !defined(HPUX_COMPATIBLE) +#define HPUX_COMPATIBLE +#endif + +#if defined(DIGITAL_UNIX_BUG_COMPATIBLE) && !defined(DIGITAL_UNIX_COMPATIBLE) +#define DIGITAL_UNIX_COMPATIBLE +#endif + +#if defined(PERL_BUG_COMPATIBLE) && !defined(PERL_COMPATIBLE) +#define PERL_COMPATIBLE +#endif + +#if defined(LINUX_BUG_COMPATIBLE) && !defined(LINUX_COMPATIBLE) +#define LINUX_COMPATIBLE +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef isdigit +#undef isdigit +#endif +#define isdigit(c) ((c) >= '0' && (c) <= '9') + +/* For copying strings longer or equal to 'breakeven_point' + * it is more efficient to call memcpy() than to do it inline. + * The value depends mostly on the processor architecture, + * but also on the compiler and its optimization capabilities. + * The value is not critical, some small value greater than zero + * will be just fine if you don't care to squeeze every drop + * of performance out of the code. + * + * Small values favor memcpy, large values favor inline code. + */ +#if defined(__alpha__) || defined(__alpha) +# define breakeven_point 2 /* AXP (DEC Alpha) - gcc or cc or egcs */ +#endif +#if defined(__i386__) || defined(__i386) +# define breakeven_point 12 /* Intel Pentium/Linux - gcc 2.96 */ +#endif +#if defined(__hppa) +# define breakeven_point 10 /* HP-PA - gcc */ +#endif +#if defined(__sparc__) || defined(__sparc) +# define breakeven_point 33 /* Sun Sparc 5 - gcc 2.8.1 */ +#endif + +/* some other values of possible interest: */ +/* #define breakeven_point 8 */ /* VAX 4000 - vaxc */ +/* #define breakeven_point 19 */ /* VAX 4000 - gcc 2.7.0 */ + +#ifndef breakeven_point +# define breakeven_point 6 /* some reasonable one-size-fits-all value */ +#endif + +#define fast_memcpy(d,s,n) \ + { register size_t nn = (size_t)(n); \ + if (nn >= breakeven_point) memcpy((d), (s), nn); \ + else if (nn > 0) { /* proc call overhead is worth only for large strings*/\ + register char *dd; register const char *ss; \ + for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } } + +#define fast_memset(d,c,n) \ + { register size_t nn = (size_t)(n); \ + if (nn >= breakeven_point) memset((d), (int)(c), nn); \ + else if (nn > 0) { /* proc call overhead is worth only for large strings*/\ + register char *dd; register const int cc=(int)(c); \ + for (dd=(d); nn>0; nn--) *dd++ = cc; } } + +/* prototypes */ + +#if defined(NEED_ASPRINTF) +int asprintf (char **ptr, const char *fmt, /*args*/ ...); +#endif +#if defined(NEED_VASPRINTF) +int vasprintf (char **ptr, const char *fmt, va_list ap); +#endif +#if defined(NEED_ASNPRINTF) +int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...); +#endif +#if defined(NEED_VASNPRINTF) +int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap); +#endif + +#if defined(HAVE_SNPRINTF) +/* declare our portable snprintf routine under name portable_snprintf */ +/* declare our portable vsnprintf routine under name portable_vsnprintf */ +#else +/* declare our portable routines under names snprintf and vsnprintf */ +#define portable_snprintf snprintf +#if !defined(NEED_SNPRINTF_ONLY) +#define portable_vsnprintf vsnprintf +#endif +#endif + +#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) +int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...); +#if !defined(NEED_SNPRINTF_ONLY) +int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap); +#endif +#endif + +/* declarations */ + +static char credits[] = "\n\ +@(#)snprintf.c, v2.2: Mark Martinec, \n\ +@(#)snprintf.c, v2.2: Copyright 1999, Mark Martinec. Frontier Artistic License applies.\n\ +@(#)snprintf.c, v2.2: http://www.ijs.si/software/snprintf/\n"; + +#if defined(NEED_ASPRINTF) +int asprintf(char **ptr, const char *fmt, /*args*/ ...) { + va_list ap; + size_t str_m; + int str_l; + + *ptr = NULL; + va_start(ap, fmt); /* measure the required size */ + str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap); + va_end(ap); + assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ + *ptr = (char *) malloc(str_m = (size_t)str_l + 1); + if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } + else { + int str_l2; + va_start(ap, fmt); + str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); + va_end(ap); + assert(str_l2 == str_l); + } + return str_l; +} +#endif + +#if defined(NEED_VASPRINTF) +int vasprintf(char **ptr, const char *fmt, va_list ap) { + size_t str_m; + int str_l; + + *ptr = NULL; + { va_list ap2; + va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */ + str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/ + va_end(ap2); + } + assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ + *ptr = (char *) malloc(str_m = (size_t)str_l + 1); + if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } + else { + int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); + assert(str_l2 == str_l); + } + return str_l; +} +#endif + +#if defined(NEED_ASNPRINTF) +int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...) { + va_list ap; + int str_l; + + *ptr = NULL; + va_start(ap, fmt); /* measure the required size */ + str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap); + va_end(ap); + assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ + if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */ + /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */ + if (str_m == 0) { /* not interested in resulting string, just return size */ + } else { + *ptr = (char *) malloc(str_m); + if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } + else { + int str_l2; + va_start(ap, fmt); + str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); + va_end(ap); + assert(str_l2 == str_l); + } + } + return str_l; +} +#endif + +#if defined(NEED_VASNPRINTF) +int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap) { + int str_l; + + *ptr = NULL; + { va_list ap2; + va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */ + str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/ + va_end(ap2); + } + assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ + if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */ + /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */ + if (str_m == 0) { /* not interested in resulting string, just return size */ + } else { + *ptr = (char *) malloc(str_m); + if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } + else { + int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); + assert(str_l2 == str_l); + } + } + return str_l; +} +#endif + +/* + * If the system does have snprintf and the portable routine is not + * specifically required, this module produces no code for snprintf/vsnprintf. + */ +#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) + +#if !defined(NEED_SNPRINTF_ONLY) +int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) { + va_list ap; + int str_l; + + va_start(ap, fmt); + str_l = portable_vsnprintf(str, str_m, fmt, ap); + va_end(ap); + return str_l; +} +#endif + +#if defined(NEED_SNPRINTF_ONLY) +int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) { +#else +int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) { +#endif + +#if defined(NEED_SNPRINTF_ONLY) + va_list ap; +#endif + size_t str_l = 0; + const char *p = fmt; + +/* In contrast with POSIX, the ISO C99 now says + * that str can be NULL and str_m can be 0. + * This is more useful than the old: if (str_m < 1) return -1; */ + +#if defined(NEED_SNPRINTF_ONLY) + va_start(ap, fmt); +#endif + if (!p) p = ""; + while (*p) { + if (*p != '%') { + /* if (str_l < str_m) str[str_l++] = *p++; -- this would be sufficient */ + /* but the following code achieves better performance for cases + * where format string is long and contains few conversions */ + const char *q = strchr(p+1,'%'); + size_t n = !q ? strlen(p) : (q-p); + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memcpy(str+str_l, p, (n>avail?avail:n)); + } + p += n; str_l += n; + } else { + const char *starting_p; + size_t min_field_width = 0, precision = 0; + int zero_padding = 0, precision_specified = 0, justify_left = 0; + int alternate_form = 0, force_sign = 0; + int space_for_positive = 1; /* If both the ' ' and '+' flags appear, + the ' ' flag should be ignored. */ + char length_modifier = '\0'; /* allowed values: \0, h, l, L */ + char tmp[32];/* temporary buffer for simple numeric->string conversion */ + + const char *str_arg; /* string address in case of string argument */ + size_t str_arg_l; /* natural field width of arg without padding + and sign */ + unsigned char uchar_arg; + /* unsigned char argument value - only defined for c conversion. + N.B. standard explicitly states the char argument for + the c conversion is unsigned */ + + size_t number_of_zeros_to_pad = 0; + /* number of zeros to be inserted for numeric conversions + as required by the precision or minimal field width */ + + size_t zero_padding_insertion_ind = 0; + /* index into tmp where zero padding is to be inserted */ + + char fmt_spec = '\0'; + /* current conversion specifier character */ + + str_arg = credits;/* just to make compiler happy (defined but not used)*/ + str_arg = NULL; + starting_p = p; p++; /* skip '%' */ + /* parse flags */ + while (*p == '0' || *p == '-' || *p == '+' || + *p == ' ' || *p == '#' || *p == '\'') { + switch (*p) { + case '0': zero_padding = 1; break; + case '-': justify_left = 1; break; + case '+': force_sign = 1; space_for_positive = 0; break; + case ' ': force_sign = 1; + /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */ +#ifdef PERL_COMPATIBLE + /* ... but in Perl the last of ' ' and '+' applies */ + space_for_positive = 1; +#endif + break; + case '#': alternate_form = 1; break; + case '\'': break; + } + p++; + } + /* If the '0' and '-' flags both appear, the '0' flag should be ignored. */ + + /* parse field width */ + if (*p == '*') { + int j; + p++; j = va_arg(ap, int); + if (j >= 0) min_field_width = j; + else { min_field_width = -j; justify_left = 1; } + } else if (isdigit((int)(*p))) { + /* size_t could be wider than unsigned int; + make sure we treat argument like common implementations do */ + unsigned int uj = *p++ - '0'; + while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0'); + min_field_width = uj; + } + /* parse precision */ + if (*p == '.') { + p++; precision_specified = 1; + if (*p == '*') { + int j = va_arg(ap, int); + p++; + if (j >= 0) precision = j; + else { + precision_specified = 0; precision = 0; + /* NOTE: + * Solaris 2.6 man page claims that in this case the precision + * should be set to 0. Digital Unix 4.0, HPUX 10 and BSD man page + * claim that this case should be treated as unspecified precision, + * which is what we do here. + */ + } + } else if (isdigit((int)(*p))) { + /* size_t could be wider than unsigned int; + make sure we treat argument like common implementations do */ + unsigned int uj = *p++ - '0'; + while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0'); + precision = uj; + } + } + /* parse 'h', 'l' and 'll' length modifiers */ + if (*p == 'h' || *p == 'l') { + length_modifier = *p; p++; + if (length_modifier == 'l' && *p == 'l') { /* double l = long long */ +#ifdef SNPRINTF_LONGLONG_SUPPORT + length_modifier = '2'; /* double l encoded as '2' */ +#else + length_modifier = 'l'; /* treat it as a single 'l' */ +#endif + p++; + } + } + fmt_spec = *p; + /* common synonyms: */ + switch (fmt_spec) { + case 'i': fmt_spec = 'd'; break; + case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; + case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; + case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; + default: break; + } + /* get parameter value, do initial processing */ + switch (fmt_spec) { + case '%': /* % behaves similar to 's' regarding flags and field widths */ + case 'c': /* c behaves similar to 's' regarding flags and field widths */ + case 's': + length_modifier = '\0'; /* wint_t and wchar_t not supported */ + /* the result of zero padding flag with non-numeric conversion specifier*/ + /* is undefined. Solaris and HPUX 10 does zero padding in this case, */ + /* Digital Unix and Linux does not. */ +#if !defined(SOLARIS_COMPATIBLE) && !defined(HPUX_COMPATIBLE) + zero_padding = 0; /* turn zero padding off for string conversions */ +#endif + str_arg_l = 1; + switch (fmt_spec) { + case '%': + str_arg = p; break; + case 'c': { + int j = va_arg(ap, int); + uchar_arg = (unsigned char) j; /* standard demands unsigned char */ + str_arg = (const char *) &uchar_arg; + break; + } + case 's': + str_arg = va_arg(ap, const char *); + if (!str_arg) str_arg_l = 0; + /* make sure not to address string beyond the specified precision !!! */ + else if (!precision_specified) str_arg_l = strlen(str_arg); + /* truncate string if necessary as requested by precision */ + else if (precision == 0) str_arg_l = 0; + else { + /* memchr on HP does not like n > 2^31 !!! */ + const char *q = memchr(str_arg, '\0', + precision <= 0x7fffffff ? precision : 0x7fffffff); + str_arg_l = !q ? precision : (q-str_arg); + } + break; + default: break; + } + break; + case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': { + /* NOTE: the u, o, x, X and p conversion specifiers imply + the value is unsigned; d implies a signed value */ + + int arg_sign = 0; + /* 0 if numeric argument is zero (or if pointer is NULL for 'p'), + +1 if greater than zero (or nonzero for unsigned arguments), + -1 if negative (unsigned argument is never negative) */ + + int int_arg = 0; unsigned int uint_arg = 0; + /* only defined for length modifier h, or for no length modifiers */ + + long int long_arg = 0; unsigned long int ulong_arg = 0; + /* only defined for length modifier l */ + + void *ptr_arg = NULL; + /* pointer argument value -only defined for p conversion */ + +#ifdef SNPRINTF_LONGLONG_SUPPORT + long long int long_long_arg = 0; + unsigned long long int ulong_long_arg = 0; + /* only defined for length modifier ll */ +#endif + if (fmt_spec == 'p') { + /* HPUX 10: An l, h, ll or L before any other conversion character + * (other than d, i, u, o, x, or X) is ignored. + * Digital Unix: + * not specified, but seems to behave as HPUX does. + * Solaris: If an h, l, or L appears before any other conversion + * specifier (other than d, i, u, o, x, or X), the behavior + * is undefined. (Actually %hp converts only 16-bits of address + * and %llp treats address as 64-bit data which is incompatible + * with (void *) argument on a 32-bit system). + */ +#ifdef SOLARIS_COMPATIBLE +# ifdef SOLARIS_BUG_COMPATIBLE + /* keep length modifiers even if it represents 'll' */ +# else + if (length_modifier == '2') length_modifier = '\0'; +# endif +#else + length_modifier = '\0'; +#endif + ptr_arg = va_arg(ap, void *); + if (ptr_arg != NULL) arg_sign = 1; + } else if (fmt_spec == 'd') { /* signed */ + switch (length_modifier) { + case '\0': + case 'h': + /* It is non-portable to specify a second argument of char or short + * to va_arg, because arguments seen by the called function + * are not char or short. C converts char and short arguments + * to int before passing them to a function. + */ + int_arg = va_arg(ap, int); + if (int_arg > 0) arg_sign = 1; + else if (int_arg < 0) arg_sign = -1; + break; + case 'l': + long_arg = va_arg(ap, long int); + if (long_arg > 0) arg_sign = 1; + else if (long_arg < 0) arg_sign = -1; + break; +#ifdef SNPRINTF_LONGLONG_SUPPORT + case '2': + long_long_arg = va_arg(ap, long long int); + if (long_long_arg > 0) arg_sign = 1; + else if (long_long_arg < 0) arg_sign = -1; + break; +#endif + } + } else { /* unsigned */ + switch (length_modifier) { + case '\0': + case 'h': + uint_arg = va_arg(ap, unsigned int); + if (uint_arg) arg_sign = 1; + break; + case 'l': + ulong_arg = va_arg(ap, unsigned long int); + if (ulong_arg) arg_sign = 1; + break; +#ifdef SNPRINTF_LONGLONG_SUPPORT + case '2': + ulong_long_arg = va_arg(ap, unsigned long long int); + if (ulong_long_arg) arg_sign = 1; + break; +#endif + } + } + str_arg = tmp; str_arg_l = 0; + /* NOTE: + * For d, i, u, o, x, and X conversions, if precision is specified, + * the '0' flag should be ignored. This is so with Solaris 2.6, + * Digital UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl. + */ +#ifndef PERL_COMPATIBLE + if (precision_specified) zero_padding = 0; +#endif + if (fmt_spec == 'd') { + if (force_sign && arg_sign >= 0) + tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; + /* leave negative numbers for sprintf to handle, + to avoid handling tricky cases like (short int)(-32768) */ +#ifdef LINUX_COMPATIBLE + } else if (fmt_spec == 'p' && force_sign && arg_sign > 0) { + tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; +#endif + } else if (alternate_form) { + if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X') ) + { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = fmt_spec; } + /* alternate form should have no effect for p conversion, but ... */ +#ifdef HPUX_COMPATIBLE + else if (fmt_spec == 'p' + /* HPUX 10: for an alternate form of p conversion, + * a nonzero result is prefixed by 0x. */ +#ifndef HPUX_BUG_COMPATIBLE + /* Actually it uses 0x prefix even for a zero value. */ + && arg_sign != 0 +#endif + ) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = 'x'; } +#endif + } + zero_padding_insertion_ind = str_arg_l; + if (!precision_specified) precision = 1; /* default precision is 1 */ + if (precision == 0 && arg_sign == 0 +#if defined(HPUX_BUG_COMPATIBLE) || defined(LINUX_COMPATIBLE) + && fmt_spec != 'p' + /* HPUX 10 man page claims: With conversion character p the result of + * converting a zero value with a precision of zero is a null string. + * Actually HP returns all zeroes, and Linux returns "(nil)". */ +#endif + ) { + /* converted to null string */ + /* When zero value is formatted with an explicit precision 0, + the resulting formatted string is empty (d, i, u, o, x, X, p). */ + } else { + char f[5]; int f_l = 0; + f[f_l++] = '%'; /* construct a simple format string for sprintf */ + if (!length_modifier) { } + else if (length_modifier=='2') { f[f_l++] = 'l'; f[f_l++] = 'l'; } + else f[f_l++] = length_modifier; + f[f_l++] = fmt_spec; f[f_l++] = '\0'; + if (fmt_spec == 'p') str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg); + else if (fmt_spec == 'd') { /* signed */ + switch (length_modifier) { + case '\0': + case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg); break; + case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg); break; +#ifdef SNPRINTF_LONGLONG_SUPPORT + case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,long_long_arg); break; +#endif + } + } else { /* unsigned */ + switch (length_modifier) { + case '\0': + case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, uint_arg); break; + case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, ulong_arg); break; +#ifdef SNPRINTF_LONGLONG_SUPPORT + case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,ulong_long_arg);break; +#endif + } + } + /* include the optional minus sign and possible "0x" + in the region before the zero padding insertion point */ + if (zero_padding_insertion_ind < str_arg_l && + tmp[zero_padding_insertion_ind] == '-') { + zero_padding_insertion_ind++; + } + if (zero_padding_insertion_ind+1 < str_arg_l && + tmp[zero_padding_insertion_ind] == '0' && + (tmp[zero_padding_insertion_ind+1] == 'x' || + tmp[zero_padding_insertion_ind+1] == 'X') ) { + zero_padding_insertion_ind += 2; + } + } + { size_t num_of_digits = str_arg_l - zero_padding_insertion_ind; + if (alternate_form && fmt_spec == 'o' +#ifdef HPUX_COMPATIBLE /* ("%#.o",0) -> "" */ + && (str_arg_l > 0) +#endif +#ifdef DIGITAL_UNIX_BUG_COMPATIBLE /* ("%#o",0) -> "00" */ +#else + /* unless zero is already the first character */ + && !(zero_padding_insertion_ind < str_arg_l + && tmp[zero_padding_insertion_ind] == '0') +#endif + ) { /* assure leading zero for alternate-form octal numbers */ + if (!precision_specified || precision < num_of_digits+1) { + /* precision is increased to force the first character to be zero, + except if a zero value is formatted with an explicit precision + of zero */ + precision = num_of_digits+1; precision_specified = 1; + } + } + /* zero padding to specified precision? */ + if (num_of_digits < precision) + number_of_zeros_to_pad = precision - num_of_digits; + } + /* zero padding to specified minimal field width? */ + if (!justify_left && zero_padding) { + int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); + if (n > 0) number_of_zeros_to_pad += n; + } + break; + } + default: /* unrecognized conversion specifier, keep format string as-is*/ + zero_padding = 0; /* turn zero padding off for non-numeric convers. */ +#ifndef DIGITAL_UNIX_COMPATIBLE + justify_left = 1; min_field_width = 0; /* reset flags */ +#endif +#if defined(PERL_COMPATIBLE) || defined(LINUX_COMPATIBLE) + /* keep the entire format string unchanged */ + str_arg = starting_p; str_arg_l = p - starting_p; + /* well, not exactly so for Linux, which does something inbetween, + * and I don't feel an urge to imitate it: "%+++++hy" -> "%+y" */ +#else + /* discard the unrecognized conversion, just keep * + * the unrecognized conversion character */ + str_arg = p; str_arg_l = 0; +#endif + if (*p) str_arg_l++; /* include invalid conversion specifier unchanged + if not at end-of-string */ + break; + } + if (*p) p++; /* step over the just processed conversion specifier */ + /* insert padding to the left as requested by min_field_width; + this does not include the zero padding in case of numerical conversions*/ + if (!justify_left) { /* left padding with blank or zero */ + int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memset(str+str_l, (zero_padding?'0':' '), (n>avail?avail:n)); + } + str_l += n; + } + } + /* zero padding as requested by the precision or by the minimal field width + * for numeric conversions required? */ + if (number_of_zeros_to_pad <= 0) { + /* will not copy first part of numeric right now, * + * force it to be copied later in its entirety */ + zero_padding_insertion_ind = 0; + } else { + /* insert first part of numerics (sign or '0x') before zero padding */ + int n = zero_padding_insertion_ind; + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memcpy(str+str_l, str_arg, (n>avail?avail:n)); + } + str_l += n; + } + /* insert zero padding as requested by the precision or min field width */ + n = number_of_zeros_to_pad; + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memset(str+str_l, '0', (n>avail?avail:n)); + } + str_l += n; + } + } + /* insert formatted string + * (or as-is conversion specifier for unknown conversions) */ + { int n = str_arg_l - zero_padding_insertion_ind; + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memcpy(str+str_l, str_arg+zero_padding_insertion_ind, + (n>avail?avail:n)); + } + str_l += n; + } + } + /* insert right padding */ + if (justify_left) { /* right blank padding to the field width */ + int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memset(str+str_l, ' ', (n>avail?avail:n)); + } + str_l += n; + } + } + } + } +#if defined(NEED_SNPRINTF_ONLY) + va_end(ap); +#endif + if (str_m > 0) { /* make sure the string is null-terminated + even at the expense of overwriting the last character + (shouldn't happen, but just in case) */ + str[str_l <= str_m-1 ? str_l : str_m-1] = '\0'; + } + /* Return the number of characters formatted (excluding trailing null + * character), that is, the number of characters that would have been + * written to the buffer if it were large enough. + * + * The value of str_l should be returned, but str_l is of unsigned type + * size_t, and snprintf is int, possibly leading to an undetected + * integer overflow, resulting in a negative return value, which is illegal. + * Both XSH5 and ISO C99 (at least the draft) are silent on this issue. + * Should errno be set to EOVERFLOW and EOF returned in this case??? + */ + return (int) str_l; +} +#endif diff --git a/app/jni/snprintf.h b/app/jni/snprintf.h new file mode 100644 index 0000000..3fd85d4 --- /dev/null +++ b/app/jni/snprintf.h @@ -0,0 +1,26 @@ +#ifndef _PORTABLE_SNPRINTF_H_ +#define _PORTABLE_SNPRINTF_H_ + +#define PORTABLE_SNPRINTF_VERSION_MAJOR 2 +#define PORTABLE_SNPRINTF_VERSION_MINOR 2 + +#ifdef HAVE_SNPRINTF +#include +#else +extern int snprintf(char *, size_t, const char *, /*args*/ ...); +extern int vsnprintf(char *, size_t, const char *, va_list); +#endif + +#if defined(HAVE_SNPRINTF) && defined(PREFER_PORTABLE_SNPRINTF) +extern int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...); +extern int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap); +//#define snprintf portable_snprintf +//#define vsnprintf portable_vsnprintf +#endif + +extern int asprintf (char **ptr, const char *fmt, /*args*/ ...); +extern int vasprintf (char **ptr, const char *fmt, va_list ap); +extern int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...); +extern int vasnprintf(char **ptr, size_t str_m, const char *fmt, va_list ap); + +#endif diff --git a/app/jni/sound.h b/app/jni/sound.h new file mode 100644 index 0000000..512ae9c --- /dev/null +++ b/app/jni/sound.h @@ -0,0 +1,120 @@ +/* +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. + +*/ + +#ifndef SOUND_H +#define SOUND_H + +#include "matrixlib.h" + + +// ==================================================================== +// Constants +// ==================================================================== + +#define DEFAULT_SOUND_PACKET_VOLUME 255 +#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 + +// Channel flags +// These channel flags can be used for sound() builtins, with SOUNDFLAG_* names +#define CHANNELFLAG_NONE 0 +#define CHANNELFLAG_RELIABLE (1 << 0) // send as reliable message (only used on server) +#define CHANNELFLAG_FORCELOOP (1 << 1) // force looping even if the sound is not looped +#define CHANNELFLAG_LOCALSOUND (1 << 2) // INTERNAL USE. Not settable by S_SetChannelFlag +#define CHANNELFLAG_PAUSED (1 << 3) // pause status +#define CHANNELFLAG_FULLVOLUME (1 << 4) // isn't affected by the general volume + +// ==================================================================== +// Types and variables +// ==================================================================== + +typedef struct sfx_s sfx_t; + +extern cvar_t mastervolume; +extern cvar_t bgmvolume; +extern cvar_t volume; +extern cvar_t snd_initialized; +extern cvar_t snd_staticvolume; +extern cvar_t snd_mutewhenidle; + + +// ==================================================================== +// Functions +// ==================================================================== + +void S_Init (void); +void S_Terminate (void); + +void S_Startup (void); +void S_Shutdown (void); +void S_UnloadAllSounds_f (void); + +void S_Update(const matrix4x4_t *listenermatrix); +void S_ExtraUpdate (void); + +sfx_t *S_PrecacheSound (const char *sample, qboolean complain, qboolean levelsound); +float S_SoundLength(const char *name); +void S_ClearUsed (void); +void S_PurgeUnused (void); +qboolean S_IsSoundPrecached (const sfx_t *sfx); +sfx_t *S_FindName(const char *name); + +// these define the "engine" channel namespace +#define CHAN_MIN_AUTO -128 +#define CHAN_MAX_AUTO 0 +#define CHAN_MIN_SINGLE 1 +#define CHAN_MAX_SINGLE 127 +#define IS_CHAN_AUTO(n) ((n) >= CHAN_MIN_AUTO && (n) <= CHAN_MAX_AUTO) +#define IS_CHAN_SINGLE(n) ((n) >= CHAN_MIN_SINGLE && (n) <= CHAN_MAX_SINGLE) +#define IS_CHAN(n) (IS_CHAN_AUTO(n) || IS_CHAN_SINGLE(n)) + +// engine channel == network channel +#define CHAN_ENGINE2NET(c) (c) +#define CHAN_NET2ENGINE(c) (c) + +// engine view of channel encodes the auto flag into the channel number (see CHAN_ constants below) +// user view uses the flags bitmask for it +#define CHAN_USER2ENGINE(c) (c) +#define CHAN_ENGINE2USER(c) (c) +#define CHAN_ENGINE2CVAR(c) (abs(c)) + +// S_StartSound returns the channel index, or -1 if an error occurred +int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation); +int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags, float fspeed); +qboolean S_LocalSound (const char *s); + +void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation); +void S_StopSound (int entnum, int entchannel); +void S_StopAllSounds (void); +void S_PauseGameSounds (qboolean toggle); + +void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx); +qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value); +void S_SetChannelVolume (unsigned int ch_ind, float fvol); +void S_SetChannelSpeed (unsigned int ch_ind, float fspeed); +float S_GetChannelPosition (unsigned int ch_ind); +float S_GetEntChannelPosition(int entnum, int entchannel); + +void S_BlockSound (void); +void S_UnblockSound (void); + +int S_GetSoundRate (void); +int S_GetSoundChannels (void); + +#endif diff --git a/app/jni/spritegn.h b/app/jni/spritegn.h new file mode 100644 index 0000000..b2d2e3b --- /dev/null +++ b/app/jni/spritegn.h @@ -0,0 +1,131 @@ +/* +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. + +*/ +// +// spritegn.h: header file for sprite generation program +// + +// ********************************************************** +// * This file must be identical in the spritegen directory * +// * and in the Quake directory, because it's used to * +// * pass data from one to the other via .spr files. * +// ********************************************************** + +#ifndef SPRITEGEN_H +#define SPRITEGEN_H + +//------------------------------------------------------- +// This program generates .spr sprite package files. +// The format of the files is as follows: +// +// dsprite_t file header structure +// +// +// dspriteframe_t frame header structure +// sprite bitmap +// +// dspriteframe_t frame header structure +// sprite bitmap +// +//------------------------------------------------------- + +#define SPRITE_VERSION 1 +#define SPRITEHL_VERSION 2 +#define SPRITE32_VERSION 32 + +#define SPRITE2_VERSION 2 + +typedef struct dsprite_s +{ + int ident; + int version; + int type; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dsprite_t; + +typedef struct dspritehl_s +{ + int ident; + int version; + int type; + int rendermode; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dspritehl_t; + +typedef struct dsprite2frame_s +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[64]; // name of pcx file +} dsprite2frame_t; + +typedef struct dsprite2_s +{ + int ident; + int version; + int numframes; + dsprite2frame_t frames[1]; // variable sized +} dsprite2_t; + +#define SPR_VP_PARALLEL_UPRIGHT 0 +#define SPR_FACING_UPRIGHT 1 +#define SPR_VP_PARALLEL 2 +#define SPR_ORIENTED 3 +#define SPR_VP_PARALLEL_ORIENTED 4 +#define SPR_LABEL 5 +#define SPR_LABEL_SCALE 6 +#define SPR_OVERHEAD 7 + +#define SPRHL_OPAQUE 0 +#define SPRHL_ADDITIVE 1 +#define SPRHL_INDEXALPHA 2 +#define SPRHL_ALPHATEST 3 + +typedef struct dspriteframe_s { + int origin[2]; + int width; + int height; +} dspriteframe_t; + +typedef struct dspritegroup_s { + int numframes; +} dspritegroup_t; + +typedef struct dspriteinterval_s { + float interval; +} dspriteinterval_t; + +typedef enum spriteframetype_e { SPR_SINGLE=0, SPR_GROUP } spriteframetype_t; + +typedef struct dspriteframetype_s { + spriteframetype_t type; +} dspriteframetype_t; + +#endif + diff --git a/app/jni/sv_demo.c b/app/jni/sv_demo.c new file mode 100644 index 0000000..cc94a2c --- /dev/null +++ b/app/jni/sv_demo.c @@ -0,0 +1,99 @@ +#include "quakedef.h" +#include "sv_demo.h" + +extern cvar_t sv_autodemo_perclient_discardable; + +void SV_StartDemoRecording(client_t *client, const char *filename, int forcetrack) +{ + prvm_prog_t *prog = SVVM_prog; + char name[MAX_QPATH]; + + if(client->sv_demo_file != NULL) + return; // we already have a demo + + strlcpy(name, filename, sizeof(name)); + FS_DefaultExtension(name, ".dem", sizeof(name)); + + Con_Printf("Recording demo for # %d (%s) to %s\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress, name); + + // Reset discardable flag for every new demo. + PRVM_serveredictfloat(client->edict, discardabledemo) = 0; + + client->sv_demo_file = FS_OpenRealFile(name, "wb", false); + if(!client->sv_demo_file) + { + Con_Print("ERROR: couldn't open.\n"); + return; + } + + FS_Printf(client->sv_demo_file, "%i\n", forcetrack); +} + +void SV_WriteDemoMessage(client_t *client, sizebuf_t *sendbuffer, qboolean clienttoserver) +{ + prvm_prog_t *prog = SVVM_prog; + int len, i; + float f; + int temp; + + if(client->sv_demo_file == NULL) + return; + if(sendbuffer->cursize == 0) + return; + + temp = sendbuffer->cursize | (clienttoserver ? DEMOMSG_CLIENT_TO_SERVER : 0); + len = LittleLong(temp); + FS_Write(client->sv_demo_file, &len, 4); + for(i = 0; i < 3; ++i) + { + f = LittleFloat(PRVM_serveredictvector(client->edict, v_angle)[i]); + FS_Write(client->sv_demo_file, &f, 4); + } + FS_Write(client->sv_demo_file, sendbuffer->data, sendbuffer->cursize); +} + +void SV_StopDemoRecording(client_t *client) +{ + prvm_prog_t *prog = SVVM_prog; + sizebuf_t buf; + unsigned char bufdata[64]; + + if(client->sv_demo_file == NULL) + return; + + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + SZ_Clear(&buf); + MSG_WriteByte(&buf, svc_disconnect); + SV_WriteDemoMessage(client, &buf, false); + + if (sv_autodemo_perclient_discardable.integer && PRVM_serveredictfloat(client->edict, discardabledemo)) + { + FS_RemoveOnClose(client->sv_demo_file); + Con_Printf("Stopped recording discardable demo for # %d (%s)\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress); + } + else + Con_Printf("Stopped recording demo for # %d (%s)\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress); + + FS_Close(client->sv_demo_file); + client->sv_demo_file = NULL; +} + +void SV_WriteNetnameIntoDemo(client_t *client) +{ + // This "pseudo packet" is written so a program can easily find out whose demo this is + sizebuf_t buf; + unsigned char bufdata[128]; + + if(client->sv_demo_file == NULL) + return; + + buf.data = bufdata; + buf.maxsize = sizeof(bufdata); + SZ_Clear(&buf); + MSG_WriteByte(&buf, svc_stufftext); + MSG_WriteUnterminatedString(&buf, "\n// this demo contains the point of view of: "); + MSG_WriteUnterminatedString(&buf, client->name); + MSG_WriteString(&buf, "\n"); + SV_WriteDemoMessage(client, &buf, false); +} diff --git a/app/jni/sv_demo.h b/app/jni/sv_demo.h new file mode 100644 index 0000000..65c1924 --- /dev/null +++ b/app/jni/sv_demo.h @@ -0,0 +1,9 @@ +#ifndef SV_DEMO_H +#define SV_DEMO_H + +void SV_StartDemoRecording(client_t *client, const char *filename, int forcetrack); +void SV_WriteDemoMessage(client_t *client, sizebuf_t *sendbuffer, qboolean clienttoserver); +void SV_StopDemoRecording(client_t *client); +void SV_WriteNetnameIntoDemo(client_t *client); + +#endif diff --git a/app/jni/sv_main.c b/app/jni/sv_main.c new file mode 100644 index 0000000..6b7973c --- /dev/null +++ b/app/jni/sv_main.c @@ -0,0 +1,4033 @@ +/* +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. + +*/ +// sv_main.c -- server main program + +#include "quakedef.h" +#include "sv_demo.h" +#include "libcurl.h" +#include "csprogs.h" +#include "thread.h" + +static void SV_SaveEntFile_f(void); +static void SV_StartDownload_f(void); +static void SV_Download_f(void); +static void SV_VM_Setup(void); +extern cvar_t net_connecttimeout; + +cvar_t sv_worldmessage = {CVAR_READONLY, "sv_worldmessage", "", "title of current level"}; +cvar_t sv_worldname = {CVAR_READONLY, "sv_worldname", "", "name of current worldmodel"}; +cvar_t sv_worldnamenoextension = {CVAR_READONLY, "sv_worldnamenoextension", "", "name of current worldmodel without extension"}; +cvar_t sv_worldbasename = {CVAR_READONLY, "sv_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"}; + +cvar_t sv_disablenotify = {0, "sv_disablenotify", "1", "suppress broadcast prints when certain cvars are changed (CVAR_NOTIFY flag in engine code)"}; +cvar_t coop = {0, "coop","0", "coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch)"}; +cvar_t deathmatch = {0, "deathmatch","0", "deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons)"}; +cvar_t fraglimit = {CVAR_NOTIFY, "fraglimit","0", "ends level if this many frags is reached by any player"}; +cvar_t gamecfg = {0, "gamecfg", "0", "unused cvar in quake, can be used by mods"}; +cvar_t noexit = {CVAR_NOTIFY, "noexit","0", "kills anyone attempting to use an exit"}; +cvar_t nomonsters = {0, "nomonsters", "0", "unused cvar in quake, can be used by mods"}; +cvar_t pausable = {0, "pausable","1", "allow players to pause or not (otherwise, only the server admin can)"}; +cvar_t pr_checkextension = {CVAR_READONLY, "pr_checkextension", "1", "indicates to QuakeC that the standard quakec extensions system is available (if 0, quakec should not attempt to use extensions)"}; +cvar_t samelevel = {CVAR_NOTIFY, "samelevel","0", "repeats same level if level ends (due to timelimit or someone hitting an exit)"}; +cvar_t skill = {0, "skill","1", "difficulty level of game, affects monster layouts in levels, 0 = easy, 1 = normal, 2 = hard, 3 = nightmare (same layout as hard but monsters fire twice)"}; +cvar_t slowmo = {0, "slowmo", "1.0", "controls game speed, 0.5 is half speed, 2 is double speed"}; + +cvar_t sv_accelerate = {0, "sv_accelerate", "10", "rate at which a player accelerates to sv_maxspeed"}; +cvar_t sv_aim = {CVAR_SAVE, "sv_aim", "2", "maximum cosine angle for quake's vertical autoaim, a value above 1 completely disables the autoaim, quake used 0.93"}; +cvar_t sv_airaccel_qw = {0, "sv_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration; when < 0, the speed is clamped against the maximum allowed forward speed after the move"}; +cvar_t sv_airaccel_qw_stretchfactor = {0, "sv_airaccel_qw_stretchfactor", "0", "when set, the maximum acceleration increase the player may get compared to forward-acceleration when strafejumping"}; +cvar_t sv_airaccel_sideways_friction = {0, "sv_airaccel_sideways_friction", "", "anti-sideways movement stabilization (reduces speed gain when zigzagging); when < 0, only so much friction is applied that braking (by accelerating backwards) cannot be stronger"}; +cvar_t sv_airaccelerate = {0, "sv_airaccelerate", "-1", "rate at which a player accelerates to sv_maxairspeed while in the air, if less than 0 the sv_accelerate variable is used instead"}; +cvar_t sv_airstopaccelerate = {0, "sv_airstopaccelerate", "0", "when set, replacement for sv_airaccelerate when moving backwards"}; +cvar_t sv_airspeedlimit_nonqw = {0, "sv_airspeedlimit_nonqw", "0", "when set, this is a soft speed limit while in air when using airaccel_qw not equal to 1"}; +cvar_t sv_airstrafeaccelerate = {0, "sv_airstrafeaccelerate", "0", "when set, replacement for sv_airaccelerate when just strafing"}; +cvar_t sv_maxairstrafespeed = {0, "sv_maxairstrafespeed", "0", "when set, replacement for sv_maxairspeed when just strafing"}; +cvar_t sv_airstrafeaccel_qw = {0, "sv_airstrafeaccel_qw", "0", "when set, replacement for sv_airaccel_qw when just strafing"}; +cvar_t sv_aircontrol = {0, "sv_aircontrol", "0", "CPMA-style air control"}; +cvar_t sv_aircontrol_power = {0, "sv_aircontrol_power", "2", "CPMA-style air control exponent"}; +cvar_t sv_aircontrol_penalty = {0, "sv_aircontrol_penalty", "0", "deceleration while using CPMA-style air control"}; +cvar_t sv_allowdownloads = {0, "sv_allowdownloads", "1", "whether to allow clients to download files from the server (does not affect http downloads)"}; +cvar_t sv_allowdownloads_archive = {0, "sv_allowdownloads_archive", "0", "whether to allow downloads of archives (pak/pk3)"}; +cvar_t sv_allowdownloads_config = {0, "sv_allowdownloads_config", "0", "whether to allow downloads of config files (cfg)"}; +cvar_t sv_allowdownloads_dlcache = {0, "sv_allowdownloads_dlcache", "0", "whether to allow downloads of dlcache files (dlcache/)"}; +cvar_t sv_allowdownloads_inarchive = {0, "sv_allowdownloads_inarchive", "0", "whether to allow downloads from archives (pak/pk3)"}; +cvar_t sv_areagrid_mingridsize = {CVAR_NOTIFY, "sv_areagrid_mingridsize", "128", "minimum areagrid cell size, smaller values work better for lots of small objects, higher values for large objects"}; +cvar_t sv_checkforpacketsduringsleep = {0, "sv_checkforpacketsduringsleep", "0", "uses select() function to wait between frames which can be interrupted by packets being received, instead of Sleep()/usleep()/SDL_Sleep() functions which do not check for packets"}; +cvar_t sv_clmovement_enable = {0, "sv_clmovement_enable", "1", "whether to allow clients to use cl_movement prediction, which can cause choppy movement on the server which may annoy other players"}; +cvar_t sv_clmovement_minping = {0, "sv_clmovement_minping", "0", "if client ping is below this time in milliseconds, then their ability to use cl_movement prediction is disabled for a while (as they don't need it)"}; +cvar_t sv_clmovement_minping_disabletime = {0, "sv_clmovement_minping_disabletime", "1000", "when client falls below minping, disable their prediction for this many milliseconds (should be at least 1000 or else their prediction may turn on/off frequently)"}; +cvar_t sv_clmovement_inputtimeout = {0, "sv_clmovement_inputtimeout", "0.2", "when a client does not send input for this many seconds, force them to move anyway (unlike QuakeWorld)"}; +cvar_t sv_cullentities_nevercullbmodels = {0, "sv_cullentities_nevercullbmodels", "0", "if enabled the clients are always notified of moving doors and lifts and other submodels of world (warning: eats a lot of network bandwidth on some levels!)"}; +cvar_t sv_cullentities_pvs = {0, "sv_cullentities_pvs", "1", "fast but loose culling of hidden entities"}; +cvar_t sv_cullentities_stats = {0, "sv_cullentities_stats", "0", "displays stats on network entities culled by various methods for each client"}; +cvar_t sv_cullentities_trace = {0, "sv_cullentities_trace", "0", "somewhat slow but very tight culling of hidden entities, minimizes network traffic and makes wallhack cheats useless"}; +cvar_t sv_cullentities_trace_delay = {0, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; +cvar_t sv_cullentities_trace_delay_players = {0, "sv_cullentities_trace_delay_players", "0.2", "number of seconds until the entity gets actually culled if it is a player entity"}; +cvar_t sv_cullentities_trace_enlarge = {0, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; +cvar_t sv_cullentities_trace_prediction = {0, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"}; +cvar_t sv_cullentities_trace_prediction_time = {0, "sv_cullentities_trace_prediction_time", "0.2", "how many seconds of prediction to use"}; +cvar_t sv_cullentities_trace_entityocclusion = {0, "sv_cullentities_trace_entityocclusion", "0", "also check if doors and other bsp models are in the way"}; +cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "2", "number of samples to test for entity culling"}; +cvar_t sv_cullentities_trace_samples_extra = {0, "sv_cullentities_trace_samples_extra", "2", "number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight"}; +cvar_t sv_cullentities_trace_samples_players = {0, "sv_cullentities_trace_samples_players", "8", "number of samples to test for entity culling when the entity is a player entity"}; +cvar_t sv_debugmove = {CVAR_NOTIFY, "sv_debugmove", "0", "disables collision detection optimizations for debugging purposes"}; +cvar_t sv_echobprint = {CVAR_SAVE, "sv_echobprint", "1", "prints gamecode bprint() calls to server console"}; +cvar_t sv_edgefriction = {0, "edgefriction", "1", "how much you slow down when nearing a ledge you might fall off, multiplier of sv_friction (Quake used 2, QuakeWorld used 1 due to a bug in physics code)"}; +cvar_t sv_entpatch = {0, "sv_entpatch", "1", "enables loading of .ent files to override entities in the bsp (for example Threewave CTF server pack contains .ent patch files enabling play of CTF on id1 maps)"}; +cvar_t sv_fixedframeratesingleplayer = {0, "sv_fixedframeratesingleplayer", "1", "allows you to use server-style timing system in singleplayer (don't run faster than sys_ticrate)"}; +cvar_t sv_freezenonclients = {CVAR_NOTIFY, "sv_freezenonclients", "0", "freezes time, except for players, allowing you to walk around and take screenshots of explosions"}; +cvar_t sv_friction = {CVAR_NOTIFY, "sv_friction","4", "how fast you slow down"}; +cvar_t sv_gameplayfix_blowupfallenzombies = {0, "sv_gameplayfix_blowupfallenzombies", "1", "causes findradius to detect SOLID_NOT entities such as zombies and corpses on the floor, allowing splash damage to apply to them"}; +cvar_t sv_gameplayfix_consistentplayerprethink = {0, "sv_gameplayfix_consistentplayerprethink", "0", "improves fairness in multiplayer by running all PlayerPreThink functions (which fire weapons) before performing physics, then running all PlayerPostThink functions"}; +cvar_t sv_gameplayfix_delayprojectiles = {0, "sv_gameplayfix_delayprojectiles", "1", "causes entities to not move on the same frame they are spawned, meaning that projectiles wait until the next frame to perform their first move, giving proper interpolation and rocket trails, but making weapons harder to use at low framerates"}; +cvar_t sv_gameplayfix_droptofloorstartsolid = {0, "sv_gameplayfix_droptofloorstartsolid", "1", "prevents items and monsters that start in a solid area from falling out of the level (makes droptofloor treat trace_startsolid as an acceptable outcome)"}; +cvar_t sv_gameplayfix_droptofloorstartsolid_nudgetocorrect = {0, "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect", "1", "tries to nudge stuck items and monsters out of walls before droptofloor is performed"}; +cvar_t sv_gameplayfix_easierwaterjump = {0, "sv_gameplayfix_easierwaterjump", "1", "changes water jumping to make it easier to get out of water (exactly like in QuakeWorld)"}; +cvar_t sv_gameplayfix_findradiusdistancetobox = {0, "sv_gameplayfix_findradiusdistancetobox", "1", "causes findradius to check the distance to the corner of a box rather than the center of the box, makes findradius detect bmodels such as very large doors that would otherwise be unaffected by splash damage"}; +cvar_t sv_gameplayfix_gravityunaffectedbyticrate = {0, "sv_gameplayfix_gravityunaffectedbyticrate", "0", "fix some ticrate issues in physics."}; +cvar_t sv_gameplayfix_grenadebouncedownslopes = {0, "sv_gameplayfix_grenadebouncedownslopes", "1", "prevents MOVETYPE_BOUNCE (grenades) from getting stuck when fired down a downward sloping surface"}; +cvar_t sv_gameplayfix_multiplethinksperframe = {0, "sv_gameplayfix_multiplethinksperframe", "1", "allows entities to think more often than the server framerate, primarily useful for very high fire rate weapons"}; +cvar_t sv_gameplayfix_noairborncorpse = {0, "sv_gameplayfix_noairborncorpse", "1", "causes entities (corpses, items, etc) sitting ontop of moving entities (players) to fall when the moving entity (player) is no longer supporting them"}; +cvar_t sv_gameplayfix_noairborncorpse_allowsuspendeditems = {0, "sv_gameplayfix_noairborncorpse_allowsuspendeditems", "1", "causes entities sitting ontop of objects that are instantaneously remove to float in midair (special hack to allow a common level design trick for floating items)"}; +cvar_t sv_gameplayfix_nudgeoutofsolid = {0, "sv_gameplayfix_nudgeoutofsolid", "0", "attempts to fix physics errors (where an object ended up in solid for some reason)"}; +cvar_t sv_gameplayfix_nudgeoutofsolid_separation = {0, "sv_gameplayfix_nudgeoutofsolid_separation", "0.03125", "keep objects this distance apart to prevent collision issues on seams"}; +cvar_t sv_gameplayfix_q2airaccelerate = {0, "sv_gameplayfix_q2airaccelerate", "0", "Quake2-style air acceleration"}; +cvar_t sv_gameplayfix_nogravityonground = {0, "sv_gameplayfix_nogravityonground", "0", "turn off gravity when on ground (to get rid of sliding)"}; +cvar_t sv_gameplayfix_setmodelrealbox = {0, "sv_gameplayfix_setmodelrealbox", "1", "fixes a bug in Quake that made setmodel always set the entity box to ('-16 -16 -16', '16 16 16') rather than properly checking the model box, breaks some poorly coded mods"}; +cvar_t sv_gameplayfix_slidemoveprojectiles = {0, "sv_gameplayfix_slidemoveprojectiles", "1", "allows MOVETYPE_FLY/FLYMISSILE/TOSS/BOUNCE/BOUNCEMISSILE entities to finish their move in a frame even if they hit something, fixes 'gravity accumulation' bug for grenades on steep slopes"}; +cvar_t sv_gameplayfix_stepdown = {0, "sv_gameplayfix_stepdown", "0", "attempts to step down stairs, not just up them (prevents the familiar thud..thud..thud.. when running down stairs and slopes)"}; +cvar_t sv_gameplayfix_stepmultipletimes = {0, "sv_gameplayfix_stepmultipletimes", "0", "applies step-up onto a ledge more than once in a single frame, when running quickly up stairs"}; +cvar_t sv_gameplayfix_nostepmoveonsteepslopes = {0, "sv_gameplayfix_nostepmoveonsteepslopes", "0", "crude fix which prevents MOVETYPE_STEP (not swimming or flying) to move on slopes whose angle is bigger than 45 degree"}; +cvar_t sv_gameplayfix_swiminbmodels = {0, "sv_gameplayfix_swiminbmodels", "1", "causes pointcontents (used to determine if you are in a liquid) to check bmodel entities as well as the world model, so you can swim around in (possibly moving) water bmodel entities"}; +cvar_t sv_gameplayfix_upwardvelocityclearsongroundflag = {0, "sv_gameplayfix_upwardvelocityclearsongroundflag", "1", "prevents monsters, items, and most other objects from being stuck to the floor when pushed around by damage, and other situations in mods"}; +cvar_t sv_gameplayfix_downtracesupportsongroundflag = {0, "sv_gameplayfix_downtracesupportsongroundflag", "1", "prevents very short moves from clearing onground (which may make the player stick to the floor at high netfps)"}; +cvar_t sv_gameplayfix_q1bsptracelinereportstexture = {0, "sv_gameplayfix_q1bsptracelinereportstexture", "1", "enables mods to get accurate trace_texture results on q1bsp by using a surface-hitting traceline implementation rather than the standard solidbsp method, q3bsp always reports texture accurately"}; +cvar_t sv_gameplayfix_unstickplayers = {0, "sv_gameplayfix_unstickplayers", "1", "big hack to try and fix the rare case of MOVETYPE_WALK entities getting stuck in the world clipping hull."}; +cvar_t sv_gameplayfix_unstickentities = {0, "sv_gameplayfix_unstickentities", "1", "hack to check if entities are crossing world collision hull and try to move them to the right position"}; +cvar_t sv_gameplayfix_fixedcheckwatertransition = {0, "sv_gameplayfix_fixedcheckwatertransition", "1", "fix two very stupid bugs in SV_CheckWaterTransition when watertype is CONTENTS_EMPTY (the bugs causes waterlevel to be 1 on first frame, -1 on second frame - the fix makes it 0 on both frames)"}; +cvar_t sv_gravity = {CVAR_NOTIFY, "sv_gravity","800", "how fast you fall (512 = roughly earth gravity)"}; +cvar_t sv_idealpitchscale = {0, "sv_idealpitchscale","0.8", "how much to look up/down slopes and stairs when not using freelook"}; +cvar_t sv_jumpstep = {CVAR_NOTIFY, "sv_jumpstep", "0", "whether you can step up while jumping"}; +cvar_t sv_jumpvelocity = {0, "sv_jumpvelocity", "270", "cvar that can be used by QuakeC code for jump velocity"}; +cvar_t sv_maxairspeed = {0, "sv_maxairspeed", "30", "maximum speed a player can accelerate to when airborn (note that it is possible to completely stop by moving the opposite direction)"}; +cvar_t sv_maxrate = {CVAR_SAVE | CVAR_NOTIFY, "sv_maxrate", "1000000", "upper limit on client rate cvar, should reflect your network connection quality"}; +cvar_t sv_maxspeed = {CVAR_NOTIFY, "sv_maxspeed", "320", "maximum speed a player can accelerate to when on ground (can be exceeded by tricks)"}; +cvar_t sv_maxvelocity = {CVAR_NOTIFY, "sv_maxvelocity","2000", "universal speed limit on all entities"}; +cvar_t sv_nostep = {CVAR_NOTIFY, "sv_nostep","0", "prevents MOVETYPE_STEP entities (monsters) from moving"}; +cvar_t sv_playerphysicsqc = {CVAR_NOTIFY, "sv_playerphysicsqc", "1", "enables QuakeC function to override player physics"}; +cvar_t sv_progs = {0, "sv_progs", "progs.dat", "selects which quakec progs.dat file to run" }; +cvar_t sv_protocolname = {0, "sv_protocolname", "DP7", "selects network protocol to host for (values include QUAKE, QUAKEDP, NEHAHRAMOVIE, DP1 and up)"}; +cvar_t sv_random_seed = {0, "sv_random_seed", "", "random seed; when set, on every map start this random seed is used to initialize the random number generator. Don't touch it unless for benchmarking or debugging"}; +cvar_t sv_ratelimitlocalplayer = {0, "sv_ratelimitlocalplayer", "0", "whether to apply rate limiting to the local player in a listen server (only useful for testing)"}; +cvar_t sv_sound_land = {0, "sv_sound_land", "demon/dland2.wav", "sound to play when MOVETYPE_STEP entity hits the ground at high speed (empty cvar disables the sound)"}; +cvar_t sv_sound_watersplash = {0, "sv_sound_watersplash", "misc/h2ohit1.wav", "sound to play when MOVETYPE_FLY/TOSS/BOUNCE/STEP entity enters or leaves water (empty cvar disables the sound)"}; +cvar_t sv_stepheight = {CVAR_NOTIFY, "sv_stepheight", "18", "how high you can step up (TW_SV_STEPCONTROL extension)"}; +cvar_t sv_stopspeed = {CVAR_NOTIFY, "sv_stopspeed","100", "how fast you come to a complete stop"}; +cvar_t sv_wallfriction = {CVAR_NOTIFY, "sv_wallfriction", "1", "how much you slow down when sliding along a wall"}; +cvar_t sv_wateraccelerate = {0, "sv_wateraccelerate", "-1", "rate at which a player accelerates to sv_maxspeed while in the air, if less than 0 the sv_accelerate variable is used instead"}; +cvar_t sv_waterfriction = {CVAR_NOTIFY, "sv_waterfriction","-1", "how fast you slow down, if less than 0 the sv_friction variable is used instead"}; +cvar_t sv_warsowbunny_airforwardaccel = {0, "sv_warsowbunny_airforwardaccel", "1.00001", "how fast you accelerate until you reach sv_maxspeed"}; +cvar_t sv_warsowbunny_accel = {0, "sv_warsowbunny_accel", "0.1585", "how fast you accelerate until after reaching sv_maxspeed (it gets harder as you near sv_warsowbunny_topspeed)"}; +cvar_t sv_warsowbunny_topspeed = {0, "sv_warsowbunny_topspeed", "925", "soft speed limit (can get faster with rjs and on ramps)"}; +cvar_t sv_warsowbunny_turnaccel = {0, "sv_warsowbunny_turnaccel", "0", "max sharpness of turns (also master switch for the sv_warsowbunny_* mode; set this to 9 to enable)"}; +cvar_t sv_warsowbunny_backtosideratio = {0, "sv_warsowbunny_backtosideratio", "0.8", "lower values make it easier to change direction without losing speed; the drawback is \"understeering\" in sharp turns"}; +cvar_t sv_onlycsqcnetworking = {0, "sv_onlycsqcnetworking", "0", "disables legacy entity networking code for higher performance (except on clients, which can still be legacy)"}; +cvar_t sv_areadebug = {0, "sv_areadebug", "0", "disables physics culling for debugging purposes (only for development)"}; +cvar_t sys_ticrate = {CVAR_SAVE, "sys_ticrate","0.0138889", "how long a server frame is in seconds, 0.05 is 20fps server rate, 0.1 is 10fps (can not be set higher than 0.1), 0 runs as many server frames as possible (makes games against bots a little smoother, overwhelms network players), 0.0138889 matches QuakeWorld physics"}; +cvar_t teamplay = {CVAR_NOTIFY, "teamplay","0", "teamplay mode, values depend on mod but typically 0 = no teams, 1 = no team damage no self damage, 2 = team damage and self damage, some mods support 3 = no team damage but can damage self"}; +cvar_t timelimit = {CVAR_NOTIFY, "timelimit","0", "ends level at this time (in minutes)"}; +cvar_t sv_threaded = {0, "sv_threaded", "0", "enables a separate thread for server code, improving performance, especially when hosting a game while playing, EXPERIMENTAL, may be crashy"}; + +cvar_t saved1 = {CVAR_SAVE, "saved1", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved2 = {CVAR_SAVE, "saved2", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved3 = {CVAR_SAVE, "saved3", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t saved4 = {CVAR_SAVE, "saved4", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t savedgamecfg = {CVAR_SAVE, "savedgamecfg", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; +cvar_t scratch1 = {0, "scratch1", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch2 = {0,"scratch2", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch3 = {0, "scratch3", "0", "unused cvar in quake, can be used by mods"}; +cvar_t scratch4 = {0, "scratch4", "0", "unused cvar in quake, can be used by mods"}; +cvar_t temp1 = {0, "temp1","0", "general cvar for mods to use, in stock id1 this selects which death animation to use on players (0 = random death, other values select specific death scenes)"}; + +cvar_t nehx00 = {0, "nehx00", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx01 = {0, "nehx01", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx02 = {0, "nehx02", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx03 = {0, "nehx03", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx04 = {0, "nehx04", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx05 = {0, "nehx05", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx06 = {0, "nehx06", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx07 = {0, "nehx07", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx08 = {0, "nehx08", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx09 = {0, "nehx09", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx10 = {0, "nehx10", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx11 = {0, "nehx11", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx12 = {0, "nehx12", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx13 = {0, "nehx13", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx14 = {0, "nehx14", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx15 = {0, "nehx15", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx16 = {0, "nehx16", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx17 = {0, "nehx17", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx18 = {0, "nehx18", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t nehx19 = {0, "nehx19", "0", "nehahra data storage cvar (used in singleplayer)"}; +cvar_t cutscene = {0, "cutscene", "1", "enables cutscenes in nehahra, can be used by other mods"}; + +cvar_t sv_autodemo_perclient = {CVAR_SAVE, "sv_autodemo_perclient", "0", "set to 1 to enable autorecorded per-client demos (they'll start to record at the beginning of a match); set it to 2 to also record client->server packets (for debugging)"}; +cvar_t sv_autodemo_perclient_nameformat = {CVAR_SAVE, "sv_autodemo_perclient_nameformat", "sv_autodemos/%Y-%m-%d_%H-%M", "The format of the sv_autodemo_perclient filename, followed by the map name, the client number and the IP address + port number, separated by underscores (the date is encoded using strftime escapes)" }; +cvar_t sv_autodemo_perclient_discardable = {CVAR_SAVE, "sv_autodemo_perclient_discardable", "0", "Allow game code to decide whether a demo should be kept or discarded."}; + +cvar_t halflifebsp = {0, "halflifebsp", "0", "indicates the current map is hlbsp format (useful to know because of different bounding box sizes)"}; + +server_t sv; +server_static_t svs; + +mempool_t *sv_mempool = NULL; + +extern cvar_t slowmo; +extern float scr_centertime_off; + +// MUST match effectnameindex_t in client.h +static const char *standardeffectnames[EFFECT_TOTAL] = +{ + "", + "TE_GUNSHOT", + "TE_GUNSHOTQUAD", + "TE_SPIKE", + "TE_SPIKEQUAD", + "TE_SUPERSPIKE", + "TE_SUPERSPIKEQUAD", + "TE_WIZSPIKE", + "TE_KNIGHTSPIKE", + "TE_EXPLOSION", + "TE_EXPLOSIONQUAD", + "TE_TAREXPLOSION", + "TE_TELEPORT", + "TE_LAVASPLASH", + "TE_SMALLFLASH", + "TE_FLAMEJET", + "EF_FLAME", + "TE_BLOOD", + "TE_SPARK", + "TE_PLASMABURN", + "TE_TEI_G3", + "TE_TEI_SMOKE", + "TE_TEI_BIGEXPLOSION", + "TE_TEI_PLASMAHIT", + "EF_STARDUST", + "TR_ROCKET", + "TR_GRENADE", + "TR_BLOOD", + "TR_WIZSPIKE", + "TR_SLIGHTBLOOD", + "TR_KNIGHTSPIKE", + "TR_VORESPIKE", + "TR_NEHAHRASMOKE", + "TR_NEXUIZPLASMA", + "TR_GLOWTRAIL", + "SVC_PARTICLE" +}; + +#define SV_REQFUNCS 0 +#define sv_reqfuncs NULL + +//#define SV_REQFUNCS (sizeof(sv_reqfuncs) / sizeof(const char *)) +//static const char *sv_reqfuncs[] = { +//}; + +#define SV_REQFIELDS (sizeof(sv_reqfields) / sizeof(prvm_required_field_t)) + +prvm_required_field_t sv_reqfields[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) +#define PRVM_DECLARE_serverglobalvector(x) +#define PRVM_DECLARE_serverglobalstring(x) +#define PRVM_DECLARE_serverglobaledict(x) +#define PRVM_DECLARE_serverglobalfunction(x) +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_serverfieldvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_serverfieldstring(x) {ev_string, #x}, +#define PRVM_DECLARE_serverfieldedict(x) {ev_entity, #x}, +#define PRVM_DECLARE_serverfieldfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + +#define SV_REQGLOBALS (sizeof(sv_reqglobals) / sizeof(prvm_required_field_t)) + +prvm_required_field_t sv_reqglobals[] = +{ +#define PRVM_DECLARE_serverglobalfloat(x) {ev_float, #x}, +#define PRVM_DECLARE_serverglobalvector(x) {ev_vector, #x}, +#define PRVM_DECLARE_serverglobalstring(x) {ev_string, #x}, +#define PRVM_DECLARE_serverglobaledict(x) {ev_entity, #x}, +#define PRVM_DECLARE_serverglobalfunction(x) {ev_function, #x}, +#define PRVM_DECLARE_clientglobalfloat(x) +#define PRVM_DECLARE_clientglobalvector(x) +#define PRVM_DECLARE_clientglobalstring(x) +#define PRVM_DECLARE_clientglobaledict(x) +#define PRVM_DECLARE_clientglobalfunction(x) +#define PRVM_DECLARE_menuglobalfloat(x) +#define PRVM_DECLARE_menuglobalvector(x) +#define PRVM_DECLARE_menuglobalstring(x) +#define PRVM_DECLARE_menuglobaledict(x) +#define PRVM_DECLARE_menuglobalfunction(x) +#define PRVM_DECLARE_serverfieldfloat(x) +#define PRVM_DECLARE_serverfieldvector(x) +#define PRVM_DECLARE_serverfieldstring(x) +#define PRVM_DECLARE_serverfieldedict(x) +#define PRVM_DECLARE_serverfieldfunction(x) +#define PRVM_DECLARE_clientfieldfloat(x) +#define PRVM_DECLARE_clientfieldvector(x) +#define PRVM_DECLARE_clientfieldstring(x) +#define PRVM_DECLARE_clientfieldedict(x) +#define PRVM_DECLARE_clientfieldfunction(x) +#define PRVM_DECLARE_menufieldfloat(x) +#define PRVM_DECLARE_menufieldvector(x) +#define PRVM_DECLARE_menufieldstring(x) +#define PRVM_DECLARE_menufieldedict(x) +#define PRVM_DECLARE_menufieldfunction(x) +#define PRVM_DECLARE_serverfunction(x) +#define PRVM_DECLARE_clientfunction(x) +#define PRVM_DECLARE_menufunction(x) +#define PRVM_DECLARE_field(x) +#define PRVM_DECLARE_global(x) +#define PRVM_DECLARE_function(x) +#include "prvm_offsets.h" +#undef PRVM_DECLARE_serverglobalfloat +#undef PRVM_DECLARE_serverglobalvector +#undef PRVM_DECLARE_serverglobalstring +#undef PRVM_DECLARE_serverglobaledict +#undef PRVM_DECLARE_serverglobalfunction +#undef PRVM_DECLARE_clientglobalfloat +#undef PRVM_DECLARE_clientglobalvector +#undef PRVM_DECLARE_clientglobalstring +#undef PRVM_DECLARE_clientglobaledict +#undef PRVM_DECLARE_clientglobalfunction +#undef PRVM_DECLARE_menuglobalfloat +#undef PRVM_DECLARE_menuglobalvector +#undef PRVM_DECLARE_menuglobalstring +#undef PRVM_DECLARE_menuglobaledict +#undef PRVM_DECLARE_menuglobalfunction +#undef PRVM_DECLARE_serverfieldfloat +#undef PRVM_DECLARE_serverfieldvector +#undef PRVM_DECLARE_serverfieldstring +#undef PRVM_DECLARE_serverfieldedict +#undef PRVM_DECLARE_serverfieldfunction +#undef PRVM_DECLARE_clientfieldfloat +#undef PRVM_DECLARE_clientfieldvector +#undef PRVM_DECLARE_clientfieldstring +#undef PRVM_DECLARE_clientfieldedict +#undef PRVM_DECLARE_clientfieldfunction +#undef PRVM_DECLARE_menufieldfloat +#undef PRVM_DECLARE_menufieldvector +#undef PRVM_DECLARE_menufieldstring +#undef PRVM_DECLARE_menufieldedict +#undef PRVM_DECLARE_menufieldfunction +#undef PRVM_DECLARE_serverfunction +#undef PRVM_DECLARE_clientfunction +#undef PRVM_DECLARE_menufunction +#undef PRVM_DECLARE_field +#undef PRVM_DECLARE_global +#undef PRVM_DECLARE_function +}; + + + +//============================================================================ + +static void SV_AreaStats_f(void) +{ + World_PrintAreaStats(&sv.world, "server"); +} + +/* +=============== +SV_Init +=============== +*/ +void SV_Init (void) +{ + // init the csqc progs cvars, since they are updated/used by the server code + // TODO: fix this since this is a quick hack to make some of [515]'s broken code run ;) [9/13/2006 Black] + extern cvar_t csqc_progname; //[515]: csqc crc check and right csprogs name according to progs.dat + extern cvar_t csqc_progcrc; + extern cvar_t csqc_progsize; + extern cvar_t csqc_usedemoprogs; + + Cvar_RegisterVariable(&sv_worldmessage); + Cvar_RegisterVariable(&sv_worldname); + Cvar_RegisterVariable(&sv_worldnamenoextension); + Cvar_RegisterVariable(&sv_worldbasename); + + Cvar_RegisterVariable (&csqc_progname); + Cvar_RegisterVariable (&csqc_progcrc); + Cvar_RegisterVariable (&csqc_progsize); + Cvar_RegisterVariable (&csqc_usedemoprogs); + + Cmd_AddCommand("sv_saveentfile", SV_SaveEntFile_f, "save map entities to .ent file (to allow external editing)"); + Cmd_AddCommand("sv_areastats", SV_AreaStats_f, "prints statistics on entity culling during collision traces"); + Cmd_AddCommand_WithClientCommand("sv_startdownload", NULL, SV_StartDownload_f, "begins sending a file to the client (network protocol use only)"); + Cmd_AddCommand_WithClientCommand("download", NULL, SV_Download_f, "downloads a specified file from the server"); + + Cvar_RegisterVariable (&sv_disablenotify); + Cvar_RegisterVariable (&coop); + Cvar_RegisterVariable (&deathmatch); + Cvar_RegisterVariable (&fraglimit); + Cvar_RegisterVariable (&gamecfg); + Cvar_RegisterVariable (&noexit); + Cvar_RegisterVariable (&nomonsters); + Cvar_RegisterVariable (&pausable); + Cvar_RegisterVariable (&pr_checkextension); + Cvar_RegisterVariable (&samelevel); + Cvar_RegisterVariable (&skill); + Cvar_RegisterVariable (&slowmo); + Cvar_RegisterVariable (&sv_accelerate); + Cvar_RegisterVariable (&sv_aim); + Cvar_RegisterVariable (&sv_airaccel_qw); + Cvar_RegisterVariable (&sv_airaccel_qw_stretchfactor); + Cvar_RegisterVariable (&sv_airaccel_sideways_friction); + Cvar_RegisterVariable (&sv_airaccelerate); + Cvar_RegisterVariable (&sv_airstopaccelerate); + Cvar_RegisterVariable (&sv_airstrafeaccelerate); + Cvar_RegisterVariable (&sv_maxairstrafespeed); + Cvar_RegisterVariable (&sv_airstrafeaccel_qw); + Cvar_RegisterVariable (&sv_airspeedlimit_nonqw); + Cvar_RegisterVariable (&sv_aircontrol); + Cvar_RegisterVariable (&sv_aircontrol_power); + Cvar_RegisterVariable (&sv_aircontrol_penalty); + Cvar_RegisterVariable (&sv_allowdownloads); + Cvar_RegisterVariable (&sv_allowdownloads_archive); + Cvar_RegisterVariable (&sv_allowdownloads_config); + Cvar_RegisterVariable (&sv_allowdownloads_dlcache); + Cvar_RegisterVariable (&sv_allowdownloads_inarchive); + Cvar_RegisterVariable (&sv_areagrid_mingridsize); + Cvar_RegisterVariable (&sv_checkforpacketsduringsleep); + Cvar_RegisterVariable (&sv_clmovement_enable); + Cvar_RegisterVariable (&sv_clmovement_minping); + Cvar_RegisterVariable (&sv_clmovement_minping_disabletime); + Cvar_RegisterVariable (&sv_clmovement_inputtimeout); + Cvar_RegisterVariable (&sv_cullentities_nevercullbmodels); + Cvar_RegisterVariable (&sv_cullentities_pvs); + Cvar_RegisterVariable (&sv_cullentities_stats); + Cvar_RegisterVariable (&sv_cullentities_trace); + Cvar_RegisterVariable (&sv_cullentities_trace_delay); + Cvar_RegisterVariable (&sv_cullentities_trace_delay_players); + Cvar_RegisterVariable (&sv_cullentities_trace_enlarge); + Cvar_RegisterVariable (&sv_cullentities_trace_entityocclusion); + Cvar_RegisterVariable (&sv_cullentities_trace_prediction); + Cvar_RegisterVariable (&sv_cullentities_trace_prediction_time); + Cvar_RegisterVariable (&sv_cullentities_trace_samples); + Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra); + Cvar_RegisterVariable (&sv_cullentities_trace_samples_players); + Cvar_RegisterVariable (&sv_debugmove); + Cvar_RegisterVariable (&sv_echobprint); + Cvar_RegisterVariable (&sv_edgefriction); + Cvar_RegisterVariable (&sv_entpatch); + Cvar_RegisterVariable (&sv_fixedframeratesingleplayer); + Cvar_RegisterVariable (&sv_freezenonclients); + Cvar_RegisterVariable (&sv_friction); + Cvar_RegisterVariable (&sv_gameplayfix_blowupfallenzombies); + Cvar_RegisterVariable (&sv_gameplayfix_consistentplayerprethink); + Cvar_RegisterVariable (&sv_gameplayfix_delayprojectiles); + Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid); + Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid_nudgetocorrect); + Cvar_RegisterVariable (&sv_gameplayfix_easierwaterjump); + Cvar_RegisterVariable (&sv_gameplayfix_findradiusdistancetobox); + Cvar_RegisterVariable (&sv_gameplayfix_gravityunaffectedbyticrate); + Cvar_RegisterVariable (&sv_gameplayfix_grenadebouncedownslopes); + Cvar_RegisterVariable (&sv_gameplayfix_multiplethinksperframe); + Cvar_RegisterVariable (&sv_gameplayfix_noairborncorpse); + Cvar_RegisterVariable (&sv_gameplayfix_noairborncorpse_allowsuspendeditems); + Cvar_RegisterVariable (&sv_gameplayfix_nudgeoutofsolid); + Cvar_RegisterVariable (&sv_gameplayfix_nudgeoutofsolid_separation); + Cvar_RegisterVariable (&sv_gameplayfix_q2airaccelerate); + Cvar_RegisterVariable (&sv_gameplayfix_nogravityonground); + Cvar_RegisterVariable (&sv_gameplayfix_setmodelrealbox); + Cvar_RegisterVariable (&sv_gameplayfix_slidemoveprojectiles); + Cvar_RegisterVariable (&sv_gameplayfix_stepdown); + Cvar_RegisterVariable (&sv_gameplayfix_stepmultipletimes); + Cvar_RegisterVariable (&sv_gameplayfix_nostepmoveonsteepslopes); + Cvar_RegisterVariable (&sv_gameplayfix_swiminbmodels); + Cvar_RegisterVariable (&sv_gameplayfix_upwardvelocityclearsongroundflag); + Cvar_RegisterVariable (&sv_gameplayfix_downtracesupportsongroundflag); + Cvar_RegisterVariable (&sv_gameplayfix_q1bsptracelinereportstexture); + Cvar_RegisterVariable (&sv_gameplayfix_unstickplayers); + Cvar_RegisterVariable (&sv_gameplayfix_unstickentities); + Cvar_RegisterVariable (&sv_gameplayfix_fixedcheckwatertransition); + Cvar_RegisterVariable (&sv_gravity); + Cvar_RegisterVariable (&sv_idealpitchscale); + Cvar_RegisterVariable (&sv_jumpstep); + Cvar_RegisterVariable (&sv_jumpvelocity); + Cvar_RegisterVariable (&sv_maxairspeed); + Cvar_RegisterVariable (&sv_maxrate); + Cvar_RegisterVariable (&sv_maxspeed); + Cvar_RegisterVariable (&sv_maxvelocity); + Cvar_RegisterVariable (&sv_nostep); + Cvar_RegisterVariable (&sv_playerphysicsqc); + Cvar_RegisterVariable (&sv_progs); + Cvar_RegisterVariable (&sv_protocolname); + Cvar_RegisterVariable (&sv_random_seed); + Cvar_RegisterVariable (&sv_ratelimitlocalplayer); + Cvar_RegisterVariable (&sv_sound_land); + Cvar_RegisterVariable (&sv_sound_watersplash); + Cvar_RegisterVariable (&sv_stepheight); + Cvar_RegisterVariable (&sv_stopspeed); + Cvar_RegisterVariable (&sv_wallfriction); + Cvar_RegisterVariable (&sv_wateraccelerate); + Cvar_RegisterVariable (&sv_waterfriction); + Cvar_RegisterVariable (&sv_warsowbunny_airforwardaccel); + Cvar_RegisterVariable (&sv_warsowbunny_accel); + Cvar_RegisterVariable (&sv_warsowbunny_topspeed); + Cvar_RegisterVariable (&sv_warsowbunny_turnaccel); + Cvar_RegisterVariable (&sv_warsowbunny_backtosideratio); + Cvar_RegisterVariable (&sv_onlycsqcnetworking); + Cvar_RegisterVariable (&sv_areadebug); + Cvar_RegisterVariable (&sys_ticrate); + Cvar_RegisterVariable (&teamplay); + Cvar_RegisterVariable (&timelimit); + Cvar_RegisterVariable (&sv_threaded); + + Cvar_RegisterVariable (&saved1); + Cvar_RegisterVariable (&saved2); + Cvar_RegisterVariable (&saved3); + Cvar_RegisterVariable (&saved4); + Cvar_RegisterVariable (&savedgamecfg); + Cvar_RegisterVariable (&scratch1); + Cvar_RegisterVariable (&scratch2); + Cvar_RegisterVariable (&scratch3); + Cvar_RegisterVariable (&scratch4); + Cvar_RegisterVariable (&temp1); + + // LordHavoc: Nehahra uses these to pass data around cutscene demos + Cvar_RegisterVariable (&nehx00); + Cvar_RegisterVariable (&nehx01); + Cvar_RegisterVariable (&nehx02); + Cvar_RegisterVariable (&nehx03); + Cvar_RegisterVariable (&nehx04); + Cvar_RegisterVariable (&nehx05); + Cvar_RegisterVariable (&nehx06); + Cvar_RegisterVariable (&nehx07); + Cvar_RegisterVariable (&nehx08); + Cvar_RegisterVariable (&nehx09); + Cvar_RegisterVariable (&nehx10); + Cvar_RegisterVariable (&nehx11); + Cvar_RegisterVariable (&nehx12); + Cvar_RegisterVariable (&nehx13); + Cvar_RegisterVariable (&nehx14); + Cvar_RegisterVariable (&nehx15); + Cvar_RegisterVariable (&nehx16); + Cvar_RegisterVariable (&nehx17); + Cvar_RegisterVariable (&nehx18); + Cvar_RegisterVariable (&nehx19); + Cvar_RegisterVariable (&cutscene); // for Nehahra but useful to other mods as well + + Cvar_RegisterVariable (&sv_autodemo_perclient); + Cvar_RegisterVariable (&sv_autodemo_perclient_nameformat); + Cvar_RegisterVariable (&sv_autodemo_perclient_discardable); + + Cvar_RegisterVariable (&halflifebsp); + + sv_mempool = Mem_AllocPool("server", 0, NULL); +} + +static void SV_SaveEntFile_f(void) +{ + char vabuf[1024]; + if (!sv.active || !sv.worldmodel) + { + Con_Print("Not running a server\n"); + return; + } + FS_WriteFile(va(vabuf, sizeof(vabuf), "%s.ent", sv.worldnamenoextension), sv.worldmodel->brush.entities, (fs_offset_t)strlen(sv.worldmodel->brush.entities)); +} + + +/* +============================================================================= + +EVENT MESSAGES + +============================================================================= +*/ + +/* +================== +SV_StartParticle + +Make sure the event gets sent to all clients +================== +*/ +void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count) +{ + int i; + + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-18) + return; + MSG_WriteByte (&sv.datagram, svc_particle); + MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); + for (i=0 ; i<3 ; i++) + MSG_WriteChar (&sv.datagram, (int)bound(-128, dir[i]*16, 127)); + MSG_WriteByte (&sv.datagram, count); + MSG_WriteByte (&sv.datagram, color); + SV_FlushBroadcastMessages(); +} + +/* +================== +SV_StartEffect + +Make sure the event gets sent to all clients +================== +*/ +void SV_StartEffect (vec3_t org, int modelindex, int startframe, int framecount, int framerate) +{ + if (modelindex >= 256 || startframe >= 256) + { + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-19) + return; + MSG_WriteByte (&sv.datagram, svc_effect2); + MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); + MSG_WriteShort (&sv.datagram, modelindex); + MSG_WriteShort (&sv.datagram, startframe); + MSG_WriteByte (&sv.datagram, framecount); + MSG_WriteByte (&sv.datagram, framerate); + } + else + { + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-17) + return; + MSG_WriteByte (&sv.datagram, svc_effect); + MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); + MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); + MSG_WriteByte (&sv.datagram, modelindex); + MSG_WriteByte (&sv.datagram, startframe); + MSG_WriteByte (&sv.datagram, framecount); + MSG_WriteByte (&sv.datagram, framerate); + } + SV_FlushBroadcastMessages(); +} + +/* +================== +SV_StartSound + +Each entity can have eight independant sound sources, like voice, +weapon, feet, etc. + +Channel 0 is an auto-allocate channel, the others override anything +already running on that entity/channel pair. + +An attenuation of 0 will play full volume everywhere in the level. +Larger attenuations will drop off. (max 4 attenuation) + +================== +*/ +void SV_StartSound (prvm_edict_t *entity, int channel, const char *sample, int volume, float attenuation, qboolean reliable, float speed) +{ + prvm_prog_t *prog = SVVM_prog; + sizebuf_t *dest; + int sound_num, field_mask, i, ent, speed4000; + + dest = (reliable ? &sv.reliable_datagram : &sv.datagram); + + if (volume < 0 || volume > 255) + { + Con_Printf ("SV_StartSound: volume = %i\n", volume); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + Con_Printf ("SV_StartSound: attenuation = %f\n", attenuation); + return; + } + + if (!IS_CHAN(channel)) + { + Con_Printf ("SV_StartSound: channel = %i\n", channel); + return; + } + + channel = CHAN_ENGINE2NET(channel); + + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-21) + return; + +// find precache number for sound + sound_num = SV_SoundIndex(sample, 1); + if (!sound_num) + return; + + ent = PRVM_NUM_FOR_EDICT(entity); + + speed4000 = (int)floor(speed * 4000.0f + 0.5f); + field_mask = 0; + if (volume != DEFAULT_SOUND_PACKET_VOLUME) + field_mask |= SND_VOLUME; + if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) + field_mask |= SND_ATTENUATION; + if (speed4000 && speed4000 != 4000) + field_mask |= SND_SPEEDUSHORT4000; + if (ent >= 8192 || channel < 0 || channel > 7) + field_mask |= SND_LARGEENTITY; + if (sound_num >= 256) + field_mask |= SND_LARGESOUND; + +// directed messages go only to the entity they are targeted on + MSG_WriteByte (dest, svc_sound); + MSG_WriteByte (dest, field_mask); + if (field_mask & SND_VOLUME) + MSG_WriteByte (dest, volume); + if (field_mask & SND_ATTENUATION) + MSG_WriteByte (dest, (int)(attenuation*64)); + if (field_mask & SND_SPEEDUSHORT4000) + MSG_WriteShort (dest, speed4000); + if (field_mask & SND_LARGEENTITY) + { + MSG_WriteShort (dest, ent); + MSG_WriteChar (dest, channel); + } + else + MSG_WriteShort (dest, (ent<<3) | channel); + if ((field_mask & SND_LARGESOUND) || sv.protocol == PROTOCOL_NEHAHRABJP2) + MSG_WriteShort (dest, sound_num); + else + MSG_WriteByte (dest, sound_num); + for (i = 0;i < 3;i++) + MSG_WriteCoord (dest, PRVM_serveredictvector(entity, origin)[i]+0.5*(PRVM_serveredictvector(entity, mins)[i]+PRVM_serveredictvector(entity, maxs)[i]), sv.protocol); + + // TODO do we have to do anything here when dest is &sv.reliable_datagram? + if(!reliable) + SV_FlushBroadcastMessages(); +} + +/* +================== +SV_StartPointSound + +Nearly the same logic as SV_StartSound, except an origin +instead of an entity is provided and channel is omitted. + +The entity sent to the client is 0 (world) and the channel +is 0 (CHAN_AUTO). SND_LARGEENTITY will never occur in this +function, therefore the check for it is omitted. + +================== +*/ +void SV_StartPointSound (vec3_t origin, const char *sample, int volume, float attenuation, float speed) +{ + int sound_num, field_mask, i, speed4000; + + if (volume < 0 || volume > 255) + { + Con_Printf ("SV_StartPointSound: volume = %i\n", volume); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + Con_Printf ("SV_StartPointSound: attenuation = %f\n", attenuation); + return; + } + + if (sv.datagram.cursize > MAX_PACKETFRAGMENT-21) + return; + + // find precache number for sound + sound_num = SV_SoundIndex(sample, 1); + if (!sound_num) + return; + + speed4000 = (int)(speed * 40.0f); + field_mask = 0; + if (volume != DEFAULT_SOUND_PACKET_VOLUME) + field_mask |= SND_VOLUME; + if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) + field_mask |= SND_ATTENUATION; + if (sound_num >= 256) + field_mask |= SND_LARGESOUND; + if (speed4000 && speed4000 != 4000) + field_mask |= SND_SPEEDUSHORT4000; + +// directed messages go only to the entity they are targeted on + MSG_WriteByte (&sv.datagram, svc_sound); + MSG_WriteByte (&sv.datagram, field_mask); + if (field_mask & SND_VOLUME) + MSG_WriteByte (&sv.datagram, volume); + if (field_mask & SND_ATTENUATION) + MSG_WriteByte (&sv.datagram, (int)(attenuation*64)); + if (field_mask & SND_SPEEDUSHORT4000) + MSG_WriteShort (&sv.datagram, speed4000); + // Always write entnum 0 for the world entity + MSG_WriteShort (&sv.datagram, (0<<3) | 0); + if (field_mask & SND_LARGESOUND) + MSG_WriteShort (&sv.datagram, sound_num); + else + MSG_WriteByte (&sv.datagram, sound_num); + for (i = 0;i < 3;i++) + MSG_WriteCoord (&sv.datagram, origin[i], sv.protocol); + SV_FlushBroadcastMessages(); +} + +/* +============================================================================== + +CLIENT SPAWNING + +============================================================================== +*/ + +/* +================ +SV_SendServerinfo + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each server load. +================ +*/ +void SV_SendServerinfo (client_t *client) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + char message[128]; + char vabuf[1024]; + + // we know that this client has a netconnection and thus is not a bot + + // edicts get reallocated on level changes, so we need to update it here + client->edict = PRVM_EDICT_NUM((client - svs.clients) + 1); + + // clear cached stuff that depends on the level + client->weaponmodel[0] = 0; + client->weaponmodelindex = 0; + + // LordHavoc: clear entityframe tracking + client->latestframenum = 0; + + // initialize the movetime, so a speedhack can't make use of the time before this client joined + client->cmd.time = sv.time; + + if (client->entitydatabase) + EntityFrame_FreeDatabase(client->entitydatabase); + if (client->entitydatabase4) + EntityFrame4_FreeDatabase(client->entitydatabase4); + if (client->entitydatabase5) + EntityFrame5_FreeDatabase(client->entitydatabase5); + + memset(client->stats, 0, sizeof(client->stats)); + memset(client->statsdeltabits, 0, sizeof(client->statsdeltabits)); + + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3) + { + if (sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) + client->entitydatabase = EntityFrame_AllocDatabase(sv_mempool); + else if (sv.protocol == PROTOCOL_DARKPLACES4) + client->entitydatabase4 = EntityFrame4_AllocDatabase(sv_mempool); + else + client->entitydatabase5 = EntityFrame5_AllocDatabase(sv_mempool); + } + + // reset csqc entity versions + for (i = 0;i < prog->max_edicts;i++) + { + client->csqcentityscope[i] = 0; + client->csqcentitysendflags[i] = 0xFFFFFF; + client->csqcentityglobalhistory[i] = 0; + } + for (i = 0;i < NUM_CSQCENTITYDB_FRAMES;i++) + { + client->csqcentityframehistory[i].num = 0; + client->csqcentityframehistory[i].framenum = -1; + } + client->csqcnumedicts = 0; + client->csqcentityframehistory_next = 0; + + SZ_Clear (&client->netconnection->message); + MSG_WriteByte (&client->netconnection->message, svc_print); + dpsnprintf (message, sizeof (message), "\nServer: %s build %s (progs %i crc)\n", gamename, buildstring, prog->filecrc); + MSG_WriteString (&client->netconnection->message,message); + + SV_StopDemoRecording(client); // to split up demos into different files + if(sv_autodemo_perclient.integer && client->netconnection) + { + char demofile[MAX_OSPATH]; + char ipaddress[MAX_QPATH]; + size_t i; + + // start a new demo file + LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true); + for(i = 0; ipaddress[i]; ++i) + if(!isalnum(ipaddress[i])) + ipaddress[i] = '-'; + dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), sv.worldbasename, PRVM_NUM_FOR_EDICT(client->edict), ipaddress); + + SV_StartDemoRecording(client, demofile, -1); + } + + //[515]: init csprogs according to version of svprogs, check the crc, etc. + if (sv.csqc_progname[0]) + { + Con_DPrintf("sending csqc info to client (\"%s\" with size %i and crc %i)\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va(vabuf, sizeof(vabuf), "csqc_progname %s\n", sv.csqc_progname)); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va(vabuf, sizeof(vabuf), "csqc_progsize %i\n", sv.csqc_progsize)); + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va(vabuf, sizeof(vabuf), "csqc_progcrc %i\n", sv.csqc_progcrc)); + + if(client->sv_demo_file != NULL) + { + int i; + static char buf[NET_MAXMESSAGE]; + sizebuf_t sb; + + sb.data = (unsigned char *) buf; + sb.maxsize = sizeof(buf); + i = 0; + while(MakeDownloadPacket(sv.csqc_progname, svs.csqc_progdata, sv.csqc_progsize, sv.csqc_progcrc, i++, &sb, sv.protocol)) + SV_WriteDemoMessage(client, &sb, false); + } + + //[515]: init stufftext string (it is sent before svc_serverinfo) + if (PRVM_GetString(prog, PRVM_serverglobalstring(SV_InitCmd))) + { + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, va(vabuf, sizeof(vabuf), "%s\n", PRVM_GetString(prog, PRVM_serverglobalstring(SV_InitCmd)))); + } + } + + //if (sv_allowdownloads.integer) + // always send the info that the server supports the protocol, even if downloads are forbidden + // only because of that, the CSQC exception can work + { + MSG_WriteByte (&client->netconnection->message, svc_stufftext); + MSG_WriteString (&client->netconnection->message, "cl_serverextension_download 2\n"); + } + + // send at this time so it's guaranteed to get executed at the right time + { + client_t *save; + save = host_client; + host_client = client; + Curl_SendRequirements(); + host_client = save; + } + + MSG_WriteByte (&client->netconnection->message, svc_serverinfo); + MSG_WriteLong (&client->netconnection->message, Protocol_NumberForEnum(sv.protocol)); + MSG_WriteByte (&client->netconnection->message, svs.maxclients); + + if (!coop.integer && deathmatch.integer) + MSG_WriteByte (&client->netconnection->message, GAME_DEATHMATCH); + else + MSG_WriteByte (&client->netconnection->message, GAME_COOP); + + MSG_WriteString (&client->netconnection->message,PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message))); + + for (i = 1;i < MAX_MODELS && sv.model_precache[i][0];i++) + MSG_WriteString (&client->netconnection->message, sv.model_precache[i]); + MSG_WriteByte (&client->netconnection->message, 0); + + for (i = 1;i < MAX_SOUNDS && sv.sound_precache[i][0];i++) + MSG_WriteString (&client->netconnection->message, sv.sound_precache[i]); + MSG_WriteByte (&client->netconnection->message, 0); + +// send music + MSG_WriteByte (&client->netconnection->message, svc_cdtrack); + MSG_WriteByte (&client->netconnection->message, (int)PRVM_serveredictfloat(prog->edicts, sounds)); + MSG_WriteByte (&client->netconnection->message, (int)PRVM_serveredictfloat(prog->edicts, sounds)); + +// set view +// store this in clientcamera, too + client->clientcamera = PRVM_NUM_FOR_EDICT(client->edict); + MSG_WriteByte (&client->netconnection->message, svc_setview); + MSG_WriteShort (&client->netconnection->message, client->clientcamera); + + MSG_WriteByte (&client->netconnection->message, svc_signonnum); + MSG_WriteByte (&client->netconnection->message, 1); + + client->prespawned = false; // need prespawn, spawn, etc + client->spawned = false; // need prespawn, spawn, etc + client->begun = false; // need prespawn, spawn, etc + client->sendsignon = 1; // send this message, and increment to 2, 2 will be set to 0 by the prespawn command + + // clear movement info until client enters the new level properly + memset(&client->cmd, 0, sizeof(client->cmd)); + client->movesequence = 0; + client->movement_highestsequence_seen = 0; + memset(&client->movement_count, 0, sizeof(client->movement_count)); +#ifdef NUM_PING_TIMES + for (i = 0;i < NUM_PING_TIMES;i++) + client->ping_times[i] = 0; + client->num_pings = 0; +#endif + client->ping = 0; + + // allow the client some time to send his keepalives, even if map loading took ages + client->netconnection->timeout = realtime + net_connecttimeout.value; +} + +/* +================ +SV_ConnectClient + +Initializes a client_t for a new net connection. This will only be called +once for a player each game, not once for each level change. +================ +*/ +void SV_ConnectClient (int clientnum, netconn_t *netconnection) +{ + prvm_prog_t *prog = SVVM_prog; + client_t *client; + int i; + + client = svs.clients + clientnum; + +// set up the client_t + if (sv.loadgame) + { + float backupparms[NUM_SPAWN_PARMS]; + memcpy(backupparms, client->spawn_parms, sizeof(backupparms)); + memset(client, 0, sizeof(*client)); + memcpy(client->spawn_parms, backupparms, sizeof(backupparms)); + } + else + memset(client, 0, sizeof(*client)); + client->active = true; + client->netconnection = netconnection; + + Con_DPrintf("Client %s connected\n", client->netconnection ? client->netconnection->address : "botclient"); + + if(client->netconnection && client->netconnection->crypto.authenticated) + { + Con_Printf("%s connection to %s has been established: client is %s@%.*s, I am %.*s@%.*s\n", + client->netconnection->crypto.use_aes ? "Encrypted" : "Authenticated", + client->netconnection->address, + client->netconnection->crypto.client_idfp[0] ? client->netconnection->crypto.client_idfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.client_keyfp[0] ? client->netconnection->crypto.client_keyfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.server_idfp[0] ? client->netconnection->crypto.server_idfp : "-", + crypto_keyfp_recommended_length, client->netconnection->crypto.server_keyfp[0] ? client->netconnection->crypto.server_keyfp : "-" + ); + } + + strlcpy(client->name, "unconnected", sizeof(client->name)); + strlcpy(client->old_name, "unconnected", sizeof(client->old_name)); + client->prespawned = false; + client->spawned = false; + client->begun = false; + client->edict = PRVM_EDICT_NUM(clientnum+1); + if (client->netconnection) + client->netconnection->message.allowoverflow = true; // we can catch it + // prepare the unreliable message buffer + client->unreliablemsg.data = client->unreliablemsg_data; + client->unreliablemsg.maxsize = sizeof(client->unreliablemsg_data); + // updated by receiving "rate" command from client, this is also the default if not using a DP client + client->rate = 1000000000; + // no limits for local player + if (client->netconnection && LHNETADDRESS_GetAddressType(&client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP) + client->rate = 1000000000; + client->connecttime = realtime; + + if (!sv.loadgame) + { + // call the progs to get default spawn parms for the new client + // set self to world to intentionally cause errors with broken SetNewParms code in some mods + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = 0; + prog->ExecuteProgram(prog, PRVM_serverfunction(SetNewParms), "QC function SetNewParms is missing"); + for (i=0 ; ispawn_parms[i] = (&PRVM_serverglobalfloat(parm1))[i]; + + // set up the entity for this client (including .colormap, .team, etc) + PRVM_ED_ClearEdict(prog, client->edict); + } + + // don't call SendServerinfo for a fresh botclient because its fields have + // not been set up by the qc yet + if (client->netconnection) + SV_SendServerinfo (client); + else + client->prespawned = client->spawned = client->begun = true; +} + + +/* +=============================================================================== + +FRAME UPDATES + +=============================================================================== +*/ + +/* +============================================================================= + +The PVS must include a small area around the client to allow head bobbing +or other small motion on the client side. Otherwise, a bob might cause an +entity that should be visible to not show up, especially when the bob +crosses a waterline. + +============================================================================= +*/ + +static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int enumber) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + unsigned int sendflags; + unsigned int version; + unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius; + unsigned int customizeentityforclient; + unsigned int sendentity; + float f; + prvm_vec_t *v; + vec3_t cullmins, cullmaxs; + dp_model_t *model; + + // fast path for games that do not use legacy entity networking + // note: still networks clients even if they are legacy + sendentity = PRVM_serveredictfunction(ent, SendEntity); + if (sv_onlycsqcnetworking.integer && !sendentity && enumber > svs.maxclients) + return false; + + // this 2 billion unit check is actually to detect NAN origins + // (we really don't want to send those) + if (!(VectorLength2(PRVM_serveredictvector(ent, origin)) < 2000000000.0*2000000000.0)) + return false; + + // EF_NODRAW prevents sending for any reason except for your own + // client, so we must keep all clients in this superset + effects = (unsigned)PRVM_serveredictfloat(ent, effects); + + // we can omit invisible entities with no effects that are not clients + // LordHavoc: this could kill tags attached to an invisible entity, I + // just hope we never have to support that case + i = (int)PRVM_serveredictfloat(ent, modelindex); + modelindex = (i >= 1 && i < MAX_MODELS && PRVM_serveredictstring(ent, model) && *PRVM_GetString(prog, PRVM_serveredictstring(ent, model)) && sv.models[i]) ? i : 0; + + flags = 0; + i = (int)(PRVM_serveredictfloat(ent, glow_size) * 0.25f); + glowsize = (unsigned char)bound(0, i, 255); + if (PRVM_serveredictfloat(ent, glow_trail)) + flags |= RENDER_GLOWTRAIL; + if (PRVM_serveredictedict(ent, viewmodelforclient)) + flags |= RENDER_VIEWMODEL; + + v = PRVM_serveredictvector(ent, color); + f = v[0]*256; + light[0] = (unsigned short)bound(0, f, 65535); + f = v[1]*256; + light[1] = (unsigned short)bound(0, f, 65535); + f = v[2]*256; + light[2] = (unsigned short)bound(0, f, 65535); + f = PRVM_serveredictfloat(ent, light_lev); + light[3] = (unsigned short)bound(0, f, 65535); + lightstyle = (unsigned char)PRVM_serveredictfloat(ent, style); + lightpflags = (unsigned char)PRVM_serveredictfloat(ent, pflags); + + if (gamemode == GAME_TENEBRAE) + { + // tenebrae's EF_FULLDYNAMIC conflicts with Q2's EF_NODRAW + if (effects & 16) + { + effects &= ~16; + lightpflags |= PFLAGS_FULLDYNAMIC; + } + // tenebrae's EF_GREEN conflicts with DP's EF_ADDITIVE + if (effects & 32) + { + effects &= ~32; + light[0] = (int)(0.2*256); + light[1] = (int)(1.0*256); + light[2] = (int)(0.2*256); + light[3] = 200; + lightpflags |= PFLAGS_FULLDYNAMIC; + } + } + + specialvisibilityradius = 0; + if (lightpflags & PFLAGS_FULLDYNAMIC) + specialvisibilityradius = max(specialvisibilityradius, light[3]); + if (glowsize) + specialvisibilityradius = max(specialvisibilityradius, glowsize * 4); + if (flags & RENDER_GLOWTRAIL) + specialvisibilityradius = max(specialvisibilityradius, 100); + if (effects & (EF_BRIGHTFIELD | EF_MUZZLEFLASH | EF_BRIGHTLIGHT | EF_DIMLIGHT | EF_RED | EF_BLUE | EF_FLAME | EF_STARDUST)) + { + if (effects & EF_BRIGHTFIELD) + specialvisibilityradius = max(specialvisibilityradius, 80); + if (effects & EF_MUZZLEFLASH) + specialvisibilityradius = max(specialvisibilityradius, 100); + if (effects & EF_BRIGHTLIGHT) + specialvisibilityradius = max(specialvisibilityradius, 400); + if (effects & EF_DIMLIGHT) + specialvisibilityradius = max(specialvisibilityradius, 200); + if (effects & EF_RED) + specialvisibilityradius = max(specialvisibilityradius, 200); + if (effects & EF_BLUE) + specialvisibilityradius = max(specialvisibilityradius, 200); + if (effects & EF_FLAME) + specialvisibilityradius = max(specialvisibilityradius, 250); + if (effects & EF_STARDUST) + specialvisibilityradius = max(specialvisibilityradius, 100); + } + + // early culling checks + // (final culling is done by SV_MarkWriteEntityStateToClient) + customizeentityforclient = PRVM_serveredictfunction(ent, customizeentityforclient); + if (!customizeentityforclient && enumber > svs.maxclients && (!modelindex && !specialvisibilityradius)) + return false; + + *cs = defaultstate; + cs->active = ACTIVE_NETWORK; + cs->number = enumber; + VectorCopy(PRVM_serveredictvector(ent, origin), cs->origin); + VectorCopy(PRVM_serveredictvector(ent, angles), cs->angles); + cs->flags = flags; + cs->effects = effects; + cs->colormap = (unsigned)PRVM_serveredictfloat(ent, colormap); + cs->modelindex = modelindex; + cs->skin = (unsigned)PRVM_serveredictfloat(ent, skin); + cs->frame = (unsigned)PRVM_serveredictfloat(ent, frame); + cs->viewmodelforclient = PRVM_serveredictedict(ent, viewmodelforclient); + cs->exteriormodelforclient = PRVM_serveredictedict(ent, exteriormodeltoclient); + cs->nodrawtoclient = PRVM_serveredictedict(ent, nodrawtoclient); + cs->drawonlytoclient = PRVM_serveredictedict(ent, drawonlytoclient); + cs->customizeentityforclient = customizeentityforclient; + cs->tagentity = PRVM_serveredictedict(ent, tag_entity); + cs->tagindex = (unsigned char)PRVM_serveredictfloat(ent, tag_index); + cs->glowsize = glowsize; + cs->traileffectnum = PRVM_serveredictfloat(ent, traileffectnum); + + // don't need to init cs->colormod because the defaultstate did that for us + //cs->colormod[0] = cs->colormod[1] = cs->colormod[2] = 32; + v = PRVM_serveredictvector(ent, colormod); + if (VectorLength2(v)) + { + i = (int)(v[0] * 32.0f);cs->colormod[0] = bound(0, i, 255); + i = (int)(v[1] * 32.0f);cs->colormod[1] = bound(0, i, 255); + i = (int)(v[2] * 32.0f);cs->colormod[2] = bound(0, i, 255); + } + + // don't need to init cs->glowmod because the defaultstate did that for us + //cs->glowmod[0] = cs->glowmod[1] = cs->glowmod[2] = 32; + v = PRVM_serveredictvector(ent, glowmod); + if (VectorLength2(v)) + { + i = (int)(v[0] * 32.0f);cs->glowmod[0] = bound(0, i, 255); + i = (int)(v[1] * 32.0f);cs->glowmod[1] = bound(0, i, 255); + i = (int)(v[2] * 32.0f);cs->glowmod[2] = bound(0, i, 255); + } + + cs->modelindex = modelindex; + + cs->alpha = 255; + f = (PRVM_serveredictfloat(ent, alpha) * 255.0f); + if (f) + { + i = (int)f; + cs->alpha = (unsigned char)bound(0, i, 255); + } + // halflife + f = (PRVM_serveredictfloat(ent, renderamt)); + if (f) + { + i = (int)f; + cs->alpha = (unsigned char)bound(0, i, 255); + } + + cs->scale = 16; + f = (PRVM_serveredictfloat(ent, scale) * 16.0f); + if (f) + { + i = (int)f; + cs->scale = (unsigned char)bound(0, i, 255); + } + + cs->glowcolor = 254; + f = PRVM_serveredictfloat(ent, glow_color); + if (f) + cs->glowcolor = (int)f; + + if (PRVM_serveredictfloat(ent, fullbright)) + cs->effects |= EF_FULLBRIGHT; + + f = PRVM_serveredictfloat(ent, modelflags); + if (f) + cs->effects |= ((unsigned int)f & 0xff) << 24; + + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_STEP) + cs->flags |= RENDER_STEP; + if (cs->number != sv.writeentitiestoclient_cliententitynumber && (cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767) + cs->flags |= RENDER_LOWPRECISION; + if (PRVM_serveredictfloat(ent, colormap) >= 1024) + cs->flags |= RENDER_COLORMAPPED; + if (cs->viewmodelforclient) + cs->flags |= RENDER_VIEWMODEL; // show relative to the view + + if (PRVM_serveredictfloat(ent, sendcomplexanimation)) + { + cs->flags |= RENDER_COMPLEXANIMATION; + if (PRVM_serveredictfloat(ent, skeletonindex) >= 1) + cs->skeletonobject = ent->priv.server->skeleton; + cs->framegroupblend[0].frame = PRVM_serveredictfloat(ent, frame); + cs->framegroupblend[1].frame = PRVM_serveredictfloat(ent, frame2); + cs->framegroupblend[2].frame = PRVM_serveredictfloat(ent, frame3); + cs->framegroupblend[3].frame = PRVM_serveredictfloat(ent, frame4); + cs->framegroupblend[0].start = PRVM_serveredictfloat(ent, frame1time); + cs->framegroupblend[1].start = PRVM_serveredictfloat(ent, frame2time); + cs->framegroupblend[2].start = PRVM_serveredictfloat(ent, frame3time); + cs->framegroupblend[3].start = PRVM_serveredictfloat(ent, frame4time); + cs->framegroupblend[1].lerp = PRVM_serveredictfloat(ent, lerpfrac); + cs->framegroupblend[2].lerp = PRVM_serveredictfloat(ent, lerpfrac3); + cs->framegroupblend[3].lerp = PRVM_serveredictfloat(ent, lerpfrac4); + cs->framegroupblend[0].lerp = 1.0f - cs->framegroupblend[1].lerp - cs->framegroupblend[2].lerp - cs->framegroupblend[3].lerp; + cs->frame = 0; // don't need the legacy frame + } + + cs->light[0] = light[0]; + cs->light[1] = light[1]; + cs->light[2] = light[2]; + cs->light[3] = light[3]; + cs->lightstyle = lightstyle; + cs->lightpflags = lightpflags; + + cs->specialvisibilityradius = specialvisibilityradius; + + // calculate the visible box of this entity (don't use the physics box + // as that is often smaller than a model, and would not count + // specialvisibilityradius) + if ((model = SV_GetModelByIndex(modelindex)) && (model->type != mod_null)) + { + float scale = cs->scale * (1.0f / 16.0f); + if (cs->angles[0] || cs->angles[2]) // pitch and roll + { + VectorMA(cs->origin, scale, model->rotatedmins, cullmins); + VectorMA(cs->origin, scale, model->rotatedmaxs, cullmaxs); + } + else if (cs->angles[1] || ((effects | model->effects) & EF_ROTATE)) + { + VectorMA(cs->origin, scale, model->yawmins, cullmins); + VectorMA(cs->origin, scale, model->yawmaxs, cullmaxs); + } + else + { + VectorMA(cs->origin, scale, model->normalmins, cullmins); + VectorMA(cs->origin, scale, model->normalmaxs, cullmaxs); + } + } + else + { + // if there is no model (or it could not be loaded), use the physics box + VectorAdd(cs->origin, PRVM_serveredictvector(ent, mins), cullmins); + VectorAdd(cs->origin, PRVM_serveredictvector(ent, maxs), cullmaxs); + } + if (specialvisibilityradius) + { + cullmins[0] = min(cullmins[0], cs->origin[0] - specialvisibilityradius); + cullmins[1] = min(cullmins[1], cs->origin[1] - specialvisibilityradius); + cullmins[2] = min(cullmins[2], cs->origin[2] - specialvisibilityradius); + cullmaxs[0] = max(cullmaxs[0], cs->origin[0] + specialvisibilityradius); + cullmaxs[1] = max(cullmaxs[1], cs->origin[1] + specialvisibilityradius); + cullmaxs[2] = max(cullmaxs[2], cs->origin[2] + specialvisibilityradius); + } + + // calculate center of bbox for network prioritization purposes + VectorMAM(0.5f, cullmins, 0.5f, cullmaxs, cs->netcenter); + + // if culling box has moved, update pvs cluster links + if (!VectorCompare(cullmins, ent->priv.server->cullmins) || !VectorCompare(cullmaxs, ent->priv.server->cullmaxs)) + { + VectorCopy(cullmins, ent->priv.server->cullmins); + VectorCopy(cullmaxs, ent->priv.server->cullmaxs); + // a value of -1 for pvs_numclusters indicates that the links are not + // cached, and should be re-tested each time, this is the case if the + // culling box touches too many pvs clusters to store, or if the world + // model does not support FindBoxClusters + ent->priv.server->pvs_numclusters = -1; + if (sv.worldmodel && sv.worldmodel->brush.FindBoxClusters) + { + i = sv.worldmodel->brush.FindBoxClusters(sv.worldmodel, cullmins, cullmaxs, MAX_ENTITYCLUSTERS, ent->priv.server->pvs_clusterlist); + if (i <= MAX_ENTITYCLUSTERS) + ent->priv.server->pvs_numclusters = i; + } + } + + // we need to do some csqc entity upkeep here + // get self.SendFlags and clear them + // (to let the QC know that they've been read) + if (sendentity) + { + sendflags = (unsigned int)PRVM_serveredictfloat(ent, SendFlags); + PRVM_serveredictfloat(ent, SendFlags) = 0; + // legacy self.Version system + if ((version = (unsigned int)PRVM_serveredictfloat(ent, Version))) + { + if (sv.csqcentityversion[enumber] != version) + sendflags = 0xFFFFFF; + sv.csqcentityversion[enumber] = version; + } + // move sendflags into the per-client sendflags + if (sendflags) + for (i = 0;i < svs.maxclients;i++) + svs.clients[i].csqcentitysendflags[enumber] |= sendflags; + // mark it as inactive for non-csqc networking + cs->active = ACTIVE_SHARED; + } + + return true; +} + +static void SV_PrepareEntitiesForSending(void) +{ + prvm_prog_t *prog = SVVM_prog; + int e; + prvm_edict_t *ent; + // send all entities that touch the pvs + sv.numsendentities = 0; + sv.sendentitiesindex[0] = NULL; + memset(sv.sendentitiesindex, 0, prog->num_edicts * sizeof(*sv.sendentitiesindex)); + for (e = 1, ent = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ent = PRVM_NEXT_EDICT(ent)) + { + if (!ent->priv.server->free && SV_PrepareEntityForSending(ent, sv.sendentities + sv.numsendentities, e)) + { + sv.sendentitiesindex[e] = sv.sendentities + sv.numsendentities; + sv.numsendentities++; + } + } +} + +#define MAX_LINEOFSIGHTTRACES 64 + +qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs) +{ + prvm_prog_t *prog = SVVM_prog; + float pitchsign; + float alpha; + float starttransformed[3], endtransformed[3]; + int blocked = 0; + int traceindex; + int originalnumtouchedicts; + int numtouchedicts = 0; + int touchindex; + matrix4x4_t matrix, imatrix; + dp_model_t *model; + prvm_edict_t *touch; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + vec3_t boxmins, boxmaxs; + vec3_t clipboxmins, clipboxmaxs; + vec3_t endpoints[MAX_LINEOFSIGHTTRACES]; + + numtraces = min(numtraces, MAX_LINEOFSIGHTTRACES); + + // expand the box a little + boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0]; + boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0]; + boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1]; + boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1]; + boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2]; + boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2]; + + VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, endpoints[0]); + for (traceindex = 1;traceindex < numtraces;traceindex++) + VectorSet(endpoints[traceindex], lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2])); + + // calculate sweep box for the entire swarm of traces + VectorCopy(eye, clipboxmins); + VectorCopy(eye, clipboxmaxs); + for (traceindex = 0;traceindex < numtraces;traceindex++) + { + clipboxmins[0] = min(clipboxmins[0], endpoints[traceindex][0]); + clipboxmins[1] = min(clipboxmins[1], endpoints[traceindex][1]); + clipboxmins[2] = min(clipboxmins[2], endpoints[traceindex][2]); + clipboxmaxs[0] = max(clipboxmaxs[0], endpoints[traceindex][0]); + clipboxmaxs[1] = max(clipboxmaxs[1], endpoints[traceindex][1]); + clipboxmaxs[2] = max(clipboxmaxs[2], endpoints[traceindex][2]); + } + + // get the list of entities in the sweep box + if (sv_cullentities_trace_entityocclusion.integer) + numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + // iterate the entities found in the sweep box and filter them + originalnumtouchedicts = numtouchedicts; + numtouchedicts = 0; + for (touchindex = 0;touchindex < originalnumtouchedicts;touchindex++) + { + touch = touchedicts[touchindex]; + if (PRVM_serveredictfloat(touch, solid) != SOLID_BSP) + continue; + model = SV_GetModelFromEdict(touch); + if (!model || !model->brush.TraceLineOfSight) + continue; + // skip obviously transparent entities + alpha = PRVM_serveredictfloat(touch, alpha); + if (alpha && alpha < 1) + continue; + if ((int)PRVM_serveredictfloat(touch, effects) & EF_ADDITIVE) + continue; + touchedicts[numtouchedicts++] = touch; + } + + // now that we have a filtered list of "interesting" entities, fire each + // ray against all of them, this gives us an early-out case when something + // is visible (which it often is) + + for (traceindex = 0;traceindex < numtraces;traceindex++) + { + // check world occlusion + if (sv.worldmodel && sv.worldmodel->brush.TraceLineOfSight) + if (!sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, eye, endpoints[traceindex])) + continue; + for (touchindex = 0;touchindex < numtouchedicts;touchindex++) + { + touch = touchedicts[touchindex]; + model = SV_GetModelFromEdict(touch); + if(model && model->brush.TraceLineOfSight) + { + // get the entity matrix + pitchsign = SV_GetPitchSign(prog, touch); + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + // see if the ray hits this entity + Matrix4x4_Transform(&imatrix, eye, starttransformed); + Matrix4x4_Transform(&imatrix, endpoints[traceindex], endtransformed); + if (!model->brush.TraceLineOfSight(model, starttransformed, endtransformed)) + { + blocked++; + break; + } + } + } + // check if the ray was blocked + if (touchindex < numtouchedicts) + continue; + // return if the ray was not blocked + return true; + } + + // no rays survived + return false; +} + +static void SV_MarkWriteEntityStateToClient(entity_state_t *s) +{ + prvm_prog_t *prog = SVVM_prog; + int isbmodel; + dp_model_t *model; + prvm_edict_t *ed; + if (sv.sententitiesconsideration[s->number] == sv.sententitiesmark) + return; + sv.sententitiesconsideration[s->number] = sv.sententitiesmark; + sv.writeentitiestoclient_stats_totalentities++; + + if (s->customizeentityforclient) + { + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = s->number; + PRVM_serverglobaledict(other) = sv.writeentitiestoclient_cliententitynumber; + prog->ExecuteProgram(prog, s->customizeentityforclient, "customizeentityforclient: NULL function"); + if(!PRVM_G_FLOAT(OFS_RETURN) || !SV_PrepareEntityForSending(PRVM_EDICT_NUM(s->number), s, s->number)) + return; + } + + // never reject player + if (s->number != sv.writeentitiestoclient_cliententitynumber) + { + // check various rejection conditions + if (s->nodrawtoclient == sv.writeentitiestoclient_cliententitynumber) + return; + if (s->drawonlytoclient && s->drawonlytoclient != sv.writeentitiestoclient_cliententitynumber) + return; + if (s->effects & EF_NODRAW) + return; + // LordHavoc: only send entities with a model or important effects + if (!s->modelindex && s->specialvisibilityradius == 0) + return; + + isbmodel = (model = SV_GetModelByIndex(s->modelindex)) != NULL && model->name[0] == '*'; + // viewmodels don't have visibility checking + if (s->viewmodelforclient) + { + if (s->viewmodelforclient != sv.writeentitiestoclient_cliententitynumber) + return; + } + else if (s->tagentity) + { + // tag attached entities simply check their parent + if (!sv.sendentitiesindex[s->tagentity]) + return; + SV_MarkWriteEntityStateToClient(sv.sendentitiesindex[s->tagentity]); + if (sv.sententities[s->tagentity] != sv.sententitiesmark) + return; + } + // always send world submodels in newer protocols because they don't + // generate much traffic (in old protocols they hog bandwidth) + // but only if sv_cullentities_nevercullbmodels is off + else if (!(s->effects & EF_NODEPTHTEST) && (!isbmodel || !sv_cullentities_nevercullbmodels.integer || sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE)) + { + // entity has survived every check so far, check if visible + ed = PRVM_EDICT_NUM(s->number); + + // if not touching a visible leaf + if (sv_cullentities_pvs.integer && !r_novis.integer && !r_trippy.integer && sv.writeentitiestoclient_pvsbytes) + { + if (ed->priv.server->pvs_numclusters < 0) + { + // entity too big for clusters list + if (sv.worldmodel && sv.worldmodel->brush.BoxTouchingPVS && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, sv.writeentitiestoclient_pvs, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) + { + sv.writeentitiestoclient_stats_culled_pvs++; + return; + } + } + else + { + int i; + // check cached clusters list + for (i = 0;i < ed->priv.server->pvs_numclusters;i++) + if (CHECKPVSBIT(sv.writeentitiestoclient_pvs, ed->priv.server->pvs_clusterlist[i])) + break; + if (i == ed->priv.server->pvs_numclusters) + { + sv.writeentitiestoclient_stats_culled_pvs++; + return; + } + } + } + + // or not seen by random tracelines + if (sv_cullentities_trace.integer && !isbmodel && sv.worldmodel->brush.TraceLineOfSight && !r_trippy.integer) + { + int samples = + s->number <= svs.maxclients + ? sv_cullentities_trace_samples_players.integer + : + s->specialvisibilityradius + ? sv_cullentities_trace_samples_extra.integer + : sv_cullentities_trace_samples.integer; + float enlarge = sv_cullentities_trace_enlarge.value; + + if(samples > 0) + { + int eyeindex; + for (eyeindex = 0;eyeindex < sv.writeentitiestoclient_numeyes;eyeindex++) + if(SV_CanSeeBox(samples, enlarge, sv.writeentitiestoclient_eyes[eyeindex], ed->priv.server->cullmins, ed->priv.server->cullmaxs)) + break; + if(eyeindex < sv.writeentitiestoclient_numeyes) + svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] = + realtime + ( + s->number <= svs.maxclients + ? sv_cullentities_trace_delay_players.value + : sv_cullentities_trace_delay.value + ); + else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number]) + { + sv.writeentitiestoclient_stats_culled_trace++; + return; + } + } + } + } + } + + // this just marks it for sending + // FIXME: it would be more efficient to send here, but the entity + // compressor isn't that flexible + sv.writeentitiestoclient_stats_visibleentities++; + sv.sententities[s->number] = sv.sententitiesmark; +} + +#if MAX_LEVELNETWORKEYES > 0 +#define MAX_EYE_RECURSION 1 // increase if recursion gets supported by portals +static void SV_AddCameraEyes(void) +{ + prvm_prog_t *prog = SVVM_prog; + int e, i, j, k; + prvm_edict_t *ed; + static int cameras[MAX_LEVELNETWORKEYES]; + static vec3_t camera_origins[MAX_LEVELNETWORKEYES]; + static int eye_levels[MAX_CLIENTNETWORKEYES]; + int n_cameras = 0; + vec3_t mi, ma; + + for(i = 0; i < sv.writeentitiestoclient_numeyes; ++i) + eye_levels[i] = 0; + + // check line of sight to portal entities and add them to PVS + for (e = 1, ed = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ed = PRVM_NEXT_EDICT(ed)) + { + if (!ed->priv.server->free) + { + if(PRVM_serveredictfunction(ed, camera_transform)) + { + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = e; + PRVM_serverglobaledict(other) = sv.writeentitiestoclient_cliententitynumber; + VectorCopy(sv.writeentitiestoclient_eyes[0], PRVM_serverglobalvector(trace_endpos)); + VectorCopy(sv.writeentitiestoclient_eyes[0], PRVM_G_VECTOR(OFS_PARM0)); + VectorClear(PRVM_G_VECTOR(OFS_PARM1)); + prog->ExecuteProgram(prog, PRVM_serveredictfunction(ed, camera_transform), "QC function e.camera_transform is missing"); + if(!VectorCompare(PRVM_serverglobalvector(trace_endpos), sv.writeentitiestoclient_eyes[0])) + { + VectorCopy(PRVM_serverglobalvector(trace_endpos), camera_origins[n_cameras]); + cameras[n_cameras] = e; + ++n_cameras; + if(n_cameras >= MAX_LEVELNETWORKEYES) + break; + } + } + } + } + + if(!n_cameras) + return; + + // i is loop counter, is reset to 0 when an eye got added + // j is camera index to check + for(i = 0, j = 0; sv.writeentitiestoclient_numeyes < MAX_CLIENTNETWORKEYES && i < n_cameras; ++i, ++j, j %= n_cameras) + { + if(!cameras[j]) + continue; + ed = PRVM_EDICT_NUM(cameras[j]); + VectorAdd(PRVM_serveredictvector(ed, origin), PRVM_serveredictvector(ed, mins), mi); + VectorAdd(PRVM_serveredictvector(ed, origin), PRVM_serveredictvector(ed, maxs), ma); + for(k = 0; k < sv.writeentitiestoclient_numeyes; ++k) + if(eye_levels[k] <= MAX_EYE_RECURSION) + { + if(SV_CanSeeBox(sv_cullentities_trace_samples.integer, sv_cullentities_trace_enlarge.value, sv.writeentitiestoclient_eyes[k], mi, ma)) + { + eye_levels[sv.writeentitiestoclient_numeyes] = eye_levels[k] + 1; + VectorCopy(camera_origins[j], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); + // Con_Printf("added eye %d: %f %f %f because we can see %f %f %f .. %f %f %f from eye %d\n", j, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][0], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][1], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][2], mi[0], mi[1], mi[2], ma[0], ma[1], ma[2], k); + sv.writeentitiestoclient_numeyes++; + cameras[j] = 0; + i = 0; + break; + } + } + } +} +#else +void SV_AddCameraEyes(void) +{ +} +#endif + +static void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int maxsize) +{ + prvm_prog_t *prog = SVVM_prog; + qboolean need_empty = false; + int i, numsendstates, numcsqcsendstates; + entity_state_t *s; + prvm_edict_t *camera; + qboolean success; + vec3_t eye; + + // if there isn't enough space to accomplish anything, skip it + if (msg->cursize + 25 > maxsize) + return; + + sv.writeentitiestoclient_msg = msg; + sv.writeentitiestoclient_clientnumber = client - svs.clients; + + sv.writeentitiestoclient_stats_culled_pvs = 0; + sv.writeentitiestoclient_stats_culled_trace = 0; + sv.writeentitiestoclient_stats_visibleentities = 0; + sv.writeentitiestoclient_stats_totalentities = 0; + sv.writeentitiestoclient_numeyes = 0; + + // get eye location + sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes + camera = PRVM_EDICT_NUM( client->clientcamera ); + VectorAdd(PRVM_serveredictvector(camera, origin), PRVM_serveredictvector(clent, view_ofs), eye); + sv.writeentitiestoclient_pvsbytes = 0; + // get the PVS values for the eye location, later FatPVS calls will merge + if (sv.worldmodel && sv.worldmodel->brush.FatPVS) + sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, eye, 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), sv.writeentitiestoclient_pvsbytes != 0); + + // add the eye to a list for SV_CanSeeBox tests + VectorCopy(eye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); + sv.writeentitiestoclient_numeyes++; + + // calculate predicted eye origin for SV_CanSeeBox tests + if (sv_cullentities_trace_prediction.integer) + { + vec_t predtime = bound(0, host_client->ping, sv_cullentities_trace_prediction_time.value); + vec3_t predeye; + VectorMA(eye, predtime, PRVM_serveredictvector(camera, velocity), predeye); + if (SV_CanSeeBox(1, 0, eye, predeye, predeye)) + { + VectorCopy(predeye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); + sv.writeentitiestoclient_numeyes++; + } + //if (!sv.writeentitiestoclient_useprediction) + // Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n"); + } + + SV_AddCameraEyes(); + + // build PVS from the new eyes + if (sv.worldmodel && sv.worldmodel->brush.FatPVS) + for(i = 1; i < sv.writeentitiestoclient_numeyes; ++i) + sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, sv.writeentitiestoclient_eyes[i], 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), sv.writeentitiestoclient_pvsbytes != 0); + + sv.sententitiesmark++; + + for (i = 0;i < sv.numsendentities;i++) + SV_MarkWriteEntityStateToClient(sv.sendentities + i); + + numsendstates = 0; + numcsqcsendstates = 0; + for (i = 0;i < sv.numsendentities;i++) + { + s = &sv.sendentities[i]; + if (sv.sententities[s->number] == sv.sententitiesmark) + { + if(s->active == ACTIVE_NETWORK) + { + if (s->exteriormodelforclient) + { + if (s->exteriormodelforclient == sv.writeentitiestoclient_cliententitynumber) + s->flags |= RENDER_EXTERIORMODEL; + else + s->flags &= ~RENDER_EXTERIORMODEL; + } + sv.writeentitiestoclient_sendstates[numsendstates++] = s; + } + else if(sv.sendentities[i].active == ACTIVE_SHARED) + sv.writeentitiestoclient_csqcsendstates[numcsqcsendstates++] = s->number; + else + Con_Printf("entity %d is in sv.sendentities and marked, but not active, please breakpoint me\n", s->number); + } + } + + if (sv_cullentities_stats.integer) + Con_Printf("client \"%s\" entities: %d total, %d visible, %d culled by: %d pvs %d trace\n", client->name, sv.writeentitiestoclient_stats_totalentities, sv.writeentitiestoclient_stats_visibleentities, sv.writeentitiestoclient_stats_culled_pvs + sv.writeentitiestoclient_stats_culled_trace, sv.writeentitiestoclient_stats_culled_pvs, sv.writeentitiestoclient_stats_culled_trace); + + if(client->entitydatabase5) + need_empty = EntityFrameCSQC_WriteFrame(msg, maxsize, numcsqcsendstates, sv.writeentitiestoclient_csqcsendstates, client->entitydatabase5->latestframenum + 1); + else + EntityFrameCSQC_WriteFrame(msg, maxsize, numcsqcsendstates, sv.writeentitiestoclient_csqcsendstates, 0); + + // force every 16th frame to be not empty (or cl_movement replay takes + // too long) + // BTW, this should normally not kick in any more due to the check + // below, except if the client stopped sending movement frames + if(client->num_skippedentityframes >= 16) + need_empty = true; + + // help cl_movement a bit more + if(client->movesequence != client->lastmovesequence) + need_empty = true; + client->lastmovesequence = client->movesequence; + + if (client->entitydatabase5) + success = EntityFrame5_WriteFrame(msg, maxsize, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence, need_empty); + else if (client->entitydatabase4) + { + success = EntityFrame4_WriteFrame(msg, maxsize, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates); + Protocol_WriteStatsReliable(); + } + else if (client->entitydatabase) + { + success = EntityFrame_WriteFrame(msg, maxsize, client->entitydatabase, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1); + Protocol_WriteStatsReliable(); + } + else + { + success = EntityFrameQuake_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates); + Protocol_WriteStatsReliable(); + } + + if(success) + client->num_skippedentityframes = 0; + else + ++client->num_skippedentityframes; +} + +/* +============= +SV_CleanupEnts + +============= +*/ +static void SV_CleanupEnts (void) +{ + prvm_prog_t *prog = SVVM_prog; + int e; + prvm_edict_t *ent; + + ent = PRVM_NEXT_EDICT(prog->edicts); + for (e=1 ; enum_edicts ; e++, ent = PRVM_NEXT_EDICT(ent)) + PRVM_serveredictfloat(ent, effects) = (int)PRVM_serveredictfloat(ent, effects) & ~EF_MUZZLEFLASH; +} + +/* +================== +SV_WriteClientdataToMessage + +================== +*/ +void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats) +{ + prvm_prog_t *prog = SVVM_prog; + int bits; + int i; + prvm_edict_t *other; + int items; + vec3_t punchvector; + int viewzoom; + const char *s; + float *statsf = (float *)stats; + float gravity; + +// +// send a damage message +// + if (PRVM_serveredictfloat(ent, dmg_take) || PRVM_serveredictfloat(ent, dmg_save)) + { + other = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, dmg_inflictor)); + MSG_WriteByte (msg, svc_damage); + MSG_WriteByte (msg, (int)PRVM_serveredictfloat(ent, dmg_save)); + MSG_WriteByte (msg, (int)PRVM_serveredictfloat(ent, dmg_take)); + for (i=0 ; i<3 ; i++) + MSG_WriteCoord (msg, PRVM_serveredictvector(other, origin)[i] + 0.5*(PRVM_serveredictvector(other, mins)[i] + PRVM_serveredictvector(other, maxs)[i]), sv.protocol); + + PRVM_serveredictfloat(ent, dmg_take) = 0; + PRVM_serveredictfloat(ent, dmg_save) = 0; + } + +// +// send the current viewpos offset from the view entity +// + SV_SetIdealPitch (); // how much to look up / down ideally + +// a fixangle might get lost in a dropped packet. Oh well. + if(PRVM_serveredictfloat(ent, fixangle)) + { + // angle fixing was requested by global thinking code... + // so store the current angles for later use + VectorCopy(PRVM_serveredictvector(ent, angles), host_client->fixangle_angles); + host_client->fixangle_angles_set = TRUE; + + // and clear fixangle for the next frame + PRVM_serveredictfloat(ent, fixangle) = 0; + } + + if (host_client->fixangle_angles_set) + { + MSG_WriteByte (msg, svc_setangle); + for (i=0 ; i < 3 ; i++) + MSG_WriteAngle (msg, host_client->fixangle_angles[i], sv.protocol); + host_client->fixangle_angles_set = FALSE; + } + + // the runes are in serverflags, pack them into the items value, also pack + // in the items2 value for mission pack huds + // (used only in the mission packs, which do not use serverflags) + items = (int)PRVM_serveredictfloat(ent, items) | ((int)PRVM_serveredictfloat(ent, items2) << 23) | ((int)PRVM_serverglobalfloat(serverflags) << 28); + + VectorCopy(PRVM_serveredictvector(ent, punchvector), punchvector); + + // cache weapon model name and index in client struct to save time + // (this search can be almost 1% of cpu time!) + s = PRVM_GetString(prog, PRVM_serveredictstring(ent, weaponmodel)); + if (strcmp(s, client->weaponmodel)) + { + strlcpy(client->weaponmodel, s, sizeof(client->weaponmodel)); + client->weaponmodelindex = SV_ModelIndex(s, 1); + } + + viewzoom = (int)(PRVM_serveredictfloat(ent, viewzoom) * 255.0f); + if (viewzoom == 0) + viewzoom = 255; + + bits = 0; + + if ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) + bits |= SU_ONGROUND; + if (PRVM_serveredictfloat(ent, waterlevel) >= 2) + bits |= SU_INWATER; + if (PRVM_serveredictfloat(ent, idealpitch)) + bits |= SU_IDEALPITCH; + + for (i=0 ; i<3 ; i++) + { + if (PRVM_serveredictvector(ent, punchangle)[i]) + bits |= (SU_PUNCH1<weaponmodelindex; + stats[STAT_HEALTH] = (int)PRVM_serveredictfloat(ent, health); + stats[STAT_AMMO] = (int)PRVM_serveredictfloat(ent, currentammo); + stats[STAT_SHELLS] = (int)PRVM_serveredictfloat(ent, ammo_shells); + stats[STAT_NAILS] = (int)PRVM_serveredictfloat(ent, ammo_nails); + stats[STAT_ROCKETS] = (int)PRVM_serveredictfloat(ent, ammo_rockets); + stats[STAT_CELLS] = (int)PRVM_serveredictfloat(ent, ammo_cells); + stats[STAT_ACTIVEWEAPON] = (int)PRVM_serveredictfloat(ent, weapon); + stats[STAT_VIEWZOOM] = viewzoom; + stats[STAT_TOTALSECRETS] = (int)PRVM_serverglobalfloat(total_secrets); + stats[STAT_TOTALMONSTERS] = (int)PRVM_serverglobalfloat(total_monsters); + // the QC bumps these itself by sending svc_'s, so we have to keep them + // zero or they'll be corrected by the engine + //stats[STAT_SECRETS] = PRVM_serverglobalfloat(found_secrets); + //stats[STAT_MONSTERS] = PRVM_serverglobalfloat(killed_monsters); + + // movement settings for prediction + // note: these are not sent in protocols with lower MAX_CL_STATS limits + stats[STAT_MOVEFLAGS] = MOVEFLAG_VALID + | (sv_gameplayfix_q2airaccelerate.integer ? MOVEFLAG_Q2AIRACCELERATE : 0) + | (sv_gameplayfix_nogravityonground.integer ? MOVEFLAG_NOGRAVITYONGROUND : 0) + | (sv_gameplayfix_gravityunaffectedbyticrate.integer ? MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE : 0) + ; + statsf[STAT_MOVEVARS_TICRATE] = sys_ticrate.value; + statsf[STAT_MOVEVARS_TIMESCALE] = slowmo.value; + statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value; + statsf[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value; + statsf[STAT_MOVEVARS_MAXSPEED] = sv_maxspeed.value; + statsf[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_maxspeed.value; // FIXME: QW has a separate cvar for this + statsf[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value; + statsf[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value >= 0 ? sv_airaccelerate.value : sv_accelerate.value; + statsf[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value >= 0 ? sv_wateraccelerate.value : sv_accelerate.value; + statsf[STAT_MOVEVARS_ENTGRAVITY] = gravity; + statsf[STAT_MOVEVARS_JUMPVELOCITY] = sv_jumpvelocity.value; + statsf[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value; + statsf[STAT_MOVEVARS_MAXAIRSPEED] = sv_maxairspeed.value; + statsf[STAT_MOVEVARS_STEPHEIGHT] = sv_stepheight.value; + statsf[STAT_MOVEVARS_AIRACCEL_QW] = sv_airaccel_qw.value; + statsf[STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR] = sv_airaccel_qw_stretchfactor.value; + statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_airaccel_sideways_friction.value; + statsf[STAT_MOVEVARS_FRICTION] = sv_friction.value; + statsf[STAT_MOVEVARS_WATERFRICTION] = sv_waterfriction.value >= 0 ? sv_waterfriction.value : sv_friction.value; + statsf[STAT_MOVEVARS_AIRSTOPACCELERATE] = sv_airstopaccelerate.value; + statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE] = sv_airstrafeaccelerate.value; + statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED] = sv_maxairstrafespeed.value; + statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW] = sv_airstrafeaccel_qw.value; + statsf[STAT_MOVEVARS_AIRCONTROL] = sv_aircontrol.value; + statsf[STAT_MOVEVARS_AIRCONTROL_POWER] = sv_aircontrol_power.value; + statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY] = sv_aircontrol_penalty.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL] = sv_warsowbunny_airforwardaccel.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL] = sv_warsowbunny_accel.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED] = sv_warsowbunny_topspeed.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL] = sv_warsowbunny_turnaccel.value; + statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO] = sv_warsowbunny_backtosideratio.value; + statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW] = sv_airspeedlimit_nonqw.value; + statsf[STAT_FRAGLIMIT] = fraglimit.value; + statsf[STAT_TIMELIMIT] = timelimit.value; + + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) + { + if (stats[STAT_VIEWHEIGHT] != DEFAULT_VIEWHEIGHT) bits |= SU_VIEWHEIGHT; + bits |= SU_ITEMS; + if (stats[STAT_WEAPONFRAME]) bits |= SU_WEAPONFRAME; + if (stats[STAT_ARMOR]) bits |= SU_ARMOR; + bits |= SU_WEAPON; + // FIXME: which protocols support this? does PROTOCOL_DARKPLACES3 support viewzoom? + if (sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) + if (viewzoom != 255) + bits |= SU_VIEWZOOM; + } + + if (bits >= 65536) + bits |= SU_EXTEND1; + if (bits >= 16777216) + bits |= SU_EXTEND2; + + // send the data + MSG_WriteByte (msg, svc_clientdata); + MSG_WriteShort (msg, bits); + if (bits & SU_EXTEND1) + MSG_WriteByte(msg, bits >> 16); + if (bits & SU_EXTEND2) + MSG_WriteByte(msg, bits >> 24); + + if (bits & SU_VIEWHEIGHT) + MSG_WriteChar (msg, stats[STAT_VIEWHEIGHT]); + + if (bits & SU_IDEALPITCH) + MSG_WriteChar (msg, (int)PRVM_serveredictfloat(ent, idealpitch)); + + for (i=0 ; i<3 ; i++) + { + if (bits & (SU_PUNCH1<begun || !client->netconnection || client->unreliablemsg.cursize + sv.datagram.cursize > client->unreliablemsg.maxsize || client->unreliablemsg_splitpoints >= (int)(sizeof(client->unreliablemsg_splitpoint)/sizeof(client->unreliablemsg_splitpoint[0]))) + continue; + SZ_Write(&client->unreliablemsg, sv.datagram.data, sv.datagram.cursize); + client->unreliablemsg_splitpoint[client->unreliablemsg_splitpoints++] = client->unreliablemsg.cursize; + } + SZ_Clear(&sv.datagram); +} + +static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg, int maxsize, int maxsize2) +{ + // scan the splitpoints to find out how many we can fit in + int numsegments, j, split; + if (!client->unreliablemsg_splitpoints) + return; + // always accept the first one if it's within 1024 bytes, this ensures + // that very big datagrams which are over the rate limit still get + // through, just to keep it working + for (numsegments = 1;numsegments < client->unreliablemsg_splitpoints;numsegments++) + if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > maxsize) + break; + // the first segment gets an exemption from the rate limiting, otherwise + // it could get dropped consistently due to a low rate limit + if (numsegments == 1) + maxsize = maxsize2; + // some will fit, so add the ones that will fit + split = client->unreliablemsg_splitpoint[numsegments-1]; + // note this discards ones that were accepted by the segments scan but + // can not fit, such as a really huge first one that will never ever + // fit in a packet... + if (msg->cursize + split <= maxsize) + SZ_Write(msg, client->unreliablemsg.data, split); + // remove the part we sent, keeping any remaining data + client->unreliablemsg.cursize -= split; + if (client->unreliablemsg.cursize > 0) + memmove(client->unreliablemsg.data, client->unreliablemsg.data + split, client->unreliablemsg.cursize); + // adjust remaining splitpoints + client->unreliablemsg_splitpoints -= numsegments; + for (j = 0;j < client->unreliablemsg_splitpoints;j++) + client->unreliablemsg_splitpoint[j] = client->unreliablemsg_splitpoint[numsegments + j] - split; +} + +/* +======================= +SV_SendClientDatagram +======================= +*/ +static void SV_SendClientDatagram (client_t *client) +{ + int clientrate, maxrate, maxsize, maxsize2, downloadsize; + sizebuf_t msg; + int stats[MAX_CL_STATS]; + static unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; + + // obey rate limit by limiting packet frequency if the packet size + // limiting fails + // (usually this is caused by reliable messages) + if (!NetConn_CanSend(client->netconnection)) + return; + + // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates + maxrate = max(NET_MINRATE, sv_maxrate.integer); + if (sv_maxrate.integer != maxrate) + Cvar_SetValueQuick(&sv_maxrate, maxrate); + + // clientrate determines the 'cleartime' of a packet + // (how long to wait before sending another, based on this packet's size) + clientrate = bound(NET_MINRATE, client->rate, maxrate); + + switch (sv.protocol) + { + case PROTOCOL_QUAKE: + case PROTOCOL_QUAKEDP: + case PROTOCOL_NEHAHRAMOVIE: + case PROTOCOL_NEHAHRABJP: + case PROTOCOL_NEHAHRABJP2: + case PROTOCOL_NEHAHRABJP3: + case PROTOCOL_QUAKEWORLD: + // no packet size limit support on Quake protocols because it just + // causes missing entities/effects + // packets are simply sent less often to obey the rate limit + maxsize = 1024; + maxsize2 = 1024; + break; + case PROTOCOL_DARKPLACES1: + case PROTOCOL_DARKPLACES2: + case PROTOCOL_DARKPLACES3: + case PROTOCOL_DARKPLACES4: + // no packet size limit support on DP1-4 protocols because they kick + // the client off if they overflow, and miss effects + // packets are simply sent less often to obey the rate limit + maxsize = sizeof(sv_sendclientdatagram_buf); + maxsize2 = sizeof(sv_sendclientdatagram_buf); + break; + default: + // DP5 and later protocols support packet size limiting which is a + // better method than limiting packet frequency as QW does + // + // at very low rates (or very small sys_ticrate) the packet size is + // not reduced below 128, but packets may be sent less often + maxsize = (int)(clientrate * sys_ticrate.value); + maxsize = bound(128, maxsize, 1400); + maxsize2 = 1400; + // csqc entities can easily exceed 128 bytes, so disable throttling in + // mods that use csqc (they are likely to use less bandwidth anyway) + if (sv.csqc_progsize > 0) + maxsize = maxsize2; + break; + } + + if (LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP && !sv_ratelimitlocalplayer.integer) + { + // for good singleplayer, send huge packets + maxsize = sizeof(sv_sendclientdatagram_buf); + maxsize2 = sizeof(sv_sendclientdatagram_buf); + // never limit frequency in singleplayer + clientrate = 1000000000; + } + + // while downloading, limit entity updates to half the packet + // (any leftover space will be used for downloading) + if (host_client->download_file) + maxsize /= 2; + + msg.data = sv_sendclientdatagram_buf; + msg.maxsize = sizeof(sv_sendclientdatagram_buf); + msg.cursize = 0; + msg.allowoverflow = false; + + if (host_client->begun) + { + // the player is in the game + MSG_WriteByte (&msg, svc_time); + MSG_WriteFloat (&msg, sv.time); + + // add the client specific data to the datagram + SV_WriteClientdataToMessage (client, client->edict, &msg, stats); + // now update the stats[] array using any registered custom fields + VM_SV_UpdateCustomStats(client, client->edict, &msg, stats); + // set host_client->statsdeltabits + Protocol_UpdateClientStats (stats); + + // add as many queued unreliable messages (effects) as we can fit + // limit effects to half of the remaining space + if (client->unreliablemsg.cursize) + SV_WriteUnreliableMessages (client, &msg, maxsize/2, maxsize2); + + // now write as many entities as we can fit, and also sends stats + SV_WriteEntitiesToClient (client, client->edict, &msg, maxsize); + } + else if (realtime > client->keepalivetime) + { + // the player isn't totally in the game yet + // send small keepalive messages if too much time has passed + // (may also be sending downloads) + client->keepalivetime = realtime + 5; + MSG_WriteChar (&msg, svc_nop); + } + + // if a download is active, see if there is room to fit some download data + // in this packet + downloadsize = min(maxsize*2,maxsize2) - msg.cursize - 7; + if (host_client->download_file && host_client->download_started && downloadsize > 0) + { + fs_offset_t downloadstart; + unsigned char data[1400]; + downloadstart = FS_Tell(host_client->download_file); + downloadsize = min(downloadsize, (int)sizeof(data)); + downloadsize = FS_Read(host_client->download_file, data, downloadsize); + // note this sends empty messages if at the end of the file, which is + // necessary to keep the packet loss logic working + // (the last blocks may be lost and need to be re-sent, and that will + // only occur if the client acks the empty end messages, revealing + // a gap in the download progress, causing the last blocks to be + // sent again) + MSG_WriteChar (&msg, svc_downloaddata); + MSG_WriteLong (&msg, downloadstart); + MSG_WriteShort (&msg, downloadsize); + if (downloadsize > 0) + SZ_Write (&msg, data, downloadsize); + } + + // reliable only if none is in progress + if(client->sendsignon != 2 && !client->netconnection->sendMessageLength) + SV_WriteDemoMessage(client, &(client->netconnection->message), false); + // unreliable + SV_WriteDemoMessage(client, &msg, false); + +// send the datagram + NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate, client->sendsignon == 2); + if (client->sendsignon == 1 && !client->netconnection->message.cursize) + client->sendsignon = 2; // prevent reliable until client sends prespawn (this is the keepalive phase) +} + +/* +======================= +SV_UpdateToReliableMessages +======================= +*/ +static void SV_UpdateToReliableMessages (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i, j; + client_t *client; + const char *name; + const char *model; + const char *skin; + int clientcamera; + +// check for changes to be sent over the reliable streams + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + // update the host_client fields we care about according to the entity fields + host_client->edict = PRVM_EDICT_NUM(i+1); + + // DP_SV_CLIENTNAME + name = PRVM_GetString(prog, PRVM_serveredictstring(host_client->edict, netname)); + if (name == NULL) + name = ""; + // always point the string back at host_client->name to keep it safe + strlcpy (host_client->name, name, sizeof (host_client->name)); + PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name); + if (strcmp(host_client->old_name, host_client->name)) + { + if (host_client->begun) + SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name); + strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name)); + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatename); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteString (&sv.reliable_datagram, host_client->name); + SV_WriteNetnameIntoDemo(host_client); + } + + // DP_SV_CLIENTCOLORS + host_client->colors = (int)PRVM_serveredictfloat(host_client->edict, clientcolors); + if (host_client->old_colors != host_client->colors) + { + host_client->old_colors = host_client->colors; + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteByte (&sv.reliable_datagram, host_client->colors); + } + + // NEXUIZ_PLAYERMODEL + model = PRVM_GetString(prog, PRVM_serveredictstring(host_client->edict, playermodel)); + if (model == NULL) + model = ""; + // always point the string back at host_client->name to keep it safe + strlcpy (host_client->playermodel, model, sizeof (host_client->playermodel)); + PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel); + + // NEXUIZ_PLAYERSKIN + skin = PRVM_GetString(prog, PRVM_serveredictstring(host_client->edict, playerskin)); + if (skin == NULL) + skin = ""; + // always point the string back at host_client->name to keep it safe + strlcpy (host_client->playerskin, skin, sizeof (host_client->playerskin)); + PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin); + + // TODO: add an extension name for this [1/17/2008 Black] + clientcamera = PRVM_serveredictedict(host_client->edict, clientcamera); + if (clientcamera > 0) + { + int oldclientcamera = host_client->clientcamera; + if (clientcamera >= prog->max_edicts || PRVM_EDICT_NUM(clientcamera)->priv.required->free) + clientcamera = PRVM_NUM_FOR_EDICT(host_client->edict); + host_client->clientcamera = clientcamera; + + if (oldclientcamera != host_client->clientcamera && host_client->netconnection) + { + MSG_WriteByte(&host_client->netconnection->message, svc_setview); + MSG_WriteShort(&host_client->netconnection->message, host_client->clientcamera); + } + } + + // frags + host_client->frags = (int)PRVM_serveredictfloat(host_client->edict, frags); + if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) + if(!host_client->begun && host_client->netconnection) + host_client->frags = -666; + if (host_client->old_frags != host_client->frags) + { + host_client->old_frags = host_client->frags; + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatefrags); + MSG_WriteByte (&sv.reliable_datagram, i); + MSG_WriteShort (&sv.reliable_datagram, host_client->frags); + } + } + + for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++) + if (client->netconnection && (client->begun || client->clientconnectcalled)) // also send MSG_ALL to people who are past ClientConnect, but not spawned yet + SZ_Write (&client->netconnection->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize); + + SZ_Clear (&sv.reliable_datagram); +} + + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages(void) +{ + int i, prepared = false; + + if (sv.protocol == PROTOCOL_QUAKEWORLD) + Sys_Error("SV_SendClientMessages: no quakeworld support\n"); + + SV_FlushBroadcastMessages(); + +// update frags, names, etc + SV_UpdateToReliableMessages(); + +// build individual updates + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + if (!host_client->active) + continue; + if (!host_client->netconnection) + continue; + + if (host_client->netconnection->message.overflowed) + { + SV_DropClient (true); // if the message couldn't send, kick off + continue; + } + + if (!prepared) + { + prepared = true; + // only prepare entities once per frame + SV_PrepareEntitiesForSending(); + } + SV_SendClientDatagram(host_client); + } + +// clear muzzle flashes + SV_CleanupEnts(); +} + +static void SV_StartDownload_f(void) +{ + if (host_client->download_file) + host_client->download_started = true; +} + +/* + * Compression extension negotiation: + * + * Server to client: + * cl_serverextension_download 2 + * + * Client to server: + * download + * e.g. + * download maps/map1.bsp lzo deflate huffman + * + * Server to client: + * cl_downloadbegin + * e.g. + * cl_downloadbegin 123456 maps/map1.bsp deflate + * + * The server may choose not to compress the file by sending no compression name, like: + * cl_downloadbegin 345678 maps/map1.bsp + * + * NOTE: the "download" command may only specify compression algorithms if + * cl_serverextension_download is 2! + * If cl_serverextension_download has a different value, the client must + * assume this extension is not supported! + */ + +static void Download_CheckExtensions(void) +{ + int i; + int argc = Cmd_Argc(); + + // first reset them all + host_client->download_deflate = false; + + for(i = 2; i < argc; ++i) + { + if(!strcmp(Cmd_Argv(i), "deflate")) + { + host_client->download_deflate = true; + break; + } + } +} + +static void SV_Download_f(void) +{ + const char *whichpack, *whichpack2, *extension; + qboolean is_csqc; // so we need to check only once + + if (Cmd_Argc() < 2) + { + SV_ClientPrintf("usage: download {}*\n"); + SV_ClientPrintf(" supported extensions: deflate\n"); + return; + } + + if (FS_CheckNastyPath(Cmd_Argv(1), false)) + { + SV_ClientPrintf("Download rejected: nasty filename \"%s\"\n", Cmd_Argv(1)); + return; + } + + if (host_client->download_file) + { + // at this point we'll assume the previous download should be aborted + Con_DPrintf("Download of %s aborted by %s starting a new download\n", host_client->download_name, host_client->name); + Host_ClientCommands("\nstopdownload\n"); + + // close the file and reset variables + FS_Close(host_client->download_file); + host_client->download_file = NULL; + host_client->download_name[0] = 0; + host_client->download_expectedposition = 0; + host_client->download_started = false; + } + + is_csqc = (sv.csqc_progname[0] && strcmp(Cmd_Argv(1), sv.csqc_progname) == 0); + + if (!sv_allowdownloads.integer && !is_csqc) + { + SV_ClientPrintf("Downloads are disabled on this server\n"); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + Download_CheckExtensions(); + + strlcpy(host_client->download_name, Cmd_Argv(1), sizeof(host_client->download_name)); + extension = FS_FileExtension(host_client->download_name); + + // host_client is asking to download a specified file + if (developer_extra.integer) + Con_DPrintf("Download request for %s by %s\n", host_client->download_name, host_client->name); + + if(is_csqc) + { + char extensions[MAX_QPATH]; // make sure this can hold all extensions + extensions[0] = '\0'; + + if(host_client->download_deflate) + strlcat(extensions, " deflate", sizeof(extensions)); + + Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name); + + if(host_client->download_deflate && svs.csqc_progdata_deflated) + host_client->download_file = FS_FileFromData(svs.csqc_progdata_deflated, svs.csqc_progsize_deflated, true); + else + host_client->download_file = FS_FileFromData(svs.csqc_progdata, sv.csqc_progsize, true); + + // no, no space is needed between %s and %s :P + Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions); + + host_client->download_expectedposition = 0; + host_client->download_started = false; + host_client->sendsignon = true; // make sure this message is sent + return; + } + + if (!FS_FileExists(host_client->download_name)) + { + SV_ClientPrintf("Download rejected: server does not have the file \"%s\"\nYou may need to separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + // check if the user is trying to download part of registered Quake(r) + whichpack = FS_WhichPack(host_client->download_name); + whichpack2 = FS_WhichPack("gfx/pop.lmp"); + if ((whichpack && whichpack2 && !strcasecmp(whichpack, whichpack2)) || FS_IsRegisteredQuakePack(host_client->download_name)) + { + SV_ClientPrintf("Download rejected: file \"%s\" is part of registered Quake(r)\nYou must purchase Quake(r) from id Software or a retailer to get this file\nPlease go to http://www.idsoftware.com/games/quake/quake/index.php?game_section=buy\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + // check if the server has forbidden archive downloads entirely + if (!sv_allowdownloads_inarchive.integer) + { + whichpack = FS_WhichPack(host_client->download_name); + if (whichpack) + { + SV_ClientPrintf("Download rejected: file \"%s\" is in an archive (\"%s\")\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name, whichpack); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_config.integer) + { + if (!strcasecmp(extension, "cfg")) + { + SV_ClientPrintf("Download rejected: file \"%s\" is a .cfg file which is forbidden for security reasons\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_dlcache.integer) + { + if (!strncasecmp(host_client->download_name, "dlcache/", 8)) + { + SV_ClientPrintf("Download rejected: file \"%s\" is in the dlcache/ directory which is forbidden for security reasons\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + if (!sv_allowdownloads_archive.integer) + { + if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) + { + SV_ClientPrintf("Download rejected: file \"%s\" is an archive\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + } + + host_client->download_file = FS_OpenVirtualFile(host_client->download_name, true); + if (!host_client->download_file) + { + SV_ClientPrintf("Download rejected: server could not open the file \"%s\"\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + return; + } + + if (FS_FileSize(host_client->download_file) > 1<<30) + { + SV_ClientPrintf("Download rejected: file \"%s\" is very large\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + return; + } + + if (FS_FileSize(host_client->download_file) < 0) + { + SV_ClientPrintf("Download rejected: file \"%s\" is not a regular file\n", host_client->download_name); + Host_ClientCommands("\nstopdownload\n"); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + return; + } + + Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name); + + /* + * we can only do this if we would actually deflate on the fly + * which we do not (yet)! + { + char extensions[MAX_QPATH]; // make sure this can hold all extensions + extensions[0] = '\0'; + + if(host_client->download_deflate) + strlcat(extensions, " deflate", sizeof(extensions)); + + // no, no space is needed between %s and %s :P + Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions); + } + */ + Host_ClientCommands("\ncl_downloadbegin %i %s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name); + + host_client->download_expectedposition = 0; + host_client->download_started = false; + host_client->sendsignon = true; // make sure this message is sent + + // the rest of the download process is handled in SV_SendClientDatagram + // and other code dealing with svc_downloaddata and clc_ackdownloaddata + // + // no svc_downloaddata messages will be sent until sv_startdownload is + // sent by the client +} + +/* +============================================================================== + +SERVER SPAWNING + +============================================================================== +*/ + +/* +================ +SV_ModelIndex + +================ +*/ +int SV_ModelIndex(const char *s, int precachemode) +{ + int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) ? 256 : MAX_MODELS); + char filename[MAX_QPATH]; + if (!s || !*s) + return 0; + // testing + //if (precachemode == 2) + // return 0; + strlcpy(filename, s, sizeof(filename)); + for (i = 2;i < limit;i++) + { + if (!sv.model_precache[i][0]) + { + if (precachemode) + { + if (sv.state != ss_loading && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5)) + { + Con_Printf("SV_ModelIndex(\"%s\"): precache_model can only be done in spawn functions\n", filename); + return 0; + } + if (precachemode == 1) + Con_Printf("SV_ModelIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename); + strlcpy(sv.model_precache[i], filename, sizeof(sv.model_precache[i])); + if (sv.state == ss_loading) + { + // running from SV_SpawnServer which is launched from the client console command interpreter + sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL); + } + else + { + if (svs.threaded) + { + // this is running on the server thread, we can't load a model here (it would crash on renderer calls), so only look it up, the svc_precache will cause it to be loaded when it reaches the client + sv.models[i] = Mod_FindName (sv.model_precache[i], s[0] == '*' ? sv.worldname : NULL); + } + else + { + // running single threaded, so we can load the model here + sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL); + } + MSG_WriteByte(&sv.reliable_datagram, svc_precache); + MSG_WriteShort(&sv.reliable_datagram, i); + MSG_WriteString(&sv.reliable_datagram, filename); + } + return i; + } + Con_Printf("SV_ModelIndex(\"%s\"): not precached\n", filename); + return 0; + } + if (!strcmp(sv.model_precache[i], filename)) + return i; + } + Con_Printf("SV_ModelIndex(\"%s\"): i (%i) == MAX_MODELS (%i)\n", filename, i, MAX_MODELS); + return 0; +} + +/* +================ +SV_SoundIndex + +================ +*/ +int SV_SoundIndex(const char *s, int precachemode) +{ + int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) ? 256 : MAX_SOUNDS); + char filename[MAX_QPATH]; + if (!s || !*s) + return 0; + // testing + //if (precachemode == 2) + // return 0; + strlcpy(filename, s, sizeof(filename)); + for (i = 1;i < limit;i++) + { + if (!sv.sound_precache[i][0]) + { + if (precachemode) + { + if (sv.state != ss_loading && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5)) + { + Con_Printf("SV_SoundIndex(\"%s\"): precache_sound can only be done in spawn functions\n", filename); + return 0; + } + if (precachemode == 1) + Con_Printf("SV_SoundIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename); + strlcpy(sv.sound_precache[i], filename, sizeof(sv.sound_precache[i])); + if (sv.state != ss_loading) + { + MSG_WriteByte(&sv.reliable_datagram, svc_precache); + MSG_WriteShort(&sv.reliable_datagram, i + 32768); + MSG_WriteString(&sv.reliable_datagram, filename); + } + return i; + } + Con_Printf("SV_SoundIndex(\"%s\"): not precached\n", filename); + return 0; + } + if (!strcmp(sv.sound_precache[i], filename)) + return i; + } + Con_Printf("SV_SoundIndex(\"%s\"): i (%i) == MAX_SOUNDS (%i)\n", filename, i, MAX_SOUNDS); + return 0; +} + +/* +================ +SV_ParticleEffectIndex + +================ +*/ +int SV_ParticleEffectIndex(const char *name) +{ + int i, argc, linenumber, effectnameindex; + int filepass; + fs_offset_t filesize; + unsigned char *filedata; + const char *text; + const char *textstart; + //const char *textend; + char argv[16][1024]; + char filename[MAX_QPATH]; + if (!sv.particleeffectnamesloaded) + { + sv.particleeffectnamesloaded = true; + memset(sv.particleeffectname, 0, sizeof(sv.particleeffectname)); + for (i = 0;i < EFFECT_TOTAL;i++) + strlcpy(sv.particleeffectname[i], standardeffectnames[i], sizeof(sv.particleeffectname[i])); + for (filepass = 0;;filepass++) + { + if (filepass == 0) + dpsnprintf(filename, sizeof(filename), "effectinfo.txt"); + else if (filepass == 1) + dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", sv.worldnamenoextension); + else + break; + filedata = FS_LoadFile(filename, tempmempool, true, &filesize); + if (!filedata) + continue; + textstart = (const char *)filedata; + //textend = (const char *)filedata + filesize; + text = textstart; + for (linenumber = 1;;linenumber++) + { + argc = 0; + for (;;) + { + if (!COM_ParseToken_Simple(&text, true, false, true) || !strcmp(com_token, "\n")) + break; + if (argc < 16) + { + strlcpy(argv[argc], com_token, sizeof(argv[argc])); + argc++; + } + } + if (com_token[0] == 0) + break; // if the loop exited and it's not a \n, it's EOF + if (argc < 1) + continue; + if (!strcmp(argv[0], "effect")) + { + if (argc == 2) + { + for (effectnameindex = 1;effectnameindex < SV_MAX_PARTICLEEFFECTNAME;effectnameindex++) + { + if (sv.particleeffectname[effectnameindex][0]) + { + if (!strcmp(sv.particleeffectname[effectnameindex], argv[1])) + break; + } + else + { + strlcpy(sv.particleeffectname[effectnameindex], argv[1], sizeof(sv.particleeffectname[effectnameindex])); + break; + } + } + // if we run out of names, abort + if (effectnameindex == SV_MAX_PARTICLEEFFECTNAME) + { + Con_Printf("%s:%i: too many effects!\n", filename, linenumber); + break; + } + } + } + } + Mem_Free(filedata); + } + } + // search for the name + for (effectnameindex = 1;effectnameindex < SV_MAX_PARTICLEEFFECTNAME && sv.particleeffectname[effectnameindex][0];effectnameindex++) + if (!strcmp(sv.particleeffectname[effectnameindex], name)) + return effectnameindex; + // return 0 if we couldn't find it + return 0; +} + +dp_model_t *SV_GetModelByIndex(int modelindex) +{ + return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL; +} + +dp_model_t *SV_GetModelFromEdict(prvm_edict_t *ed) +{ + prvm_prog_t *prog = SVVM_prog; + int modelindex; + if (!ed || ed->priv.server->free) + return NULL; + modelindex = (int)PRVM_serveredictfloat(ed, modelindex); + return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL; +} + +/* +================ +SV_CreateBaseline + +================ +*/ +static void SV_CreateBaseline (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i, entnum, large; + prvm_edict_t *svent; + + // LordHavoc: clear *all* baselines (not just active ones) + for (entnum = 0;entnum < prog->max_edicts;entnum++) + { + // get the current server version + svent = PRVM_EDICT_NUM(entnum); + + // LordHavoc: always clear state values, whether the entity is in use or not + svent->priv.server->baseline = defaultstate; + + if (svent->priv.server->free) + continue; + if (entnum > svs.maxclients && !PRVM_serveredictfloat(svent, modelindex)) + continue; + + // create entity baseline + VectorCopy (PRVM_serveredictvector(svent, origin), svent->priv.server->baseline.origin); + VectorCopy (PRVM_serveredictvector(svent, angles), svent->priv.server->baseline.angles); + svent->priv.server->baseline.frame = (int)PRVM_serveredictfloat(svent, frame); + svent->priv.server->baseline.skin = (int)PRVM_serveredictfloat(svent, skin); + if (entnum > 0 && entnum <= svs.maxclients) + { + svent->priv.server->baseline.colormap = entnum; + svent->priv.server->baseline.modelindex = SV_ModelIndex("progs/player.mdl", 1); + } + else + { + svent->priv.server->baseline.colormap = 0; + svent->priv.server->baseline.modelindex = (int)PRVM_serveredictfloat(svent, modelindex); + } + + large = false; + if (svent->priv.server->baseline.modelindex & 0xFF00 || svent->priv.server->baseline.frame & 0xFF00) + { + large = true; + if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + large = false; + } + + // add to the message + if (large) + MSG_WriteByte (&sv.signon, svc_spawnbaseline2); + else + MSG_WriteByte (&sv.signon, svc_spawnbaseline); + MSG_WriteShort (&sv.signon, entnum); + + if (large) + { + MSG_WriteShort (&sv.signon, svent->priv.server->baseline.modelindex); + MSG_WriteShort (&sv.signon, svent->priv.server->baseline.frame); + } + else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + { + MSG_WriteShort (&sv.signon, svent->priv.server->baseline.modelindex); + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.frame); + } + else + { + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.modelindex); + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.frame); + } + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.colormap); + MSG_WriteByte (&sv.signon, svent->priv.server->baseline.skin); + for (i=0 ; i<3 ; i++) + { + MSG_WriteCoord(&sv.signon, svent->priv.server->baseline.origin[i], sv.protocol); + MSG_WriteAngle(&sv.signon, svent->priv.server->baseline.angles[i], sv.protocol); + } + } +} + +/* +================ +SV_Prepare_CSQC + +Load csprogs.dat and comperss it so it doesn't need to be +reloaded on request. +================ +*/ +static void SV_Prepare_CSQC(void) +{ + fs_offset_t progsize; + + if(svs.csqc_progdata) + { + Con_DPrintf("Unloading old CSQC data.\n"); + Mem_Free(svs.csqc_progdata); + if(svs.csqc_progdata_deflated) + Mem_Free(svs.csqc_progdata_deflated); + } + + svs.csqc_progdata = NULL; + svs.csqc_progdata_deflated = NULL; + + sv.csqc_progname[0] = 0; + svs.csqc_progdata = FS_LoadFile(csqc_progname.string, sv_mempool, false, &progsize); + + if(progsize > 0) + { + size_t deflated_size; + + sv.csqc_progsize = (int)progsize; + sv.csqc_progcrc = CRC_Block(svs.csqc_progdata, progsize); + strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname)); + Con_DPrintf("server detected csqc progs file \"%s\" with size %i and crc %i\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); + + Con_DPrint("Compressing csprogs.dat\n"); + //unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool); + svs.csqc_progdata_deflated = FS_Deflate(svs.csqc_progdata, progsize, &deflated_size, -1, sv_mempool); + svs.csqc_progsize_deflated = (int)deflated_size; + if(svs.csqc_progdata_deflated) + { + Con_DPrintf("Deflated: %g%%\n", 100.0 - 100.0 * (deflated_size / (float)progsize)); + Con_DPrintf("Uncompressed: %u\nCompressed: %u\n", (unsigned)sv.csqc_progsize, (unsigned)svs.csqc_progsize_deflated); + } + else + Con_DPrintf("Cannot compress - need zlib for this. Using uncompressed progs only.\n"); + } +} + +/* +================ +SV_SaveSpawnparms + +Grabs the current state of each client for saving across the +transition to another level +================ +*/ +void SV_SaveSpawnparms (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i, j; + + svs.serverflags = (int)PRVM_serverglobalfloat(serverflags); + + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + if (!host_client->active) + continue; + + // call the progs to get default spawn parms for the new client + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(SetChangeParms), "QC function SetChangeParms is missing"); + for (j=0 ; jspawn_parms[j] = (&PRVM_serverglobalfloat(parm1))[j]; + } +} + +/* +================ +SV_SpawnServer + +This is called at the start of each level +================ +*/ + +void SV_SpawnServer (const char *server) +{ + prvm_prog_t *prog = SVVM_prog; + prvm_edict_t *ent; + int i; + char *entities; + dp_model_t *worldmodel; + char modelname[sizeof(sv.worldname)]; + char vabuf[1024]; + + Con_DPrintf("SpawnServer: %s\n", server); + + dpsnprintf (modelname, sizeof(modelname), "maps/%s.bsp", server); + + if (!FS_FileExists(modelname)) + { + dpsnprintf (modelname, sizeof(modelname), "maps/%s", server); + if (!FS_FileExists(modelname)) + { + Con_Printf("SpawnServer: no map file named maps/%s.bsp\n", server); + return; + } + } + +// SV_LockThreadMutex(); + + if(cls.state == ca_dedicated) + Sys_MakeProcessNice(); + + if (cls.state != ca_dedicated) + { + SCR_BeginLoadingPlaque(false); + S_StopAllSounds(); + } + + if(sv.active) + { + World_End(&sv.world); + if(PRVM_serverfunction(SV_Shutdown)) + { + func_t s = PRVM_serverfunction(SV_Shutdown); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again + prog->ExecuteProgram(prog, s,"SV_Shutdown() required"); + } + } + + // free q3 shaders so that any newly downloaded shaders will be active + Mod_FreeQ3Shaders(); + + worldmodel = Mod_ForName(modelname, false, developer.integer > 0, NULL); + if (!worldmodel || !worldmodel->TraceBox) + { + Con_Printf("Couldn't load map %s\n", modelname); + + if(cls.state == ca_dedicated) + Sys_MakeProcessMean(); + +// SV_UnlockThreadMutex(); + + return; + } + + Collision_Cache_Reset(true); + + // let's not have any servers with no name + if (hostname.string[0] == 0) + Cvar_Set ("hostname", "UNNAMED"); + scr_centertime_off = 0; + + svs.changelevel_issued = false; // now safe to issue another + + // make the map a required file for clients + Curl_ClearRequirements(); + Curl_RequireFile(modelname); + +// +// tell all connected clients that we are going to a new level +// + if (sv.active) + { + client_t *client; + for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) + { + if (client->netconnection) + { + MSG_WriteByte(&client->netconnection->message, svc_stufftext); + MSG_WriteString(&client->netconnection->message, "reconnect\n"); + } + } + } + else + { + // open server port + //NetConn_OpenServerPorts(true); + + //Just use the loop connection + NetConn_OpenServerPorts(0); + } + +// +// make cvars consistant +// + if (coop.integer) + Cvar_SetValue ("deathmatch", 0); + // LordHavoc: it can be useful to have skills outside the range 0-3... + //current_skill = bound(0, (int)(skill.value + 0.5), 3); + //Cvar_SetValue ("skill", (float)current_skill); + current_skill = (int)(skill.value + 0.5); + +// +// set up the new server +// + memset (&sv, 0, sizeof(sv)); + // if running a local client, make sure it doesn't try to access the last + // level's data which is no longer valiud + cls.signon = 0; + + Cvar_SetValue("halflifebsp", worldmodel->brush.ishlbsp); + + if(*sv_random_seed.string) + { + srand(sv_random_seed.integer); + Con_Printf("NOTE: random seed is %d; use for debugging/benchmarking only!\nUnset sv_random_seed to get real random numbers again.\n", sv_random_seed.integer); + } + + SV_VM_Setup(); + + sv.active = true; + + // set level base name variables for later use + strlcpy (sv.name, server, sizeof (sv.name)); + strlcpy(sv.worldname, modelname, sizeof(sv.worldname)); + FS_StripExtension(sv.worldname, sv.worldnamenoextension, sizeof(sv.worldnamenoextension)); + strlcpy(sv.worldbasename, !strncmp(sv.worldnamenoextension, "maps/", 5) ? sv.worldnamenoextension + 5 : sv.worldnamenoextension, sizeof(sv.worldbasename)); + //Cvar_SetQuick(&sv_worldmessage, sv.worldmessage); // set later after QC is spawned + Cvar_SetQuick(&sv_worldname, sv.worldname); + Cvar_SetQuick(&sv_worldnamenoextension, sv.worldnamenoextension); + Cvar_SetQuick(&sv_worldbasename, sv.worldbasename); + + sv.protocol = Protocol_EnumForName(sv_protocolname.string); + if (sv.protocol == PROTOCOL_UNKNOWN) + { + char buffer[1024]; + Protocol_Names(buffer, sizeof(buffer)); + Con_Printf("Unknown sv_protocolname \"%s\", valid values are:\n%s\n", sv_protocolname.string, buffer); + sv.protocol = PROTOCOL_QUAKE; + } + +// load progs to get entity field count + //PR_LoadProgs ( sv_progs.string ); + + sv.datagram.maxsize = sizeof(sv.datagram_buf); + sv.datagram.cursize = 0; + sv.datagram.data = sv.datagram_buf; + + sv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf); + sv.reliable_datagram.cursize = 0; + sv.reliable_datagram.data = sv.reliable_datagram_buf; + + sv.signon.maxsize = sizeof(sv.signon_buf); + sv.signon.cursize = 0; + sv.signon.data = sv.signon_buf; + +// leave slots at start for clients only + //prog->num_edicts = svs.maxclients+1; + + sv.state = ss_loading; + prog->allowworldwrites = true; + sv.paused = false; + + sv.time = 1.0; + + Mod_ClearUsed(); + worldmodel->used = true; + + sv.worldmodel = worldmodel; + sv.models[1] = sv.worldmodel; + +// +// clear world interaction links +// + World_SetSize(&sv.world, sv.worldname, sv.worldmodel->normalmins, sv.worldmodel->normalmaxs, prog); + World_Start(&sv.world); + + strlcpy(sv.sound_precache[0], "", sizeof(sv.sound_precache[0])); + + strlcpy(sv.model_precache[0], "", sizeof(sv.model_precache[0])); + strlcpy(sv.model_precache[1], sv.worldname, sizeof(sv.model_precache[1])); + for (i = 1;i < sv.worldmodel->brush.numsubmodels && i+1 < MAX_MODELS;i++) + { + dpsnprintf(sv.model_precache[i+1], sizeof(sv.model_precache[i+1]), "*%i", i); + sv.models[i+1] = Mod_ForName (sv.model_precache[i+1], false, false, sv.worldname); + } + if(i < sv.worldmodel->brush.numsubmodels) + Con_Printf("Too many submodels (MAX_MODELS is %i)\n", MAX_MODELS); + +// +// load the rest of the entities +// + // AK possible hack since num_edicts is still 0 + ent = PRVM_EDICT_NUM(0); + memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); + ent->priv.server->free = false; + PRVM_serveredictstring(ent, model) = PRVM_SetEngineString(prog, sv.worldname); + PRVM_serveredictfloat(ent, modelindex) = 1; // world model + PRVM_serveredictfloat(ent, solid) = SOLID_BSP; + PRVM_serveredictfloat(ent, movetype) = MOVETYPE_PUSH; + VectorCopy(sv.world.mins, PRVM_serveredictvector(ent, mins)); + VectorCopy(sv.world.maxs, PRVM_serveredictvector(ent, maxs)); + VectorCopy(sv.world.mins, PRVM_serveredictvector(ent, absmin)); + VectorCopy(sv.world.maxs, PRVM_serveredictvector(ent, absmax)); + + if (coop.value) + PRVM_serverglobalfloat(coop) = coop.integer; + else + PRVM_serverglobalfloat(deathmatch) = deathmatch.integer; + + PRVM_serverglobalstring(mapname) = PRVM_SetEngineString(prog, sv.name); + +// serverflags are for cross level information (sigils) + PRVM_serverglobalfloat(serverflags) = svs.serverflags; + + // we need to reset the spawned flag on all connected clients here so that + // their thinks don't run during startup (before PutClientInServer) + // we also need to set up the client entities now + // and we need to set the ->edict pointers to point into the progs edicts + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + host_client->begun = false; + host_client->edict = PRVM_EDICT_NUM(i + 1); + PRVM_ED_ClearEdict(prog, host_client->edict); + } + + // load replacement entity file if found + if (sv_entpatch.integer && (entities = (char *)FS_LoadFile(va(vabuf, sizeof(vabuf), "%s.ent", sv.worldnamenoextension), tempmempool, true, NULL))) + { + Con_Printf("Loaded %s.ent\n", sv.worldnamenoextension); + PRVM_ED_LoadFromFile(prog, entities); + Mem_Free(entities); + } + else + PRVM_ED_LoadFromFile(prog, sv.worldmodel->brush.entities); + + + // LordHavoc: clear world angles (to fix e3m3.bsp) + VectorClear(PRVM_serveredictvector(prog->edicts, angles)); + +// all setup is completed, any further precache statements are errors +// sv.state = ss_active; // LordHavoc: workaround for svc_precache bug + prog->allowworldwrites = false; + +// run two frames to allow everything to settle + sv.time = 1.0001; + for (i = 0;i < 2;i++) + { + sv.frametime = 0.1; + SV_Physics (); + } + + if (cls.state == ca_dedicated) + Mod_PurgeUnused(); + +// create a baseline for more efficient communications + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + SV_CreateBaseline (); + + sv.state = ss_active; // LordHavoc: workaround for svc_precache bug + +// send serverinfo to all connected clients, and set up botclients coming back from a level change + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + { + host_client->clientconnectcalled = false; // do NOT call ClientDisconnect if he drops before ClientConnect! + if (!host_client->active) + continue; + if (host_client->netconnection) + SV_SendServerinfo(host_client); + else + { + int j; + // if client is a botclient coming from a level change, we need to + // set up client info that normally requires networking + + // copy spawn parms out of the client_t + for (j=0 ; j< NUM_SPAWN_PARMS ; j++) + (&PRVM_serverglobalfloat(parm1))[j] = host_client->spawn_parms[j]; + + // call the spawn function + host_client->clientconnectcalled = true; + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing"); + prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing"); + host_client->begun = true; + } + } + + // update the map title cvar + strlcpy(sv.worldmessage, PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message)), sizeof(sv.worldmessage)); // map title (not related to filename) + Cvar_SetQuick(&sv_worldmessage, sv.worldmessage); + + Con_DPrint("Server spawned.\n"); + NetConn_Heartbeat (2); + + if(cls.state == ca_dedicated) + Sys_MakeProcessMean(); + +// SV_UnlockThreadMutex(); +} + +///////////////////////////////////////////////////// +// SV VM stuff + +static void SVVM_begin_increase_edicts(prvm_prog_t *prog) +{ + // links don't survive the transition, so unlink everything + World_UnlinkAll(&sv.world); +} + +static void SVVM_end_increase_edicts(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *ent; + + // link every entity except world + for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++) + if (!ent->priv.server->free) + SV_LinkEdict(ent); +} + +static void SVVM_init_edict(prvm_prog_t *prog, prvm_edict_t *e) +{ + // LordHavoc: for consistency set these here + int num = PRVM_NUM_FOR_EDICT(e) - 1; + + e->priv.server->move = false; // don't move on first frame + + if (num >= 0 && num < svs.maxclients) + { + // set colormap and team on newly created player entity + PRVM_serveredictfloat(e, colormap) = num + 1; + PRVM_serveredictfloat(e, team) = (svs.clients[num].colors & 15) + 1; + // set netname/clientcolors back to client values so that + // DP_SV_CLIENTNAME and DP_SV_CLIENTCOLORS will not immediately + // reset them + PRVM_serveredictstring(e, netname) = PRVM_SetEngineString(prog, svs.clients[num].name); + PRVM_serveredictfloat(e, clientcolors) = svs.clients[num].colors; + // NEXUIZ_PLAYERMODEL and NEXUIZ_PLAYERSKIN + PRVM_serveredictstring(e, playermodel) = PRVM_SetEngineString(prog, svs.clients[num].playermodel); + PRVM_serveredictstring(e, playerskin) = PRVM_SetEngineString(prog, svs.clients[num].playerskin); + // Assign netaddress (IP Address, etc) + if(svs.clients[num].netconnection != NULL) + { + // Acquire Readable Address + LHNETADDRESS_ToString(&svs.clients[num].netconnection->peeraddress, svs.clients[num].netaddress, sizeof(svs.clients[num].netaddress), false); + PRVM_serveredictstring(e, netaddress) = PRVM_SetEngineString(prog, svs.clients[num].netaddress); + } + else + PRVM_serveredictstring(e, netaddress) = PRVM_SetEngineString(prog, "null/botclient"); + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_idfp[0]) + PRVM_serveredictstring(e, crypto_idfp) = PRVM_SetEngineString(prog, svs.clients[num].netconnection->crypto.client_idfp); + else + PRVM_serveredictstring(e, crypto_idfp) = 0; + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_keyfp[0]) + PRVM_serveredictstring(e, crypto_keyfp) = PRVM_SetEngineString(prog, svs.clients[num].netconnection->crypto.client_keyfp); + else + PRVM_serveredictstring(e, crypto_keyfp) = 0; + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.server_keyfp[0]) + PRVM_serveredictstring(e, crypto_mykeyfp) = PRVM_SetEngineString(prog, svs.clients[num].netconnection->crypto.server_keyfp); + else + PRVM_serveredictstring(e, crypto_mykeyfp) = 0; + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.use_aes) + PRVM_serveredictstring(e, crypto_encryptmethod) = PRVM_SetEngineString(prog, "AES128"); + else + PRVM_serveredictstring(e, crypto_encryptmethod) = 0; + if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated) + PRVM_serveredictstring(e, crypto_signmethod) = PRVM_SetEngineString(prog, "HMAC-SHA256"); + else + PRVM_serveredictstring(e, crypto_signmethod) = 0; + } +} + +static void SVVM_free_edict(prvm_prog_t *prog, prvm_edict_t *ed) +{ + int i; + int e; + + World_UnlinkEdict(ed); // unlink from world bsp + + PRVM_serveredictstring(ed, model) = 0; + PRVM_serveredictfloat(ed, takedamage) = 0; + PRVM_serveredictfloat(ed, modelindex) = 0; + PRVM_serveredictfloat(ed, colormap) = 0; + PRVM_serveredictfloat(ed, skin) = 0; + PRVM_serveredictfloat(ed, frame) = 0; + VectorClear(PRVM_serveredictvector(ed, origin)); + VectorClear(PRVM_serveredictvector(ed, angles)); + PRVM_serveredictfloat(ed, nextthink) = -1; + PRVM_serveredictfloat(ed, solid) = 0; + + VM_RemoveEdictSkeleton(prog, ed); + World_Physics_RemoveFromEntity(&sv.world, ed); + World_Physics_RemoveJointFromEntity(&sv.world, ed); + + // make sure csqc networking is aware of the removed entity + e = PRVM_NUM_FOR_EDICT(ed); + sv.csqcentityversion[e] = 0; + for (i = 0;i < svs.maxclients;i++) + { + if (svs.clients[i].csqcentityscope[e]) + svs.clients[i].csqcentityscope[e] = 1; // removed, awaiting send + svs.clients[i].csqcentitysendflags[e] = 0xFFFFFF; + } +} + +static void SVVM_count_edicts(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *ent; + int active, models, solid, step; + + active = models = solid = step = 0; + for (i=0 ; inum_edicts ; i++) + { + ent = PRVM_EDICT_NUM(i); + if (ent->priv.server->free) + continue; + active++; + if (PRVM_serveredictfloat(ent, solid)) + solid++; + if (PRVM_serveredictstring(ent, model)) + models++; + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_STEP) + step++; + } + + Con_Printf("num_edicts:%3i\n", prog->num_edicts); + Con_Printf("active :%3i\n", active); + Con_Printf("view :%3i\n", models); + Con_Printf("touch :%3i\n", solid); + Con_Printf("step :%3i\n", step); +} + +static qboolean SVVM_load_edict(prvm_prog_t *prog, prvm_edict_t *ent) +{ + // remove things from different skill levels or deathmatch + if (gamemode != GAME_TRANSFUSION) //Transfusion does this in QC + { + if (deathmatch.integer) + { + if (((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_DEATHMATCH)) + { + return false; + } + } + else if ((current_skill <= 0 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_EASY )) + || (current_skill == 1 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_MEDIUM)) + || (current_skill >= 2 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_HARD ))) + { + return false; + } + } + return true; +} + +static void SV_VM_Setup(void) +{ + prvm_prog_t *prog = SVVM_prog; + PRVM_Prog_Init(prog); + + // allocate the mempools + // TODO: move the magic numbers/constants into #defines [9/13/2006 Black] + prog->progs_mempool = Mem_AllocPool("Server Progs", 0, NULL); + prog->builtins = vm_sv_builtins; + prog->numbuiltins = vm_sv_numbuiltins; + prog->max_edicts = 512; + if (sv.protocol == PROTOCOL_QUAKE) + prog->limit_edicts = 640; // before quake mission pack 1 this was 512 + else if (sv.protocol == PROTOCOL_QUAKEDP) + prog->limit_edicts = 2048; // guessing + else if (sv.protocol == PROTOCOL_NEHAHRAMOVIE) + prog->limit_edicts = 2048; // guessing! + else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + prog->limit_edicts = 4096; // guessing! + else + prog->limit_edicts = MAX_EDICTS; + prog->reserved_edicts = svs.maxclients; + prog->edictprivate_size = sizeof(edict_engineprivate_t); + prog->name = "server"; + prog->extensionstring = vm_sv_extensions; + prog->loadintoworld = true; + + // all callbacks must be defined (pointers are not checked before calling) + prog->begin_increase_edicts = SVVM_begin_increase_edicts; + prog->end_increase_edicts = SVVM_end_increase_edicts; + prog->init_edict = SVVM_init_edict; + prog->free_edict = SVVM_free_edict; + prog->count_edicts = SVVM_count_edicts; + prog->load_edict = SVVM_load_edict; + prog->init_cmd = SVVM_init_cmd; + prog->reset_cmd = SVVM_reset_cmd; + prog->error_cmd = Host_Error; + prog->ExecuteProgram = SVVM_ExecuteProgram; + + PRVM_Prog_Load(prog, sv_progs.string, NULL, 0, SV_REQFUNCS, sv_reqfuncs, SV_REQFIELDS, sv_reqfields, SV_REQGLOBALS, sv_reqglobals); + + // some mods compiled with scrambling compilers lack certain critical + // global names and field names such as "self" and "time" and "nextthink" + // so we have to set these offsets manually, matching the entvars_t + // but we only do this if the prog header crc matches, otherwise it's totally freeform + if (prog->progs_crc == PROGHEADER_CRC || prog->progs_crc == PROGHEADER_CRC_TENEBRAE) + { + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, modelindex); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, absmin); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, absmax); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ltime); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, movetype); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, solid); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, origin); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, oldorigin); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, velocity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, angles); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, avelocity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, punchangle); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, classname); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, model); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, frame); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, skin); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, effects); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, mins); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, maxs); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, size); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, touch); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, use); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, think); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, blocked); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, nextthink); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, groundentity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, health); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, frags); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weapon); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weaponmodel); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weaponframe); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, currentammo); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_shells); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_nails); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_rockets); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_cells); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, items); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, takedamage); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, chain); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, deadflag); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, view_ofs); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button0); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button1); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button2); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, impulse); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, fixangle); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, v_angle); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, idealpitch); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, netname); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, enemy); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, flags); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, colormap); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, team); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, max_health); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, teleport_time); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, armortype); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, armorvalue); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, waterlevel); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, watertype); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ideal_yaw); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, yaw_speed); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, aiment); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, goalentity); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, spawnflags); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, target); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, targetname); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_take); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_save); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_inflictor); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, owner); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, movedir); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, message); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, sounds); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise1); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise2); + PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise3); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, self); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, other); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, world); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, time); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, frametime); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, force_retouch); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, mapname); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, deathmatch); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, coop); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, teamplay); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, serverflags); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, total_secrets); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, total_monsters); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, found_secrets); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, killed_monsters); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm1); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm2); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm3); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm4); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm5); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm6); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm7); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm8); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm9); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm10); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm11); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm12); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm13); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm14); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm15); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm16); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_forward); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_up); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_right); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_allsolid); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_startsolid); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_fraction); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_endpos); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_plane_normal); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_plane_dist); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_ent); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_inopen); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_inwater); + PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, msg_entity); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, main); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, StartFrame); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PlayerPreThink); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PlayerPostThink); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientKill); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientConnect); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PutClientInServer); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientDisconnect); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, SetNewParms); +// PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, SetChangeParms); + } + else + Con_DPrintf("%s: %s system vars have been modified (CRC %i != engine %i), will not load in other engines", prog->name, sv_progs.string, prog->progs_crc, PROGHEADER_CRC); + + // OP_STATE is always supported on server because we add fields/globals for it + prog->flag |= PRVM_OP_STATE; + + VM_CustomStats_Clear();//[515]: csqc + + SV_Prepare_CSQC(); +} + +extern cvar_t host_maxwait; +extern cvar_t host_framerate; +static int SV_ThreadFunc(void *voiddata) +{ + prvm_prog_t *prog = SVVM_prog; + qboolean playing = false; + double sv_timer = 0; + double sv_deltarealtime, sv_oldrealtime, sv_realtime; + double wait; + int i; + char vabuf[1024]; + sv_realtime = Sys_DirtyTime(); + while (!svs.threadstop) + { + // FIXME: we need to handle Host_Error in the server thread somehow +// if (setjmp(sv_abortframe)) +// continue; // something bad happened in the server game + + sv_oldrealtime = sv_realtime; + sv_realtime = Sys_DirtyTime(); + sv_deltarealtime = sv_realtime - sv_oldrealtime; + if (sv_deltarealtime < 0 || sv_deltarealtime >= 1800) sv_deltarealtime = 0; + + sv_timer += sv_deltarealtime; + + svs.perf_acc_realtime += sv_deltarealtime; + + // at this point we start doing real server work, and must block on any client activity pertaining to the server (such as executing SV_SpawnServer) + SV_LockThreadMutex(); + + // Look for clients who have spawned + playing = false; + if (sv.active) + for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) + if(host_client->begun) + if(host_client->netconnection) + playing = true; + if(sv.time < 10) + { + // don't accumulate time for the first 10 seconds of a match + // so things can settle + svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; + } + else if(svs.perf_acc_realtime > 5) + { + svs.perf_cpuload = 1 - svs.perf_acc_sleeptime / svs.perf_acc_realtime; + svs.perf_lost = svs.perf_acc_lost / svs.perf_acc_realtime; + if(svs.perf_acc_offset_samples > 0) + { + svs.perf_offset_max = svs.perf_acc_offset_max; + svs.perf_offset_avg = svs.perf_acc_offset / svs.perf_acc_offset_samples; + svs.perf_offset_sdev = sqrt(svs.perf_acc_offset_squared / svs.perf_acc_offset_samples - svs.perf_offset_avg * svs.perf_offset_avg); + } + if(svs.perf_lost > 0 && developer_extra.integer) + if(playing) + Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); + svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; + } + + // get new packets + if (sv.active) + NetConn_ServerFrame(); + + // if the accumulators haven't become positive yet, wait a while + wait = sv_timer * -1000000.0; + if (wait >= 1) + { + double time0, delta; + SV_UnlockThreadMutex(); // don't keep mutex locked while sleeping + if (host_maxwait.value <= 0) + wait = min(wait, 1000000.0); + else + wait = min(wait, host_maxwait.value * 1000.0); + if(wait < 1) + wait = 1; // because we cast to int + time0 = Sys_DirtyTime(); + Sys_Sleep((int)wait); + delta = Sys_DirtyTime() - time0;if (delta < 0 || delta >= 1800) delta = 0; + svs.perf_acc_sleeptime += delta; + continue; + } + + if (sv.active && sv_timer > 0) + { + // execute one server frame + double advancetime; + float offset; + + if (sys_ticrate.value <= 0) + advancetime = min(sv_timer, 0.1); // don't step more than 100ms + else + advancetime = sys_ticrate.value; + + if(advancetime > 0) + { + offset = sv_timer + (Sys_DirtyTime() - sv_realtime); // LordHavoc: FIXME: I don't understand this line + ++svs.perf_acc_offset_samples; + svs.perf_acc_offset += offset; + svs.perf_acc_offset_squared += offset * offset; + if(svs.perf_acc_offset_max < offset) + svs.perf_acc_offset_max = offset; + } + + // only advance time if not paused + // the game also pauses in singleplayer when menu or console is used + sv.frametime = advancetime * slowmo.value; + if (host_framerate.value) + sv.frametime = host_framerate.value; + if (sv.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) + sv.frametime = 0; + + sv_timer -= advancetime; + + // move things around and think unless paused + if (sv.frametime) + SV_Physics(); + + // send all messages to the clients + SV_SendClientMessages(); + + if (sv.paused == 1 && sv_realtime > sv.pausedstart && sv.pausedstart > 0) + { + PRVM_serverglobalfloat(time) = sv.time; + prog->globals.fp[OFS_PARM0] = sv_realtime - sv.pausedstart; + prog->ExecuteProgram(prog, PRVM_serverfunction(SV_PausedTic), "QC function SV_PausedTic is missing"); + } + + // send an heartbeat if enough time has passed since the last one + NetConn_Heartbeat(0); + + } + + // we're back to safe code now + SV_UnlockThreadMutex(); + + // if there is some time remaining from this frame, reset the timers + if (sv_timer >= 0) + { + svs.perf_acc_lost += sv_timer; + sv_timer = 0; + } + } + return 0; +} + +void SV_StartThread(void) +{ + if (!sv_threaded.integer || !Thread_HasThreads()) + return; + svs.threaded = true; + svs.threadstop = false; + svs.threadmutex = Thread_CreateMutex(); + svs.thread = Thread_CreateThread(SV_ThreadFunc, NULL); +} + +void SV_StopThread(void) +{ + if (!svs.threaded) + return; + svs.threadstop = true; + Thread_WaitThread(svs.thread, 0); + Thread_DestroyMutex(svs.threadmutex); + svs.threaded = false; +} diff --git a/app/jni/sv_move.c b/app/jni/sv_move.c new file mode 100644 index 0000000..13b96c7 --- /dev/null +++ b/app/jni/sv_move.c @@ -0,0 +1,448 @@ +/* +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. + +*/ +// sv_move.c -- monster movement + +#include "quakedef.h" +#include "prvm_cmds.h" + +/* +============= +SV_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean SV_CheckBottom (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); + VectorAdd (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (!(SV_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*sv_stepheight.value; + trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent)); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent)); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) + return false; + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done and false is returned +============= +*/ +qboolean SV_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace) +{ + prvm_prog_t *prog = SVVM_prog; + float dz; + vec3_t oldorg, neworg, end, traceendpos, entorigin, entmins, entmaxs; + trace_t trace; + int i; + prvm_edict_t *enemy; + +// try the move + VectorCopy (PRVM_serveredictvector(ent, origin), oldorg); + VectorAdd (PRVM_serveredictvector(ent, origin), move, neworg); + VectorCopy(PRVM_serveredictvector(ent, mins), entmins); + VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); + +// flying monsters don't step up + if ( (int)PRVM_serveredictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (PRVM_serveredictvector(ent, origin), move, neworg); + if (noenemy) + enemy = prog->edicts; + else + { + enemy = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, enemy)); + if (i == 0 && enemy != prog->edicts) + { + dz = PRVM_serveredictvector(ent, origin)[2] - PRVM_serveredictvector(enemy, origin)[2]; + if (dz > 40) + neworg[2] -= 8; + if (dz < 30) + neworg[2] += 8; + } + } + VectorCopy(PRVM_serveredictvector(ent, origin), entorigin); + trace = SV_TraceBox(entorigin, entmins, entmaxs, neworg, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + + if (trace.fraction == 1) + { + VectorCopy(trace.endpos, traceendpos); + if (((int)PRVM_serveredictfloat(ent, flags) & FL_SWIM) && !(SV_PointSuperContents(traceendpos) & SUPERCONTENTS_LIQUIDSMASK)) + return false; // swim monster left water + + VectorCopy (traceendpos, PRVM_serveredictvector(ent, origin)); + if (relink) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + return true; + } + + if (enemy == prog->edicts) + break; + } + + return false; + } + +// push down from a step height above the wished position + neworg[2] += sv_stepheight.value; + VectorCopy (neworg, end); + end[2] -= sv_stepheight.value*2; + + trace = SV_TraceBox(neworg, entmins, entmaxs, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + + if (trace.startsolid) + { + neworg[2] -= sv_stepheight.value; + trace = SV_TraceBox(neworg, entmins, entmaxs, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + if (trace.startsolid) + return false; + } + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) + { + VectorAdd (PRVM_serveredictvector(ent, origin), move, PRVM_serveredictvector(ent, origin)); + if (relink) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); + + if (!SV_CheckBottom (ent)) + { + if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + return true; + } + VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); + return false; + } + + if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_PARTIALGROUND; + +// gameplayfix: check if reached pretty steep plane and bail + if ( ! ( (int)PRVM_serveredictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) && sv_gameplayfix_nostepmoveonsteepslopes.integer ) + { + if (trace.plane.normal[ 2 ] < 0.5) + { + VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); + return false; + } + } + + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + +// the move is ok + if (relink) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + return true; +} + + +//============================================================================ + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +static qboolean SV_StepDirection (prvm_edict_t *ent, float yaw, float dist) +{ + prvm_prog_t *prog = SVVM_prog; + vec3_t move, oldorigin; + float delta; + + PRVM_serveredictfloat(ent, ideal_yaw) = yaw; + VM_changeyaw(prog); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (PRVM_serveredictvector(ent, origin), oldorigin); + if (SV_movestep (ent, move, false, false, false)) + { + delta = PRVM_serveredictvector(ent, angles)[YAW] - PRVM_serveredictfloat(ent, ideal_yaw); + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, PRVM_serveredictvector(ent, origin)); + } + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + return true; + } + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +static void SV_FixCheckBottom (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +static void SV_NewChaseDir (prvm_edict_t *actor, prvm_edict_t *enemy, float dist) +{ + prvm_prog_t *prog = SVVM_prog; + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + olddir = ANGLEMOD((int)(PRVM_serveredictfloat(actor, ideal_yaw)/45)*45); + turnaround = ANGLEMOD(olddir - 180); + + deltax = PRVM_serveredictvector(enemy, origin)[0] - PRVM_serveredictvector(actor, origin)[0]; + deltay = PRVM_serveredictvector(enemy, origin)[1] - PRVM_serveredictvector(actor, origin)[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || fabs(deltay)>fabs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + PRVM_serveredictfloat(actor, ideal_yaw) = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!SV_CheckBottom (actor)) + SV_FixCheckBottom (actor); + +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +static qboolean SV_CloseEnough (prvm_edict_t *ent, prvm_edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->priv.server->areamins[i] > ent->priv.server->areamaxs[i] + dist) + return false; + if (goal->priv.server->areamaxs[i] < ent->priv.server->areamins[i] - dist) + return false; + } + return true; +} + +/* +====================== +SV_MoveToGoal + +====================== +*/ +void VM_SV_MoveToGoal(prvm_prog_t *prog) +{ + prvm_edict_t *ent, *goal; + float dist; + + VM_SAFEPARMCOUNT(1, SV_MoveToGoal); + + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + goal = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, goalentity)); + dist = PRVM_G_FLOAT(OFS_PARM0); + + if ( !( (int)PRVM_serveredictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + { + PRVM_G_FLOAT(OFS_RETURN) = 0; + return; + } + +// if the next step hits the enemy, return immediately + if ( PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, enemy)) != prog->edicts && SV_CloseEnough (ent, goal, dist) ) + return; + +// bump around... + if ( (rand()&3)==1 || + !SV_StepDirection (ent, PRVM_serveredictfloat(ent, ideal_yaw), dist)) + { + SV_NewChaseDir (ent, goal, dist); + } +} + diff --git a/app/jni/sv_phys.c b/app/jni/sv_phys.c new file mode 100644 index 0000000..e88baa2 --- /dev/null +++ b/app/jni/sv_phys.c @@ -0,0 +1,3227 @@ +/* +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. + +*/ +// sv_phys.c + +#include "quakedef.h" +#include "prvm_cmds.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + +#define MOVE_EPSILON 0.01 + +void SV_Physics_Toss (prvm_edict_t *ent); + +int SV_GetPitchSign(prvm_prog_t *prog, prvm_edict_t *ent) +{ + dp_model_t *model; + if ( + (model = SV_GetModelFromEdict(ent)) + ? + model->type == mod_alias + : + ( + (((unsigned char)PRVM_serveredictfloat(ent, pflags)) & PFLAGS_FULLDYNAMIC) + || + ((gamemode == GAME_TENEBRAE) && ((unsigned int)PRVM_serveredictfloat(ent, effects) & (16 | 32))) + ) + ) + return -1; + return 1; +} + +/* +=============================================================================== + +LINE TESTING IN HULLS + +=============================================================================== +*/ + +int SV_GenericHitSuperContentsMask(const prvm_edict_t *passedict) +{ + prvm_prog_t *prog = SVVM_prog; + if (passedict) + { + int dphitcontentsmask = (int)PRVM_serveredictfloat(passedict, dphitcontentsmask); + if (dphitcontentsmask) + return dphitcontentsmask; + else if (PRVM_serveredictfloat(passedict, solid) == SOLID_SLIDEBOX) + { + if ((int)PRVM_serveredictfloat(passedict, flags) & FL_MONSTER) + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_MONSTERCLIP; + else + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP; + } + else if (PRVM_serveredictfloat(passedict, solid) == SOLID_CORPSE) + return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; + else if (PRVM_serveredictfloat(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; +} + +/* +================== +SV_TracePoint +================== +*/ +trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +{ + prvm_prog_t *prog = SVVM_prog; + int i, bodysupercontents; + int passedictprog; + float pitchsign = 1; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // temporary storage because prvm_vec_t may differ from vec_t + 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]; + + //return SV_TraceBox(start, vec3_origin, vec3_origin, end, type, passedict, hitsupercontentsmask); + + 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, sv.worldmodel, clipstart, hitsupercontentsmask); + cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog->edicts; + 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) + if (passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = PRVM_EDICT_TO_PROG(passedict); + // precalculate passedict's owner edict pointer for comparisons + traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_serveredictedict(passedict, owner)) : 0; + + // 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 + numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_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_serveredictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(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_serveredictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + { + model = SV_GetModelFromEdict(touch); + pitchsign = SV_GetPitchSign(prog, touch); + } + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + VM_GenerateFrameGroupBlend(prog, touch->priv.server->framegroupblend, touch); + VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model, sv.time); + VM_UpdateEdictSkeleton(prog, touch, model, touch->priv.server->frameblend); + VectorCopy(PRVM_serveredictvector(touch, mins), touchmins); + VectorCopy(PRVM_serveredictvector(touch, maxs), touchmaxs); + if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(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); + + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(touch, solid) == SOLID_BSP); + } + +finished: + return cliptrace; +} + +/* +================== +SV_TraceLine +================== +*/ +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +trace_t SV_TraceLine(const vec3_t start, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +#else +trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask) +#endif +{ + prvm_prog_t *prog = SVVM_prog; + int i, bodysupercontents; + int passedictprog; + float pitchsign = 1; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // temporary storage because prvm_vec_t may differ from vec_t + 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 SV_TracePoint(start, type, passedict, hitsupercontentsmask); + + 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 SV_TracePoint(start, type, passedict, hitsupercontentsmask); +#endif + + //return SV_TraceBox(start, vec3_origin, vec3_origin, end, type, passedict, hitsupercontentsmask); + + 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, sv.worldmodel, clipstart, clipend, hitsupercontentsmask, false); + cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog->edicts; + 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) + if (passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = PRVM_EDICT_TO_PROG(passedict); + // precalculate passedict's owner edict pointer for comparisons + traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_serveredictedict(passedict, owner)) : 0; + + // 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 + numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_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_serveredictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(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_serveredictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + { + model = SV_GetModelFromEdict(touch); + pitchsign = SV_GetPitchSign(prog, touch); + } + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + VM_GenerateFrameGroupBlend(prog, touch->priv.server->framegroupblend, touch); + VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model, sv.time); + VM_UpdateEdictSkeleton(prog, touch, model, touch->priv.server->frameblend); + VectorCopy(PRVM_serveredictvector(touch, mins), touchmins); + VectorCopy(PRVM_serveredictvector(touch, maxs), touchmaxs); + if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(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, false); + + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(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; +} + +/* +================== +SV_Move +================== +*/ +#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND +#if COLLISIONPARANOID >= 1 +trace_t SV_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) +#else +trace_t SV_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) +#endif +#else +#if COLLISIONPARANOID >= 1 +trace_t SV_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) +#else +trace_t SV_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) +#endif +#endif +{ + prvm_prog_t *prog = SVVM_prog; + vec3_t hullmins, hullmaxs; + int i, bodysupercontents; + int passedictprog; + float pitchsign = 1; + qboolean pointtrace; + prvm_edict_t *traceowner, *touch; + trace_t trace; + // temporary storage because prvm_vec_t may differ from vec_t + 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 = SV_TracePoint(shiftstart, type, passedict, hitsupercontentsmask); + else + trace = SV_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask); + 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 = SV_TracePoint(shiftstart, type, passedict, hitsupercontentsmask); + else + trace = SV_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask); + VectorSubtract(trace.endpos, mins, trace.endpos); + return trace; + } +#endif + + 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, sv.worldmodel, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask); + cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; + if (cliptrace.startsolid || cliptrace.fraction < 1) + cliptrace.ent = prog->edicts; + 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 (sv.worldmodel && sv.worldmodel->brush.RoundUpToHullSize) + sv.worldmodel->brush.RoundUpToHullSize(sv.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) + if (passedict == prog->edicts) + passedict = NULL; + // precalculate prog value for passedict for comparisons + passedictprog = PRVM_EDICT_TO_PROG(passedict); + // 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_serveredictedict(passedict, owner)) : 0; + + // 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 + numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_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_serveredictfloat(touch, solid) < SOLID_BBOX) + continue; + if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(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_serveredictedict(touch, owner)) + continue; + // don't clip points against points (they can't collide) + if (pointtrace && VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) + continue; + } + + bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; + + // might interact, so do an exact clip + model = NULL; + if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) + { + model = SV_GetModelFromEdict(touch); + pitchsign = SV_GetPitchSign(prog, touch); + } + if (model) + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + else + Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + VM_GenerateFrameGroupBlend(prog, touch->priv.server->framegroupblend, touch); + VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model, sv.time); + VM_UpdateEdictSkeleton(prog, touch, model, touch->priv.server->frameblend); + VectorCopy(PRVM_serveredictvector(touch, mins), touchmins); + VectorCopy(PRVM_serveredictvector(touch, maxs), touchmaxs); + if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(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); + + Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(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; +} + +#if COLLISIONPARANOID >= 1 +trace_t SV_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) +{ + int endstuck; + trace_t trace; + vec3_t temp; + trace = SV_TraceBox_(start, mins, maxs, end, type, passedict, hitsupercontentsmask); + if (passedict) + { + VectorCopy(trace.endpos, temp); + endstuck = SV_TraceBox_(temp, mins, maxs, temp, type, passedict, hitsupercontentsmask).startsolid; +#if COLLISIONPARANOID < 3 + if (trace.startsolid || endstuck) +#endif + Con_Printf("%s{e%i:%f %f %f:%f %f %f:%f:%f %f %f%s%s}\n", (trace.startsolid || endstuck) ? "^3" : "", passedict ? (int)(passedict - prog->edicts) : -1, PRVM_serveredictvector(passedict, origin)[0], PRVM_serveredictvector(passedict, origin)[1], PRVM_serveredictvector(passedict, origin)[2], end[0] - PRVM_serveredictvector(passedict, origin)[0], end[1] - PRVM_serveredictvector(passedict, origin)[1], end[2] - PRVM_serveredictvector(passedict, origin)[2], trace.fraction, trace.endpos[0] - PRVM_serveredictvector(passedict, origin)[0], trace.endpos[1] - PRVM_serveredictvector(passedict, origin)[1], trace.endpos[2] - PRVM_serveredictvector(passedict, origin)[2], trace.startsolid ? " startstuck" : "", endstuck ? " endstuck" : ""); + } + return trace; +} +#endif + +int SV_PointSuperContents(const vec3_t point) +{ + prvm_prog_t *prog = SVVM_prog; + int supercontents = 0; + int i; + prvm_edict_t *touch; + vec3_t transformed; + // matrices to transform into/out of other entity's space + matrix4x4_t matrix, imatrix; + // model of other entity + dp_model_t *model; + int frame; + // list of entities to test for collisions + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + + // get world supercontents at this point + if (sv.worldmodel && sv.worldmodel->PointSuperContents) + supercontents = sv.worldmodel->PointSuperContents(sv.worldmodel, 0, point); + + // if sv_gameplayfix_swiminbmodels is off we're done + if (!sv_gameplayfix_swiminbmodels.integer) + return supercontents; + + // get list of entities at this point + numtouchedicts = SV_EntitiesInBox(point, point, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + + // we only care about SOLID_BSP for pointcontents + if (PRVM_serveredictfloat(touch, solid) != SOLID_BSP) + continue; + + // might interact, so do an exact clip + model = SV_GetModelFromEdict(touch); + if (!model || !model->PointSuperContents) + continue; + Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); + Matrix4x4_Invert_Simple(&imatrix, &matrix); + Matrix4x4_Transform(&imatrix, point, transformed); + frame = (int)PRVM_serveredictfloat(touch, frame); + supercontents |= model->PointSuperContents(model, bound(0, frame, (model->numframes - 1)), transformed); + } + + return supercontents; +} + +/* +=============================================================================== + +Linking entities into the world culling system + +=============================================================================== +*/ + +int SV_EntitiesInBox(const vec3_t mins, const vec3_t maxs, int maxedicts, prvm_edict_t **resultedicts) +{ + prvm_prog_t *prog = SVVM_prog; + vec3_t paddedmins, paddedmaxs; + if (maxedicts < 1 || resultedicts == NULL) + return 0; + // LordHavoc: discovered this actually causes its own bugs (dm6 teleporters being too close to info_teleport_destination) + //VectorSet(paddedmins, mins[0] - 10, mins[1] - 10, mins[2] - 1); + //VectorSet(paddedmaxs, maxs[0] + 10, maxs[1] + 10, maxs[2] + 1); + VectorCopy(mins, paddedmins); + VectorCopy(maxs, paddedmaxs); + if (sv_areadebug.integer) + { + int numresultedicts = 0; + int edictindex; + prvm_edict_t *ed; + for (edictindex = 1;edictindex < prog->num_edicts;edictindex++) + { + ed = PRVM_EDICT_NUM(edictindex); + if (!ed->priv.required->free && BoxesOverlap(PRVM_serveredictvector(ed, absmin), PRVM_serveredictvector(ed, absmax), paddedmins, paddedmaxs)) + { + resultedicts[numresultedicts++] = ed; + if (numresultedicts == maxedicts) + break; + } + } + return numresultedicts; + } + else + return World_EntitiesInBox(&sv.world, paddedmins, paddedmaxs, maxedicts, resultedicts); +} + +void SV_LinkEdict_TouchAreaGrid_Call(prvm_edict_t *touch, prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(touch); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(ent); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobalfloat(trace_allsolid) = false; + PRVM_serverglobalfloat(trace_startsolid) = false; + PRVM_serverglobalfloat(trace_fraction) = 1; + PRVM_serverglobalfloat(trace_inwater) = false; + PRVM_serverglobalfloat(trace_inopen) = true; + VectorCopy (PRVM_serveredictvector(touch, origin), PRVM_serverglobalvector(trace_endpos)); + VectorSet (PRVM_serverglobalvector(trace_plane_normal), 0, 0, 1); + PRVM_serverglobalfloat(trace_plane_dist) = 0; + PRVM_serverglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(ent); + PRVM_serverglobalfloat(trace_dpstartcontents) = 0; + PRVM_serverglobalfloat(trace_dphitcontents) = 0; + PRVM_serverglobalfloat(trace_dphitq3surfaceflags) = 0; + PRVM_serverglobalstring(trace_dphittexturename) = 0; + prog->ExecuteProgram(prog, PRVM_serveredictfunction(touch, touch), "QC function self.touch is missing"); +} + +void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + int i, numtouchedicts, old_self, old_other; + prvm_edict_t *touch; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + + if (ent == prog->edicts) + return; // don't add the world + + if (ent->priv.server->free) + return; + + if (PRVM_serveredictfloat(ent, solid) == SOLID_NOT) + return; + + // build a list of edicts to touch, because the link loop can be corrupted + // by IncreaseEdicts called during touch functions + numtouchedicts = SV_EntitiesInBox(ent->priv.server->areamins, ent->priv.server->areamaxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + + old_self = PRVM_serverglobaledict(self); + old_other = PRVM_serverglobaledict(other); + for (i = 0;i < numtouchedicts;i++) + { + touch = touchedicts[i]; + if (touch != ent && (int)PRVM_serveredictfloat(touch, solid) == SOLID_TRIGGER && PRVM_serveredictfunction(touch, touch)) + { + SV_LinkEdict_TouchAreaGrid_Call(touch, ent); + } + } + PRVM_serverglobaledict(self) = old_self; + PRVM_serverglobaledict(other) = old_other; +} + +static void RotateBBox(const vec3_t mins, const vec3_t maxs, const vec3_t angles, vec3_t rotatedmins, vec3_t rotatedmaxs) +{ + vec3_t v, u; + matrix4x4_t m; + Matrix4x4_CreateFromQuakeEntity(&m, 0, 0, 0, angles[PITCH], angles[YAW], angles[ROLL], 1.0); + + v[0] = mins[0]; v[1] = mins[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); + VectorCopy(u, rotatedmins); VectorCopy(u, rotatedmaxs); + v[0] = maxs[0]; v[1] = mins[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = mins[0]; v[1] = maxs[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = maxs[0]; v[1] = maxs[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = mins[0]; v[1] = mins[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = maxs[0]; v[1] = mins[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = mins[0]; v[1] = maxs[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; + v[0] = maxs[0]; v[1] = maxs[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); + if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; + if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; +} + +/* +=============== +SV_LinkEdict + +=============== +*/ +void SV_LinkEdict (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + dp_model_t *model; + vec3_t mins, maxs, entmins, entmaxs, entangles; + int modelindex; + + if (ent == prog->edicts) + return; // don't add the world + + if (ent->priv.server->free) + return; + + modelindex = (int)PRVM_serveredictfloat(ent, modelindex); + if (modelindex < 0 || modelindex >= MAX_MODELS) + { + Con_Printf("edict %i: SOLID_BSP with invalid modelindex!\n", PRVM_NUM_FOR_EDICT(ent)); + modelindex = 0; + } + model = SV_GetModelByIndex(modelindex); + + VM_GenerateFrameGroupBlend(prog, ent->priv.server->framegroupblend, ent); + VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model, sv.time); + VM_UpdateEdictSkeleton(prog, ent, model, ent->priv.server->frameblend); + +// set the abs box + + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_PHYSICS) + { + // TODO maybe should do this for rotating SOLID_BSP too? Would behave better with rotating doors + // TODO special handling for spheres? + VectorCopy(PRVM_serveredictvector(ent, mins), entmins); + VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); + VectorCopy(PRVM_serveredictvector(ent, angles), entangles); + RotateBBox(entmins, entmaxs, entangles, mins, maxs); + VectorAdd(PRVM_serveredictvector(ent, origin), mins, mins); + VectorAdd(PRVM_serveredictvector(ent, origin), maxs, maxs); + } + else if (PRVM_serveredictfloat(ent, solid) == SOLID_BSP) + { + if (model != NULL) + { + if (!model->TraceBox) + Con_DPrintf("edict %i: SOLID_BSP with non-collidable model\n", PRVM_NUM_FOR_EDICT(ent)); + + if (PRVM_serveredictvector(ent, angles)[0] || PRVM_serveredictvector(ent, angles)[2] || PRVM_serveredictvector(ent, avelocity)[0] || PRVM_serveredictvector(ent, avelocity)[2]) + { + VectorAdd(PRVM_serveredictvector(ent, origin), model->rotatedmins, mins); + VectorAdd(PRVM_serveredictvector(ent, origin), model->rotatedmaxs, maxs); + } + else if (PRVM_serveredictvector(ent, angles)[1] || PRVM_serveredictvector(ent, avelocity)[1]) + { + VectorAdd(PRVM_serveredictvector(ent, origin), model->yawmins, mins); + VectorAdd(PRVM_serveredictvector(ent, origin), model->yawmaxs, maxs); + } + else + { + VectorAdd(PRVM_serveredictvector(ent, origin), model->normalmins, mins); + VectorAdd(PRVM_serveredictvector(ent, origin), model->normalmaxs, maxs); + } + } + else + { + // SOLID_BSP with no model is valid, mainly because some QC setup code does so temporarily + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); + } + } + else + { + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); + } + +// +// to make items easier to pick up and allow them to be grabbed off +// of shelves, the abs sizes are expanded +// + if ((int)PRVM_serveredictfloat(ent, flags) & FL_ITEM) + { + mins[0] -= 15; + mins[1] -= 15; + mins[2] -= 1; + maxs[0] += 15; + maxs[1] += 15; + maxs[2] += 1; + } + else + { + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + mins[0] -= 1; + mins[1] -= 1; + mins[2] -= 1; + maxs[0] += 1; + maxs[1] += 1; + maxs[2] += 1; + } + + VectorCopy(mins, PRVM_serveredictvector(ent, absmin)); + VectorCopy(maxs, PRVM_serveredictvector(ent, absmax)); + + World_LinkEdict(&sv.world, ent, mins, maxs); +} + +/* +=============================================================================== + +Utility functions + +=============================================================================== +*/ + +/* +============ +SV_TestEntityPosition + +returns true if the entity is in solid currently +============ +*/ +static int SV_TestEntityPosition (prvm_edict_t *ent, vec3_t offset) +{ + prvm_prog_t *prog = SVVM_prog; + int contents; + vec3_t org, entorigin, entmins, entmaxs; + trace_t trace; + contents = SV_GenericHitSuperContentsMask(ent); + VectorAdd(PRVM_serveredictvector(ent, origin), offset, org); + VectorCopy(PRVM_serveredictvector(ent, origin), entorigin); + VectorCopy(PRVM_serveredictvector(ent, mins), entmins); + VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); + trace = SV_TraceBox(org, entmins, entmaxs, entorigin, ((PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), ent, contents); + if (trace.startsupercontents & contents) + return true; + else + { + if (sv.worldmodel->brushq1.numclipnodes && !VectorCompare(PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs))) + { + // q1bsp/hlbsp use hulls and if the entity does not exactly match + // a hull size it is incorrectly tested, so this code tries to + // 'fix' it slightly... + // FIXME: this breaks entities larger than the hull size + int i; + vec3_t v, m1, m2, s; + VectorAdd(org, entmins, m1); + VectorAdd(org, entmaxs, m2); + VectorSubtract(m2, m1, s); +#define EPSILON (1.0f / 32.0f) + if (s[0] >= EPSILON*2) {m1[0] += EPSILON;m2[0] -= EPSILON;} + if (s[1] >= EPSILON*2) {m1[1] += EPSILON;m2[1] -= EPSILON;} + if (s[2] >= EPSILON*2) {m1[2] += EPSILON;m2[2] -= EPSILON;} + for (i = 0;i < 8;i++) + { + v[0] = (i & 1) ? m2[0] : m1[0]; + v[1] = (i & 2) ? m2[1] : m1[1]; + v[2] = (i & 4) ? m2[2] : m1[2]; + if (SV_PointSuperContents(v) & contents) + return true; + } + } + } + // if the trace found a better position for the entity, move it there + if (VectorDistance2(trace.endpos, PRVM_serveredictvector(ent, origin)) >= 0.0001) + { +#if 0 + // please switch back to this code when trace.endpos sometimes being in solid bug is fixed + VectorCopy(trace.endpos, PRVM_serveredictvector(ent, origin)); +#else + // verify if the endpos is REALLY outside solid + VectorCopy(trace.endpos, org); + trace = SV_TraceBox(org, entmins, entmaxs, org, MOVE_NOMONSTERS, ent, contents); + if(trace.startsolid) + Con_Printf("SV_TestEntityPosition: trace.endpos detected to be in solid. NOT using it.\n"); + else + VectorCopy(org, PRVM_serveredictvector(ent, origin)); +#endif + } + return false; +} + +// DRESK - Support for Entity Contents Transition Event +/* +================ +SV_CheckContentsTransition + +returns true if entity had a valid contentstransition function call +================ +*/ +static int SV_CheckContentsTransition(prvm_edict_t *ent, const int nContents) +{ + prvm_prog_t *prog = SVVM_prog; + int bValidFunctionCall; + + // Default Valid Function Call to False + bValidFunctionCall = false; + + if(PRVM_serveredictfloat(ent, watertype) != nContents) + { // Changed Contents + // Acquire Contents Transition Function from QC + if(PRVM_serveredictfunction(ent, contentstransition)) + { // Valid Function; Execute + // Assign Valid Function + bValidFunctionCall = true; + // Prepare Parameters (Original Contents, New Contents) + // Original Contents + PRVM_G_FLOAT(OFS_PARM0) = PRVM_serveredictfloat(ent, watertype); + // New Contents + PRVM_G_FLOAT(OFS_PARM1) = nContents; + // Assign Self + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + // Set Time + PRVM_serverglobalfloat(time) = sv.time; + // Execute VM Function + prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, contentstransition), "contentstransition: NULL function"); + } + } + + // Return if Function Call was Valid + return bValidFunctionCall; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + float wishspeed; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (PRVM_IS_NAN(PRVM_serveredictvector(ent, velocity)[i])) + { + Con_Printf("Got a NaN velocity on entity #%i (%s)\n", PRVM_NUM_FOR_EDICT(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); + PRVM_serveredictvector(ent, velocity)[i] = 0; + } + if (PRVM_IS_NAN(PRVM_serveredictvector(ent, origin)[i])) + { + Con_Printf("Got a NaN origin on entity #%i (%s)\n", PRVM_NUM_FOR_EDICT(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); + PRVM_serveredictvector(ent, origin)[i] = 0; + } + } + + // LordHavoc: a hack to ensure that the (rather silly) id1 quakec + // player_run/player_stand1 does not horribly malfunction if the + // velocity becomes a denormalized float + if (VectorLength2(PRVM_serveredictvector(ent, velocity)) < 0.0001) + VectorClear(PRVM_serveredictvector(ent, velocity)); + + // LordHavoc: max velocity fix, inspired by Maddes's source fixes, but this is faster + wishspeed = DotProduct(PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, velocity)); + if (wishspeed > sv_maxvelocity.value * sv_maxvelocity.value) + { + wishspeed = sv_maxvelocity.value / sqrt(wishspeed); + PRVM_serveredictvector(ent, velocity)[0] *= wishspeed; + PRVM_serveredictvector(ent, velocity)[1] *= wishspeed; + PRVM_serveredictvector(ent, velocity)[2] *= wishspeed; + } +} + +/* +============= +SV_RunThink + +Runs thinking code if time. There is some play in the exact time the think +function will be called, because it is called before any movement is done +in a frame. Not used for pushmove objects, because they must be exact. +Returns false if the entity removed itself. +============= +*/ +static qboolean SV_RunThink (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + int iterations; + + // don't let things stay in the past. + // it is possible to start that way by a trigger with a local time. + if (PRVM_serveredictfloat(ent, nextthink) <= 0 || PRVM_serveredictfloat(ent, nextthink) > sv.time + sv.frametime) + return true; + + for (iterations = 0;iterations < 128 && !ent->priv.server->free;iterations++) + { + PRVM_serverglobalfloat(time) = max(sv.time, PRVM_serveredictfloat(ent, nextthink)); + PRVM_serveredictfloat(ent, nextthink) = 0; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); + prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, think), "QC function self.think is missing"); + // mods often set nextthink to time to cause a think every frame, + // we don't want to loop in that case, so exit if the new nextthink is + // <= the time the qc was told, also exit if it is past the end of the + // frame + if (PRVM_serveredictfloat(ent, nextthink) <= PRVM_serverglobalfloat(time) || PRVM_serveredictfloat(ent, nextthink) > sv.time + sv.frametime || !sv_gameplayfix_multiplethinksperframe.integer) + break; + } + return !ent->priv.server->free; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +static void SV_Impact (prvm_edict_t *e1, trace_t *trace) +{ + prvm_prog_t *prog = SVVM_prog; + int restorevm_tempstringsbuf_cursize; + int old_self, old_other; + prvm_edict_t *e2 = (prvm_edict_t *)trace->ent; + + old_self = PRVM_serverglobaledict(self); + old_other = PRVM_serverglobaledict(other); + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + + VM_SetTraceGlobals(prog, trace); + + if (!e1->priv.server->free && !e2->priv.server->free && PRVM_serveredictfunction(e1, touch) && PRVM_serveredictfloat(e1, solid) != SOLID_NOT) + { + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(e1); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(e2); + prog->ExecuteProgram(prog, PRVM_serveredictfunction(e1, touch), "QC function self.touch is missing"); + } + + if (!e1->priv.server->free && !e2->priv.server->free && PRVM_serveredictfunction(e2, touch) && PRVM_serveredictfloat(e2, solid) != SOLID_NOT) + { + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(e2); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(e1); + VectorCopy(PRVM_serveredictvector(e2, origin), PRVM_serverglobalvector(trace_endpos)); + VectorNegate(trace->plane.normal, PRVM_serverglobalvector(trace_plane_normal)); + PRVM_serverglobalfloat(trace_plane_dist) = -trace->plane.dist; + PRVM_serverglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(e1); + PRVM_serverglobalfloat(trace_dpstartcontents) = 0; + PRVM_serverglobalfloat(trace_dphitcontents) = 0; + PRVM_serverglobalfloat(trace_dphitq3surfaceflags) = 0; + PRVM_serverglobalstring(trace_dphittexturename) = 0; + prog->ExecuteProgram(prog, PRVM_serveredictfunction(e2, touch), "QC function self.touch is missing"); + } + + PRVM_serverglobaledict(self) = old_self; + PRVM_serverglobaledict(other) = old_other; + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 +static void ClipVelocity (prvm_vec3_t in, vec3_t normal, prvm_vec3_t out, prvm_vec_t overbounce) +{ + int i; + float backoff; + + backoff = -DotProduct (in, normal) * overbounce; + VectorMA(in, backoff, normal, out); + + for (i = 0;i < 3;i++) + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +8 = teleported by touch method +If stepnormal is not NULL, the plane normal of any vertical wall hit will be stored +============ +*/ +static float SV_Gravity (prvm_edict_t *ent); +static qboolean SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, qboolean dolink); +#define MAX_CLIP_PLANES 5 +static int SV_FlyMove (prvm_edict_t *ent, float time, qboolean applygravity, float *stepnormal, int hitsupercontentsmask, float stepheight) +{ + prvm_prog_t *prog = SVVM_prog; + int blocked, bumpcount; + int i, j, numplanes; + float d, time_left, gravity; + vec3_t dir, push, planes[MAX_CLIP_PLANES]; + prvm_vec3_t primal_velocity, original_velocity, new_velocity, restore_velocity; +#if 0 + vec3_t end; +#endif + trace_t trace; + if (time <= 0) + return 0; + gravity = 0; + + VectorCopy(PRVM_serveredictvector(ent, velocity), restore_velocity); + + if(applygravity) + { + gravity = SV_Gravity(ent); + + if(!sv_gameplayfix_nogravityonground.integer || !((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) + { + if (sv_gameplayfix_gravityunaffectedbyticrate.integer) + PRVM_serveredictvector(ent, velocity)[2] -= gravity * 0.5f; + else + PRVM_serveredictvector(ent, velocity)[2] -= gravity; + } + } + + blocked = 0; + VectorCopy(PRVM_serveredictvector(ent, velocity), original_velocity); + VectorCopy(PRVM_serveredictvector(ent, velocity), primal_velocity); + numplanes = 0; + time_left = time; + for (bumpcount = 0;bumpcount < MAX_CLIP_PLANES;bumpcount++) + { + if (!PRVM_serveredictvector(ent, velocity)[0] && !PRVM_serveredictvector(ent, velocity)[1] && !PRVM_serveredictvector(ent, velocity)[2]) + break; + + VectorScale(PRVM_serveredictvector(ent, velocity), time_left, push); + if(!SV_PushEntity(&trace, ent, push, false)) + { + // we got teleported by a touch function + // let's abort the move + blocked |= 8; + break; + } + + // this code is used by MOVETYPE_WALK and MOVETYPE_STEP and SV_UnstickEntity + // abort move if we're stuck in the world (and didn't make it out) + if (trace.worldstartsolid && trace.allsolid) + { + VectorCopy(restore_velocity, PRVM_serveredictvector(ent, velocity)); + return 3; + } + + if (trace.fraction == 1) + break; + if (trace.plane.normal[2]) + { + if (trace.plane.normal[2] > 0.7) + { + // floor + blocked |= 1; + + if (!trace.ent) + { + Con_Printf ("SV_FlyMove: !trace.ent"); + trace.ent = prog->edicts; + } + + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + } + } + else if (stepheight) + { + // step - handle it immediately + vec3_t org; + vec3_t steppush; + trace_t steptrace; + trace_t steptrace2; + trace_t steptrace3; + //Con_Printf("step %f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + VectorSet(steppush, 0, 0, stepheight); + VectorCopy(PRVM_serveredictvector(ent, origin), org); + if(!SV_PushEntity(&steptrace, ent, steppush, false)) + { + blocked |= 8; + break; + } + //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + if(!SV_PushEntity(&steptrace2, ent, push, false)) + { + blocked |= 8; + break; + } + //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + VectorSet(steppush, 0, 0, org[2] - PRVM_serveredictvector(ent, origin)[2]); + if(!SV_PushEntity(&steptrace3, ent, steppush, false)) + { + blocked |= 8; + break; + } + //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + // accept the new position if it made some progress... + if (fabs(PRVM_serveredictvector(ent, origin)[0] - org[0]) >= 0.03125 || fabs(PRVM_serveredictvector(ent, origin)[1] - org[1]) >= 0.03125) + { + //Con_Printf("accepted (delta %f %f %f)\n", PRVM_serveredictvector(ent, origin)[0] - org[0], PRVM_serveredictvector(ent, origin)[1] - org[1], PRVM_serveredictvector(ent, origin)[2] - org[2]); + trace = steptrace2; + VectorCopy(PRVM_serveredictvector(ent, origin), trace.endpos); + time_left *= 1 - trace.fraction; + numplanes = 0; + continue; + } + else + { + //Con_Printf("REJECTED (delta %f %f %f)\n", PRVM_serveredictvector(ent, origin)[0] - org[0], PRVM_serveredictvector(ent, origin)[1] - org[1], PRVM_serveredictvector(ent, origin)[2] - org[2]); + VectorCopy(org, PRVM_serveredictvector(ent, origin)); + } + } + else + { + // step - return it to caller + blocked |= 2; + // save the trace for player extrafriction + if (stepnormal) + VectorCopy(trace.plane.normal, stepnormal); + } + if (trace.fraction >= 0.001) + { + // actually covered some distance + VectorCopy(PRVM_serveredictvector(ent, velocity), original_velocity); + numplanes = 0; + } + + time_left *= 1 - trace.fraction; + + // clipped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { + // this shouldn't really happen + VectorClear(PRVM_serveredictvector(ent, velocity)); + blocked = 3; + break; + } + + /* + for (i = 0;i < numplanes;i++) + if (DotProduct(trace.plane.normal, planes[i]) > 0.99) + break; + if (i < numplanes) + { + VectorAdd(PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity)); + continue; + } + */ + + VectorCopy(trace.plane.normal, planes[numplanes]); + numplanes++; + + // modify original_velocity so it parallels all of the clip planes + for (i = 0;i < numplanes;i++) + { + ClipVelocity(original_velocity, planes[i], new_velocity, 1); + for (j = 0;j < numplanes;j++) + { + if (j != i) + { + // not ok + if (DotProduct(new_velocity, planes[j]) < 0) + break; + } + } + if (j == numplanes) + break; + } + + if (i != numplanes) + { + // go along this plane + VectorCopy(new_velocity, PRVM_serveredictvector(ent, velocity)); + } + else + { + // go along the crease + if (numplanes != 2) + { + VectorClear(PRVM_serveredictvector(ent, velocity)); + blocked = 7; + break; + } + CrossProduct(planes[0], planes[1], dir); + // LordHavoc: thanks to taniwha of QuakeForge for pointing out this fix for slowed falling in corners + VectorNormalize(dir); + d = DotProduct(dir, PRVM_serveredictvector(ent, velocity)); + VectorScale(dir, d, PRVM_serveredictvector(ent, velocity)); + } + + // if current velocity is against the original velocity, + // stop dead to avoid tiny occilations in sloping corners + if (DotProduct(PRVM_serveredictvector(ent, velocity), primal_velocity) <= 0) + { + VectorClear(PRVM_serveredictvector(ent, velocity)); + break; + } + } + + //Con_Printf("entity %i final: blocked %i velocity %f %f %f\n", ent - prog->edicts, blocked, PRVM_serveredictvector(ent, velocity)[0], PRVM_serveredictvector(ent, velocity)[1], PRVM_serveredictvector(ent, velocity)[2]); + + /* + if ((blocked & 1) == 0 && bumpcount > 1) + { + // LordHavoc: fix the 'fall to your death in a wedge corner' glitch + // flag ONGROUND if there's ground under it + trace = SV_TraceBox(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), end, MOVE_NORMAL, ent, hitsupercontentsmask); + } + */ + + // LordHavoc: this came from QW and allows you to get out of water more easily + if (sv_gameplayfix_easierwaterjump.integer && ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP) && !(blocked & 8)) + VectorCopy(primal_velocity, PRVM_serveredictvector(ent, velocity)); + + if(applygravity) + { + if(!sv_gameplayfix_nogravityonground.integer || !((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) + { + if (sv_gameplayfix_gravityunaffectedbyticrate.integer) + PRVM_serveredictvector(ent, velocity)[2] -= gravity * 0.5f; + } + } + + return blocked; +} + +/* +============ +SV_Gravity + +============ +*/ +static float SV_Gravity (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + float ent_gravity; + + ent_gravity = PRVM_serveredictfloat(ent, gravity); + if (!ent_gravity) + ent_gravity = 1.0f; + return ent_gravity * sv_gravity.value * sv.frametime; +} + + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +static qboolean SV_NudgeOutOfSolid_PivotIsKnownGood(prvm_edict_t *ent, vec3_t pivot) +{ + prvm_prog_t *prog = SVVM_prog; + int bump; + trace_t stucktrace; + vec3_t stuckorigin; + vec3_t stuckmins, stuckmaxs; + vec3_t goodmins, goodmaxs; + vec3_t testorigin; + vec_t nudge; + vec3_t move; + VectorCopy(PRVM_serveredictvector(ent, origin), stuckorigin); + VectorCopy(PRVM_serveredictvector(ent, mins), stuckmins); + VectorCopy(PRVM_serveredictvector(ent, maxs), stuckmaxs); + VectorCopy(pivot, goodmins); + VectorCopy(pivot, goodmaxs); + for (bump = 0;bump < 6;bump++) + { + int coord = 2-(bump >> 1); + //int coord = (bump >> 1); + int dir = (bump & 1); + int subbump; + + for(subbump = 0; ; ++subbump) + { + VectorCopy(stuckorigin, testorigin); + if(dir) + { + // pushing maxs + testorigin[coord] += stuckmaxs[coord] - goodmaxs[coord]; + } + else + { + // pushing mins + testorigin[coord] += stuckmins[coord] - goodmins[coord]; + } + + stucktrace = SV_TraceBox(stuckorigin, goodmins, goodmaxs, testorigin, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent)); + if (stucktrace.bmodelstartsolid) + { + // BAD BAD, can't fix that + return false; + } + + if (stucktrace.fraction >= 1) + break; // it WORKS! + + if(subbump >= 10) + { + // BAD BAD, can't fix that + return false; + } + + // we hit something... let's move out of it + VectorSubtract(stucktrace.endpos, testorigin, move); + nudge = DotProduct(stucktrace.plane.normal, move) + 0.03125f; // FIXME cvar this constant + VectorMA(stuckorigin, nudge, stucktrace.plane.normal, stuckorigin); + } + /* + if(subbump > 0) + Con_Printf("subbump: %d\n", subbump); + */ + + if(dir) + { + // pushing maxs + goodmaxs[coord] = stuckmaxs[coord]; + } + else + { + // pushing mins + goodmins[coord] = stuckmins[coord]; + } + } + + // WE WIN + VectorCopy(stuckorigin, PRVM_serveredictvector(ent, origin)); + + return true; +} + +qboolean SV_NudgeOutOfSolid(prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + int bump, pass; + trace_t stucktrace; + vec3_t stuckorigin; + vec3_t stuckmins, stuckmaxs; + vec_t nudge; + vec_t separation = sv_gameplayfix_nudgeoutofsolid_separation.value; + if (sv.worldmodel && sv.worldmodel->brushq1.numclipnodes) + separation = 0.0f; // when using hulls, it can not be enlarged + VectorCopy(PRVM_serveredictvector(ent, mins), stuckmins); + VectorCopy(PRVM_serveredictvector(ent, maxs), stuckmaxs); + stuckmins[0] -= separation; + stuckmins[1] -= separation; + stuckmins[2] -= separation; + stuckmaxs[0] += separation; + stuckmaxs[1] += separation; + stuckmaxs[2] += separation; + // first pass we try to get it out of brush entities + // second pass we try to get it out of world only (can't win them all) + for (pass = 0;pass < 2;pass++) + { + VectorCopy(PRVM_serveredictvector(ent, origin), stuckorigin); + for (bump = 0;bump < 10;bump++) + { + stucktrace = SV_TraceBox(stuckorigin, stuckmins, stuckmaxs, stuckorigin, pass ? MOVE_WORLDONLY : MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent)); + if (!stucktrace.bmodelstartsolid || stucktrace.startdepth >= 0) + { + // found a good location, use it + VectorCopy(stuckorigin, PRVM_serveredictvector(ent, origin)); + return true; + } + nudge = -stucktrace.startdepth; + VectorMA(stuckorigin, nudge, stucktrace.startdepthnormal, stuckorigin); + } + } + return false; +} + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +The trace struct is filled with the trace that has been done. +Returns true if the push did not result in the entity being teleported by QC code. +============ +*/ +static qboolean SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, qboolean dolink) +{ + prvm_prog_t *prog = SVVM_prog; + int solid; + int movetype; + int type; + vec3_t mins, maxs; + vec3_t start; + vec3_t end; + + solid = (int)PRVM_serveredictfloat(ent, solid); + movetype = (int)PRVM_serveredictfloat(ent, movetype); + VectorCopy(PRVM_serveredictvector(ent, mins), mins); + VectorCopy(PRVM_serveredictvector(ent, maxs), maxs); + + // move start position out of solids + if (sv_gameplayfix_nudgeoutofsolid.integer && sv_gameplayfix_nudgeoutofsolid_separation.value >= 0) + { + SV_NudgeOutOfSolid(ent); + } + + VectorCopy(PRVM_serveredictvector(ent, origin), start); + VectorAdd(start, push, end); + + if (movetype == MOVETYPE_FLYMISSILE) + type = MOVE_MISSILE; + else if (movetype == MOVETYPE_FLY_WORLDONLY) + type = MOVE_WORLDONLY; + else if (solid == SOLID_TRIGGER || solid == SOLID_NOT) + type = MOVE_NOMONSTERS; // only clip against bmodels + else + type = MOVE_NORMAL; + + *trace = SV_TraceBox(start, mins, maxs, end, type, ent, SV_GenericHitSuperContentsMask(ent)); + // fail the move if stuck in world + if (trace->worldstartsolid) + return true; + + VectorCopy(trace->endpos, PRVM_serveredictvector(ent, origin)); + + ent->priv.required->mark = PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN; // -2: setorigin running + + SV_LinkEdict(ent); + +#if 0 + if(!trace->startsolid) + if(SV_TraceBox(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), PRVM_serveredictvector(ent, origin), type, ent, SV_GenericHitSuperContentsMask(ent)).startsolid) + { + Con_Printf("something eeeeevil happened\n"); + } +#endif + + if (dolink) + SV_LinkEdict_TouchAreaGrid(ent); + + if((PRVM_serveredictfloat(ent, solid) >= SOLID_TRIGGER && trace->ent && (!((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) || PRVM_serveredictedict(ent, groundentity) != PRVM_EDICT_TO_PROG(trace->ent)))) + SV_Impact (ent, trace); + + if(ent->priv.required->mark == PRVM_EDICT_MARK_SETORIGIN_CAUGHT) + { + ent->priv.required->mark = 0; + return false; + } + else if(ent->priv.required->mark == PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN) + { + ent->priv.required->mark = 0; + return true; + } + else + { + Con_Printf("The edict mark had been overwritten! Please debug this.\n"); + return true; + } +} + + +/* +============ +SV_PushMove + +============ +*/ +static void SV_PushMove (prvm_edict_t *pusher, float movetime) +{ + prvm_prog_t *prog = SVVM_prog; + int i, e, index; + int pusherowner, pusherprog; + int checkcontents; + qboolean rotated; + float savesolid, movetime2, pushltime; + vec3_t mins, maxs, move, move1, moveangle, pushorig, pushang, a, forward, left, up, org, pushermins, pushermaxs, checkorigin, checkmins, checkmaxs; + int num_moved; + int numcheckentities; + static prvm_edict_t *checkentities[MAX_EDICTS]; + dp_model_t *pushermodel; + trace_t trace, trace2; + matrix4x4_t pusherfinalmatrix, pusherfinalimatrix; + static unsigned short moved_edicts[MAX_EDICTS]; + vec3_t pivot; + + if (!PRVM_serveredictvector(pusher, velocity)[0] && !PRVM_serveredictvector(pusher, velocity)[1] && !PRVM_serveredictvector(pusher, velocity)[2] && !PRVM_serveredictvector(pusher, avelocity)[0] && !PRVM_serveredictvector(pusher, avelocity)[1] && !PRVM_serveredictvector(pusher, avelocity)[2]) + { + PRVM_serveredictfloat(pusher, ltime) += movetime; + return; + } + + switch ((int) PRVM_serveredictfloat(pusher, solid)) + { + // LordHavoc: valid pusher types + case SOLID_BSP: + case SOLID_BBOX: + case SOLID_SLIDEBOX: + case SOLID_CORPSE: // LordHavoc: this would be weird... + break; + // LordHavoc: no collisions + case SOLID_NOT: + case SOLID_TRIGGER: + VectorMA (PRVM_serveredictvector(pusher, origin), movetime, PRVM_serveredictvector(pusher, velocity), PRVM_serveredictvector(pusher, origin)); + VectorMA (PRVM_serveredictvector(pusher, angles), movetime, PRVM_serveredictvector(pusher, avelocity), PRVM_serveredictvector(pusher, angles)); + PRVM_serveredictvector(pusher, angles)[0] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[0] * (1.0 / 360.0)); + PRVM_serveredictvector(pusher, angles)[1] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[1] * (1.0 / 360.0)); + PRVM_serveredictvector(pusher, angles)[2] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[2] * (1.0 / 360.0)); + PRVM_serveredictfloat(pusher, ltime) += movetime; + SV_LinkEdict(pusher); + return; + default: + Con_Printf("SV_PushMove: entity #%i, unrecognized solid type %f\n", PRVM_NUM_FOR_EDICT(pusher), PRVM_serveredictfloat(pusher, solid)); + return; + } + index = (int) PRVM_serveredictfloat(pusher, modelindex); + if (index < 1 || index >= MAX_MODELS) + { + Con_Printf("SV_PushMove: entity #%i has an invalid modelindex %f\n", PRVM_NUM_FOR_EDICT(pusher), PRVM_serveredictfloat(pusher, modelindex)); + return; + } + pushermodel = SV_GetModelByIndex(index); + pusherowner = PRVM_serveredictedict(pusher, owner); + pusherprog = PRVM_EDICT_TO_PROG(pusher); + + rotated = VectorLength2(PRVM_serveredictvector(pusher, angles)) + VectorLength2(PRVM_serveredictvector(pusher, avelocity)) > 0; + + movetime2 = movetime; + VectorScale(PRVM_serveredictvector(pusher, velocity), movetime2, move1); + VectorScale(PRVM_serveredictvector(pusher, avelocity), movetime2, moveangle); + if (moveangle[0] || moveangle[2]) + { + for (i = 0;i < 3;i++) + { + if (move1[i] > 0) + { + mins[i] = pushermodel->rotatedmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->rotatedmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + else + { + mins[i] = pushermodel->rotatedmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->rotatedmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + } + } + else if (moveangle[1]) + { + for (i = 0;i < 3;i++) + { + if (move1[i] > 0) + { + mins[i] = pushermodel->yawmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->yawmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + else + { + mins[i] = pushermodel->yawmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->yawmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + } + } + else + { + for (i = 0;i < 3;i++) + { + if (move1[i] > 0) + { + mins[i] = pushermodel->normalmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->normalmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + else + { + mins[i] = pushermodel->normalmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; + maxs[i] = pushermodel->normalmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; + } + } + } + + VectorNegate (moveangle, a); + AngleVectorsFLU (a, forward, left, up); + + VectorCopy (PRVM_serveredictvector(pusher, origin), pushorig); + VectorCopy (PRVM_serveredictvector(pusher, angles), pushang); + pushltime = PRVM_serveredictfloat(pusher, ltime); + +// move the pusher to its final position + + VectorMA (PRVM_serveredictvector(pusher, origin), movetime, PRVM_serveredictvector(pusher, velocity), PRVM_serveredictvector(pusher, origin)); + VectorMA (PRVM_serveredictvector(pusher, angles), movetime, PRVM_serveredictvector(pusher, avelocity), PRVM_serveredictvector(pusher, angles)); + PRVM_serveredictfloat(pusher, ltime) += movetime; + SV_LinkEdict(pusher); + + pushermodel = SV_GetModelFromEdict(pusher); + Matrix4x4_CreateFromQuakeEntity(&pusherfinalmatrix, PRVM_serveredictvector(pusher, origin)[0], PRVM_serveredictvector(pusher, origin)[1], PRVM_serveredictvector(pusher, origin)[2], PRVM_serveredictvector(pusher, angles)[0], PRVM_serveredictvector(pusher, angles)[1], PRVM_serveredictvector(pusher, angles)[2], 1); + Matrix4x4_Invert_Simple(&pusherfinalimatrix, &pusherfinalmatrix); + + savesolid = PRVM_serveredictfloat(pusher, solid); + +// see if any solid entities are inside the final position + num_moved = 0; + + if (PRVM_serveredictfloat(pusher, movetype) == MOVETYPE_FAKEPUSH) // Tenebrae's MOVETYPE_PUSH variant that doesn't push... + numcheckentities = 0; + else // MOVETYPE_PUSH + numcheckentities = SV_EntitiesInBox(mins, maxs, MAX_EDICTS, checkentities); + for (e = 0;e < numcheckentities;e++) + { + prvm_edict_t *check = checkentities[e]; + int movetype = (int)PRVM_serveredictfloat(check, movetype); + switch(movetype) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_FOLLOW: + case MOVETYPE_NOCLIP: + case MOVETYPE_FLY_WORLDONLY: + continue; + default: + break; + } + + if (PRVM_serveredictedict(check, owner) == pusherprog) + continue; + + if (pusherowner == PRVM_EDICT_TO_PROG(check)) + continue; + + //Con_Printf("%i %s ", PRVM_NUM_FOR_EDICT(check), PRVM_GetString(PRVM_serveredictstring(check, classname))); + + // tell any MOVETYPE_STEP entity that it may need to check for water transitions + check->priv.server->waterposition_forceupdate = true; + + checkcontents = SV_GenericHitSuperContentsMask(check); + + // if the entity is standing on the pusher, it will definitely be moved + // if the entity is not standing on the pusher, but is in the pusher's + // final position, move it + if (!((int)PRVM_serveredictfloat(check, flags) & FL_ONGROUND) || PRVM_PROG_TO_EDICT(PRVM_serveredictedict(check, groundentity)) != pusher) + { + VectorCopy(PRVM_serveredictvector(pusher, mins), pushermins); + VectorCopy(PRVM_serveredictvector(pusher, maxs), pushermaxs); + VectorCopy(PRVM_serveredictvector(check, origin), checkorigin); + VectorCopy(PRVM_serveredictvector(check, mins), checkmins); + VectorCopy(PRVM_serveredictvector(check, maxs), checkmaxs); + Collision_ClipToGenericEntity(&trace, pushermodel, pusher->priv.server->frameblend, &pusher->priv.server->skeleton, pushermins, pushermaxs, SUPERCONTENTS_BODY, &pusherfinalmatrix, &pusherfinalimatrix, checkorigin, checkmins, checkmaxs, checkorigin, checkcontents); + //trace = SV_TraceBox(PRVM_serveredictvector(check, origin), PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs), PRVM_serveredictvector(check, origin), MOVE_NOMONSTERS, check, checkcontents); + if (!trace.startsolid) + { + //Con_Printf("- not in solid\n"); + continue; + } + } + + VectorLerp(PRVM_serveredictvector(check, mins), 0.5f, PRVM_serveredictvector(check, maxs), pivot); + //VectorClear(pivot); + + if (rotated) + { + vec3_t org2; + VectorSubtract (PRVM_serveredictvector(check, origin), PRVM_serveredictvector(pusher, origin), org); + VectorAdd (org, pivot, org); + org2[0] = DotProduct (org, forward); + org2[1] = DotProduct (org, left); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move); + VectorAdd (move, move1, move); + } + else + VectorCopy (move1, move); + + //Con_Printf("- pushing %f %f %f\n", move[0], move[1], move[2]); + + VectorCopy (PRVM_serveredictvector(check, origin), check->priv.server->moved_from); + VectorCopy (PRVM_serveredictvector(check, angles), check->priv.server->moved_fromangles); + moved_edicts[num_moved++] = PRVM_NUM_FOR_EDICT(check); + + // physics objects need better collisions than this code can do + if (movetype == MOVETYPE_PHYSICS) + { + VectorAdd(PRVM_serveredictvector(check, origin), move, PRVM_serveredictvector(check, origin)); + SV_LinkEdict(check); + SV_LinkEdict_TouchAreaGrid(check); + continue; + } + + // try moving the contacted entity + PRVM_serveredictfloat(pusher, solid) = SOLID_NOT; + if(!SV_PushEntity (&trace, check, move, true)) + { + // entity "check" got teleported + PRVM_serveredictvector(check, angles)[1] += trace.fraction * moveangle[1]; + PRVM_serveredictfloat(pusher, solid) = savesolid; // was SOLID_BSP + continue; // pushed enough + } + // FIXME: turn players specially + PRVM_serveredictvector(check, angles)[1] += trace.fraction * moveangle[1]; + PRVM_serveredictfloat(pusher, solid) = savesolid; // was SOLID_BSP + //Con_Printf("%s:%d frac %f startsolid %d bmodelstartsolid %d allsolid %d\n", __FILE__, __LINE__, trace.fraction, trace.startsolid, trace.bmodelstartsolid, trace.allsolid); + + // this trace.fraction < 1 check causes items to fall off of pushers + // if they pass under or through a wall + // the groundentity check causes items to fall off of ledges + if (PRVM_serveredictfloat(check, movetype) != MOVETYPE_WALK && (trace.fraction < 1 || PRVM_PROG_TO_EDICT(PRVM_serveredictedict(check, groundentity)) != pusher)) + PRVM_serveredictfloat(check, flags) = (int)PRVM_serveredictfloat(check, flags) & ~FL_ONGROUND; + + // if it is still inside the pusher, block + VectorCopy(PRVM_serveredictvector(pusher, mins), pushermins); + VectorCopy(PRVM_serveredictvector(pusher, maxs), pushermaxs); + VectorCopy(PRVM_serveredictvector(check, origin), checkorigin); + VectorCopy(PRVM_serveredictvector(check, mins), checkmins); + VectorCopy(PRVM_serveredictvector(check, maxs), checkmaxs); + Collision_ClipToGenericEntity(&trace, pushermodel, pusher->priv.server->frameblend, &pusher->priv.server->skeleton, pushermins, pushermaxs, SUPERCONTENTS_BODY, &pusherfinalmatrix, &pusherfinalimatrix, checkorigin, checkmins, checkmaxs, checkorigin, checkcontents); + if (trace.startsolid) + { + vec3_t move2; + if(SV_NudgeOutOfSolid_PivotIsKnownGood(check, pivot)) + { + // hack to invoke all necessary movement triggers + VectorClear(move2); + if(!SV_PushEntity(&trace2, check, move2, true)) + { + // entity "check" got teleported + continue; + } + // we could fix it + continue; + } + + // still inside pusher, so it's really blocked + + // fail the move + if (PRVM_serveredictvector(check, mins)[0] == PRVM_serveredictvector(check, maxs)[0]) + continue; + if (PRVM_serveredictfloat(check, solid) == SOLID_NOT || PRVM_serveredictfloat(check, solid) == SOLID_TRIGGER) + { + // corpse + PRVM_serveredictvector(check, mins)[0] = PRVM_serveredictvector(check, mins)[1] = 0; + VectorCopy (PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs)); + continue; + } + + VectorCopy (pushorig, PRVM_serveredictvector(pusher, origin)); + VectorCopy (pushang, PRVM_serveredictvector(pusher, angles)); + PRVM_serveredictfloat(pusher, ltime) = pushltime; + SV_LinkEdict(pusher); + + // move back any entities we already moved + for (i = 0;i < num_moved;i++) + { + prvm_edict_t *ed = PRVM_EDICT_NUM(moved_edicts[i]); + VectorCopy (ed->priv.server->moved_from, PRVM_serveredictvector(ed, origin)); + VectorCopy (ed->priv.server->moved_fromangles, PRVM_serveredictvector(ed, angles)); + SV_LinkEdict(ed); + } + + // if the pusher has a "blocked" function, call it, otherwise just stay in place until the obstacle is gone + if (PRVM_serveredictfunction(pusher, blocked)) + { + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(pusher); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(check); + prog->ExecuteProgram(prog, PRVM_serveredictfunction(pusher, blocked), "QC function self.blocked is missing"); + } + break; + } + } + PRVM_serveredictvector(pusher, angles)[0] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[0] * (1.0 / 360.0)); + PRVM_serveredictvector(pusher, angles)[1] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[1] * (1.0 / 360.0)); + PRVM_serveredictvector(pusher, angles)[2] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[2] * (1.0 / 360.0)); +} + +/* +================ +SV_Physics_Pusher + +================ +*/ +static void SV_Physics_Pusher (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + float thinktime, oldltime, movetime; + + oldltime = PRVM_serveredictfloat(ent, ltime); + + thinktime = PRVM_serveredictfloat(ent, nextthink); + if (thinktime < PRVM_serveredictfloat(ent, ltime) + sv.frametime) + { + movetime = thinktime - PRVM_serveredictfloat(ent, ltime); + if (movetime < 0) + movetime = 0; + } + else + movetime = sv.frametime; + + if (movetime) + // advances PRVM_serveredictfloat(ent, ltime) if not blocked + SV_PushMove (ent, movetime); + + if (thinktime > oldltime && thinktime <= PRVM_serveredictfloat(ent, ltime)) + { + PRVM_serveredictfloat(ent, nextthink) = 0; + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); + prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, think), "QC function self.think is missing"); + } +} + + +/* +=============================================================================== + +CLIENT MOVEMENT + +=============================================================================== +*/ + +static float unstickoffsets[] = +{ + // poutting -/+z changes first as they are least weird + 0, 0, -1, + 0, 0, 1, + // x or y changes + -1, 0, 0, + 1, 0, 0, + 0, -1, 0, + 0, 1, 0, + // x and y changes + -1, -1, 0, + 1, -1, 0, + -1, 1, 0, + 1, 1, 0, +}; + +typedef enum unstickresult_e +{ + UNSTICK_STUCK = 0, + UNSTICK_GOOD = 1, + UNSTICK_UNSTUCK = 2 +} +unstickresult_t; + +static unstickresult_t SV_UnstickEntityReturnOffset (prvm_edict_t *ent, vec3_t offset) +{ + prvm_prog_t *prog = SVVM_prog; + int i, maxunstick; + + // if not stuck in a bmodel, just return + if (!SV_TestEntityPosition(ent, vec3_origin)) + return UNSTICK_GOOD; + + for (i = 0;i < (int)(sizeof(unstickoffsets) / sizeof(unstickoffsets[0]));i += 3) + { + if (!SV_TestEntityPosition(ent, unstickoffsets + i)) + { + VectorCopy(unstickoffsets + i, offset); + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); + return UNSTICK_UNSTUCK; + } + } + + maxunstick = (int) ((PRVM_serveredictvector(ent, maxs)[2] - PRVM_serveredictvector(ent, mins)[2]) * 0.36); + // magic number 0.36 allows unsticking by up to 17 units with the largest supported bbox + + for(i = 2; i <= maxunstick; ++i) + { + VectorClear(offset); + offset[2] = -i; + if (!SV_TestEntityPosition(ent, offset)) + { + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); + return UNSTICK_UNSTUCK; + } + offset[2] = i; + if (!SV_TestEntityPosition(ent, offset)) + { + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); + return UNSTICK_UNSTUCK; + } + } + + return UNSTICK_STUCK; +} + +qboolean SV_UnstickEntity (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + vec3_t offset; + switch(SV_UnstickEntityReturnOffset(ent, offset)) + { + case UNSTICK_GOOD: + return true; + case UNSTICK_UNSTUCK: + Con_DPrintf("Unstuck entity %i (classname \"%s\") with offset %f %f %f.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname)), offset[0], offset[1], offset[2]); + return true; + case UNSTICK_STUCK: + if (developer_extra.integer) + Con_DPrintf("Stuck entity %i (classname \"%s\").\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); + return false; + default: + Con_Printf("SV_UnstickEntityReturnOffset returned a value outside its enum.\n"); + return false; + } +} + +/* +============= +SV_CheckStuck + +This is a big hack to try and fix the rare case of getting stuck in the world +clipping hull. +============= +*/ +static void SV_CheckStuck (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + vec3_t offset; + + switch(SV_UnstickEntityReturnOffset(ent, offset)) + { + case UNSTICK_GOOD: + VectorCopy (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, oldorigin)); + break; + case UNSTICK_UNSTUCK: + Con_DPrintf("Unstuck player entity %i (classname \"%s\") with offset %f %f %f.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname)), offset[0], offset[1], offset[2]); + break; + case UNSTICK_STUCK: + VectorSubtract(PRVM_serveredictvector(ent, oldorigin), PRVM_serveredictvector(ent, origin), offset); + if (!SV_TestEntityPosition(ent, offset)) + { + Con_DPrintf("Unstuck player entity %i (classname \"%s\") by restoring oldorigin.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); + } + else + Con_DPrintf("Stuck player entity %i (classname \"%s\").\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); + break; + default: + Con_Printf("SV_UnstickEntityReturnOffset returned a value outside its enum.\n"); + } +} + + +/* +============= +SV_CheckWater +============= +*/ +static qboolean SV_CheckWater (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + int cont; + int nNativeContents; + vec3_t point; + + point[0] = PRVM_serveredictvector(ent, origin)[0]; + point[1] = PRVM_serveredictvector(ent, origin)[1]; + point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, mins)[2] + 1; + + // DRESK - Support for Entity Contents Transition Event + // NOTE: Some logic needed to be slightly re-ordered + // to not affect performance and allow for the feature. + + // Acquire Super Contents Prior to Resets + cont = SV_PointSuperContents(point); + // Acquire Native Contents Here + nNativeContents = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, cont); + + // DRESK - Support for Entity Contents Transition Event + if(PRVM_serveredictfloat(ent, watertype)) + // Entity did NOT Spawn; Check + SV_CheckContentsTransition(ent, nNativeContents); + + + PRVM_serveredictfloat(ent, waterlevel) = 0; + PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY; + cont = SV_PointSuperContents(point); + if (cont & (SUPERCONTENTS_LIQUIDSMASK)) + { + PRVM_serveredictfloat(ent, watertype) = nNativeContents; + PRVM_serveredictfloat(ent, waterlevel) = 1; + point[2] = PRVM_serveredictvector(ent, origin)[2] + (PRVM_serveredictvector(ent, mins)[2] + PRVM_serveredictvector(ent, maxs)[2])*0.5; + if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK)) + { + PRVM_serveredictfloat(ent, waterlevel) = 2; + point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, view_ofs)[2]; + if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK)) + PRVM_serveredictfloat(ent, waterlevel) = 3; + } + } + + return PRVM_serveredictfloat(ent, waterlevel) > 1; +} + +/* +============ +SV_WallFriction + +============ +*/ +static void SV_WallFriction (prvm_edict_t *ent, float *stepnormal) +{ + prvm_prog_t *prog = SVVM_prog; + float d, i; + vec3_t forward, into, side, v_angle; + + VectorCopy(PRVM_serveredictvector(ent, v_angle), v_angle); + AngleVectors (v_angle, forward, NULL, NULL); + if ((d = DotProduct (stepnormal, forward) + 0.5) < 0) + { + // cut the tangential velocity + i = DotProduct (stepnormal, PRVM_serveredictvector(ent, velocity)); + VectorScale (stepnormal, i, into); + VectorSubtract (PRVM_serveredictvector(ent, velocity), into, side); + PRVM_serveredictvector(ent, velocity)[0] = side[0] * (1 + d); + PRVM_serveredictvector(ent, velocity)[1] = side[1] * (1 + d); + } +} + +#if 0 +/* +===================== +SV_TryUnstick + +Player has come to a dead stop, possibly due to the problem with limited +float precision at some angle joins in the BSP hull. + +Try fixing by pushing one pixel in each direction. + +This is a hack, but in the interest of good gameplay... +====================== +*/ +int SV_TryUnstick (prvm_edict_t *ent, vec3_t oldvel) +{ + int i, clip; + vec3_t oldorg, dir; + + VectorCopy (PRVM_serveredictvector(ent, origin), oldorg); + VectorClear (dir); + + for (i=0 ; i<8 ; i++) + { + // try pushing a little in an axial direction + switch (i) + { + case 0: dir[0] = 2; dir[1] = 0; break; + case 1: dir[0] = 0; dir[1] = 2; break; + case 2: dir[0] = -2; dir[1] = 0; break; + case 3: dir[0] = 0; dir[1] = -2; break; + case 4: dir[0] = 2; dir[1] = 2; break; + case 5: dir[0] = -2; dir[1] = 2; break; + case 6: dir[0] = 2; dir[1] = -2; break; + case 7: dir[0] = -2; dir[1] = -2; break; + } + + SV_PushEntity (&trace, ent, dir, false, true); + + // retry the original move + PRVM_serveredictvector(ent, velocity)[0] = oldvel[0]; + PRVM_serveredictvector(ent, velocity)[1] = oldvel[1]; + PRVM_serveredictvector(ent, velocity)[2] = 0; + clip = SV_FlyMove (ent, 0.1, NULL, SV_GenericHitSuperContentsMask(ent)); + + if (fabs(oldorg[1] - PRVM_serveredictvector(ent, origin)[1]) > 4 + || fabs(oldorg[0] - PRVM_serveredictvector(ent, origin)[0]) > 4) + { + Con_DPrint("TryUnstick - success.\n"); + return clip; + } + + // go back to the original pos and try again + VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); + } + + // still not moving + VectorClear (PRVM_serveredictvector(ent, velocity)); + Con_DPrint("TryUnstick - failure.\n"); + return 7; +} +#endif + +/* +===================== +SV_WalkMove + +Only used by players +====================== +*/ +static void SV_WalkMove (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + int clip; + int oldonground; + //int originalmove_clip; + int originalmove_flags; + int originalmove_groundentity; + int hitsupercontentsmask; + int type; + vec3_t upmove, downmove, start_origin, start_velocity, stepnormal, originalmove_origin, originalmove_velocity, entmins, entmaxs; + trace_t downtrace, trace; + qboolean applygravity; + + // if frametime is 0 (due to client sending the same timestamp twice), + // don't move + if (sv.frametime <= 0) + return; + + if (sv_gameplayfix_unstickplayers.integer) + SV_CheckStuck (ent); + + applygravity = !SV_CheckWater (ent) && PRVM_serveredictfloat(ent, movetype) == MOVETYPE_WALK && ! ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP); + + hitsupercontentsmask = SV_GenericHitSuperContentsMask(ent); + + SV_CheckVelocity(ent); + + // do a regular slide move unless it looks like you ran into a step + oldonground = (int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND; + + VectorCopy (PRVM_serveredictvector(ent, origin), start_origin); + VectorCopy (PRVM_serveredictvector(ent, velocity), start_velocity); + + clip = SV_FlyMove (ent, sv.frametime, applygravity, NULL, hitsupercontentsmask, sv_gameplayfix_stepmultipletimes.integer ? sv_stepheight.value : 0); + + if(sv_gameplayfix_downtracesupportsongroundflag.integer) + if(!(clip & 1)) + { + // only try this if there was no floor in the way in the trace (no, + // this check seems to be not REALLY necessary, because if clip & 1, + // our trace will hit that thing too) + VectorSet(upmove, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] + 1); + VectorSet(downmove, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] - 1); + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLYMISSILE) + type = MOVE_MISSILE; + else if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLY_WORLDONLY) + type = MOVE_WORLDONLY; + else if (PRVM_serveredictfloat(ent, solid) == SOLID_TRIGGER || PRVM_serveredictfloat(ent, solid) == SOLID_NOT) + type = MOVE_NOMONSTERS; // only clip against bmodels + else + type = MOVE_NORMAL; + VectorCopy(PRVM_serveredictvector(ent, mins), entmins); + VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); + trace = SV_TraceBox(upmove, entmins, entmaxs, downmove, type, ent, SV_GenericHitSuperContentsMask(ent)); + if(trace.fraction < 1 && trace.plane.normal[2] > 0.7) + clip |= 1; // but we HAVE found a floor + } + + // if the move did not hit the ground at any point, we're not on ground + if(!(clip & 1)) + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + + SV_CheckVelocity(ent); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + if(clip & 8) // teleport + return; + + if ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP) + return; + + if (sv_nostep.integer) + return; + + VectorCopy(PRVM_serveredictvector(ent, origin), originalmove_origin); + VectorCopy(PRVM_serveredictvector(ent, velocity), originalmove_velocity); + //originalmove_clip = clip; + originalmove_flags = (int)PRVM_serveredictfloat(ent, flags); + originalmove_groundentity = PRVM_serveredictedict(ent, groundentity); + + // if move didn't block on a step, return + if (clip & 2) + { + // if move was not trying to move into the step, return + if (fabs(start_velocity[0]) < 0.03125 && fabs(start_velocity[1]) < 0.03125) + return; + + if (PRVM_serveredictfloat(ent, movetype) != MOVETYPE_FLY) + { + // return if gibbed by a trigger + if (PRVM_serveredictfloat(ent, movetype) != MOVETYPE_WALK) + return; + + // only step up while jumping if that is enabled + if (sv_jumpstep.integer) + if (!oldonground && PRVM_serveredictfloat(ent, waterlevel) == 0) + return; + } + + // try moving up and forward to go up a step + // back to start pos + VectorCopy (start_origin, PRVM_serveredictvector(ent, origin)); + VectorCopy (start_velocity, PRVM_serveredictvector(ent, velocity)); + + // move up + VectorClear (upmove); + upmove[2] = sv_stepheight.value; + if(!SV_PushEntity(&trace, ent, upmove, true)) + { + // we got teleported when upstepping... must abort the move + return; + } + + // move forward + PRVM_serveredictvector(ent, velocity)[2] = 0; + clip = SV_FlyMove (ent, sv.frametime, applygravity, stepnormal, hitsupercontentsmask, 0); + PRVM_serveredictvector(ent, velocity)[2] += start_velocity[2]; + if(clip & 8) + { + // we got teleported when upstepping... must abort the move + // note that z velocity handling may not be what QC expects here, but we cannot help it + return; + } + + SV_CheckVelocity(ent); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + // check for stuckness, possibly due to the limited precision of floats + // in the clipping hulls + if (clip + && fabs(originalmove_origin[1] - PRVM_serveredictvector(ent, origin)[1]) < 0.03125 + && fabs(originalmove_origin[0] - PRVM_serveredictvector(ent, origin)[0]) < 0.03125) + { + //Con_Printf("wall\n"); + // stepping up didn't make any progress, revert to original move + VectorCopy(originalmove_origin, PRVM_serveredictvector(ent, origin)); + VectorCopy(originalmove_velocity, PRVM_serveredictvector(ent, velocity)); + //clip = originalmove_clip; + PRVM_serveredictfloat(ent, flags) = originalmove_flags; + PRVM_serveredictedict(ent, groundentity) = originalmove_groundentity; + // now try to unstick if needed + //clip = SV_TryUnstick (ent, oldvel); + return; + } + + //Con_Printf("step - "); + + // extra friction based on view angle + if (clip & 2 && sv_wallfriction.integer) + SV_WallFriction (ent, stepnormal); + } + // don't do the down move if stepdown is disabled, moving upward, not in water, or the move started offground or ended onground + else if (!sv_gameplayfix_stepdown.integer || PRVM_serveredictfloat(ent, waterlevel) >= 3 || start_velocity[2] >= (1.0 / 32.0) || !oldonground || ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) + return; + + // move down + VectorClear (downmove); + downmove[2] = -sv_stepheight.value + start_velocity[2]*sv.frametime; + if(!SV_PushEntity (&downtrace, ent, downmove, true)) + { + // we got teleported when downstepping... must abort the move + return; + } + + if (downtrace.fraction < 1 && downtrace.plane.normal[2] > 0.7) + { + // this has been disabled so that you can't jump when you are stepping + // up while already jumping (also known as the Quake2 double jump bug) +#if 0 + // LordHavoc: disabled this check so you can walk on monsters/players + //if (PRVM_serveredictfloat(ent, solid) == SOLID_BSP) + { + //Con_Printf("onground\n"); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(downtrace.ent); + } +#endif + } + else + { + //Con_Printf("slope\n"); + // if the push down didn't end up on good ground, use the move without + // the step up. This happens near wall / slope combinations, and can + // cause the player to hop up higher on a slope too steep to climb + VectorCopy(originalmove_origin, PRVM_serveredictvector(ent, origin)); + VectorCopy(originalmove_velocity, PRVM_serveredictvector(ent, velocity)); + //clip = originalmove_clip; + PRVM_serveredictfloat(ent, flags) = originalmove_flags; + PRVM_serveredictedict(ent, groundentity) = originalmove_groundentity; + } + + SV_CheckVelocity(ent); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); +} + +//============================================================================ + +/* +============= +SV_Physics_Follow + +Entities that are "stuck" to another entity +============= +*/ +static void SV_Physics_Follow (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + vec3_t vf, vr, vu, angles, v; + prvm_edict_t *e; + + // regular thinking + if (!SV_RunThink (ent)) + return; + + // LordHavoc: implemented rotation on MOVETYPE_FOLLOW objects + e = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, aiment)); + if (PRVM_serveredictvector(e, angles)[0] == PRVM_serveredictvector(ent, punchangle)[0] && PRVM_serveredictvector(e, angles)[1] == PRVM_serveredictvector(ent, punchangle)[1] && PRVM_serveredictvector(e, angles)[2] == PRVM_serveredictvector(ent, punchangle)[2]) + { + // quick case for no rotation + VectorAdd(PRVM_serveredictvector(e, origin), PRVM_serveredictvector(ent, view_ofs), PRVM_serveredictvector(ent, origin)); + } + else + { + angles[0] = -PRVM_serveredictvector(ent, punchangle)[0]; + angles[1] = PRVM_serveredictvector(ent, punchangle)[1]; + angles[2] = PRVM_serveredictvector(ent, punchangle)[2]; + AngleVectors (angles, vf, vr, vu); + v[0] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[0] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[0] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[0]; + v[1] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[1] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[1] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[1]; + v[2] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[2] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[2] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[2]; + angles[0] = -PRVM_serveredictvector(e, angles)[0]; + angles[1] = PRVM_serveredictvector(e, angles)[1]; + angles[2] = PRVM_serveredictvector(e, angles)[2]; + AngleVectors (angles, vf, vr, vu); + PRVM_serveredictvector(ent, origin)[0] = v[0] * vf[0] + v[1] * vf[1] + v[2] * vf[2] + PRVM_serveredictvector(e, origin)[0]; + PRVM_serveredictvector(ent, origin)[1] = v[0] * vr[0] + v[1] * vr[1] + v[2] * vr[2] + PRVM_serveredictvector(e, origin)[1]; + PRVM_serveredictvector(ent, origin)[2] = v[0] * vu[0] + v[1] * vu[1] + v[2] * vu[2] + PRVM_serveredictvector(e, origin)[2]; + } + VectorAdd (PRVM_serveredictvector(e, angles), PRVM_serveredictvector(ent, v_angle), PRVM_serveredictvector(ent, angles)); + SV_LinkEdict(ent); + //SV_LinkEdict_TouchAreaGrid(ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_CheckWaterTransition + +============= +*/ +static void SV_CheckWaterTransition (prvm_edict_t *ent) +{ + vec3_t entorigin; + prvm_prog_t *prog = SVVM_prog; + // LordHavoc: bugfixes in this function are keyed to the sv_gameplayfix_bugfixedcheckwatertransition cvar - if this cvar is 0 then all the original bugs should be reenabled for compatibility + int cont; + VectorCopy(PRVM_serveredictvector(ent, origin), entorigin); + cont = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(entorigin)); + if (!PRVM_serveredictfloat(ent, watertype)) + { + // just spawned here + if (!sv_gameplayfix_fixedcheckwatertransition.integer) + { + PRVM_serveredictfloat(ent, watertype) = cont; + PRVM_serveredictfloat(ent, waterlevel) = 1; + return; + } + } + // DRESK - Support for Entity Contents Transition Event + // NOTE: Call here BEFORE updating the watertype below, + // and suppress watersplash sound if a valid function + // call was made to allow for custom "splash" sounds. + else if( !SV_CheckContentsTransition(ent, cont) ) + { // Contents Transition Function Invalid; Potentially Play Water Sound + // check if the entity crossed into or out of water + if (sv_sound_watersplash.string && ((PRVM_serveredictfloat(ent, watertype) == CONTENTS_WATER || PRVM_serveredictfloat(ent, watertype) == CONTENTS_SLIME) != (cont == CONTENTS_WATER || cont == CONTENTS_SLIME))) + SV_StartSound (ent, 0, sv_sound_watersplash.string, 255, 1, false, 1.0f); + } + + if (cont <= CONTENTS_WATER) + { + PRVM_serveredictfloat(ent, watertype) = cont; + PRVM_serveredictfloat(ent, waterlevel) = 1; + } + else + { + PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY; + PRVM_serveredictfloat(ent, waterlevel) = sv_gameplayfix_fixedcheckwatertransition.integer ? 0 : cont; + } +} + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ + +void SV_Physics_Toss (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + trace_t trace; + vec3_t move; + vec_t movetime; + int bump; + prvm_edict_t *groundentity; + float d, ent_gravity; + float bouncefactor; + float bouncestop; + +// if onground, return without moving + if ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) + { + groundentity = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, groundentity)); + if (PRVM_serveredictvector(ent, velocity)[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer) + { + // don't stick to ground if onground and moving upward + PRVM_serveredictfloat(ent, flags) -= FL_ONGROUND; + } + else if (!PRVM_serveredictedict(ent, groundentity) || !sv_gameplayfix_noairborncorpse.integer) + { + // we can trust FL_ONGROUND if groundentity is world because it never moves + return; + } + else if (ent->priv.server->suspendedinairflag && groundentity->priv.server->free) + { + // if ent was supported by a brush model on previous frame, + // and groundentity is now freed, set groundentity to 0 (world) + // which leaves it suspended in the air + PRVM_serveredictedict(ent, groundentity) = 0; + if (sv_gameplayfix_noairborncorpse_allowsuspendeditems.integer) + return; + } + else if (BoxesOverlap(ent->priv.server->cullmins, ent->priv.server->cullmaxs, groundentity->priv.server->cullmins, groundentity->priv.server->cullmaxs)) + { + // don't slide if still touching the groundentity + return; + } + } + ent->priv.server->suspendedinairflag = false; + + SV_CheckVelocity (ent); + +// add gravity + if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_TOSS || PRVM_serveredictfloat(ent, movetype) == MOVETYPE_BOUNCE) + PRVM_serveredictvector(ent, velocity)[2] -= SV_Gravity(ent); + +// move angles + VectorMA (PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); + + movetime = sv.frametime; + for (bump = 0;bump < MAX_CLIP_PLANES && movetime > 0;bump++) + { + // move origin + VectorScale(PRVM_serveredictvector(ent, velocity), movetime, move); + if(!SV_PushEntity(&trace, ent, move, true)) + return; // teleported + if (ent->priv.server->free) + return; + if (trace.bmodelstartsolid && sv_gameplayfix_unstickentities.integer) + { + // try to unstick the entity + SV_UnstickEntity(ent); + if(!SV_PushEntity(&trace, ent, move, true)) + return; // teleported + if (ent->priv.server->free) + return; + } + if (trace.fraction == 1) + break; + movetime *= 1 - min(1, trace.fraction); + switch((int)PRVM_serveredictfloat(ent, movetype)) + { + case MOVETYPE_BOUNCEMISSILE: + bouncefactor = PRVM_serveredictfloat(ent, bouncefactor); + if (!bouncefactor) + bouncefactor = 1.0f; + + ClipVelocity(PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1 + bouncefactor); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + if (!sv_gameplayfix_slidemoveprojectiles.integer) + movetime = 0; + break; + case MOVETYPE_BOUNCE: + bouncefactor = PRVM_serveredictfloat(ent, bouncefactor); + if (!bouncefactor) + bouncefactor = 0.5f; + + bouncestop = PRVM_serveredictfloat(ent, bouncestop); + if (!bouncestop) + bouncestop = 60.0f / 800.0f; + + ClipVelocity(PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1 + bouncefactor); + ent_gravity = PRVM_serveredictfloat(ent, gravity); + if (!ent_gravity) + ent_gravity = 1.0f; + // LordHavoc: fixed grenades not bouncing when fired down a slope + if (sv_gameplayfix_grenadebouncedownslopes.integer) + d = fabs(DotProduct(trace.plane.normal, PRVM_serveredictvector(ent, velocity))); + else + d = PRVM_serveredictvector(ent, velocity)[2]; + if (trace.plane.normal[2] > 0.7 && d < sv_gravity.value * bouncestop * ent_gravity) + { + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + VectorClear(PRVM_serveredictvector(ent, velocity)); + VectorClear(PRVM_serveredictvector(ent, avelocity)); + movetime = 0; + } + else + { + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + if (!sv_gameplayfix_slidemoveprojectiles.integer) + movetime = 0; + } + break; + default: + ClipVelocity (PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1.0); + if (trace.plane.normal[2] > 0.7) + { + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + if (PRVM_serveredictfloat(((prvm_edict_t *)trace.ent), solid) == SOLID_BSP) + ent->priv.server->suspendedinairflag = true; + VectorClear (PRVM_serveredictvector(ent, velocity)); + VectorClear (PRVM_serveredictvector(ent, avelocity)); + } + else + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; + movetime = 0; + break; + } + } + +// check for in water + SV_CheckWaterTransition (ent); +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +============= +*/ +static void SV_Physics_Step (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + int flags = (int)PRVM_serveredictfloat(ent, flags); + + // DRESK + // Backup Velocity in the event that movetypesteplandevent is called, + // to provide a parameter with the entity's velocity at impact. + vec3_t backupVelocity; + VectorCopy(PRVM_serveredictvector(ent, velocity), backupVelocity); + // don't fall at all if fly/swim + if (!(flags & (FL_FLY | FL_SWIM))) + { + if (flags & FL_ONGROUND) + { + // freefall if onground and moving upward + // freefall if not standing on a world surface (it may be a lift or trap door) + if (PRVM_serveredictvector(ent, velocity)[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer) + { + PRVM_serveredictfloat(ent, flags) -= FL_ONGROUND; + SV_CheckVelocity(ent); + SV_FlyMove(ent, sv.frametime, true, NULL, SV_GenericHitSuperContentsMask(ent), 0); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + ent->priv.server->waterposition_forceupdate = true; + } + } + else + { + // freefall if not onground + int hitsound = PRVM_serveredictvector(ent, velocity)[2] < sv_gravity.value * -0.1; + + SV_CheckVelocity(ent); + SV_FlyMove(ent, sv.frametime, true, NULL, SV_GenericHitSuperContentsMask(ent), 0); + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + // just hit ground + if (hitsound && (int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) + { + // DRESK - Check for Entity Land Event Function + if(PRVM_serveredictfunction(ent, movetypesteplandevent)) + { // Valid Function; Execute + // Prepare Parameters + // Assign Velocity at Impact + PRVM_G_VECTOR(OFS_PARM0)[0] = backupVelocity[0]; + PRVM_G_VECTOR(OFS_PARM0)[1] = backupVelocity[1]; + PRVM_G_VECTOR(OFS_PARM0)[2] = backupVelocity[2]; + // Assign Self + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + // Set Time + PRVM_serverglobalfloat(time) = sv.time; + // Execute VM Function + prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, movetypesteplandevent), "movetypesteplandevent: NULL function"); + } + else + // Check for Engine Landing Sound + if(sv_sound_land.string) + SV_StartSound(ent, 0, sv_sound_land.string, 255, 1, false, 1.0f); + } + ent->priv.server->waterposition_forceupdate = true; + } + } + +// regular thinking + if (!SV_RunThink(ent)) + return; + + if (ent->priv.server->waterposition_forceupdate || !VectorCompare(PRVM_serveredictvector(ent, origin), ent->priv.server->waterposition_origin)) + { + ent->priv.server->waterposition_forceupdate = false; + VectorCopy(PRVM_serveredictvector(ent, origin), ent->priv.server->waterposition_origin); + SV_CheckWaterTransition(ent); + } +} + +//============================================================================ + +static void SV_Physics_Entity (prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + // don't run think/move on newly spawned projectiles as it messes up + // movement interpolation and rocket trails, and is inconsistent with + // respect to entities spawned in the same frame + // (if an ent spawns a higher numbered ent, it moves in the same frame, + // but if it spawns a lower numbered ent, it doesn't - this never moves + // ents in the first frame regardless) + qboolean runmove = ent->priv.server->move; + ent->priv.server->move = true; + if (!runmove && sv_gameplayfix_delayprojectiles.integer > 0) + return; + switch ((int) PRVM_serveredictfloat(ent, movetype)) + { + case MOVETYPE_PUSH: + case MOVETYPE_FAKEPUSH: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + // LordHavoc: manually inlined the thinktime check here because MOVETYPE_NONE is used on so many objects + if (PRVM_serveredictfloat(ent, nextthink) > 0 && PRVM_serveredictfloat(ent, nextthink) <= sv.time + sv.frametime) + SV_RunThink (ent); + break; + case MOVETYPE_FOLLOW: + SV_Physics_Follow (ent); + break; + case MOVETYPE_NOCLIP: + if (SV_RunThink(ent)) + { + SV_CheckWater(ent); + VectorMA(PRVM_serveredictvector(ent, origin), sv.frametime, PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, origin)); + VectorMA(PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); + } + SV_LinkEdict(ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_WALK: + if (SV_RunThink (ent)) + SV_WalkMove (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_BOUNCEMISSILE: + case MOVETYPE_FLYMISSILE: + case MOVETYPE_FLY: + case MOVETYPE_FLY_WORLDONLY: + // regular thinking + if (SV_RunThink (ent)) + SV_Physics_Toss (ent); + break; + case MOVETYPE_PHYSICS: + if (SV_RunThink(ent)) + { + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + } + break; + default: + Con_Printf ("SV_Physics: bad movetype %i\n", (int)PRVM_serveredictfloat(ent, movetype)); + break; + } +} + +void SV_Physics_ClientMove(void) +{ + prvm_prog_t *prog = SVVM_prog; + prvm_edict_t *ent; + ent = host_client->edict; + + // call player physics, this needs the proper frametime + PRVM_serverglobalfloat(frametime) = sv.frametime; + SV_ClientThink(); + + // call standard client pre-think, with frametime = 0 + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobalfloat(frametime) = 0; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + prog->ExecuteProgram(prog, PRVM_serverfunction(PlayerPreThink), "QC function PlayerPreThink is missing"); + PRVM_serverglobalfloat(frametime) = sv.frametime; + + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + + // perform MOVETYPE_WALK behavior + SV_WalkMove (ent); + + // call standard player post-think, with frametime = 0 + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobalfloat(frametime) = 0; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + prog->ExecuteProgram(prog, PRVM_serverfunction(PlayerPostThink), "QC function PlayerPostThink is missing"); + PRVM_serverglobalfloat(frametime) = sv.frametime; + + if(PRVM_serveredictfloat(ent, fixangle)) + { + // angle fixing was requested by physics code... + // so store the current angles for later use + VectorCopy(PRVM_serveredictvector(ent, angles), host_client->fixangle_angles); + host_client->fixangle_angles_set = TRUE; + + // and clear fixangle for the next frame + PRVM_serveredictfloat(ent, fixangle) = 0; + } +} + +static void SV_Physics_ClientEntity_PreThink(prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + // don't do physics on disconnected clients, FrikBot relies on this + if (!host_client->begun) + return; + + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + + // don't run physics here if running asynchronously + if (host_client->clmovement_inputtimeout <= 0) + { + SV_ClientThink(); + //host_client->cmd.time = max(host_client->cmd.time, sv.time); + } + + // make sure the velocity is still sane (not a NaN) + SV_CheckVelocity(ent); + + // call standard client pre-think + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + prog->ExecuteProgram(prog, PRVM_serverfunction(PlayerPreThink), "QC function PlayerPreThink is missing"); + + // make sure the velocity is still sane (not a NaN) + SV_CheckVelocity(ent); +} + +static void SV_Physics_ClientEntity_PostThink(prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + // don't do physics on disconnected clients, FrikBot relies on this + if (!host_client->begun) + return; + + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + + // call standard player post-think + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); + prog->ExecuteProgram(prog, PRVM_serverfunction(PlayerPostThink), "QC function PlayerPostThink is missing"); + + // make sure the velocity is still sane (not a NaN) + SV_CheckVelocity(ent); + + if(PRVM_serveredictfloat(ent, fixangle)) + { + // angle fixing was requested by physics code... + // so store the current angles for later use + VectorCopy(PRVM_serveredictvector(ent, angles), host_client->fixangle_angles); + host_client->fixangle_angles_set = TRUE; + + // and clear fixangle for the next frame + PRVM_serveredictfloat(ent, fixangle) = 0; + } + + // decrement the countdown variable used to decide when to go back to + // synchronous physics + if (host_client->clmovement_inputtimeout > sv.frametime) + host_client->clmovement_inputtimeout -= sv.frametime; + else + host_client->clmovement_inputtimeout = 0; +} + +static void SV_Physics_ClientEntity(prvm_edict_t *ent) +{ + prvm_prog_t *prog = SVVM_prog; + // don't do physics on disconnected clients, FrikBot relies on this + if (!host_client->begun) + { + memset(&host_client->cmd, 0, sizeof(host_client->cmd)); + return; + } + + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + + switch ((int) PRVM_serveredictfloat(ent, movetype)) + { + case MOVETYPE_PUSH: + case MOVETYPE_FAKEPUSH: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + // LordHavoc: manually inlined the thinktime check here because MOVETYPE_NONE is used on so many objects + if (PRVM_serveredictfloat(ent, nextthink) > 0 && PRVM_serveredictfloat(ent, nextthink) <= sv.time + sv.frametime) + SV_RunThink (ent); + break; + case MOVETYPE_FOLLOW: + SV_Physics_Follow (ent); + break; + case MOVETYPE_NOCLIP: + SV_RunThink(ent); + SV_CheckWater(ent); + VectorMA(PRVM_serveredictvector(ent, origin), sv.frametime, PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, origin)); + VectorMA(PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_WALK: + SV_RunThink (ent); + // don't run physics here if running asynchronously + if (host_client->clmovement_inputtimeout <= 0) + SV_WalkMove (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_BOUNCEMISSILE: + case MOVETYPE_FLYMISSILE: + // regular thinking + SV_RunThink (ent); + SV_Physics_Toss (ent); + break; + case MOVETYPE_FLY: + case MOVETYPE_FLY_WORLDONLY: + SV_RunThink (ent); + SV_WalkMove (ent); + break; + case MOVETYPE_PHYSICS: + SV_RunThink (ent); + break; + default: + Con_Printf ("SV_Physics_ClientEntity: bad movetype %i\n", (int)PRVM_serveredictfloat(ent, movetype)); + break; + } + + SV_CheckVelocity (ent); + + SV_LinkEdict(ent); + SV_LinkEdict_TouchAreaGrid(ent); + + SV_CheckVelocity (ent); +} + +/* +================ +SV_Physics + +================ +*/ +void SV_Physics (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + prvm_edict_t *ent; + +// let the progs know that a new frame has started + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobalfloat(frametime) = sv.frametime; + prog->ExecuteProgram(prog, PRVM_serverfunction(StartFrame), "QC function StartFrame is missing"); + + // run physics engine + World_Physics_Frame(&sv.world, sv.frametime, sv_gravity.value); + +// +// treat each object in turn +// + + // if force_retouch, relink all the entities + if (PRVM_serverglobalfloat(force_retouch) > 0) + for (i = 1, ent = PRVM_EDICT_NUM(i);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->free) + SV_LinkEdict_TouchAreaGrid(ent); // force retouch even for stationary + + if (sv_gameplayfix_consistentplayerprethink.integer) + { + // run physics on the client entities in 3 stages + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + if (!ent->priv.server->free) + SV_Physics_ClientEntity_PreThink(ent); + + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + if (!ent->priv.server->free) + SV_Physics_ClientEntity(ent); + + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + if (!ent->priv.server->free) + SV_Physics_ClientEntity_PostThink(ent); + } + else + { + // run physics on the client entities + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + { + if (!ent->priv.server->free) + { + SV_Physics_ClientEntity_PreThink(ent); + SV_Physics_ClientEntity(ent); + SV_Physics_ClientEntity_PostThink(ent); + } + } + } + + // run physics on all the non-client entities + if (!sv_freezenonclients.integer) + { + for (;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->free) + SV_Physics_Entity(ent); + // make a second pass to see if any ents spawned this frame and make + // sure they run their move/think + if (sv_gameplayfix_delayprojectiles.integer < 0) + for (i = svs.maxclients + 1, ent = PRVM_EDICT_NUM(i);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->move && !ent->priv.server->free) + SV_Physics_Entity(ent); + } + + if (PRVM_serverglobalfloat(force_retouch) > 0) + PRVM_serverglobalfloat(force_retouch) = max(0, PRVM_serverglobalfloat(force_retouch) - 1); + + // LordHavoc: endframe support + if (PRVM_serverfunction(EndFrame)) + { + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); + PRVM_serverglobalfloat(time) = sv.time; + prog->ExecuteProgram(prog, PRVM_serverfunction(EndFrame), "QC function EndFrame is missing"); + } + + // decrement prog->num_edicts if the highest number entities died + for (;PRVM_ED_CanAlloc(prog, PRVM_EDICT_NUM(prog->num_edicts - 1));prog->num_edicts--); + + if (!sv_freezenonclients.integer) + sv.time += sv.frametime; +} diff --git a/app/jni/sv_user.c b/app/jni/sv_user.c new file mode 100644 index 0000000..a65705f --- /dev/null +++ b/app/jni/sv_user.c @@ -0,0 +1,965 @@ +/* +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. + +*/ +// sv_user.c -- server code for moving users + +#include "quakedef.h" +#include "sv_demo.h" +#define DEBUGMOVES 0 + +static usercmd_t cmd; +extern cvar_t sv_autodemo_perclient; + +/* +=============== +SV_SetIdealPitch +=============== +*/ +#define MAX_FORWARD 6 +void SV_SetIdealPitch (void) +{ + prvm_prog_t *prog = SVVM_prog; + float angleval, sinval, cosval, step, dir; + trace_t tr; + vec3_t top, bottom; + float z[MAX_FORWARD]; + int i, j; + int steps; + + if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_ONGROUND)) + return; + + angleval = PRVM_serveredictvector(host_client->edict, angles)[YAW] * M_PI*2 / 360; + sinval = sin(angleval); + cosval = cos(angleval); + + for (i=0 ; iedict, origin)[0] + cosval*(i+3)*12; + top[1] = PRVM_serveredictvector(host_client->edict, origin)[1] + sinval*(i+3)*12; + top[2] = PRVM_serveredictvector(host_client->edict, origin)[2] + PRVM_serveredictvector(host_client->edict, view_ofs)[2]; + + bottom[0] = top[0]; + bottom[1] = top[1]; + bottom[2] = top[2] - 160; + + tr = SV_TraceLine(top, bottom, MOVE_NOMONSTERS, host_client->edict, SUPERCONTENTS_SOLID); + // if looking at a wall, leave ideal the way is was + if (tr.startsolid) + return; + + // near a dropoff + if (tr.fraction == 1) + return; + + z[i] = top[2] + tr.fraction*(bottom[2]-top[2]); + } + + dir = 0; + steps = 0; + for (j=1 ; j -ON_EPSILON && step < ON_EPSILON) + continue; + + // mixed changes + if (dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ) ) + return; + + steps++; + dir = step; + } + + if (!dir) + { + PRVM_serveredictfloat(host_client->edict, idealpitch) = 0; + return; + } + + if (steps < 2) + return; + PRVM_serveredictfloat(host_client->edict, idealpitch) = -dir * sv_idealpitchscale.value; +} + +static vec3_t wishdir, forward, right, up; +static float wishspeed; + +static qboolean onground; + +/* +================== +SV_UserFriction + +================== +*/ +static void SV_UserFriction (void) +{ + prvm_prog_t *prog = SVVM_prog; + float speed, newspeed, control, friction; + vec3_t start, stop; + trace_t trace; + + speed = sqrt(PRVM_serveredictvector(host_client->edict, velocity)[0]*PRVM_serveredictvector(host_client->edict, velocity)[0]+PRVM_serveredictvector(host_client->edict, velocity)[1]*PRVM_serveredictvector(host_client->edict, velocity)[1]); + if (!speed) + return; + + // if the leading edge is over a dropoff, increase friction + start[0] = stop[0] = PRVM_serveredictvector(host_client->edict, origin)[0] + PRVM_serveredictvector(host_client->edict, velocity)[0]/speed*16; + start[1] = stop[1] = PRVM_serveredictvector(host_client->edict, origin)[1] + PRVM_serveredictvector(host_client->edict, velocity)[1]/speed*16; + start[2] = PRVM_serveredictvector(host_client->edict, origin)[2] + PRVM_serveredictvector(host_client->edict, mins)[2]; + stop[2] = start[2] - 34; + + trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, host_client->edict, SV_GenericHitSuperContentsMask(host_client->edict)); + + if (trace.fraction == 1.0) + friction = sv_friction.value*sv_edgefriction.value; + else + friction = sv_friction.value; + + // apply friction + control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed; + newspeed = speed - sv.frametime*control*friction; + + if (newspeed < 0) + newspeed = 0; + else + newspeed /= speed; + + VectorScale(PRVM_serveredictvector(host_client->edict, velocity), newspeed, PRVM_serveredictvector(host_client->edict, velocity)); +} + +/* +============== +SV_Accelerate +============== +*/ +static void SV_Accelerate (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (PRVM_serveredictvector(host_client->edict, velocity), wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + accelspeed = sv_accelerate.value*sv.frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed*wishdir[i]; +} + +extern cvar_t sv_gameplayfix_q2airaccelerate; +static void SV_AirAccelerate (vec3_t wishveloc) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + float addspeed, wishspd, accelspeed, currentspeed; + + wishspd = VectorNormalizeLength (wishveloc); + if (wishspd > sv_maxairspeed.value) + wishspd = sv_maxairspeed.value; + currentspeed = DotProduct (PRVM_serveredictvector(host_client->edict, velocity), wishveloc); + addspeed = wishspd - currentspeed; + if (addspeed <= 0) + return; + accelspeed = (sv_airaccelerate.value < 0 ? sv_accelerate.value : sv_airaccelerate.value)*(sv_gameplayfix_q2airaccelerate.integer ? wishspd : wishspeed) * sv.frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed*wishveloc[i]; +} + + +static void DropPunchAngle (void) +{ + prvm_prog_t *prog = SVVM_prog; + vec_t len; + vec3_t punchangle, punchvector; + + VectorCopy(PRVM_serveredictvector(host_client->edict, punchangle), punchangle); + VectorCopy(PRVM_serveredictvector(host_client->edict, punchvector), punchvector); + + len = VectorNormalizeLength(punchangle); + if (len > 0) + { + len -= 10*sv.frametime; + if (len < 0) + len = 0; + VectorScale(punchangle, len, punchangle); + } + + len = VectorNormalizeLength(punchvector); + if (len > 0) + { + len -= 20*sv.frametime; + if (len < 0) + len = 0; + VectorScale(punchvector, len, punchvector); + } + + VectorCopy(punchangle, PRVM_serveredictvector(host_client->edict, punchangle)); + VectorCopy(punchvector, PRVM_serveredictvector(host_client->edict, punchvector)); +} + +/* +=================== +SV_WaterMove + +=================== +*/ +static void SV_WaterMove (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + vec3_t wishvel, v_angle; + vec_t speed, newspeed, wishspeed, addspeed, accelspeed, temp; + + // user intentions + VectorCopy(PRVM_serveredictvector(host_client->edict, v_angle), v_angle); + AngleVectors(v_angle, forward, right, up); + + for (i=0 ; i<3 ; i++) + wishvel[i] = forward[i]*cmd.forwardmove + right[i]*cmd.sidemove; + + if (!cmd.forwardmove && !cmd.sidemove && !cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else + wishvel[2] += cmd.upmove; + + wishspeed = VectorLength(wishvel); + if (wishspeed > sv_maxspeed.value) + { + temp = sv_maxspeed.value/wishspeed; + VectorScale (wishvel, temp, wishvel); + wishspeed = sv_maxspeed.value; + } + wishspeed *= 0.7; + + // water friction + speed = VectorLength(PRVM_serveredictvector(host_client->edict, velocity)); + if (speed) + { + newspeed = speed - sv.frametime * speed * (sv_waterfriction.value < 0 ? sv_friction.value : sv_waterfriction.value); + if (newspeed < 0) + newspeed = 0; + temp = newspeed/speed; + VectorScale(PRVM_serveredictvector(host_client->edict, velocity), temp, PRVM_serveredictvector(host_client->edict, velocity)); + } + else + newspeed = 0; + + // water acceleration + if (!wishspeed) + return; + + addspeed = wishspeed - newspeed; + if (addspeed <= 0) + return; + + VectorNormalize (wishvel); + accelspeed = (sv_wateraccelerate.value < 0 ? sv_accelerate.value : sv_wateraccelerate.value) * wishspeed * sv.frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed * wishvel[i]; +} + +static void SV_WaterJump (void) +{ + prvm_prog_t *prog = SVVM_prog; + if (sv.time > PRVM_serveredictfloat(host_client->edict, teleport_time) || !PRVM_serveredictfloat(host_client->edict, waterlevel)) + { + PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) & ~FL_WATERJUMP; + PRVM_serveredictfloat(host_client->edict, teleport_time) = 0; + } + PRVM_serveredictvector(host_client->edict, velocity)[0] = PRVM_serveredictvector(host_client->edict, movedir)[0]; + PRVM_serveredictvector(host_client->edict, velocity)[1] = PRVM_serveredictvector(host_client->edict, movedir)[1]; +} + + +/* +=================== +SV_AirMove + +=================== +*/ +static void SV_AirMove (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + vec3_t wishvel; + float fmove, smove, temp; + + // LordHavoc: correct quake movement speed bug when looking up/down + wishvel[0] = wishvel[2] = 0; + wishvel[1] = PRVM_serveredictvector(host_client->edict, angles)[1]; + AngleVectors (wishvel, forward, right, up); + + fmove = cmd.forwardmove; + smove = cmd.sidemove; + +// hack to not let you back into teleporter + if (sv.time < PRVM_serveredictfloat(host_client->edict, teleport_time) && fmove < 0) + fmove = 0; + + for (i=0 ; i<3 ; i++) + wishvel[i] = forward[i]*fmove + right[i]*smove; + + if ((int)PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_WALK) + wishvel[2] += cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalizeLength(wishdir); + if (wishspeed > sv_maxspeed.value) + { + temp = sv_maxspeed.value/wishspeed; + VectorScale (wishvel, temp, wishvel); + wishspeed = sv_maxspeed.value; + } + + if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NOCLIP) + { + // noclip + VectorCopy (wishvel, PRVM_serveredictvector(host_client->edict, velocity)); + } + else if (onground) + { + SV_UserFriction (); + SV_Accelerate (); + } + else + { + // not on ground, so little effect on velocity + SV_AirAccelerate (wishvel); + } +} + +/* +=================== +SV_ClientThink + +the move fields specify an intended velocity in pix/sec +the angle fields specify an exact angular motion in degrees +=================== +*/ +void SV_ClientThink (void) +{ + prvm_prog_t *prog = SVVM_prog; + vec3_t v_angle, angles, velocity; + + //Con_Printf("clientthink for %ims\n", (int) (sv.frametime * 1000)); + + SV_ApplyClientMove(); + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(host_client->edict); + + // LordHavoc: QuakeC replacement for SV_ClientThink (player movement) + if (PRVM_serverfunction(SV_PlayerPhysics) && sv_playerphysicsqc.integer) + { + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(SV_PlayerPhysics), "QC function SV_PlayerPhysics is missing"); + SV_CheckVelocity(host_client->edict); + return; + } + + if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NONE) + return; + + onground = ((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_ONGROUND) != 0; + + DropPunchAngle (); + + // if dead, behave differently + if (PRVM_serveredictfloat(host_client->edict, health) <= 0) + return; + + cmd = host_client->cmd; + + // angles + // show 1/3 the pitch angle and all the roll angle + VectorAdd (PRVM_serveredictvector(host_client->edict, v_angle), PRVM_serveredictvector(host_client->edict, punchangle), v_angle); + VectorCopy(PRVM_serveredictvector(host_client->edict, angles), angles); + VectorCopy(PRVM_serveredictvector(host_client->edict, velocity), velocity); + PRVM_serveredictvector(host_client->edict, angles)[ROLL] = V_CalcRoll (angles, velocity)*4; + if (!PRVM_serveredictfloat(host_client->edict, fixangle)) + { + PRVM_serveredictvector(host_client->edict, angles)[PITCH] = -v_angle[PITCH]/3; + PRVM_serveredictvector(host_client->edict, angles)[YAW] = v_angle[YAW]; + } + + if ( (int)PRVM_serveredictfloat(host_client->edict, flags) & FL_WATERJUMP ) + { + SV_WaterJump (); + SV_CheckVelocity(host_client->edict); + return; + } + + /* + // Player is (somehow) outside of the map, or flying, or noclipping + if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP && (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_FLY || SV_TestEntityPosition (host_client->edict))) + //if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NOCLIP || PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_FLY || SV_TestEntityPosition (host_client->edict)) + { + SV_FreeMove (); + return; + } + */ + + // walk + if ((PRVM_serveredictfloat(host_client->edict, waterlevel) >= 2) && (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)) + { + SV_WaterMove (); + SV_CheckVelocity(host_client->edict); + return; + } + + SV_AirMove (); + SV_CheckVelocity(host_client->edict); +} + +/* +=================== +SV_ReadClientMove +=================== +*/ +int sv_numreadmoves = 0; +usercmd_t sv_readmoves[CL_MAX_USERCMDS]; +static void SV_ReadClientMove (void) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + usercmd_t newmove; + usercmd_t *move = &newmove; + + memset(move, 0, sizeof(*move)); + + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // read ping time + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5 && sv.protocol != PROTOCOL_DARKPLACES6) + move->sequence = MSG_ReadLong(&sv_message); + move->time = move->clienttime = MSG_ReadFloat(&sv_message); + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + move->receivetime = (float)sv.time; + +#if DEBUGMOVES + Con_Printf("%s move%i #%i %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", move->time > move->receivetime ? "^3read future" : "^4read normal", sv_numreadmoves + 1, move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove); +#endif + // limit reported time to current time + // (incase the client is trying to cheat) + move->time = min(move->time, move->receivetime + sv.frametime); + + // read current angles + for (i = 0;i < 3;i++) + { + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + move->viewangles[i] = MSG_ReadAngle8i(&sv_message); + else if (sv.protocol == PROTOCOL_DARKPLACES1) + move->viewangles[i] = MSG_ReadAngle16i(&sv_message); + else if (sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) + move->viewangles[i] = MSG_ReadAngle32f(&sv_message); + else + move->viewangles[i] = MSG_ReadAngle16i(&sv_message); + } + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // read movement + move->forwardmove = MSG_ReadCoord16i(&sv_message); + move->sidemove = MSG_ReadCoord16i(&sv_message); + move->upmove = MSG_ReadCoord16i(&sv_message); + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // read buttons + // be sure to bitwise OR them into the move->buttons because we want to + // accumulate button presses from multiple packets per actual move + if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) + move->buttons = MSG_ReadByte(&sv_message); + else + move->buttons = MSG_ReadLong(&sv_message); + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // read impulse + move->impulse = MSG_ReadByte(&sv_message); + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + + // PRYDON_CLIENTCURSOR + if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5) + { + // 30 bytes + move->cursor_screen[0] = MSG_ReadShort(&sv_message) * (1.0f / 32767.0f); + move->cursor_screen[1] = MSG_ReadShort(&sv_message) * (1.0f / 32767.0f); + move->cursor_start[0] = MSG_ReadFloat(&sv_message); + move->cursor_start[1] = MSG_ReadFloat(&sv_message); + move->cursor_start[2] = MSG_ReadFloat(&sv_message); + move->cursor_impact[0] = MSG_ReadFloat(&sv_message); + move->cursor_impact[1] = MSG_ReadFloat(&sv_message); + move->cursor_impact[2] = MSG_ReadFloat(&sv_message); + move->cursor_entitynumber = (unsigned short)MSG_ReadShort(&sv_message); + if (move->cursor_entitynumber >= prog->max_edicts) + { + Con_DPrintf("SV_ReadClientMessage: client send bad cursor_entitynumber\n"); + move->cursor_entitynumber = 0; + } + // as requested by FrikaC, cursor_trace_ent is reset to world if the + // entity is free at time of receipt + if (PRVM_EDICT_NUM(move->cursor_entitynumber)->priv.server->free) + move->cursor_entitynumber = 0; + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + } + + // if the previous move has not been applied yet, we need to accumulate + // the impulse/buttons from it + if (!host_client->cmd.applied) + { + if (!move->impulse) + move->impulse = host_client->cmd.impulse; + move->buttons |= host_client->cmd.buttons; + } + + // now store this move for later execution + // (we have to buffer the moves because of old ones being repeated) + if (sv_numreadmoves < CL_MAX_USERCMDS) + sv_readmoves[sv_numreadmoves++] = *move; + + // movement packet loss tracking + if(move->sequence) + { + if(move->sequence > host_client->movement_highestsequence_seen) + { + if(host_client->movement_highestsequence_seen) + { + // mark moves in between as lost + if(move->sequence - host_client->movement_highestsequence_seen - 1 < NETGRAPH_PACKETS) + for(i = host_client->movement_highestsequence_seen + 1; i < move->sequence; ++i) + host_client->movement_count[i % NETGRAPH_PACKETS] = -1; + else + memset(host_client->movement_count, -1, sizeof(host_client->movement_count)); + } + // mark THIS move as seen for the first time + host_client->movement_count[move->sequence % NETGRAPH_PACKETS] = 1; + // update highest sequence seen + host_client->movement_highestsequence_seen = move->sequence; + } + else + if(host_client->movement_count[move->sequence % NETGRAPH_PACKETS] >= 0) + ++host_client->movement_count[move->sequence % NETGRAPH_PACKETS]; + } + else + { + host_client->movement_highestsequence_seen = 0; + memset(host_client->movement_count, 0, sizeof(host_client->movement_count)); + } +} + +static void SV_ExecuteClientMoves(void) +{ + prvm_prog_t *prog = SVVM_prog; + int moveindex; + float moveframetime; + double oldframetime; + double oldframetime2; +#ifdef NUM_PING_TIMES + double total; +#endif + if (sv_numreadmoves < 1) + return; + // only start accepting input once the player is spawned + if (!host_client->begun) + return; +#if DEBUGMOVES + Con_Printf("SV_ExecuteClientMoves: read %i moves at sv.time %f\n", sv_numreadmoves, (float)sv.time); +#endif + // disable clientside movement prediction in some cases + if (ceil(max(sv_readmoves[sv_numreadmoves-1].receivetime - sv_readmoves[sv_numreadmoves-1].time, 0) * 1000.0) < sv_clmovement_minping.integer) + host_client->clmovement_disabletimeout = realtime + sv_clmovement_minping_disabletime.value / 1000.0; + // several conditions govern whether clientside movement prediction is allowed + if (sv_readmoves[sv_numreadmoves-1].sequence && sv_clmovement_enable.integer && sv_clmovement_inputtimeout.value > 0 && host_client->clmovement_disabletimeout <= realtime && PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_WALK && (!PRVM_serveredictfloat(host_client->edict, disableclientprediction))) + { + // process the moves in order and ignore old ones + // but always trust the latest move + // (this deals with bogus initial move sequences after level change, + // where the client will eventually catch up with the level change + // and reset its move sequence) + for (moveindex = 0;moveindex < sv_numreadmoves;moveindex++) + { + usercmd_t *move = sv_readmoves + moveindex; + if (host_client->movesequence < move->sequence || moveindex == sv_numreadmoves - 1) + { +#if DEBUGMOVES + Con_Printf("%smove #%i %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", (move->time - host_client->cmd.time) > sv.frametime * 1.01 ? "^1" : "^2", move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove); +#endif + // this is a new move + move->time = bound(sv.time - 1, move->time, sv.time); // prevent slowhack/speedhack combos + move->time = max(move->time, host_client->cmd.time); // prevent backstepping of time + moveframetime = bound(0, move->time - host_client->cmd.time, min(0.1, sv_clmovement_inputtimeout.value)); + + // discard (treat like lost) moves with too low distance from + // the previous one to prevent hacks using float inaccuracy + // clients will see this as packet loss in the netgraph + // this should also apply if a move cannot get + // executed because it came too late and + // already was performed serverside + if(moveframetime < 0.0005) + { + // count the move as LOST if we don't + // execute it but it has higher + // sequence count + if(host_client->movesequence) + if(move->sequence > host_client->movesequence) + host_client->movement_count[(move->sequence) % NETGRAPH_PACKETS] = -1; + continue; + } + + //Con_Printf("movesequence = %i (%i lost), moveframetime = %f\n", move->sequence, move->sequence ? move->sequence - host_client->movesequence - 1 : 0, moveframetime); + host_client->cmd = *move; + host_client->movesequence = move->sequence; + + // if using prediction, we need to perform moves when packets are + // received, even if multiple occur in one frame + // (they can't go beyond the current time so there is no cheat issue + // with this approach, and if they don't send input for a while they + // start moving anyway, so the longest 'lagaport' possible is + // determined by the sv_clmovement_inputtimeout cvar) + if (moveframetime <= 0) + continue; + oldframetime = PRVM_serverglobalfloat(frametime); + oldframetime2 = sv.frametime; + // update ping time for qc to see while executing this move + host_client->ping = host_client->cmd.receivetime - host_client->cmd.time; + // the server and qc frametime values must be changed temporarily + PRVM_serverglobalfloat(frametime) = sv.frametime = moveframetime; + // if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction) + if (sv.frametime > 0.05) + { + PRVM_serverglobalfloat(frametime) = sv.frametime = moveframetime * 0.5f; + SV_Physics_ClientMove(); + } + SV_Physics_ClientMove(); + sv.frametime = oldframetime2; + PRVM_serverglobalfloat(frametime) = oldframetime; + host_client->clmovement_inputtimeout = sv_clmovement_inputtimeout.value; + } + } + } + else + { + // try to gather button bits from old moves, but only if their time is + // advancing (ones with the same timestamp can't be trusted) + for (moveindex = 0;moveindex < sv_numreadmoves-1;moveindex++) + { + usercmd_t *move = sv_readmoves + moveindex; + if (host_client->cmd.time < move->time) + { + sv_readmoves[sv_numreadmoves-1].buttons |= move->buttons; + if (move->impulse) + sv_readmoves[sv_numreadmoves-1].impulse = move->impulse; + } + } + // now copy the new move + host_client->cmd = sv_readmoves[sv_numreadmoves-1]; + host_client->cmd.time = max(host_client->cmd.time, sv.time); + // physics will run up to sv.time, so allow no predicted moves + // before that otherwise, there is a speedhack by turning + // prediction on and off repeatedly on client side because the + // engine would run BOTH client and server physics for the same + // time + host_client->movesequence = 0; + // make sure that normal physics takes over immediately + host_client->clmovement_inputtimeout = 0; + } + + // calculate average ping time + host_client->ping = host_client->cmd.receivetime - host_client->cmd.clienttime; +#ifdef NUM_PING_TIMES + host_client->ping_times[host_client->num_pings % NUM_PING_TIMES] = host_client->cmd.receivetime - host_client->cmd.clienttime; + host_client->num_pings++; + for (i=0, total = 0;i < NUM_PING_TIMES;i++) + total += host_client->ping_times[i]; + host_client->ping = total / NUM_PING_TIMES; +#endif +} + +void SV_ApplyClientMove (void) +{ + prvm_prog_t *prog = SVVM_prog; + usercmd_t *move = &host_client->cmd; + int j, movementloss, packetloss; + + if (!move->receivetime) + return; + + // note: a move can be applied multiple times if the client packets are + // not coming as often as the physics is executed, and the move must be + // applied before running qc each time because the id1 qc had a bug where + // it clears self.button2 in PlayerJump, causing pogostick behavior if + // moves are not applied every time before calling qc + move->applied = true; + + // set the edict fields + PRVM_serveredictfloat(host_client->edict, button0) = move->buttons & 1; + PRVM_serveredictfloat(host_client->edict, button2) = (move->buttons & 2)>>1; + if (move->impulse) + PRVM_serveredictfloat(host_client->edict, impulse) = move->impulse; + // only send the impulse to qc once + move->impulse = 0; + + movementloss = packetloss = 0; + if(host_client->netconnection) + { + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (host_client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) + packetloss++; + for (j = 0;j < NETGRAPH_PACKETS;j++) + if (host_client->movement_count[j] < 0) + movementloss++; + } + + VectorCopy(move->viewangles, PRVM_serveredictvector(host_client->edict, v_angle)); + PRVM_serveredictfloat(host_client->edict, button3) = ((move->buttons >> 2) & 1); + PRVM_serveredictfloat(host_client->edict, button4) = ((move->buttons >> 3) & 1); + PRVM_serveredictfloat(host_client->edict, button5) = ((move->buttons >> 4) & 1); + PRVM_serveredictfloat(host_client->edict, button6) = ((move->buttons >> 5) & 1); + PRVM_serveredictfloat(host_client->edict, button7) = ((move->buttons >> 6) & 1); + PRVM_serveredictfloat(host_client->edict, button8) = ((move->buttons >> 7) & 1); + PRVM_serveredictfloat(host_client->edict, button9) = ((move->buttons >> 11) & 1); + PRVM_serveredictfloat(host_client->edict, button10) = ((move->buttons >> 12) & 1); + PRVM_serveredictfloat(host_client->edict, button11) = ((move->buttons >> 13) & 1); + PRVM_serveredictfloat(host_client->edict, button12) = ((move->buttons >> 14) & 1); + PRVM_serveredictfloat(host_client->edict, button13) = ((move->buttons >> 15) & 1); + PRVM_serveredictfloat(host_client->edict, button14) = ((move->buttons >> 16) & 1); + PRVM_serveredictfloat(host_client->edict, button15) = ((move->buttons >> 17) & 1); + PRVM_serveredictfloat(host_client->edict, button16) = ((move->buttons >> 18) & 1); + PRVM_serveredictfloat(host_client->edict, buttonuse) = ((move->buttons >> 8) & 1); + PRVM_serveredictfloat(host_client->edict, buttonchat) = ((move->buttons >> 9) & 1); + PRVM_serveredictfloat(host_client->edict, cursor_active) = ((move->buttons >> 10) & 1); + VectorSet(PRVM_serveredictvector(host_client->edict, movement), move->forwardmove, move->sidemove, move->upmove); + VectorCopy(move->cursor_screen, PRVM_serveredictvector(host_client->edict, cursor_screen)); + VectorCopy(move->cursor_start, PRVM_serveredictvector(host_client->edict, cursor_trace_start)); + VectorCopy(move->cursor_impact, PRVM_serveredictvector(host_client->edict, cursor_trace_endpos)); + PRVM_serveredictedict(host_client->edict, cursor_trace_ent) = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM(move->cursor_entitynumber)); + PRVM_serveredictfloat(host_client->edict, ping) = host_client->ping * 1000.0; + PRVM_serveredictfloat(host_client->edict, ping_packetloss) = packetloss / (float) NETGRAPH_PACKETS; + PRVM_serveredictfloat(host_client->edict, ping_movementloss) = movementloss / (float) NETGRAPH_PACKETS; +} + +static void SV_FrameLost(int framenum) +{ + if (host_client->entitydatabase5) + { + EntityFrame5_LostFrame(host_client->entitydatabase5, framenum); + EntityFrameCSQC_LostFrame(host_client, framenum); + } +} + +static void SV_FrameAck(int framenum) +{ + if (host_client->entitydatabase) + EntityFrame_AckFrame(host_client->entitydatabase, framenum); + else if (host_client->entitydatabase4) + EntityFrame4_AckFrame(host_client->entitydatabase4, framenum, true); + else if (host_client->entitydatabase5) + EntityFrame5_AckFrame(host_client->entitydatabase5, framenum); +} + +/* +=================== +SV_ReadClientMessage +=================== +*/ +void SV_ReadClientMessage(void) +{ + prvm_prog_t *prog = SVVM_prog; + int cmd, num, start; + char *s, *p, *q; + + if(sv_autodemo_perclient.integer >= 2) + SV_WriteDemoMessage(host_client, &(host_client->netconnection->message), true); + + //MSG_BeginReading (); + sv_numreadmoves = 0; + + for(;;) + { + if (!host_client->active) + { + // a command caused an error + SV_DropClient (false); + return; + } + + if (sv_message.badread) + { + Con_Print("SV_ReadClientMessage: badread\n"); + SV_DropClient (false); + return; + } + + cmd = MSG_ReadByte(&sv_message); + if (cmd == -1) + { + // end of message + // apply the moves that were read this frame + SV_ExecuteClientMoves(); + break; + } + + switch (cmd) + { + default: + Con_Printf("SV_ReadClientMessage: unknown command char %i (at offset 0x%x)\n", cmd, sv_message.readcount); + if (developer_networking.integer) + Com_HexDumpToConsole(sv_message.data, sv_message.cursize); + SV_DropClient (false); + return; + + case clc_nop: + break; + + case clc_stringcmd: + // allow reliable messages now as the client is done with initial loading + if (host_client->sendsignon == 2) + host_client->sendsignon = 0; + s = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)); + q = NULL; + for(p = s; *p; ++p) switch(*p) + { + case 10: + case 13: + if(!q) + q = p; + break; + default: + if(q) + goto clc_stringcmd_invalid; // newline seen, THEN something else -> possible exploit + break; + } + if(q) + *q = 0; + if (strncasecmp(s, "spawn", 5) == 0 + || strncasecmp(s, "begin", 5) == 0 + || strncasecmp(s, "prespawn", 8) == 0) + Cmd_ExecuteString (s, src_client, true); + else if (PRVM_serverfunction(SV_ParseClientCommand)) + { + int restorevm_tempstringsbuf_cursize; + restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); + prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ParseClientCommand), "QC function SV_ParseClientCommand is missing"); + prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; + } + else + Cmd_ExecuteString (s, src_client, true); + break; + +clc_stringcmd_invalid: + Con_Printf("Received invalid stringcmd from %s\n", host_client->name); + if(developer.integer > 0) + Com_HexDumpToConsole((unsigned char *) s, strlen(s)); + break; + + case clc_disconnect: + SV_DropClient (false); // client wants to disconnect + return; + + case clc_move: + SV_ReadClientMove(); + break; + + case clc_ackdownloaddata: + start = MSG_ReadLong(&sv_message); + num = MSG_ReadShort(&sv_message); + if (host_client->download_file && host_client->download_started) + { + if (host_client->download_expectedposition == start) + { + int size = (int)FS_FileSize(host_client->download_file); + // a data block was successfully received by the client, + // update the expected position on the next data block + host_client->download_expectedposition = start + num; + // if this was the last data block of the file, it's done + if (host_client->download_expectedposition >= FS_FileSize(host_client->download_file)) + { + // tell the client that the download finished + // we need to calculate the crc now + // + // note: at this point the OS probably has the file + // entirely in memory, so this is a faster operation + // now than it was when the download started. + // + // it is also preferable to do this at the end of the + // download rather than the start because it reduces + // potential for Denial Of Service attacks against the + // server. + int crc; + unsigned char *temp; + FS_Seek(host_client->download_file, 0, SEEK_SET); + temp = (unsigned char *) Mem_Alloc(tempmempool, size); + FS_Read(host_client->download_file, temp, size); + crc = CRC_Block(temp, size); + Mem_Free(temp); + // calculated crc, send the file info to the client + // (so that it can verify the data) + Host_ClientCommands("\ncl_downloadfinished %i %i %s\n", size, crc, host_client->download_name); + Con_DPrintf("Download of %s by %s has finished\n", host_client->download_name, host_client->name); + FS_Close(host_client->download_file); + host_client->download_file = NULL; + host_client->download_name[0] = 0; + host_client->download_expectedposition = 0; + host_client->download_started = false; + } + } + else + { + // a data block was lost, reset to the expected position + // and resume sending from there + FS_Seek(host_client->download_file, host_client->download_expectedposition, SEEK_SET); + } + } + break; + + case clc_ackframe: + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + num = MSG_ReadLong(&sv_message); + if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); + if (developer_networkentities.integer >= 10) + Con_Printf("recv clc_ackframe %i\n", num); + // if the client hasn't progressed through signons yet, + // ignore any clc_ackframes we get (they're probably from the + // previous level) + if (host_client->begun && host_client->latestframenum < num) + { + int i; + for (i = host_client->latestframenum + 1;i < num;i++) + SV_FrameLost(i); + SV_FrameAck(num); + host_client->latestframenum = num; + } + break; + } + } +} + diff --git a/app/jni/svbsp.c b/app/jni/svbsp.c new file mode 100644 index 0000000..437d82a --- /dev/null +++ b/app/jni/svbsp.c @@ -0,0 +1,453 @@ + +// Shadow Volume BSP code written by Forest "LordHavoc" Hale on 2003-11-06 and placed into public domain. +// Modified by LordHavoc (to make it work and other nice things like that) on 2007-01-24 and 2007-01-25 +// Optimized by LordHavoc on 2009-12-24 and 2009-12-25 + +#include +#include +#include "svbsp.h" +#include "polygon.h" + +#define MAX_SVBSP_POLYGONPOINTS 64 +#define SVBSP_CLIP_EPSILON (1.0f / 1024.0f) + +#define SVBSP_DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) + +typedef struct svbsp_polygon_s +{ + float points[MAX_SVBSP_POLYGONPOINTS][3]; + //unsigned char splitflags[MAX_SVBSP_POLYGONPOINTS]; + int facesplitflag; + int numpoints; +} +svbsp_polygon_t; + +static void SVBSP_PlaneFromPoints(float *plane4f, const float *p1, const float *p2, const float *p3) +{ + float ilength; + // calculate unnormalized plane + plane4f[0] = (p1[1] - p2[1]) * (p3[2] - p2[2]) - (p1[2] - p2[2]) * (p3[1] - p2[1]); + plane4f[1] = (p1[2] - p2[2]) * (p3[0] - p2[0]) - (p1[0] - p2[0]) * (p3[2] - p2[2]); + plane4f[2] = (p1[0] - p2[0]) * (p3[1] - p2[1]) - (p1[1] - p2[1]) * (p3[0] - p2[0]); + plane4f[3] = SVBSP_DotProduct(plane4f, p1); + // normalize the plane normal and adjust distance accordingly + ilength = (float)sqrt(SVBSP_DotProduct(plane4f, plane4f)); + if (ilength) + ilength = 1.0f / ilength; + plane4f[0] *= ilength; + plane4f[1] *= ilength; + plane4f[2] *= ilength; + plane4f[3] *= ilength; +} + +static void SVBSP_DividePolygon(const svbsp_polygon_t *poly, const float *plane, svbsp_polygon_t *front, svbsp_polygon_t *back, const float *dists, const int *sides) +{ + int i, j, count = poly->numpoints, frontcount = 0, backcount = 0; + float frac, ifrac, c[3], pdist, ndist; + const float *nextpoint; + const float *points = poly->points[0]; + float *outfront = front->points[0]; + float *outback = back->points[0]; + for(i = 0;i < count;i++, points += 3) + { + j = i + 1; + if (j >= count) + j = 0; + if (!(sides[i] & 2)) + { + outfront[frontcount*3+0] = points[0]; + outfront[frontcount*3+1] = points[1]; + outfront[frontcount*3+2] = points[2]; + frontcount++; + } + if (!(sides[i] & 1)) + { + outback[backcount*3+0] = points[0]; + outback[backcount*3+1] = points[1]; + outback[backcount*3+2] = points[2]; + backcount++; + } + if ((sides[i] | sides[j]) == 3) + { + // don't allow splits if remaining points would overflow point buffer + if (frontcount + (count - i) > MAX_SVBSP_POLYGONPOINTS - 1) + continue; + if (backcount + (count - i) > MAX_SVBSP_POLYGONPOINTS - 1) + continue; + nextpoint = poly->points[j]; + pdist = dists[i]; + ndist = dists[j]; + frac = pdist / (pdist - ndist); + ifrac = 1.0f - frac; + c[0] = points[0] * ifrac + frac * nextpoint[0]; + c[1] = points[1] * ifrac + frac * nextpoint[1]; + c[2] = points[2] * ifrac + frac * nextpoint[2]; + outfront[frontcount*3+0] = c[0]; + outfront[frontcount*3+1] = c[1]; + outfront[frontcount*3+2] = c[2]; + frontcount++; + outback[backcount*3+0] = c[0]; + outback[backcount*3+1] = c[1]; + outback[backcount*3+2] = c[2]; + backcount++; + } + } + front->numpoints = frontcount; + back->numpoints = backcount; +} + +void SVBSP_Init(svbsp_t *b, const float *origin, int maxnodes, svbsp_node_t *nodes) +{ + memset(b, 0, sizeof(*b)); + b->origin[0] = origin[0]; + b->origin[1] = origin[1]; + b->origin[2] = origin[2]; + b->numnodes = 3; + b->maxnodes = maxnodes; + b->nodes = nodes; + b->ranoutofnodes = 0; + b->stat_occluders_rejected = 0; + b->stat_occluders_accepted = 0; + b->stat_occluders_fragments_accepted = 0; + b->stat_occluders_fragments_rejected = 0; + b->stat_queries_rejected = 0; + b->stat_queries_accepted = 0; + b->stat_queries_fragments_accepted = 0; + b->stat_queries_fragments_rejected = 0; + + // the bsp tree must be initialized to have two perpendicular splits axes + // through origin, otherwise the polygon insertions would affect the + // opposite side of the tree, which would be disasterous. + // + // so this code has to make 3 nodes and 4 leafs, and since the leafs are + // represented by empty/solid state numbers in this system rather than + // actual structs, we only need to make the 3 nodes. + + // root node + // this one splits the world into +X and -X sides + b->nodes[0].plane[0] = 1; + b->nodes[0].plane[1] = 0; + b->nodes[0].plane[2] = 0; + b->nodes[0].plane[3] = origin[0]; + b->nodes[0].parent = -1; + b->nodes[0].children[0] = 1; + b->nodes[0].children[1] = 2; + + // +X side node + // this one splits the +X half of the world into +Y and -Y + b->nodes[1].plane[0] = 0; + b->nodes[1].plane[1] = 1; + b->nodes[1].plane[2] = 0; + b->nodes[1].plane[3] = origin[1]; + b->nodes[1].parent = 0; + b->nodes[1].children[0] = -1; + b->nodes[1].children[1] = -1; + + // -X side node + // this one splits the -X half of the world into +Y and -Y + b->nodes[2].plane[0] = 0; + b->nodes[2].plane[1] = 1; + b->nodes[2].plane[2] = 0; + b->nodes[2].plane[3] = origin[1]; + b->nodes[2].parent = 0; + b->nodes[2].children[0] = -1; + b->nodes[2].children[1] = -1; +} + +static void SVBSP_InsertOccluderPolygonNodes(svbsp_t *b, int *parentnodenumpointer, int parentnodenum, const svbsp_polygon_t *poly, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) +{ + // now we need to create up to numpoints + 1 new nodes, forming a BSP tree + // describing the occluder polygon's shadow volume + int i, j, p; + svbsp_node_t *node; + + // points and lines are valid testers but not occluders + if (poly->numpoints < 3) + return; + + // if there aren't enough nodes remaining, skip it + if (b->numnodes + poly->numpoints + 1 >= b->maxnodes) + { + b->ranoutofnodes = 1; + return; + } + + // add one node per side, then the actual occluding face node + + // thread safety notes: + // DO NOT multithread insertion, it could be made 'safe' but the results + // would be inconsistent. + // + // it is completely safe to multithread queries in all cases. + // + // if an insertion is occurring the query will give intermediate results, + // being blocked by some volumes but not others, which is perfectly okay + // for visibility culling intended only to reduce rendering work + + // note down the first available nodenum for the *parentnodenumpointer + // line which is done last to allow multithreaded queries during an + // insertion + for (i = 0, p = poly->numpoints - 1;i < poly->numpoints;p = i, i++) + { +#if 1 + // see if a parent plane describes this side + for (j = parentnodenum;j >= 0;j = b->nodes[j].parent) + { + float *parentnodeplane = b->nodes[j].plane; + if (fabs(SVBSP_DotProduct(poly->points[p], parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON + && fabs(SVBSP_DotProduct(poly->points[i], parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON + && fabs(SVBSP_DotProduct(b->origin , parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON) + break; + } + if (j >= 0) + continue; // already have a matching parent plane +#endif +#if 0 + // skip any sides that were classified as belonging to a parent plane + if (poly->splitflags[i]) + continue; +#endif + // create a side plane + // anything infront of this is not inside the shadow volume + node = b->nodes + b->numnodes++; + SVBSP_PlaneFromPoints(node->plane, b->origin, poly->points[p], poly->points[i]); + // we need to flip the plane if it puts any part of the polygon on the + // wrong side + // (in this way this code treats all polygons as float sided) + // + // because speed is important this stops as soon as it finds proof + // that the orientation is right or wrong + // (we know that the plane is on one edge of the polygon, so there is + // never a case where points lie on both sides, so the first hint is + // sufficient) + for (j = 0;j < poly->numpoints;j++) + { + float d = SVBSP_DotProduct(poly->points[j], node->plane) - node->plane[3]; + if (d < -SVBSP_CLIP_EPSILON) + break; + if (d > SVBSP_CLIP_EPSILON) + { + node->plane[0] *= -1; + node->plane[1] *= -1; + node->plane[2] *= -1; + node->plane[3] *= -1; + break; + } + } + node->parent = parentnodenum; + node->children[0] = -1; // empty + node->children[1] = -1; // empty + // link this child into the tree + *parentnodenumpointer = parentnodenum = (int)(node - b->nodes); + // now point to the child pointer for the next node to update later + parentnodenumpointer = &node->children[1]; + } + +#if 1 + // skip the face plane if it lies on a parent plane + if (!poly->facesplitflag) +#endif + { + // add the face-plane node + // infront is empty, behind is shadow + node = b->nodes + b->numnodes++; + SVBSP_PlaneFromPoints(node->plane, poly->points[0], poly->points[1], poly->points[2]); + // this is a flip check similar to the one above + // this one checks if the plane faces the origin, if not, flip it + if (SVBSP_DotProduct(b->origin, node->plane) - node->plane[3] < -SVBSP_CLIP_EPSILON) + { + node->plane[0] *= -1; + node->plane[1] *= -1; + node->plane[2] *= -1; + node->plane[3] *= -1; + } + node->parent = parentnodenum; + node->children[0] = -1; // empty + node->children[1] = -2; // shadow + // link this child into the tree + // (with the addition of this node, queries will now be culled by it) + *parentnodenumpointer = (int)(node - b->nodes); + } +} + +static int SVBSP_AddPolygonNode(svbsp_t *b, int *parentnodenumpointer, int parentnodenum, const svbsp_polygon_t *poly, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) +{ + int i; + int s; + int facesplitflag = poly->facesplitflag; + int bothsides; + float plane[4]; + float d; + svbsp_polygon_t front; + svbsp_polygon_t back; + svbsp_node_t *node; + int sides[MAX_SVBSP_POLYGONPOINTS]; + float dists[MAX_SVBSP_POLYGONPOINTS]; + if (poly->numpoints < 1) + return 0; + // recurse through plane nodes + while (*parentnodenumpointer >= 0) + { + // get node info + parentnodenum = *parentnodenumpointer; + node = b->nodes + parentnodenum; + plane[0] = node->plane[0]; + plane[1] = node->plane[1]; + plane[2] = node->plane[2]; + plane[3] = node->plane[3]; + // calculate point dists for clipping + bothsides = 0; + for (i = 0;i < poly->numpoints;i++) + { + d = SVBSP_DotProduct(poly->points[i], plane) - plane[3]; + s = 0; + if (d > SVBSP_CLIP_EPSILON) + s = 1; + if (d < -SVBSP_CLIP_EPSILON) + s = 2; + bothsides |= s; + dists[i] = d; + sides[i] = s; + } + // see which side the polygon is on + switch(bothsides) + { + default: + case 0: + // no need to split, this polygon is on the plane + // this case only occurs for polygons on the face plane, usually + // the same polygon (inserted twice - once as occluder, once as + // tester) + // if this is an occluder, it is redundant + if (insertoccluder) + return 1; // occluded + // if this is a tester, test the front side, because it is + // probably the same polygon that created this node... + facesplitflag = 1; + parentnodenumpointer = &node->children[0]; + continue; + case 1: + // no need to split, just go to one side + parentnodenumpointer = &node->children[0]; + continue; + case 2: + // no need to split, just go to one side + parentnodenumpointer = &node->children[1]; + continue; + case 3: + // lies on both sides of the plane, we need to split it +#if 1 + SVBSP_DividePolygon(poly, plane, &front, &back, dists, sides); +#else + PolygonF_Divide(poly->numpoints, poly->points[0], plane[0], plane[1], plane[2], plane[3], SVBSP_CLIP_EPSILON, MAX_SVBSP_POLYGONPOINTS, front.points[0], &front.numpoints, MAX_SVBSP_POLYGONPOINTS, back.points[0], &back.numpoints, NULL); + if (front.numpoints > MAX_SVBSP_POLYGONPOINTS) + front.numpoints = MAX_SVBSP_POLYGONPOINTS; + if (back.numpoints > MAX_SVBSP_POLYGONPOINTS) + back.numpoints = MAX_SVBSP_POLYGONPOINTS; +#endif + front.facesplitflag = facesplitflag; + back.facesplitflag = facesplitflag; + // recurse the sides and return the resulting occlusion flags + i = SVBSP_AddPolygonNode(b, &node->children[0], *parentnodenumpointer, &front, insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); + i |= SVBSP_AddPolygonNode(b, &node->children[1], *parentnodenumpointer, &back , insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); + return i; + } + } + // leaf node + if (*parentnodenumpointer == -1) + { + // empty leaf node; and some geometry survived + // if inserting an occluder, replace this empty leaf with a shadow volume +#if 0 + for (i = 0;i < poly->numpoints-2;i++) + { + Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); + Debug_PolygonVertex(poly->points[ 0][0], poly->points[ 0][1], poly->points[ 0][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); + Debug_PolygonVertex(poly->points[i+1][0], poly->points[i+1][1], poly->points[i+1][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); + Debug_PolygonVertex(poly->points[i+2][0], poly->points[i+2][1], poly->points[i+2][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); + Debug_PolygonEnd(); + } +#endif + if (insertoccluder) + { + b->stat_occluders_fragments_accepted++; + SVBSP_InsertOccluderPolygonNodes(b, parentnodenumpointer, parentnodenum, poly, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); + } + else + b->stat_queries_fragments_accepted++; + if (fragmentcallback) + fragmentcallback(fragmentcallback_pointer1, fragmentcallback_number1, b, poly->numpoints, poly->points[0]); + return 2; + } + else + { + // otherwise it's a solid leaf which destroys all polygons inside it + if (insertoccluder) + b->stat_occluders_fragments_rejected++; + else + b->stat_queries_fragments_rejected++; +#if 0 + for (i = 0;i < poly->numpoints-2;i++) + { + Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); + Debug_PolygonVertex(poly->points[ 0][0], poly->points[ 0][1], poly->points[ 0][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); + Debug_PolygonVertex(poly->points[i+1][0], poly->points[i+1][1], poly->points[i+1][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); + Debug_PolygonVertex(poly->points[i+2][0], poly->points[i+2][1], poly->points[i+2][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); + Debug_PolygonEnd(); + } +#endif + } + return 1; +} + +int SVBSP_AddPolygon(svbsp_t *b, int numpoints, const float *points, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) +{ + int i; + int nodenum; + svbsp_polygon_t poly; + // don't even consider an empty polygon + // note we still allow points and lines to be tested... + if (numpoints < 1) + return 0; + // if the polygon has too many points, we would crash + if (numpoints > MAX_SVBSP_POLYGONPOINTS) + return 0; + poly.numpoints = numpoints; + for (i = 0;i < numpoints;i++) + { + poly.points[i][0] = points[i*3+0]; + poly.points[i][1] = points[i*3+1]; + poly.points[i][2] = points[i*3+2]; + //poly.splitflags[i] = 0; // this edge is a valid BSP splitter - clipped edges are not (because they lie on a bsp plane) + poly.facesplitflag = 0; // this face is a valid BSP Splitter - if it lies on a bsp plane it is not + } +#if 0 +//if (insertoccluder) + for (i = 0;i < poly.numpoints-2;i++) + { + Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); + Debug_PolygonVertex(poly.points[ 0][0], poly.points[ 0][1], poly.points[ 0][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); + Debug_PolygonVertex(poly.points[i+1][0], poly.points[i+1][1], poly.points[i+1][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); + Debug_PolygonVertex(poly.points[i+2][0], poly.points[i+2][1], poly.points[i+2][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); + Debug_PolygonEnd(); + } +#endif + nodenum = 0; + i = SVBSP_AddPolygonNode(b, &nodenum, -1, &poly, insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); + if (insertoccluder) + { + if (i & 2) + b->stat_occluders_accepted++; + else + b->stat_occluders_rejected++; + } + else + { + if (i & 2) + b->stat_queries_accepted++; + else + b->stat_queries_rejected++; + } + return i; +} + diff --git a/app/jni/svbsp.h b/app/jni/svbsp.h new file mode 100644 index 0000000..ca87f0d --- /dev/null +++ b/app/jni/svbsp.h @@ -0,0 +1,75 @@ + +// Shadow Volume BSP code written by Forest "LordHavoc" Hale on 2003-11-06 and placed into public domain. +// Modified by LordHavoc (to make it work and other nice things like that) on 2007-01-24 and 2007-01-25 + +#ifndef SVBSP_H +#define SVBSP_H + +typedef struct svbsp_node_s +{ + // notes: + // leaf nodes are not stored, these are always structural nodes + // (they always have a plane and two children) + // children[] can be -1 for empty leaf or -2 for shadow leaf, >= 0 is node + // parent can be -1 if this is the root node, >= 0 is a node + int parent, children[2], padding; + // node plane, splits space + float plane[4]; +} +svbsp_node_t; + +typedef struct svbsp_s +{ + // lightsource or view origin + float origin[3]; + // current number of nodes in the svbsp + int numnodes; + // how big the nodes array is + int maxnodes; + // first node is the root node + svbsp_node_t *nodes; + // non-zero indicates that an insertion failed because of lack of nodes + int ranoutofnodes; + // tree statistics + // note: do not use multithreading when gathering statistics! + // (the code updating these counters is not thread-safe, increments may + // sometimes fail when multithreaded) + int stat_occluders_rejected; + int stat_occluders_accepted; + int stat_occluders_fragments_rejected; + int stat_occluders_fragments_accepted; + int stat_queries_rejected; + int stat_queries_accepted; + int stat_queries_fragments_rejected; + int stat_queries_fragments_accepted; +} +svbsp_t; + +// this function initializes a tree to prepare for polygon insertions +// +// the maxnodes needed for a given polygon set can vary wildly, if there are +// not enough maxnodes then later polygons will not be inserted and the field +// svbsp_t->ranoutofnodes will be non-zero +// +// as a rule of thumb the minimum nodes needed for a polygon set is +// numpolygons * (averagepolygonvertices + 1) +void SVBSP_Init(svbsp_t *b, const float *origin, int maxnodes, svbsp_node_t *nodes); + +// this function tests if any part of a polygon is not in shadow, and returns +// non-zero if the polygon is not completely shadowed +// +// returns 0 if the polygon was rejected (not facing origin or no points) +// returns 1 if all of the polygon is in shadow +// returns 2 if all of the polygon is unshadowed +// returns 3 if some of the polygon is shadowed and some unshadowed +// +// it also can add a new shadow volume (insertoccluder parameter) +// +// additionally it calls your fragmentcallback on each unshadowed clipped +// part of the polygon +// (beware that polygons often get split heavily, even if entirely unshadowed) +// +// thread-safety notes: do not multi-thread insertions! +int SVBSP_AddPolygon(svbsp_t *b, int numpoints, const float *points, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1); + +#endif diff --git a/app/jni/svvm_cmds.c b/app/jni/svvm_cmds.c new file mode 100644 index 0000000..e9d51fd --- /dev/null +++ b/app/jni/svvm_cmds.c @@ -0,0 +1,3847 @@ +#include "quakedef.h" + +#include "prvm_cmds.h" +#include "jpeg.h" + +//============================================================================ +// Server + + + +const char *vm_sv_extensions = +"BX_WAL_SUPPORT " +"DP_BUTTONCHAT " +"DP_BUTTONUSE " +"DP_CL_LOADSKY " +"DP_CON_ALIASPARAMETERS " +"DP_CON_BESTWEAPON " +"DP_CON_EXPANDCVAR " +"DP_CON_SET " +"DP_CON_SETA " +"DP_CON_STARTMAP " +"DP_CRYPTO " +"DP_CSQC_BINDMAPS " +"DP_CSQC_ENTITYWORLDOBJECT " +"DP_CSQC_ENTITYMODELLIGHT " +"DP_CSQC_ENTITYTRANSPARENTSORTING_OFFSET " +"DP_CSQC_MAINVIEW " +"DP_CSQC_MINFPS_QUALITY " +"DP_CSQC_MULTIFRAME_INTERPOLATION " +"DP_CSQC_BOXPARTICLES " +"DP_CSQC_SPAWNPARTICLE " +"DP_CSQC_QUERYRENDERENTITY " +"DP_CSQC_ROTATEMOVES " +"DP_CSQC_SETPAUSE " +"DP_CSQC_V_CALCREFDEF_WIP1 " +"DP_CSQC_V_CALCREFDEF_WIP2 " +"DP_EF_ADDITIVE " +"DP_EF_BLUE " +"DP_EF_DOUBLESIDED " +"DP_EF_DYNAMICMODELLIGHT " +"DP_EF_FLAME " +"DP_EF_FULLBRIGHT " +"DP_EF_NODEPTHTEST " +"DP_EF_NODRAW " +"DP_EF_NOGUNBOB " +"DP_EF_NOSELFSHADOW " +"DP_EF_NOSHADOW " +"DP_EF_RED " +"DP_EF_RESTARTANIM_BIT " +"DP_EF_STARDUST " +"DP_EF_TELEPORT_BIT " +"DP_ENT_ALPHA " +"DP_ENT_COLORMOD " +"DP_ENT_CUSTOMCOLORMAP " +"DP_ENT_EXTERIORMODELTOCLIENT " +"DP_ENT_GLOW " +"DP_ENT_GLOWMOD " +"DP_ENT_LOWPRECISION " +"DP_ENT_SCALE " +"DP_ENT_TRAILEFFECTNUM " +"DP_ENT_VIEWMODEL " +"DP_GFX_EXTERNALTEXTURES " +"DP_GFX_EXTERNALTEXTURES_PERMAP " +"DP_GFX_FOG " +"DP_GFX_MODEL_INTERPOLATION " +"DP_GFX_QUAKE3MODELTAGS " +"DP_GFX_SKINFILES " +"DP_GFX_SKYBOX " +"DP_GFX_FONTS " +"DP_GFX_FONTS_FREETYPE " +"DP_UTF8 " +"DP_FONT_VARIABLEWIDTH " +"DP_HALFLIFE_MAP " +"DP_HALFLIFE_MAP_CVAR " +"DP_HALFLIFE_SPRITE " +"DP_INPUTBUTTONS " +"DP_LIGHTSTYLE_STATICVALUE " +"DP_LITSPRITES " +"DP_LITSUPPORT " +"DP_MONSTERWALK " +"DP_MOVETYPEBOUNCEMISSILE " +"DP_MOVETYPEFLYWORLDONLY " +"DP_MOVETYPEFOLLOW " +"DP_NULL_MODEL " +"DP_QC_ASINACOSATANATAN2TAN " +"DP_QC_AUTOCVARS " +"DP_QC_CHANGEPITCH " +"DP_QC_CMD " +"DP_QC_COPYENTITY " +"DP_QC_CRC16 " +"DP_QC_CVAR_DEFSTRING " +"DP_QC_CVAR_DESCRIPTION " +"DP_QC_CVAR_STRING " +"DP_QC_CVAR_TYPE " +"DP_QC_DIGEST " +"DP_QC_DIGEST_SHA256 " +"DP_QC_EDICT_NUM " +"DP_QC_ENTITYDATA " +"DP_QC_ENTITYSTRING " +"DP_QC_ETOS " +"DP_QC_EXTRESPONSEPACKET " +"DP_QC_FINDCHAIN " +"DP_QC_FINDCHAINFLAGS " +"DP_QC_FINDCHAINFLOAT " +"DP_QC_FINDCHAIN_TOFIELD " +"DP_QC_FINDFLAGS " +"DP_QC_FINDFLOAT " +"DP_QC_FS_SEARCH " +"DP_QC_GETLIGHT " +"DP_QC_GETSURFACE " +"DP_QC_GETSURFACETRIANGLE " +"DP_QC_GETSURFACEPOINTATTRIBUTE " +"DP_QC_GETTAGINFO " +"DP_QC_GETTAGINFO_BONEPROPERTIES " +"DP_QC_GETTIME " +"DP_QC_GETTIME_CDTRACK " +"DP_QC_I18N " +"DP_QC_LOG " +"DP_QC_MINMAXBOUND " +"DP_QC_MULTIPLETEMPSTRINGS " +"DP_QC_NUM_FOR_EDICT " +"DP_QC_RANDOMVEC " +"DP_QC_SINCOSSQRTPOW " +"DP_QC_SPRINTF " +"DP_QC_STRFTIME " +"DP_QC_STRINGBUFFERS " +"DP_QC_STRINGBUFFERS_CVARLIST " +"DP_QC_STRINGBUFFERS_EXT_WIP " +"DP_QC_STRINGCOLORFUNCTIONS " +"DP_QC_STRING_CASE_FUNCTIONS " +"DP_QC_STRREPLACE " +"DP_QC_TOKENIZEBYSEPARATOR " +"DP_QC_TOKENIZE_CONSOLE " +"DP_QC_TRACEBOX " +"DP_QC_TRACETOSS " +"DP_QC_TRACE_MOVETYPE_HITMODEL " +"DP_QC_TRACE_MOVETYPE_WORLDONLY " +"DP_QC_UNLIMITEDTEMPSTRINGS " +"DP_QC_URI_ESCAPE " +"DP_QC_URI_GET " +"DP_QC_URI_POST " +"DP_QC_VECTOANGLES_WITH_ROLL " +"DP_QC_VECTORVECTORS " +"DP_QC_WHICHPACK " +"DP_QUAKE2_MODEL " +"DP_QUAKE2_SPRITE " +"DP_QUAKE3_MAP " +"DP_QUAKE3_MODEL " +"DP_REGISTERCVAR " +"DP_SKELETONOBJECTS " +"DP_SND_DIRECTIONLESSATTNNONE " +"DP_SND_FAKETRACKS " +"DP_SND_SOUND7_WIP1 " +"DP_SND_SOUND7_WIP2 " +"DP_SND_OGGVORBIS " +"DP_SND_SETPARAMS " +"DP_SND_STEREOWAV " +"DP_SND_GETSOUNDTIME " +"DP_VIDEO_DPV " +"DP_VIDEO_SUBTITLES " +"DP_SOLIDCORPSE " +"DP_SPRITE32 " +"DP_SV_BOTCLIENT " +"DP_SV_BOUNCEFACTOR " +"DP_SV_CLIENTCAMERA " +"DP_SV_CLIENTCOLORS " +"DP_SV_CLIENTNAME " +"DP_SV_CMD " +"DP_SV_CUSTOMIZEENTITYFORCLIENT " +"DP_SV_DISCARDABLEDEMO " +"DP_SV_DRAWONLYTOCLIENT " +"DP_SV_DROPCLIENT " +"DP_SV_EFFECT " +"DP_SV_ENTITYCONTENTSTRANSITION " +"DP_SV_MODELFLAGS_AS_EFFECTS " +"DP_SV_MOVETYPESTEP_LANDEVENT " +"DP_SV_NETADDRESS " +"DP_SV_NODRAWTOCLIENT " +"DP_SV_ONENTITYNOSPAWNFUNCTION " +"DP_SV_ONENTITYPREPOSTSPAWNFUNCTION " +"DP_SV_PING " +"DP_SV_PING_PACKETLOSS " +"DP_SV_PLAYERPHYSICS " +"DP_PHYSICS_ODE " +"DP_SV_POINTPARTICLES " +"DP_SV_POINTSOUND " +"DP_SV_PRECACHEANYTIME " +"DP_SV_PRINT " +"DP_SV_PUNCHVECTOR " +"DP_SV_QCSTATUS " +"DP_SV_ROTATINGBMODEL " +"DP_SV_SETCOLOR " +"DP_SV_SHUTDOWN " +"DP_SV_SLOWMO " +"DP_SV_SPAWNFUNC_PREFIX " +"DP_SV_WRITEPICTURE " +"DP_SV_WRITEUNTERMINATEDSTRING " +"DP_TE_BLOOD " +"DP_TE_BLOODSHOWER " +"DP_TE_CUSTOMFLASH " +"DP_TE_EXPLOSIONRGB " +"DP_TE_FLAMEJET " +"DP_TE_PARTICLECUBE " +"DP_TE_PARTICLERAIN " +"DP_TE_PARTICLESNOW " +"DP_TE_PLASMABURN " +"DP_TE_QUADEFFECTS1 " +"DP_TE_SMALLFLASH " +"DP_TE_SPARK " +"DP_TE_STANDARDEFFECTBUILTINS " +"DP_TRACE_HITCONTENTSMASK_SURFACEINFO " +"DP_VIEWZOOM " +"EXT_BITSHIFT " +"FRIK_FILE " +"FTE_CSQC_SKELETONOBJECTS " +"FTE_QC_CHECKPVS " +"FTE_STRINGS " +"KRIMZON_SV_PARSECLIENTCOMMAND " +"NEH_CMD_PLAY2 " +"NEH_RESTOREGAME " +"NEXUIZ_PLAYERMODEL " +"NXQ_GFX_LETTERBOX " +"PRYDON_CLIENTCURSOR " +"TENEBRAE_GFX_DLIGHTS " +"TW_SV_STEPCONTROL " +"ZQ_PAUSE " +//"EXT_CSQC " // not ready yet +; + +/* +================= +VM_SV_setorigin + +This is the only valid way to move an object without using the physics of the world (setting velocity and waiting). Directly changing origin will not set internal links correctly, so clipping would be messed up. This should be called when an object is spawned, and then only if it is teleported. + +setorigin (entity, origin) +================= +*/ +static void VM_SV_setorigin(prvm_prog_t *prog) +{ + prvm_edict_t *e; + + VM_SAFEPARMCOUNT(2, VM_setorigin); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning(prog, "setorigin: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning(prog, "setorigin: can not modify free entity\n"); + return; + } + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), PRVM_serveredictvector(e, origin)); + if(e->priv.required->mark == PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN) + e->priv.required->mark = PRVM_EDICT_MARK_SETORIGIN_CAUGHT; + SV_LinkEdict(e); +} + +// TODO: rotate param isnt used.. could be a bug. please check this and remove it if possible [1/10/2008 Black] +static void SetMinMaxSize (prvm_prog_t *prog, prvm_edict_t *e, float *min, float *max, qboolean rotate) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (min[i] > max[i]) + prog->error_cmd("SetMinMaxSize: backwards mins/maxs"); + +// set derived values + VectorCopy (min, PRVM_serveredictvector(e, mins)); + VectorCopy (max, PRVM_serveredictvector(e, maxs)); + VectorSubtract (max, min, PRVM_serveredictvector(e, size)); + + SV_LinkEdict(e); +} + +/* +================= +VM_SV_setsize + +the size box is rotated by the current angle +LordHavoc: no it isn't... + +setsize (entity, minvector, maxvector) +================= +*/ +static void VM_SV_setsize(prvm_prog_t *prog) +{ + prvm_edict_t *e; + vec3_t mins, maxs; + + VM_SAFEPARMCOUNT(3, VM_setsize); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning(prog, "setsize: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning(prog, "setsize: can not modify free entity\n"); + return; + } + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), mins); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), maxs); + SetMinMaxSize(prog, e, mins, maxs, false); +} + + +/* +================= +VM_SV_setmodel + +setmodel(entity, model) +================= +*/ +static vec3_t quakemins = {-16, -16, -16}, quakemaxs = {16, 16, 16}; +static void VM_SV_setmodel(prvm_prog_t *prog) +{ + prvm_edict_t *e; + dp_model_t *mod; + int i; + + VM_SAFEPARMCOUNT(2, VM_setmodel); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning(prog, "setmodel: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning(prog, "setmodel: can not modify free entity\n"); + return; + } + i = SV_ModelIndex(PRVM_G_STRING(OFS_PARM1), 1); + PRVM_serveredictstring(e, model) = PRVM_SetEngineString(prog, sv.model_precache[i]); + PRVM_serveredictfloat(e, modelindex) = i; + + mod = SV_GetModelByIndex(i); + + if (mod) + { + if (mod->type != mod_alias || sv_gameplayfix_setmodelrealbox.integer) + SetMinMaxSize(prog, e, mod->normalmins, mod->normalmaxs, true); + else + SetMinMaxSize(prog, e, quakemins, quakemaxs, true); + } + else + SetMinMaxSize(prog, e, vec3_origin, vec3_origin, true); +} + +/* +================= +VM_SV_sprint + +single print to a specific client + +sprint(clientent, value) +================= +*/ +static void VM_SV_sprint(prvm_prog_t *prog) +{ + client_t *client; + int entnum; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_sprint); + + VM_VarString(prog, 1, string, sizeof(string)); + + entnum = PRVM_G_EDICTNUM(OFS_PARM0); + // LordHavoc: div0 requested that sprintto world operate like print + if (entnum == 0) + { + Con_Print(string); + return; + } + + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) + { + VM_Warning(prog, "tried to centerprint to a non-client\n"); + return; + } + + client = svs.clients + entnum-1; + if (!client->netconnection) + return; + + MSG_WriteChar(&client->netconnection->message,svc_print); + MSG_WriteString(&client->netconnection->message, string); +} + + +/* +================= +VM_SV_centerprint + +single print to a specific client + +centerprint(clientent, value) +================= +*/ +static void VM_SV_centerprint(prvm_prog_t *prog) +{ + client_t *client; + int entnum; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_centerprint); + + entnum = PRVM_G_EDICTNUM(OFS_PARM0); + + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) + { + VM_Warning(prog, "tried to centerprint to a non-client\n"); + return; + } + + client = svs.clients + entnum-1; + if (!client->netconnection) + return; + + VM_VarString(prog, 1, string, sizeof(string)); + MSG_WriteChar(&client->netconnection->message,svc_centerprint); + MSG_WriteString(&client->netconnection->message, string); +} + +/* +================= +VM_SV_particle + +particle(origin, color, count) +================= +*/ +static void VM_SV_particle(prvm_prog_t *prog) +{ + vec3_t org, dir; + int color; + int count; + + VM_SAFEPARMCOUNT(4, VM_SV_particle); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), dir); + color = (int)PRVM_G_FLOAT(OFS_PARM2); + count = (int)PRVM_G_FLOAT(OFS_PARM3); + SV_StartParticle (org, dir, color, count); +} + + +/* +================= +VM_SV_ambientsound + +================= +*/ +static void VM_SV_ambientsound(prvm_prog_t *prog) +{ + const char *samp; + vec3_t pos; + prvm_vec_t vol, attenuation; + int soundnum, large; + + VM_SAFEPARMCOUNT(4, VM_SV_ambientsound); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); + samp = PRVM_G_STRING(OFS_PARM1); + vol = PRVM_G_FLOAT(OFS_PARM2); + attenuation = PRVM_G_FLOAT(OFS_PARM3); + +// check to see if samp was properly precached + soundnum = SV_SoundIndex(samp, 1); + if (!soundnum) + return; + + large = false; + if (soundnum >= 256) + large = true; + + // add an svc_spawnambient command to the level signon packet + + if (large) + MSG_WriteByte (&sv.signon, svc_spawnstaticsound2); + else + MSG_WriteByte (&sv.signon, svc_spawnstaticsound); + + MSG_WriteVector(&sv.signon, pos, sv.protocol); + + if (large || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + MSG_WriteShort (&sv.signon, soundnum); + else + MSG_WriteByte (&sv.signon, soundnum); + + MSG_WriteByte (&sv.signon, (int)(vol*255)); + MSG_WriteByte (&sv.signon, (int)(attenuation*64)); + +} + +/* +================= +VM_SV_sound + +Each entity can have eight independant sound sources, like voice, +weapon, feet, etc. + +Channel 0 is an auto-allocate channel, the others override anything +already running on that entity/channel pair. + +An attenuation of 0 will play full volume everywhere in the level. +Larger attenuations will drop off. + +================= +*/ +static void VM_SV_sound(prvm_prog_t *prog) +{ + const char *sample; + int channel; + prvm_edict_t *entity; + int volume; + int flags; + float attenuation; + float pitchchange; + + VM_SAFEPARMCOUNTRANGE(4, 7, VM_SV_sound); + + entity = PRVM_G_EDICT(OFS_PARM0); + channel = (int)PRVM_G_FLOAT(OFS_PARM1); + sample = PRVM_G_STRING(OFS_PARM2); + volume = (int)(PRVM_G_FLOAT(OFS_PARM3) * 255); + if (prog->argc < 5) + { + Con_DPrintf("VM_SV_sound: given only 4 parameters, expected 5, assuming attenuation = ATTN_NORMAL\n"); + attenuation = 1; + } + else + attenuation = PRVM_G_FLOAT(OFS_PARM4); + if (prog->argc < 6) + pitchchange = 0; + else + pitchchange = PRVM_G_FLOAT(OFS_PARM5) * 0.01f; + + if (prog->argc < 7) + { + flags = 0; + if(channel >= 8 && channel <= 15) // weird QW feature + { + flags |= CHANNELFLAG_RELIABLE; + channel -= 8; + } + } + else + { + // LordHavoc: we only let the qc set certain flags, others are off-limits + flags = (int)PRVM_G_FLOAT(OFS_PARM6) & (CHANNELFLAG_RELIABLE | CHANNELFLAG_FORCELOOP | CHANNELFLAG_PAUSED); + } + + if (volume < 0 || volume > 255) + { + VM_Warning(prog, "SV_StartSound: volume must be in range 0-1\n"); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + VM_Warning(prog, "SV_StartSound: attenuation must be in range 0-4\n"); + return; + } + + channel = CHAN_USER2ENGINE(channel); + + if (!IS_CHAN(channel)) + { + VM_Warning(prog, "SV_StartSound: channel must be in range 0-127\n"); + return; + } + + SV_StartSound (entity, channel, sample, volume, attenuation, flags & CHANNELFLAG_RELIABLE, pitchchange); +} + +/* +================= +VM_SV_pointsound + +Follows the same logic as VM_SV_sound, except instead of +an entity, an origin for the sound is provided, and channel +is omitted (since no entity is being tracked). + +================= +*/ +static void VM_SV_pointsound(prvm_prog_t *prog) +{ + const char *sample; + int volume; + float attenuation; + float pitchchange; + vec3_t org; + + VM_SAFEPARMCOUNTRANGE(4, 5, VM_SV_pointsound); + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + sample = PRVM_G_STRING(OFS_PARM1); + volume = (int)(PRVM_G_FLOAT(OFS_PARM2) * 255); + attenuation = PRVM_G_FLOAT(OFS_PARM3); + pitchchange = prog->argc < 5 ? 0 : PRVM_G_FLOAT(OFS_PARM4) * 0.01f; + + if (volume < 0 || volume > 255) + { + VM_Warning(prog, "SV_StartPointSound: volume must be in range 0-1\n"); + return; + } + + if (attenuation < 0 || attenuation > 4) + { + VM_Warning(prog, "SV_StartPointSound: attenuation must be in range 0-4\n"); + return; + } + + SV_StartPointSound (org, sample, volume, attenuation, pitchchange); +} + +/* +================= +VM_SV_traceline + +Used for use tracing and shot targeting +Traces are blocked by bbox and exact bsp entityes, and also slide box entities +if the tryents flag is set. + +traceline (vector1, vector2, movetype, ignore) +================= +*/ +static void VM_SV_traceline(prvm_prog_t *prog) +{ + vec3_t v1, v2; + trace_t trace; + int move; + prvm_edict_t *ent; + + VM_SAFEPARMCOUNTRANGE(4, 8, VM_SV_traceline); // allow more parameters for future expansion + + prog->xfunction->builtinsprofile += 30; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), v1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), v2); + move = (int)PRVM_G_FLOAT(OFS_PARM2); + ent = PRVM_G_EDICT(OFS_PARM3); + + if (VEC_IS_NAN(v1[0]) || VEC_IS_NAN(v1[1]) || VEC_IS_NAN(v1[2]) || VEC_IS_NAN(v2[0]) || VEC_IS_NAN(v2[1]) || VEC_IS_NAN(v2[2])) + prog->error_cmd("%s: NAN errors detected in traceline('%f %f %f', '%f %f %f', %i, entity %i)\n", prog->name, v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); + + trace = SV_TraceLine(v1, v2, move, ent, SV_GenericHitSuperContentsMask(ent)); + + VM_SetTraceGlobals(prog, &trace); +} + + +/* +================= +VM_SV_tracebox + +Used for use tracing and shot targeting +Traces are blocked by bbox and exact bsp entityes, and also slide box entities +if the tryents flag is set. + +tracebox (vector1, vector mins, vector maxs, vector2, tryents) +================= +*/ +// LordHavoc: added this for my own use, VERY useful, similar to traceline +static void VM_SV_tracebox(prvm_prog_t *prog) +{ + vec3_t v1, v2, m1, m2; + trace_t trace; + int move; + prvm_edict_t *ent; + + VM_SAFEPARMCOUNTRANGE(6, 8, VM_SV_tracebox); // allow more parameters for future expansion + + prog->xfunction->builtinsprofile += 30; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), v1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), m1); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), m2); + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), v2); + move = (int)PRVM_G_FLOAT(OFS_PARM4); + ent = PRVM_G_EDICT(OFS_PARM5); + + if (VEC_IS_NAN(v1[0]) || VEC_IS_NAN(v1[1]) || VEC_IS_NAN(v1[2]) || VEC_IS_NAN(v2[0]) || VEC_IS_NAN(v2[1]) || VEC_IS_NAN(v2[2])) + prog->error_cmd("%s: NAN errors detected in tracebox('%f %f %f', '%f %f %f', '%f %f %f', '%f %f %f', %i, entity %i)\n", prog->name, v1[0], v1[1], v1[2], m1[0], m1[1], m1[2], m2[0], m2[1], m2[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); + + trace = SV_TraceBox(v1, m1, m2, v2, move, ent, SV_GenericHitSuperContentsMask(ent)); + + VM_SetTraceGlobals(prog, &trace); +} + +static trace_t SV_Trace_Toss(prvm_prog_t *prog, prvm_edict_t *tossent, prvm_edict_t *ignore) +{ + int i; + float gravity; + vec3_t move, end, tossentorigin, tossentmins, tossentmaxs; + vec3_t original_origin; + vec3_t original_velocity; + vec3_t original_angles; + vec3_t original_avelocity; + trace_t trace; + + VectorCopy(PRVM_serveredictvector(tossent, origin) , original_origin ); + VectorCopy(PRVM_serveredictvector(tossent, velocity) , original_velocity ); + VectorCopy(PRVM_serveredictvector(tossent, angles) , original_angles ); + VectorCopy(PRVM_serveredictvector(tossent, avelocity), original_avelocity); + + gravity = PRVM_serveredictfloat(tossent, gravity); + if (!gravity) + gravity = 1.0f; + gravity *= sv_gravity.value * 0.025; + + for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds + { + SV_CheckVelocity (tossent); + PRVM_serveredictvector(tossent, velocity)[2] -= gravity; + VectorMA (PRVM_serveredictvector(tossent, angles), 0.05, PRVM_serveredictvector(tossent, avelocity), PRVM_serveredictvector(tossent, angles)); + VectorScale (PRVM_serveredictvector(tossent, velocity), 0.05, move); + VectorAdd (PRVM_serveredictvector(tossent, origin), move, end); + VectorCopy(PRVM_serveredictvector(tossent, origin), tossentorigin); + VectorCopy(PRVM_serveredictvector(tossent, mins), tossentmins); + VectorCopy(PRVM_serveredictvector(tossent, maxs), tossentmaxs); + trace = SV_TraceBox(tossentorigin, tossentmins, tossentmaxs, end, MOVE_NORMAL, tossent, SV_GenericHitSuperContentsMask(tossent)); + VectorCopy (trace.endpos, PRVM_serveredictvector(tossent, origin)); + PRVM_serveredictvector(tossent, velocity)[2] -= gravity; + + if (trace.fraction < 1) + break; + } + + VectorCopy(original_origin , PRVM_serveredictvector(tossent, origin) ); + VectorCopy(original_velocity , PRVM_serveredictvector(tossent, velocity) ); + VectorCopy(original_angles , PRVM_serveredictvector(tossent, angles) ); + VectorCopy(original_avelocity, PRVM_serveredictvector(tossent, avelocity)); + + return trace; +} + +static void VM_SV_tracetoss(prvm_prog_t *prog) +{ + trace_t trace; + prvm_edict_t *ent; + prvm_edict_t *ignore; + + VM_SAFEPARMCOUNT(2, VM_SV_tracetoss); + + prog->xfunction->builtinsprofile += 600; + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning(prog, "tracetoss: can not use world entity\n"); + return; + } + ignore = PRVM_G_EDICT(OFS_PARM1); + + trace = SV_Trace_Toss(prog, ent, ignore); + + VM_SetTraceGlobals(prog, &trace); +} + +//============================================================================ + +static int checkpvsbytes; +static unsigned char checkpvs[MAX_MAP_LEAFS/8]; + +static int VM_SV_newcheckclient(prvm_prog_t *prog, int check) +{ + int i; + prvm_edict_t *ent; + vec3_t org; + +// cycle to the next one + + check = bound(1, check, svs.maxclients); + if (check == svs.maxclients) + i = 1; + else + i = check + 1; + + for ( ; ; i++) + { + // count the cost + prog->xfunction->builtinsprofile++; + // wrap around + if (i == svs.maxclients+1) + i = 1; + // look up the client's edict + ent = PRVM_EDICT_NUM(i); + // check if it is to be ignored, but never ignore the one we started on (prevent infinite loop) + if (i != check && (ent->priv.server->free || PRVM_serveredictfloat(ent, health) <= 0 || ((int)PRVM_serveredictfloat(ent, flags) & FL_NOTARGET))) + continue; + // found a valid client (possibly the same one again) + break; + } + +// get the PVS for the entity + VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, view_ofs), org); + checkpvsbytes = 0; + if (sv.worldmodel && sv.worldmodel->brush.FatPVS) + checkpvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, org, 0, checkpvs, sizeof(checkpvs), false); + + return i; +} + +/* +================= +VM_SV_checkclient + +Returns a client (or object that has a client enemy) that would be a +valid target. + +If there is more than one valid option, they are cycled each frame + +If (self.origin + self.viewofs) is not in the PVS of the current target, +it is not returned at all. + +name checkclient () +================= +*/ +int c_invis, c_notvis; +static void VM_SV_checkclient(prvm_prog_t *prog) +{ + prvm_edict_t *ent, *self; + vec3_t view; + + VM_SAFEPARMCOUNT(0, VM_SV_checkclient); + + // find a new check if on a new frame + if (sv.time - sv.lastchecktime >= 0.1) + { + sv.lastcheck = VM_SV_newcheckclient(prog, sv.lastcheck); + sv.lastchecktime = sv.time; + } + + // return check if it might be visible + ent = PRVM_EDICT_NUM(sv.lastcheck); + if (ent->priv.server->free || PRVM_serveredictfloat(ent, health) <= 0) + { + VM_RETURN_EDICT(prog->edicts); + return; + } + + // if current entity can't possibly see the check entity, return 0 + self = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + VectorAdd(PRVM_serveredictvector(self, origin), PRVM_serveredictvector(self, view_ofs), view); + if (sv.worldmodel && checkpvsbytes && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, checkpvs, view, view)) + { + c_notvis++; + VM_RETURN_EDICT(prog->edicts); + return; + } + + // might be able to see it + c_invis++; + VM_RETURN_EDICT(ent); +} + +//============================================================================ + +/* +================= +VM_SV_checkpvs + +Checks if an entity is in a point's PVS. +Should be fast but can be inexact. + +float checkpvs(vector viewpos, entity viewee) = #240; +================= +*/ +static void VM_SV_checkpvs(prvm_prog_t *prog) +{ + vec3_t viewpos, absmin, absmax; + prvm_edict_t *viewee; +#if 1 + unsigned char *pvs; +#else + int fatpvsbytes; + unsigned char fatpvs[MAX_MAP_LEAFS/8]; +#endif + + VM_SAFEPARMCOUNT(2, VM_SV_checkpvs); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), viewpos); + viewee = PRVM_G_EDICT(OFS_PARM1); + + if(viewee->priv.server->free) + { + VM_Warning(prog, "checkpvs: can not check free entity\n"); + PRVM_G_FLOAT(OFS_RETURN) = 4; + return; + } + +#if 1 + if(!sv.worldmodel || !sv.worldmodel->brush.GetPVS || !sv.worldmodel->brush.BoxTouchingPVS) + { + // no PVS support on this worldmodel... darn + PRVM_G_FLOAT(OFS_RETURN) = 3; + return; + } + pvs = sv.worldmodel->brush.GetPVS(sv.worldmodel, viewpos); + if(!pvs) + { + // viewpos isn't in any PVS... darn + PRVM_G_FLOAT(OFS_RETURN) = 2; + return; + } + VectorCopy(PRVM_serveredictvector(viewee, absmin), absmin); + VectorCopy(PRVM_serveredictvector(viewee, absmax), absmax); + PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, pvs, absmin, absmax); +#else + // using fat PVS like FTEQW does (slow) + if(!sv.worldmodel || !sv.worldmodel->brush.FatPVS || !sv.worldmodel->brush.BoxTouchingPVS) + { + // no PVS support on this worldmodel... darn + PRVM_G_FLOAT(OFS_RETURN) = 3; + return; + } + fatpvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, viewpos, 8, fatpvs, sizeof(fatpvs), false); + if(!fatpvsbytes) + { + // viewpos isn't in any PVS... darn + PRVM_G_FLOAT(OFS_RETURN) = 2; + return; + } + VectorCopy(PRVM_serveredictvector(viewee, absmin), absmin); + VectorCopy(PRVM_serveredictvector(viewee, absmax), absmax); + PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, fatpvs, absmin, absmax); +#endif +} + + +/* +================= +VM_SV_stuffcmd + +Sends text over to the client's execution buffer + +stuffcmd (clientent, value, ...) +================= +*/ +static void VM_SV_stuffcmd(prvm_prog_t *prog) +{ + int entnum; + client_t *old; + char string[VM_STRINGTEMP_LENGTH]; + + VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_stuffcmd); + + entnum = PRVM_G_EDICTNUM(OFS_PARM0); + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) + { + VM_Warning(prog, "Can't stuffcmd to a non-client\n"); + return; + } + + VM_VarString(prog, 1, string, sizeof(string)); + + old = host_client; + host_client = svs.clients + entnum-1; + Host_ClientCommands ("%s", string); + host_client = old; +} + +/* +================= +VM_SV_findradius + +Returns a chain of entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static void VM_SV_findradius(prvm_prog_t *prog) +{ + prvm_edict_t *ent, *chain; + vec_t radius, radius2; + vec3_t org, eorg, mins, maxs; + int i; + int numtouchedicts; + static prvm_edict_t *touchedicts[MAX_EDICTS]; + int chainfield; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_SV_findradius); + + if(prog->argc == 3) + chainfield = PRVM_G_INT(OFS_PARM2); + else + chainfield = prog->fieldoffsets.chain; + if (chainfield < 0) + prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); + + chain = (prvm_edict_t *)prog->edicts; + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + radius = PRVM_G_FLOAT(OFS_PARM1); + radius2 = radius * radius; + + mins[0] = org[0] - (radius + 1); + mins[1] = org[1] - (radius + 1); + mins[2] = org[2] - (radius + 1); + maxs[0] = org[0] + (radius + 1); + maxs[1] = org[1] + (radius + 1); + maxs[2] = org[2] + (radius + 1); + numtouchedicts = SV_EntitiesInBox(mins, maxs, MAX_EDICTS, touchedicts); + if (numtouchedicts > MAX_EDICTS) + { + // this never happens + Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); + numtouchedicts = MAX_EDICTS; + } + for (i = 0;i < numtouchedicts;i++) + { + ent = touchedicts[i]; + prog->xfunction->builtinsprofile++; + // Quake did not return non-solid entities but darkplaces does + // (note: this is the reason you can't blow up fallen zombies) + if (PRVM_serveredictfloat(ent, solid) == SOLID_NOT && !sv_gameplayfix_blowupfallenzombies.integer) + continue; + // LordHavoc: compare against bounding box rather than center so it + // doesn't miss large objects, and use DotProduct instead of Length + // for a major speedup + VectorSubtract(org, PRVM_serveredictvector(ent, origin), eorg); + if (sv_gameplayfix_findradiusdistancetobox.integer) + { + eorg[0] -= bound(PRVM_serveredictvector(ent, mins)[0], eorg[0], PRVM_serveredictvector(ent, maxs)[0]); + eorg[1] -= bound(PRVM_serveredictvector(ent, mins)[1], eorg[1], PRVM_serveredictvector(ent, maxs)[1]); + eorg[2] -= bound(PRVM_serveredictvector(ent, mins)[2], eorg[2], PRVM_serveredictvector(ent, maxs)[2]); + } + else + VectorMAMAM(1, eorg, -0.5f, PRVM_serveredictvector(ent, mins), -0.5f, PRVM_serveredictvector(ent, maxs), eorg); + if (DotProduct(eorg, eorg) < radius2) + { + PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); + chain = ent; + } + } + + VM_RETURN_EDICT(chain); +} + +static void VM_SV_precache_sound(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_precache_sound); + PRVM_G_FLOAT(OFS_RETURN) = SV_SoundIndex(PRVM_G_STRING(OFS_PARM0), 2); +} + +static void VM_SV_precache_model(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_precache_model); + SV_ModelIndex(PRVM_G_STRING(OFS_PARM0), 2); + PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); +} + +/* +=============== +VM_SV_walkmove + +float(float yaw, float dist[, settrace]) walkmove +=============== +*/ +static void VM_SV_walkmove(prvm_prog_t *prog) +{ + prvm_edict_t *ent; + float yaw, dist; + vec3_t move; + mfunction_t *oldf; + int oldself; + qboolean settrace; + + VM_SAFEPARMCOUNTRANGE(2, 3, VM_SV_walkmove); + + // assume failure if it returns early + PRVM_G_FLOAT(OFS_RETURN) = 0; + + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning(prog, "walkmove: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "walkmove: can not modify free entity\n"); + return; + } + yaw = PRVM_G_FLOAT(OFS_PARM0); + dist = PRVM_G_FLOAT(OFS_PARM1); + settrace = prog->argc >= 3 && PRVM_G_FLOAT(OFS_PARM2); + + if ( !( (int)PRVM_serveredictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + return; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + +// save program state, because SV_movestep may call other progs + oldf = prog->xfunction; + oldself = PRVM_serverglobaledict(self); + + PRVM_G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true, false, settrace); + + +// restore program state + prog->xfunction = oldf; + PRVM_serverglobaledict(self) = oldself; +} + +/* +=============== +VM_SV_droptofloor + +void() droptofloor +=============== +*/ + +static void VM_SV_droptofloor(prvm_prog_t *prog) +{ + prvm_edict_t *ent; + vec3_t end, entorigin, entmins, entmaxs; + trace_t trace; + + VM_SAFEPARMCOUNTRANGE(0, 2, VM_SV_droptofloor); // allow 2 parameters because the id1 defs.qc had an incorrect prototype + + // assume failure if it returns early + PRVM_G_FLOAT(OFS_RETURN) = 0; + + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning(prog, "droptofloor: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "droptofloor: can not modify free entity\n"); + return; + } + + VectorCopy (PRVM_serveredictvector(ent, origin), end); + end[2] -= 256; + + if (sv_gameplayfix_droptofloorstartsolid_nudgetocorrect.integer) + SV_NudgeOutOfSolid(ent); + + VectorCopy(PRVM_serveredictvector(ent, origin), entorigin); + VectorCopy(PRVM_serveredictvector(ent, mins), entmins); + VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); + trace = SV_TraceBox(entorigin, entmins, entmaxs, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + if (trace.startsolid && sv_gameplayfix_droptofloorstartsolid.integer) + { + vec3_t offset, org; + VectorSet(offset, 0.5f * (PRVM_serveredictvector(ent, mins)[0] + PRVM_serveredictvector(ent, maxs)[0]), 0.5f * (PRVM_serveredictvector(ent, mins)[1] + PRVM_serveredictvector(ent, maxs)[1]), PRVM_serveredictvector(ent, mins)[2]); + VectorAdd(PRVM_serveredictvector(ent, origin), offset, org); + trace = SV_TraceLine(org, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent)); + VectorSubtract(trace.endpos, offset, trace.endpos); + if (trace.startsolid) + { + Con_DPrintf("droptofloor at %f %f %f - COULD NOT FIX BADLY PLACED ENTITY\n", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + SV_LinkEdict(ent); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = 0; + PRVM_G_FLOAT(OFS_RETURN) = 1; + } + else if (trace.fraction < 1) + { + Con_DPrintf("droptofloor at %f %f %f - FIXED BADLY PLACED ENTITY\n", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); + VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); + if (sv_gameplayfix_droptofloorstartsolid_nudgetocorrect.integer) + SV_NudgeOutOfSolid(ent); + SV_LinkEdict(ent); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + PRVM_G_FLOAT(OFS_RETURN) = 1; + // if support is destroyed, keep suspended (gross hack for floating items in various maps) + ent->priv.server->suspendedinairflag = true; + } + } + else + { + if (!trace.allsolid && trace.fraction < 1) + { + VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); + SV_LinkEdict(ent); + PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; + PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); + PRVM_G_FLOAT(OFS_RETURN) = 1; + // if support is destroyed, keep suspended (gross hack for floating items in various maps) + ent->priv.server->suspendedinairflag = true; + } + } +} + +/* +=============== +VM_SV_lightstyle + +void(float style, string value) lightstyle +=============== +*/ +static void VM_SV_lightstyle(prvm_prog_t *prog) +{ + int style; + const char *val; + client_t *client; + int j; + + VM_SAFEPARMCOUNT(2, VM_SV_lightstyle); + + style = (int)PRVM_G_FLOAT(OFS_PARM0); + val = PRVM_G_STRING(OFS_PARM1); + + if( (unsigned) style >= MAX_LIGHTSTYLES ) { + prog->error_cmd( "PF_lightstyle: style: %i >= 64", style ); + } + +// change the string in sv + strlcpy(sv.lightstyles[style], val, sizeof(sv.lightstyles[style])); + +// send message to all clients on this server + if (sv.state != ss_active) + return; + + for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++) + { + if (client->active && client->netconnection) + { + MSG_WriteChar (&client->netconnection->message, svc_lightstyle); + MSG_WriteChar (&client->netconnection->message,style); + MSG_WriteString (&client->netconnection->message, val); + } + } +} + +/* +============= +VM_SV_checkbottom +============= +*/ +static void VM_SV_checkbottom(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_checkbottom); + PRVM_G_FLOAT(OFS_RETURN) = SV_CheckBottom (PRVM_G_EDICT(OFS_PARM0)); +} + +/* +============= +VM_SV_pointcontents +============= +*/ +static void VM_SV_pointcontents(prvm_prog_t *prog) +{ + vec3_t point; + VM_SAFEPARMCOUNT(1, VM_SV_pointcontents); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), point); + PRVM_G_FLOAT(OFS_RETURN) = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(point)); +} + +/* +============= +VM_SV_aim + +Pick a vector for the player to shoot along +vector aim(entity, missilespeed) +============= +*/ +static void VM_SV_aim(prvm_prog_t *prog) +{ + prvm_edict_t *ent, *check, *bestent; + vec3_t start, dir, end, bestdir; + int i, j; + trace_t tr; + float dist, bestdist; + //float speed; + + VM_SAFEPARMCOUNT(2, VM_SV_aim); + + // assume failure if it returns early + VectorCopy(PRVM_serverglobalvector(v_forward), PRVM_G_VECTOR(OFS_RETURN)); + // if sv_aim is so high it can't possibly accept anything, skip out early + if (sv_aim.value >= 1) + return; + + ent = PRVM_G_EDICT(OFS_PARM0); + if (ent == prog->edicts) + { + VM_Warning(prog, "aim: can not use world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "aim: can not use free entity\n"); + return; + } + //speed = PRVM_G_FLOAT(OFS_PARM1); + + VectorCopy (PRVM_serveredictvector(ent, origin), start); + start[2] += 20; + +// try sending a trace straight + VectorCopy (PRVM_serverglobalvector(v_forward), dir); + VectorMA (start, 2048, dir, end); + tr = SV_TraceLine(start, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY); + if (tr.ent && PRVM_serveredictfloat(((prvm_edict_t *)tr.ent), takedamage) == DAMAGE_AIM + && (!teamplay.integer || PRVM_serveredictfloat(ent, team) <=0 || PRVM_serveredictfloat(ent, team) != PRVM_serveredictfloat(((prvm_edict_t *)tr.ent), team)) ) + { + VectorCopy (PRVM_serverglobalvector(v_forward), PRVM_G_VECTOR(OFS_RETURN)); + return; + } + + +// try all possible entities + VectorCopy (dir, bestdir); + bestdist = sv_aim.value; + bestent = NULL; + + check = PRVM_NEXT_EDICT(prog->edicts); + for (i=1 ; inum_edicts ; i++, check = PRVM_NEXT_EDICT(check) ) + { + prog->xfunction->builtinsprofile++; + if (PRVM_serveredictfloat(check, takedamage) != DAMAGE_AIM) + continue; + if (check == ent) + continue; + if (teamplay.integer && PRVM_serveredictfloat(ent, team) > 0 && PRVM_serveredictfloat(ent, team) == PRVM_serveredictfloat(check, team)) + continue; // don't aim at teammate + for (j=0 ; j<3 ; j++) + end[j] = PRVM_serveredictvector(check, origin)[j] + + 0.5*(PRVM_serveredictvector(check, mins)[j] + PRVM_serveredictvector(check, maxs)[j]); + VectorSubtract (end, start, dir); + VectorNormalize (dir); + dist = DotProduct (dir, PRVM_serverglobalvector(v_forward)); + if (dist < bestdist) + continue; // to far to turn + tr = SV_TraceLine(start, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY); + if (tr.ent == check) + { // can shoot at this one + bestdist = dist; + bestent = check; + } + } + + if (bestent) + { + VectorSubtract (PRVM_serveredictvector(bestent, origin), PRVM_serveredictvector(ent, origin), dir); + dist = DotProduct (dir, PRVM_serverglobalvector(v_forward)); + VectorScale (PRVM_serverglobalvector(v_forward), dist, end); + end[2] = dir[2]; + VectorNormalize (end); + VectorCopy (end, PRVM_G_VECTOR(OFS_RETURN)); + } + else + { + VectorCopy (bestdir, PRVM_G_VECTOR(OFS_RETURN)); + } +} + +/* +=============================================================================== + +MESSAGE WRITING + +=============================================================================== +*/ + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_ENTITY 5 + +static sizebuf_t *WriteDest(prvm_prog_t *prog) +{ + int entnum; + int dest; + prvm_edict_t *ent; + + dest = (int)PRVM_G_FLOAT(OFS_PARM0); + switch (dest) + { + case MSG_BROADCAST: + return &sv.datagram; + + case MSG_ONE: + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(msg_entity)); + entnum = PRVM_NUM_FOR_EDICT(ent); + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active || !svs.clients[entnum-1].netconnection) + { + VM_Warning(prog, "WriteDest: tried to write to non-client\n"); + return &sv.reliable_datagram; + } + else + return &svs.clients[entnum-1].netconnection->message; + + default: + VM_Warning(prog, "WriteDest: bad destination\n"); + case MSG_ALL: + return &sv.reliable_datagram; + + case MSG_INIT: + return &sv.signon; + + case MSG_ENTITY: + return sv.writeentitiestoclient_msg; + } + + //return NULL; +} + +static void VM_SV_WriteByte(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteByte); + MSG_WriteByte (WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM1)); +} + +static void VM_SV_WriteChar(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteChar); + MSG_WriteChar (WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM1)); +} + +static void VM_SV_WriteShort(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteShort); + MSG_WriteShort (WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM1)); +} + +static void VM_SV_WriteLong(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteLong); + MSG_WriteLong (WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM1)); +} + +static void VM_SV_WriteAngle(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteAngle); + MSG_WriteAngle (WriteDest(prog), PRVM_G_FLOAT(OFS_PARM1), sv.protocol); +} + +static void VM_SV_WriteCoord(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteCoord); + MSG_WriteCoord (WriteDest(prog), PRVM_G_FLOAT(OFS_PARM1), sv.protocol); +} + +static void VM_SV_WriteString(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteString); + MSG_WriteString (WriteDest(prog), PRVM_G_STRING(OFS_PARM1)); +} + +static void VM_SV_WriteUnterminatedString(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteUnterminatedString); + MSG_WriteUnterminatedString (WriteDest(prog), PRVM_G_STRING(OFS_PARM1)); +} + + +static void VM_SV_WriteEntity(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_WriteEntity); + MSG_WriteShort (WriteDest(prog), PRVM_G_EDICTNUM(OFS_PARM1)); +} + +// writes a picture as at most size bytes of data +// message: +// IMGNAME \0 SIZE(short) IMGDATA +// if failed to read/compress: +// IMGNAME \0 \0 \0 +//#501 void(float dest, string name, float maxsize) WritePicture (DP_SV_WRITEPICTURE)) +static void VM_SV_WritePicture(prvm_prog_t *prog) +{ + const char *imgname; + void *buf; + size_t size; + + VM_SAFEPARMCOUNT(3, VM_SV_WritePicture); + + imgname = PRVM_G_STRING(OFS_PARM1); + size = (size_t) PRVM_G_FLOAT(OFS_PARM2); + if(size > 65535) + size = 65535; + + MSG_WriteString(WriteDest(prog), imgname); + if(Image_Compress(imgname, size, &buf, &size)) + { + // actual picture + MSG_WriteShort(WriteDest(prog), size); + SZ_Write(WriteDest(prog), (unsigned char *) buf, size); + } + else + { + // placeholder + MSG_WriteShort(WriteDest(prog), 0); + } +} + +////////////////////////////////////////////////////////// + +static void VM_SV_makestatic(prvm_prog_t *prog) +{ + prvm_edict_t *ent; + int i, large; + + // allow 0 parameters due to an id1 qc bug in which this function is used + // with no parameters (but directly after setmodel with self in OFS_PARM0) + VM_SAFEPARMCOUNTRANGE(0, 1, VM_SV_makestatic); + + if (prog->argc >= 1) + ent = PRVM_G_EDICT(OFS_PARM0); + else + ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); + if (ent == prog->edicts) + { + VM_Warning(prog, "makestatic: can not modify world entity\n"); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "makestatic: can not modify free entity\n"); + return; + } + + large = false; + if (PRVM_serveredictfloat(ent, modelindex) >= 256 || PRVM_serveredictfloat(ent, frame) >= 256) + large = true; + + if (large) + { + MSG_WriteByte (&sv.signon,svc_spawnstatic2); + MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); + MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); + } + else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) + { + MSG_WriteByte (&sv.signon,svc_spawnstatic); + MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); + } + else + { + MSG_WriteByte (&sv.signon,svc_spawnstatic); + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); + } + + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, colormap)); + MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, skin)); + for (i=0 ; i<3 ; i++) + { + MSG_WriteCoord(&sv.signon, PRVM_serveredictvector(ent, origin)[i], sv.protocol); + MSG_WriteAngle(&sv.signon, PRVM_serveredictvector(ent, angles)[i], sv.protocol); + } + +// throw the entity away now + PRVM_ED_Free(prog, ent); +} + +//============================================================================= + +/* +============== +VM_SV_setspawnparms +============== +*/ +static void VM_SV_setspawnparms(prvm_prog_t *prog) +{ + prvm_edict_t *ent; + int i; + client_t *client; + + VM_SAFEPARMCOUNT(1, VM_SV_setspawnparms); + + ent = PRVM_G_EDICT(OFS_PARM0); + i = PRVM_NUM_FOR_EDICT(ent); + if (i < 1 || i > svs.maxclients || !svs.clients[i-1].active) + { + Con_Print("tried to setspawnparms on a non-client\n"); + return; + } + + // copy spawn parms out of the client_t + client = svs.clients + i-1; + for (i=0 ; i< NUM_SPAWN_PARMS ; i++) + (&PRVM_serverglobalfloat(parm1))[i] = client->spawn_parms[i]; +} + +/* +================= +VM_SV_getlight + +Returns a color vector indicating the lighting at the requested point. + +(Internal Operation note: actually measures the light beneath the point, just like + the model lighting on the client) + +getlight(vector) +================= +*/ +static void VM_SV_getlight(prvm_prog_t *prog) +{ + vec3_t ambientcolor, diffusecolor, diffusenormal; + vec3_t p; + VM_SAFEPARMCOUNT(1, VM_SV_getlight); + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), p); + VectorClear(ambientcolor); + VectorClear(diffusecolor); + VectorClear(diffusenormal); + if (sv.worldmodel && sv.worldmodel->brush.LightPoint) + sv.worldmodel->brush.LightPoint(sv.worldmodel, p, ambientcolor, diffusecolor, diffusenormal); + VectorMA(ambientcolor, 0.5, diffusecolor, PRVM_G_VECTOR(OFS_RETURN)); +} + +typedef struct +{ + unsigned char type; // 1/2/8 or other value if isn't used + int fieldoffset; +}customstat_t; + +static customstat_t *vm_customstats = NULL; //[515]: it starts from 0, not 32 +static int vm_customstats_last; + +void VM_CustomStats_Clear (void) +{ + if(vm_customstats) + { + Z_Free(vm_customstats); + vm_customstats = NULL; + vm_customstats_last = -1; + } +} + +void VM_SV_UpdateCustomStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats) +{ + prvm_prog_t *prog = SVVM_prog; + int i; + char s[17]; + + if(!vm_customstats) + return; + + for(i=0; i= (MAX_CL_STATS-32)) + { + VM_Warning(prog, "PF_SV_AddStat: index >= MAX_CL_STATS\n"); + return; + } + if(i > (MAX_CL_STATS-32-4) && type == 1) + { + VM_Warning(prog, "PF_SV_AddStat: index > (MAX_CL_STATS-4) with string\n"); + return; + } + vm_customstats[i].type = type; + vm_customstats[i].fieldoffset = off; + if(vm_customstats_last < i) + vm_customstats_last = i; +} + +/* +================= +VM_SV_copyentity + +copies data from one entity to another + +copyentity(src, dst) +================= +*/ +static void VM_SV_copyentity(prvm_prog_t *prog) +{ + prvm_edict_t *in, *out; + VM_SAFEPARMCOUNT(2, VM_SV_copyentity); + in = PRVM_G_EDICT(OFS_PARM0); + if (in == prog->edicts) + { + VM_Warning(prog, "copyentity: can not read world entity\n"); + return; + } + if (in->priv.server->free) + { + VM_Warning(prog, "copyentity: can not read free entity\n"); + return; + } + out = PRVM_G_EDICT(OFS_PARM1); + if (out == prog->edicts) + { + VM_Warning(prog, "copyentity: can not modify world entity\n"); + return; + } + if (out->priv.server->free) + { + VM_Warning(prog, "copyentity: can not modify free entity\n"); + return; + } + memcpy(out->fields.fp, in->fields.fp, prog->entityfields * sizeof(prvm_vec_t)); + SV_LinkEdict(out); +} + + +/* +================= +VM_SV_setcolor + +sets the color of a client and broadcasts the update to all connected clients + +setcolor(clientent, value) +================= +*/ +static void VM_SV_setcolor(prvm_prog_t *prog) +{ + client_t *client; + int entnum, i; + + VM_SAFEPARMCOUNT(2, VM_SV_setcolor); + entnum = PRVM_G_EDICTNUM(OFS_PARM0); + i = (int)PRVM_G_FLOAT(OFS_PARM1); + + if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) + { + Con_Print("tried to setcolor a non-client\n"); + return; + } + + client = svs.clients + entnum-1; + if (client->edict) + { + PRVM_serveredictfloat(client->edict, clientcolors) = i; + PRVM_serveredictfloat(client->edict, team) = (i & 15) + 1; + } + client->colors = i; + if (client->old_colors != client->colors) + { + client->old_colors = client->colors; + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, client - svs.clients); + MSG_WriteByte (&sv.reliable_datagram, client->colors); + } +} + +/* +================= +VM_SV_effect + +effect(origin, modelname, startframe, framecount, framerate) +================= +*/ +static void VM_SV_effect(prvm_prog_t *prog) +{ + int i; + const char *s; + vec3_t org; + VM_SAFEPARMCOUNT(5, VM_SV_effect); + s = PRVM_G_STRING(OFS_PARM1); + if (!s[0]) + { + VM_Warning(prog, "effect: no model specified\n"); + return; + } + + i = SV_ModelIndex(s, 1); + if (!i) + { + VM_Warning(prog, "effect: model not precached\n"); + return; + } + + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + { + VM_Warning(prog, "effect: framecount < 1\n"); + return; + } + + if (PRVM_G_FLOAT(OFS_PARM4) < 1) + { + VM_Warning(prog, "effect: framerate < 1\n"); + return; + } + + VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); + SV_StartEffect(org, i, (int)PRVM_G_FLOAT(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4)); +} + +static void VM_SV_te_blood(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_blood); + if (PRVM_G_FLOAT(OFS_PARM2) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_BLOOD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // velocity + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[0], 127)); + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[1], 127)); + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[2], 127)); + // count + MSG_WriteByte(&sv.datagram, bound(0, (int) PRVM_G_FLOAT(OFS_PARM2), 255)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_bloodshower(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(4, VM_SV_te_bloodshower); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_BLOODSHOWER); + // min + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // max + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // speed + MSG_WriteCoord(&sv.datagram, PRVM_G_FLOAT(OFS_PARM2), sv.protocol); + // count + MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_explosionrgb(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(2, VM_SV_te_explosionrgb); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSIONRGB); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // color + MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[0] * 255), 255)); + MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[1] * 255), 255)); + MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[2] * 255), 255)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_particlecube(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(7, VM_SV_te_particlecube); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_PARTICLECUBE); + // min + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // max + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // velocity + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + // count + MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); + // color + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); + // gravity true/false + MSG_WriteByte(&sv.datagram, ((int) PRVM_G_FLOAT(OFS_PARM5)) != 0); + // randomvel + MSG_WriteCoord(&sv.datagram, PRVM_G_FLOAT(OFS_PARM6), sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_particlerain(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(5, VM_SV_te_particlerain); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_PARTICLERAIN); + // min + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // max + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // velocity + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + // count + MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); + // color + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_particlesnow(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(5, VM_SV_te_particlesnow); + if (PRVM_G_FLOAT(OFS_PARM3) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_PARTICLESNOW); + // min + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // max + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // velocity + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + // count + MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); + // color + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_spark(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_spark); + if (PRVM_G_FLOAT(OFS_PARM2) < 1) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SPARK); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // velocity + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[0], 127)); + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[1], 127)); + MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[2], 127)); + // count + MSG_WriteByte(&sv.datagram, bound(0, (int) PRVM_G_FLOAT(OFS_PARM2), 255)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_gunshotquad(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_gunshotquad); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_GUNSHOTQUAD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_spikequad(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_spikequad); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SPIKEQUAD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_superspikequad(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_superspikequad); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SUPERSPIKEQUAD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_explosionquad(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_explosionquad); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSIONQUAD); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_smallflash(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_smallflash); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SMALLFLASH); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_customflash(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(4, VM_SV_te_customflash); + if (PRVM_G_FLOAT(OFS_PARM1) < 8 || PRVM_G_FLOAT(OFS_PARM2) < (1.0 / 256.0)) + return; + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_CUSTOMFLASH); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // radius + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM1) / 8 - 1, 255)); + // lifetime + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM2) * 256 - 1, 255)); + // color + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[0] * 255, 255)); + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[1] * 255, 255)); + MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[2] * 255, 255)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_gunshot(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_gunshot); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_GUNSHOT); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_spike(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_spike); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SPIKE); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_superspike(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_superspike); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_SUPERSPIKE); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_explosion(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_explosion); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSION); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_tarexplosion(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_tarexplosion); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_TAREXPLOSION); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_wizspike(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_wizspike); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_WIZSPIKE); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_knightspike(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_knightspike); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_KNIGHTSPIKE); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_lavasplash(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_lavasplash); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LAVASPLASH); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_teleport(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_teleport); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_TELEPORT); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_explosion2(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_explosion2); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_EXPLOSION2); + // origin + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // color + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM1)); + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM2)); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_lightning1(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_lightning1); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING1); + // owner entity + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + // start + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // end + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_lightning2(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_lightning2); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING2); + // owner entity + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + // start + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // end + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_lightning3(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_lightning3); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_LIGHTNING3); + // owner entity + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + // start + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // end + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_beam(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_beam); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_BEAM); + // owner entity + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + // start + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // end + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_plasmaburn(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(1, VM_SV_te_plasmaburn); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_PLASMABURN); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + SV_FlushBroadcastMessages(); +} + +static void VM_SV_te_flamejet(prvm_prog_t *prog) +{ + VM_SAFEPARMCOUNT(3, VM_SV_te_flamejet); + MSG_WriteByte(&sv.datagram, svc_temp_entity); + MSG_WriteByte(&sv.datagram, TE_FLAMEJET); + // org + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); + // vel + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); + MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); + // count + MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM2)); + SV_FlushBroadcastMessages(); +} + +//void(entity e, string s) clientcommand = #440; // executes a command string as if it came from the specified client +//this function originally written by KrimZon, made shorter by LordHavoc +static void VM_SV_clientcommand(prvm_prog_t *prog) +{ + client_t *temp_client; + int i; + VM_SAFEPARMCOUNT(2, VM_SV_clientcommand); + + //find client for this entity + i = (PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0)) - 1); + if (i < 0 || i >= svs.maxclients || !svs.clients[i].active) + { + Con_Print("PF_clientcommand: entity is not a client\n"); + return; + } + + temp_client = host_client; + host_client = svs.clients + i; + Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client, true); + host_client = temp_client; +} + +//void(entity e, entity tagentity, string tagname) setattachment = #443; // attachs e to a tag on tagentity (note: use "" to attach to entity origin/angles instead of a tag) +static void VM_SV_setattachment(prvm_prog_t *prog) +{ + prvm_edict_t *e = PRVM_G_EDICT(OFS_PARM0); + prvm_edict_t *tagentity = PRVM_G_EDICT(OFS_PARM1); + const char *tagname = PRVM_G_STRING(OFS_PARM2); + dp_model_t *model; + int tagindex; + VM_SAFEPARMCOUNT(3, VM_SV_setattachment); + + if (e == prog->edicts) + { + VM_Warning(prog, "setattachment: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning(prog, "setattachment: can not modify free entity\n"); + return; + } + + if (tagentity == NULL) + tagentity = prog->edicts; + + tagindex = 0; + + if (tagentity != NULL && tagentity != prog->edicts && tagname && tagname[0]) + { + model = SV_GetModelFromEdict(tagentity); + if (model) + { + tagindex = Mod_Alias_GetTagIndexForName(model, (int)PRVM_serveredictfloat(tagentity, skin), tagname); + if (tagindex == 0) + Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i (model \"%s\") but could not find it\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity), model->name); + } + else + Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i but it has no model\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity)); + } + + PRVM_serveredictedict(e, tag_entity) = PRVM_EDICT_TO_PROG(tagentity); + PRVM_serveredictfloat(e, tag_index) = tagindex; +} + +///////////////////////////////////////// +// DP_MD3_TAGINFO extension coded by VorteX + +static int SV_GetTagIndex (prvm_prog_t *prog, prvm_edict_t *e, const char *tagname) +{ + int i; + + i = (int)PRVM_serveredictfloat(e, modelindex); + if (i < 1 || i >= MAX_MODELS) + return -1; + + return Mod_Alias_GetTagIndexForName(SV_GetModelByIndex(i), (int)PRVM_serveredictfloat(e, skin), tagname); +} + +static int SV_GetExtendedTagInfo (prvm_prog_t *prog, prvm_edict_t *e, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) +{ + int r; + dp_model_t *model; + + *tagname = NULL; + *parentindex = 0; + Matrix4x4_CreateIdentity(tag_localmatrix); + + if (tagindex >= 0 && (model = SV_GetModelFromEdict(e)) && model->num_bones) + { + r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)PRVM_serveredictfloat(e, skin), e->priv.server->frameblend, &e->priv.server->skeleton, tagindex - 1, parentindex, tagname, tag_localmatrix); + + if(!r) // success? + *parentindex += 1; + + return r; + } + + return 1; +} + +void SV_GetEntityMatrix (prvm_prog_t *prog, prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix) +{ + float scale; + float pitchsign = 1; + + scale = PRVM_serveredictfloat(ent, scale); + if (!scale) + scale = 1.0f; + + if (viewmatrix) + Matrix4x4_CreateFromQuakeEntity(out, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, view_ofs)[2], PRVM_serveredictvector(ent, v_angle)[0], PRVM_serveredictvector(ent, v_angle)[1], PRVM_serveredictvector(ent, v_angle)[2], scale * cl_viewmodel_scale.value); + else + { + pitchsign = SV_GetPitchSign(prog, ent); + Matrix4x4_CreateFromQuakeEntity(out, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2], pitchsign * PRVM_serveredictvector(ent, angles)[0], PRVM_serveredictvector(ent, angles)[1], PRVM_serveredictvector(ent, angles)[2], scale); + } +} + +static int SV_GetEntityLocalTagMatrix(prvm_prog_t *prog, prvm_edict_t *ent, int tagindex, matrix4x4_t *out) +{ + dp_model_t *model; + if (tagindex >= 0 && (model = SV_GetModelFromEdict(ent)) && model->animscenes) + { + VM_GenerateFrameGroupBlend(prog, ent->priv.server->framegroupblend, ent); + VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model, sv.time); + VM_UpdateEdictSkeleton(prog, ent, model, ent->priv.server->frameblend); + return Mod_Alias_GetTagMatrix(model, ent->priv.server->frameblend, &ent->priv.server->skeleton, tagindex, out); + } + *out = identitymatrix; + return 0; +} + +// Warnings/errors code: +// 0 - normal (everything all-right) +// 1 - world entity +// 2 - free entity +// 3 - null or non-precached model +// 4 - no tags with requested index +// 5 - runaway loop at attachment chain +extern cvar_t cl_bob; +extern cvar_t cl_bobcycle; +extern cvar_t cl_bobup; +static int SV_GetTagMatrix (prvm_prog_t *prog, matrix4x4_t *out, prvm_edict_t *ent, int tagindex) +{ + int ret; + int modelindex, attachloop; + matrix4x4_t entitymatrix, tagmatrix, attachmatrix; + dp_model_t *model; + + *out = identitymatrix; // warnings and errors return identical matrix + + if (ent == prog->edicts) + return 1; + if (ent->priv.server->free) + return 2; + + modelindex = (int)PRVM_serveredictfloat(ent, modelindex); + if (modelindex <= 0 || modelindex >= MAX_MODELS) + return 3; + + model = SV_GetModelByIndex(modelindex); + + VM_GenerateFrameGroupBlend(prog, ent->priv.server->framegroupblend, ent); + VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model, sv.time); + VM_UpdateEdictSkeleton(prog, ent, model, ent->priv.server->frameblend); + + tagmatrix = identitymatrix; + // DP_GFX_QUAKE3MODELTAGS, scan all chain and stop on unattached entity + attachloop = 0; + for (;;) + { + if (attachloop >= 256) // prevent runaway looping + return 5; + // apply transformation by child's tagindex on parent entity and then + // by parent entity itself + ret = SV_GetEntityLocalTagMatrix(prog, ent, tagindex - 1, &attachmatrix); + if (ret && attachloop == 0) + return ret; + SV_GetEntityMatrix(prog, ent, &entitymatrix, false); + Matrix4x4_Concat(&tagmatrix, &attachmatrix, out); + Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); + // next iteration we process the parent entity + if (PRVM_serveredictedict(ent, tag_entity)) + { + tagindex = (int)PRVM_serveredictfloat(ent, tag_index); + ent = PRVM_EDICT_NUM(PRVM_serveredictedict(ent, tag_entity)); + } + else + break; + attachloop++; + } + + // RENDER_VIEWMODEL magic + if (PRVM_serveredictedict(ent, viewmodelforclient)) + { + Matrix4x4_Copy(&tagmatrix, out); + ent = PRVM_EDICT_NUM(PRVM_serveredictedict(ent, viewmodelforclient)); + + SV_GetEntityMatrix(prog, ent, &entitymatrix, true); + Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); + + /* + // Cl_bob, ported from rendering code + if (PRVM_serveredictfloat(ent, health) > 0 && cl_bob.value && cl_bobcycle.value) + { + double bob, cycle; + // LordHavoc: this code is *weird*, but not replacable (I think it + // should be done in QC on the server, but oh well, quake is quake) + // LordHavoc: figured out bobup: the time at which the sin is at 180 + // degrees (which allows lengthening or squishing the peak or valley) + cycle = sv.time/cl_bobcycle.value; + cycle -= (int)cycle; + if (cycle < cl_bobup.value) + cycle = sin(M_PI * cycle / cl_bobup.value); + else + cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); + // bob is proportional to velocity in the xy plane + // (don't count Z, or jumping messes it up) + bob = sqrt(PRVM_serveredictvector(ent, velocity)[0]*PRVM_serveredictvector(ent, velocity)[0] + PRVM_serveredictvector(ent, velocity)[1]*PRVM_serveredictvector(ent, velocity)[1])*cl_bob.value; + bob = bob*0.3 + bob*0.7*cycle; + Matrix4x4_AdjustOrigin(out, 0, 0, bound(-7, bob, 4)); + } + */ + } + return 0; +} + +//float(entity ent, string tagname) gettagindex; + +static void VM_SV_gettagindex(prvm_prog_t *prog) +{ + prvm_edict_t *ent; + const char *tag_name; + int tag_index; + + VM_SAFEPARMCOUNT(2, VM_SV_gettagindex); + + ent = PRVM_G_EDICT(OFS_PARM0); + tag_name = PRVM_G_STRING(OFS_PARM1); + + if (ent == prog->edicts) + { + VM_Warning(prog, "VM_SV_gettagindex(entity #%i): can't affect world entity\n", PRVM_NUM_FOR_EDICT(ent)); + return; + } + if (ent->priv.server->free) + { + VM_Warning(prog, "VM_SV_gettagindex(entity #%i): can't affect free entity\n", PRVM_NUM_FOR_EDICT(ent)); + return; + } + + tag_index = 0; + if (!SV_GetModelFromEdict(ent)) + Con_DPrintf("VM_SV_gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent)); + else + { + tag_index = SV_GetTagIndex(prog, ent, tag_name); + if (tag_index == 0) + if(developer_extra.integer) + Con_DPrintf("VM_SV_gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name); + } + PRVM_G_FLOAT(OFS_RETURN) = tag_index; +} + +//vector(entity ent, float tagindex) gettaginfo; +static void VM_SV_gettaginfo(prvm_prog_t *prog) +{ + prvm_edict_t *e; + int tagindex; + matrix4x4_t tag_matrix; + matrix4x4_t tag_localmatrix; + int parentindex; + const char *tagname; + int returncode; + vec3_t forward, left, up, origin; + const dp_model_t *model; + + VM_SAFEPARMCOUNT(2, VM_SV_gettaginfo); + + e = PRVM_G_EDICT(OFS_PARM0); + tagindex = (int)PRVM_G_FLOAT(OFS_PARM1); + + returncode = SV_GetTagMatrix(prog, &tag_matrix, e, tagindex); + Matrix4x4_ToVectors(&tag_matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_serverglobalvector(v_forward)); + VectorNegate(left, PRVM_serverglobalvector(v_right)); + VectorCopy(up, PRVM_serverglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); + model = SV_GetModelFromEdict(e); + VM_GenerateFrameGroupBlend(prog, e->priv.server->framegroupblend, e); + VM_FrameBlendFromFrameGroupBlend(e->priv.server->frameblend, e->priv.server->framegroupblend, model, sv.time); + VM_UpdateEdictSkeleton(prog, e, model, e->priv.server->frameblend); + SV_GetExtendedTagInfo(prog, e, tagindex, &parentindex, &tagname, &tag_localmatrix); + Matrix4x4_ToVectors(&tag_localmatrix, forward, left, up, origin); + + PRVM_serverglobalfloat(gettaginfo_parent) = parentindex; + PRVM_serverglobalstring(gettaginfo_name) = tagname ? PRVM_SetTempString(prog, tagname) : 0; + VectorCopy(forward, PRVM_serverglobalvector(gettaginfo_forward)); + VectorNegate(left, PRVM_serverglobalvector(gettaginfo_right)); + VectorCopy(up, PRVM_serverglobalvector(gettaginfo_up)); + VectorCopy(origin, PRVM_serverglobalvector(gettaginfo_offset)); + + switch(returncode) + { + case 1: + VM_Warning(prog, "gettagindex: can't affect world entity\n"); + break; + case 2: + VM_Warning(prog, "gettagindex: can't affect free entity\n"); + break; + case 3: + Con_DPrintf("SV_GetTagMatrix(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(e)); + break; + case 4: + Con_DPrintf("SV_GetTagMatrix(entity #%i): model has no tag with requested index %i\n", PRVM_NUM_FOR_EDICT(e), tagindex); + break; + case 5: + Con_DPrintf("SV_GetTagMatrix(entity #%i): runaway loop at attachment chain\n", PRVM_NUM_FOR_EDICT(e)); + break; + } +} + +//void(entity clent) dropclient (DP_SV_DROPCLIENT) +static void VM_SV_dropclient(prvm_prog_t *prog) +{ + int clientnum; + client_t *oldhostclient; + VM_SAFEPARMCOUNT(1, VM_SV_dropclient); + clientnum = PRVM_G_EDICTNUM(OFS_PARM0) - 1; + if (clientnum < 0 || clientnum >= svs.maxclients) + { + VM_Warning(prog, "dropclient: not a client\n"); + return; + } + if (!svs.clients[clientnum].active) + { + VM_Warning(prog, "dropclient: that client slot is not connected\n"); + return; + } + oldhostclient = host_client; + host_client = svs.clients + clientnum; + SV_DropClient(false); + host_client = oldhostclient; +} + +//entity() spawnclient (DP_SV_BOTCLIENT) +static void VM_SV_spawnclient(prvm_prog_t *prog) +{ + int i; + prvm_edict_t *ed; + VM_SAFEPARMCOUNT(0, VM_SV_spawnclient); + prog->xfunction->builtinsprofile += 2; + ed = prog->edicts; + for (i = 0;i < svs.maxclients;i++) + { + if (!svs.clients[i].active) + { + prog->xfunction->builtinsprofile += 100; + SV_ConnectClient (i, NULL); + // this has to be set or else ClientDisconnect won't be called + // we assume the qc will call ClientConnect... + svs.clients[i].clientconnectcalled = true; + ed = PRVM_EDICT_NUM(i + 1); + break; + } + } + VM_RETURN_EDICT(ed); +} + +//float(entity clent) clienttype (DP_SV_BOTCLIENT) +static void VM_SV_clienttype(prvm_prog_t *prog) +{ + int clientnum; + VM_SAFEPARMCOUNT(1, VM_SV_clienttype); + clientnum = PRVM_G_EDICTNUM(OFS_PARM0) - 1; + if (clientnum < 0 || clientnum >= svs.maxclients) + PRVM_G_FLOAT(OFS_RETURN) = 3; + else if (!svs.clients[clientnum].active) + PRVM_G_FLOAT(OFS_RETURN) = 0; + else if (svs.clients[clientnum].netconnection) + PRVM_G_FLOAT(OFS_RETURN) = 1; + else + PRVM_G_FLOAT(OFS_RETURN) = 2; +} + +/* +=============== +VM_SV_serverkey + +string(string key) serverkey +=============== +*/ +static void VM_SV_serverkey(prvm_prog_t *prog) +{ + char string[VM_STRINGTEMP_LENGTH]; + VM_SAFEPARMCOUNT(1, VM_SV_serverkey); + InfoString_GetValue(svs.serverinfo, PRVM_G_STRING(OFS_PARM0), string, sizeof(string)); + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); +} + +//#333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) +static void VM_SV_setmodelindex(prvm_prog_t *prog) +{ + prvm_edict_t *e; + dp_model_t *mod; + int i; + VM_SAFEPARMCOUNT(2, VM_SV_setmodelindex); + + e = PRVM_G_EDICT(OFS_PARM0); + if (e == prog->edicts) + { + VM_Warning(prog, "setmodelindex: can not modify world entity\n"); + return; + } + if (e->priv.server->free) + { + VM_Warning(prog, "setmodelindex: can not modify free entity\n"); + return; + } + i = (int)PRVM_G_FLOAT(OFS_PARM1); + if (i <= 0 || i >= MAX_MODELS) + { + VM_Warning(prog, "setmodelindex: invalid modelindex\n"); + return; + } + if (!sv.model_precache[i][0]) + { + VM_Warning(prog, "setmodelindex: model not precached\n"); + return; + } + + PRVM_serveredictstring(e, model) = PRVM_SetEngineString(prog, sv.model_precache[i]); + PRVM_serveredictfloat(e, modelindex) = i; + + mod = SV_GetModelByIndex(i); + + if (mod) + { + if (mod->type != mod_alias || sv_gameplayfix_setmodelrealbox.integer) + SetMinMaxSize(prog, e, mod->normalmins, mod->normalmaxs, true); + else + SetMinMaxSize(prog, e, quakemins, quakemaxs, true); + } + else + SetMinMaxSize(prog, e, vec3_origin, vec3_origin, true); +} + +//#334 string(float mdlindex) modelnameforindex (EXT_CSQC) +static void VM_SV_modelnameforindex(prvm_prog_t *prog) +{ + int i; + VM_SAFEPARMCOUNT(1, VM_SV_modelnameforindex); + + PRVM_G_INT(OFS_RETURN) = OFS_NULL; + + i = (int)PRVM_G_FLOAT(OFS_PARM0); + if (i <= 0 || i >= MAX_MODELS) + { + VM_Warning(prog, "modelnameforindex: invalid modelindex\n"); + return; + } + if (!sv.model_precache[i][0]) + { + VM_Warning(prog, "modelnameforindex: model not precached\n"); + return; + } + + PRVM_G_INT(OFS_RETURN) = PRVM_SetEngineString(prog, sv.model_precache[i]); +} + +//#335 float(string effectname) particleeffectnum (EXT_CSQC) +static void VM_SV_particleeffectnum(prvm_prog_t *prog) +{ + int i; + VM_SAFEPARMCOUNT(1, VM_SV_particleeffectnum); + i = SV_ParticleEffectIndex(PRVM_G_STRING(OFS_PARM0)); + if (i == 0) + i = -1; + PRVM_G_FLOAT(OFS_RETURN) = i; +} + +// #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) +static void VM_SV_trailparticles(prvm_prog_t *prog) +{ + vec3_t start, end; + VM_SAFEPARMCOUNT(4, VM_SV_trailparticles); + + if ((int)PRVM_G_FLOAT(OFS_PARM0) < 0) + return; + + MSG_WriteByte(&sv.datagram, svc_trailparticles); + MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); + MSG_WriteShort(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM1)); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), start); + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), end); + MSG_WriteVector(&sv.datagram, start, sv.protocol); + MSG_WriteVector(&sv.datagram, end, sv.protocol); + SV_FlushBroadcastMessages(); +} + +//#337 void(float effectnum, vector origin, vector dir, float count) pointparticles (EXT_CSQC) +static void VM_SV_pointparticles(prvm_prog_t *prog) +{ + int effectnum, count; + vec3_t org, vel; + VM_SAFEPARMCOUNTRANGE(4, 8, VM_SV_pointparticles); + + if ((int)PRVM_G_FLOAT(OFS_PARM0) < 0) + return; + + effectnum = (int)PRVM_G_FLOAT(OFS_PARM0); + VectorCopy(PRVM_G_VECTOR(OFS_PARM1), org); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); + count = bound(0, (int)PRVM_G_FLOAT(OFS_PARM3), 65535); + if (count == 1 && !VectorLength2(vel)) + { + // 1+2+12=15 bytes + MSG_WriteByte(&sv.datagram, svc_pointparticles1); + MSG_WriteShort(&sv.datagram, effectnum); + MSG_WriteVector(&sv.datagram, org, sv.protocol); + } + else + { + // 1+2+12+12+2=29 bytes + MSG_WriteByte(&sv.datagram, svc_pointparticles); + MSG_WriteShort(&sv.datagram, effectnum); + MSG_WriteVector(&sv.datagram, org, sv.protocol); + MSG_WriteVector(&sv.datagram, vel, sv.protocol); + MSG_WriteShort(&sv.datagram, count); + } + + SV_FlushBroadcastMessages(); +} + +//PF_setpause, // void(float pause) setpause = #531; +static void VM_SV_setpause(prvm_prog_t *prog) { + int pauseValue; + pauseValue = (int)PRVM_G_FLOAT(OFS_PARM0); + if (pauseValue != 0) { //pause the game + sv.paused = 1; + sv.pausedstart = realtime; + } else { //disable pause, in case it was enabled + if (sv.paused != 0) { + sv.paused = 0; + sv.pausedstart = 0; + } + } + // send notification to all clients + MSG_WriteByte(&sv.reliable_datagram, svc_setpause); + MSG_WriteByte(&sv.reliable_datagram, sv.paused); +} + +// #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. +static void VM_SV_skel_create(prvm_prog_t *prog) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = SV_GetModelByIndex(modelindex); + skeleton_t *skeleton; + int i; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (!model || !model->num_bones) + return; + for (i = 0;i < MAX_EDICTS;i++) + if (!prog->skeletons[i]) + break; + if (i == MAX_EDICTS) + return; + prog->skeletons[i] = skeleton = (skeleton_t *)Mem_Alloc(prog->progs_mempool, sizeof(skeleton_t) + model->num_bones * sizeof(matrix4x4_t)); + PRVM_G_FLOAT(OFS_RETURN) = i + 1; + skeleton->model = model; + skeleton->relativetransforms = (matrix4x4_t *)(skeleton+1); + // initialize to identity matrices + for (i = 0;i < skeleton->model->num_bones;i++) + skeleton->relativetransforms[i] = identitymatrix; +} + +// #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +static void VM_SV_skel_build(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + prvm_edict_t *ed = PRVM_G_EDICT(OFS_PARM1); + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM2); + float retainfrac = PRVM_G_FLOAT(OFS_PARM3); + int firstbone = PRVM_G_FLOAT(OFS_PARM4) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM5) - 1; + dp_model_t *model = SV_GetModelByIndex(modelindex); + int numblends; + int bonenum; + int blendindex; + framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; + frameblend_t frameblend[MAX_FRAMEBLENDS]; + matrix4x4_t bonematrix; + matrix4x4_t matrix; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + firstbone = max(0, firstbone); + lastbone = min(lastbone, model->num_bones - 1); + lastbone = min(lastbone, skeleton->model->num_bones - 1); + VM_GenerateFrameGroupBlend(prog, framegroupblend, ed); + VM_FrameBlendFromFrameGroupBlend(frameblend, framegroupblend, model, sv.time); + for (numblends = 0;numblends < MAX_FRAMEBLENDS && frameblend[numblends].lerp;numblends++) + ; + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + { + memset(&bonematrix, 0, sizeof(bonematrix)); + for (blendindex = 0;blendindex < numblends;blendindex++) + { + Matrix4x4_FromBonePose7s(&matrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + bonenum)); + Matrix4x4_Accumulate(&bonematrix, &matrix, frameblend[blendindex].lerp); + } + Matrix4x4_Normalize3(&bonematrix, &bonematrix); + Matrix4x4_Interpolate(&skeleton->relativetransforms[bonenum], &bonematrix, &skeleton->relativetransforms[bonenum], retainfrac); + } + PRVM_G_FLOAT(OFS_RETURN) = skeletonindex + 1; +} + +// #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton +static void VM_SV_skel_get_numbones(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->num_bones; +} + +// #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) +static void VM_SV_skel_get_bonename(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + PRVM_G_INT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, skeleton->model->data_bones[bonenum].name); +} + +// #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +static void VM_SV_skel_get_boneparent(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->data_bones[bonenum].parent + 1; +} + +// #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex +static void VM_SV_skel_find_bone(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + const char *tagname = PRVM_G_STRING(OFS_PARM1); + skeleton_t *skeleton; + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + PRVM_G_FLOAT(OFS_RETURN) = Mod_Alias_GetTagIndexForName(skeleton->model, 0, tagname) + 1; +} + +// #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +static void VM_SV_skel_get_bonerel(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + matrix4x4_t matrix; + vec3_t forward, left, up, origin; + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + VectorClear(PRVM_clientglobalvector(v_forward)); + VectorClear(PRVM_clientglobalvector(v_right)); + VectorClear(PRVM_clientglobalvector(v_up)); + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + matrix = skeleton->relativetransforms[bonenum]; + Matrix4x4_ToVectors(&matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); +} + +// #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +static void VM_SV_skel_get_boneabs(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + vec3_t forward, left, up, origin; + VectorClear(PRVM_G_VECTOR(OFS_RETURN)); + VectorClear(PRVM_clientglobalvector(v_forward)); + VectorClear(PRVM_clientglobalvector(v_right)); + VectorClear(PRVM_clientglobalvector(v_up)); + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + matrix = skeleton->relativetransforms[bonenum]; + // convert to absolute + while ((bonenum = skeleton->model->data_bones[bonenum].parent) >= 0) + { + temp = matrix; + Matrix4x4_Concat(&matrix, &skeleton->relativetransforms[bonenum], &temp); + } + Matrix4x4_ToVectors(&matrix, forward, left, up, origin); + VectorCopy(forward, PRVM_clientglobalvector(v_forward)); + VectorNegate(left, PRVM_clientglobalvector(v_right)); + VectorCopy(up, PRVM_clientglobalvector(v_up)); + VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); +} + +// #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +static void VM_SV_skel_set_bone(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + skeleton->relativetransforms[bonenum] = matrix; +} + +// #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +static void VM_SV_skel_mul_bone(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + if (bonenum < 0 || bonenum >= skeleton->model->num_bones) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + temp = skeleton->relativetransforms[bonenum]; + Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); +} + +// #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +static void VM_SV_skel_mul_bones(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int firstbone = PRVM_G_FLOAT(OFS_PARM1) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM2) - 1; + int bonenum; + vec3_t forward, left, up, origin; + skeleton_t *skeleton; + matrix4x4_t matrix; + matrix4x4_t temp; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin); + VectorCopy(PRVM_clientglobalvector(v_forward), forward); + VectorNegate(PRVM_clientglobalvector(v_right), left); + VectorCopy(PRVM_clientglobalvector(v_up), up); + Matrix4x4_FromVectors(&matrix, forward, left, up, origin); + firstbone = max(0, firstbone); + lastbone = min(lastbone, skeleton->model->num_bones - 1); + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + { + temp = skeleton->relativetransforms[bonenum]; + Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); + } +} + +// #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +static void VM_SV_skel_copybones(prvm_prog_t *prog) +{ + int skeletonindexdst = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + int skeletonindexsrc = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; + int firstbone = PRVM_G_FLOAT(OFS_PARM2) - 1; + int lastbone = PRVM_G_FLOAT(OFS_PARM3) - 1; + int bonenum; + skeleton_t *skeletondst; + skeleton_t *skeletonsrc; + if (skeletonindexdst < 0 || skeletonindexdst >= MAX_EDICTS || !(skeletondst = prog->skeletons[skeletonindexdst])) + return; + if (skeletonindexsrc < 0 || skeletonindexsrc >= MAX_EDICTS || !(skeletonsrc = prog->skeletons[skeletonindexsrc])) + return; + firstbone = max(0, firstbone); + lastbone = min(lastbone, skeletondst->model->num_bones - 1); + lastbone = min(lastbone, skeletonsrc->model->num_bones - 1); + for (bonenum = firstbone;bonenum <= lastbone;bonenum++) + skeletondst->relativetransforms[bonenum] = skeletonsrc->relativetransforms[bonenum]; +} + +// #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +static void VM_SV_skel_delete(prvm_prog_t *prog) +{ + int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; + skeleton_t *skeleton; + if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) + return; + Mem_Free(skeleton); + prog->skeletons[skeletonindex] = NULL; +} + +// #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found +static void VM_SV_frameforname(prvm_prog_t *prog) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = SV_GetModelByIndex(modelindex); + const char *name = PRVM_G_STRING(OFS_PARM1); + int i; + PRVM_G_FLOAT(OFS_RETURN) = -1; + if (!model || !model->animscenes) + return; + for (i = 0;i < model->numframes;i++) + { + if (!strcasecmp(model->animscenes[i].name, name)) + { + PRVM_G_FLOAT(OFS_RETURN) = i; + break; + } + } +} + +// #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +static void VM_SV_frameduration(prvm_prog_t *prog) +{ + int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); + dp_model_t *model = SV_GetModelByIndex(modelindex); + int framenum = (int)PRVM_G_FLOAT(OFS_PARM1); + PRVM_G_FLOAT(OFS_RETURN) = 0; + if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes) + return; + if (model->animscenes[framenum].framerate) + PRVM_G_FLOAT(OFS_RETURN) = model->animscenes[framenum].framecount / model->animscenes[framenum].framerate; +} + + +prvm_builtin_t vm_sv_builtins[] = { +NULL, // #0 NULL function (not callable) (QUAKE) +VM_makevectors, // #1 void(vector ang) makevectors (QUAKE) +VM_SV_setorigin, // #2 void(entity e, vector o) setorigin (QUAKE) +VM_SV_setmodel, // #3 void(entity e, string m) setmodel (QUAKE) +VM_SV_setsize, // #4 void(entity e, vector min, vector max) setsize (QUAKE) +NULL, // #5 void(entity e, vector min, vector max) setabssize (QUAKE) +VM_break, // #6 void() break (QUAKE) +VM_random, // #7 float() random (QUAKE) +VM_SV_sound, // #8 void(entity e, float chan, string samp) sound (QUAKE) +VM_normalize, // #9 vector(vector v) normalize (QUAKE) +VM_error, // #10 void(string e) error (QUAKE) +VM_objerror, // #11 void(string e) objerror (QUAKE) +VM_vlen, // #12 float(vector v) vlen (QUAKE) +VM_vectoyaw, // #13 float(vector v) vectoyaw (QUAKE) +VM_spawn, // #14 entity() spawn (QUAKE) +VM_remove, // #15 void(entity e) remove (QUAKE) +VM_SV_traceline, // #16 void(vector v1, vector v2, float tryents) traceline (QUAKE) +VM_SV_checkclient, // #17 entity() checkclient (QUAKE) +VM_find, // #18 entity(entity start, .string fld, string match) find (QUAKE) +VM_SV_precache_sound, // #19 void(string s) precache_sound (QUAKE) +VM_SV_precache_model, // #20 void(string s) precache_model (QUAKE) +VM_SV_stuffcmd, // #21 void(entity client, string s, ...) stuffcmd (QUAKE) +VM_SV_findradius, // #22 entity(vector org, float rad) findradius (QUAKE) +VM_bprint, // #23 void(string s, ...) bprint (QUAKE) +VM_SV_sprint, // #24 void(entity client, string s, ...) sprint (QUAKE) +VM_dprint, // #25 void(string s, ...) dprint (QUAKE) +VM_ftos, // #26 string(float f) ftos (QUAKE) +VM_vtos, // #27 string(vector v) vtos (QUAKE) +VM_coredump, // #28 void() coredump (QUAKE) +VM_traceon, // #29 void() traceon (QUAKE) +VM_traceoff, // #30 void() traceoff (QUAKE) +VM_eprint, // #31 void(entity e) eprint (QUAKE) +VM_SV_walkmove, // #32 float(float yaw, float dist) walkmove (QUAKE) +NULL, // #33 (QUAKE) +VM_SV_droptofloor, // #34 float() droptofloor (QUAKE) +VM_SV_lightstyle, // #35 void(float style, string value) lightstyle (QUAKE) +VM_rint, // #36 float(float v) rint (QUAKE) +VM_floor, // #37 float(float v) floor (QUAKE) +VM_ceil, // #38 float(float v) ceil (QUAKE) +NULL, // #39 (QUAKE) +VM_SV_checkbottom, // #40 float(entity e) checkbottom (QUAKE) +VM_SV_pointcontents, // #41 float(vector v) pointcontents (QUAKE) +NULL, // #42 (QUAKE) +VM_fabs, // #43 float(float f) fabs (QUAKE) +VM_SV_aim, // #44 vector(entity e, float speed) aim (QUAKE) +VM_cvar, // #45 float(string s) cvar (QUAKE) +VM_localcmd, // #46 void(string s) localcmd (QUAKE) +VM_nextent, // #47 entity(entity e) nextent (QUAKE) +VM_SV_particle, // #48 void(vector o, vector d, float color, float count) particle (QUAKE) +VM_changeyaw, // #49 void() ChangeYaw (QUAKE) +NULL, // #50 (QUAKE) +VM_vectoangles, // #51 vector(vector v) vectoangles (QUAKE) +VM_SV_WriteByte, // #52 void(float to, float f) WriteByte (QUAKE) +VM_SV_WriteChar, // #53 void(float to, float f) WriteChar (QUAKE) +VM_SV_WriteShort, // #54 void(float to, float f) WriteShort (QUAKE) +VM_SV_WriteLong, // #55 void(float to, float f) WriteLong (QUAKE) +VM_SV_WriteCoord, // #56 void(float to, float f) WriteCoord (QUAKE) +VM_SV_WriteAngle, // #57 void(float to, float f) WriteAngle (QUAKE) +VM_SV_WriteString, // #58 void(float to, string s) WriteString (QUAKE) +VM_SV_WriteEntity, // #59 void(float to, entity e) WriteEntity (QUAKE) +VM_sin, // #60 float(float f) sin (DP_QC_SINCOSSQRTPOW) (QUAKE) +VM_cos, // #61 float(float f) cos (DP_QC_SINCOSSQRTPOW) (QUAKE) +VM_sqrt, // #62 float(float f) sqrt (DP_QC_SINCOSSQRTPOW) (QUAKE) +VM_changepitch, // #63 void(entity ent) changepitch (DP_QC_CHANGEPITCH) (QUAKE) +VM_SV_tracetoss, // #64 void(entity e, entity ignore) tracetoss (DP_QC_TRACETOSS) (QUAKE) +VM_etos, // #65 string(entity ent) etos (DP_QC_ETOS) (QUAKE) +NULL, // #66 (QUAKE) +VM_SV_MoveToGoal, // #67 void(float step) movetogoal (QUAKE) +VM_precache_file, // #68 string(string s) precache_file (QUAKE) +VM_SV_makestatic, // #69 void(entity e) makestatic (QUAKE) +VM_changelevel, // #70 void(string s) changelevel (QUAKE) +NULL, // #71 (QUAKE) +VM_cvar_set, // #72 void(string var, string val) cvar_set (QUAKE) +VM_SV_centerprint, // #73 void(entity client, strings) centerprint (QUAKE) +VM_SV_ambientsound, // #74 void(vector pos, string samp, float vol, float atten) ambientsound (QUAKE) +VM_SV_precache_model, // #75 string(string s) precache_model2 (QUAKE) +VM_SV_precache_sound, // #76 string(string s) precache_sound2 (QUAKE) +VM_precache_file, // #77 string(string s) precache_file2 (QUAKE) +VM_SV_setspawnparms, // #78 void(entity e) setspawnparms (QUAKE) +NULL, // #79 void(entity killer, entity killee) logfrag (QUAKEWORLD) +NULL, // #80 string(entity e, string keyname) infokey (QUAKEWORLD) +VM_stof, // #81 float(string s) stof (FRIK_FILE) +NULL, // #82 void(vector where, float set) multicast (QUAKEWORLD) +NULL, // #83 (QUAKE) +NULL, // #84 (QUAKE) +NULL, // #85 (QUAKE) +NULL, // #86 (QUAKE) +NULL, // #87 (QUAKE) +NULL, // #88 (QUAKE) +NULL, // #89 (QUAKE) +VM_SV_tracebox, // #90 void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox (DP_QC_TRACEBOX) +VM_randomvec, // #91 vector() randomvec (DP_QC_RANDOMVEC) +VM_SV_getlight, // #92 vector(vector org) getlight (DP_QC_GETLIGHT) +VM_registercvar, // #93 float(string name, string value) registercvar (DP_REGISTERCVAR) +VM_min, // #94 float(float a, floats) min (DP_QC_MINMAXBOUND) +VM_max, // #95 float(float a, floats) max (DP_QC_MINMAXBOUND) +VM_bound, // #96 float(float minimum, float val, float maximum) bound (DP_QC_MINMAXBOUND) +VM_pow, // #97 float(float f, float f) pow (DP_QC_SINCOSSQRTPOW) +VM_findfloat, // #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT) +VM_checkextension, // #99 float(string s) checkextension (the basis of the extension system) +// FrikaC and Telejano range #100-#199 +NULL, // #100 +NULL, // #101 +NULL, // #102 +NULL, // #103 +NULL, // #104 +NULL, // #105 +NULL, // #106 +NULL, // #107 +NULL, // #108 +NULL, // #109 +VM_fopen, // #110 float(string filename, float mode) fopen (FRIK_FILE) +VM_fclose, // #111 void(float fhandle) fclose (FRIK_FILE) +VM_fgets, // #112 string(float fhandle) fgets (FRIK_FILE) +VM_fputs, // #113 void(float fhandle, string s) fputs (FRIK_FILE) +VM_strlen, // #114 float(string s) strlen (FRIK_FILE) +VM_strcat, // #115 string(string s1, string s2, ...) strcat (FRIK_FILE) +VM_substring, // #116 string(string s, float start, float length) substring (FRIK_FILE) +VM_stov, // #117 vector(string) stov (FRIK_FILE) +VM_strzone, // #118 string(string s) strzone (FRIK_FILE) +VM_strunzone, // #119 void(string s) strunzone (FRIK_FILE) +NULL, // #120 +NULL, // #121 +NULL, // #122 +NULL, // #123 +NULL, // #124 +NULL, // #125 +NULL, // #126 +NULL, // #127 +NULL, // #128 +NULL, // #129 +NULL, // #130 +NULL, // #131 +NULL, // #132 +NULL, // #133 +NULL, // #134 +NULL, // #135 +NULL, // #136 +NULL, // #137 +NULL, // #138 +NULL, // #139 +NULL, // #140 +NULL, // #141 +NULL, // #142 +NULL, // #143 +NULL, // #144 +NULL, // #145 +NULL, // #146 +NULL, // #147 +NULL, // #148 +NULL, // #149 +NULL, // #150 +NULL, // #151 +NULL, // #152 +NULL, // #153 +NULL, // #154 +NULL, // #155 +NULL, // #156 +NULL, // #157 +NULL, // #158 +NULL, // #159 +NULL, // #160 +NULL, // #161 +NULL, // #162 +NULL, // #163 +NULL, // #164 +NULL, // #165 +NULL, // #166 +NULL, // #167 +NULL, // #168 +NULL, // #169 +NULL, // #170 +NULL, // #171 +NULL, // #172 +NULL, // #173 +NULL, // #174 +NULL, // #175 +NULL, // #176 +NULL, // #177 +NULL, // #178 +NULL, // #179 +NULL, // #180 +NULL, // #181 +NULL, // #182 +NULL, // #183 +NULL, // #184 +NULL, // #185 +NULL, // #186 +NULL, // #187 +NULL, // #188 +NULL, // #189 +NULL, // #190 +NULL, // #191 +NULL, // #192 +NULL, // #193 +NULL, // #194 +NULL, // #195 +NULL, // #196 +NULL, // #197 +NULL, // #198 +NULL, // #199 +// FTEQW range #200-#299 +NULL, // #200 +NULL, // #201 +NULL, // #202 +NULL, // #203 +NULL, // #204 +NULL, // #205 +NULL, // #206 +NULL, // #207 +NULL, // #208 +NULL, // #209 +NULL, // #210 +NULL, // #211 +NULL, // #212 +NULL, // #213 +NULL, // #214 +NULL, // #215 +NULL, // #216 +NULL, // #217 +VM_bitshift, // #218 float(float number, float quantity) bitshift (EXT_BITSHIFT) +NULL, // #219 +NULL, // #220 +VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) +VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) +VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) +VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) +VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) +VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) +VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) +VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) +VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) +VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) +NULL, // #231 +VM_SV_AddStat, // #232 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) +NULL, // #233 +NULL, // #234 +NULL, // #235 +NULL, // #236 +NULL, // #237 +NULL, // #238 +NULL, // #239 +VM_SV_checkpvs, // #240 float(vector viewpos, entity viewee) checkpvs; +NULL, // #241 +NULL, // #242 +NULL, // #243 +NULL, // #244 +NULL, // #245 +NULL, // #246 +NULL, // #247 +NULL, // #248 +NULL, // #249 +NULL, // #250 +NULL, // #251 +NULL, // #252 +NULL, // #253 +NULL, // #254 +NULL, // #255 +NULL, // #256 +NULL, // #257 +NULL, // #258 +NULL, // #259 +NULL, // #260 +NULL, // #261 +NULL, // #262 +VM_SV_skel_create, // #263 float(float modlindex) skel_create = #263; // (DP_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. +VM_SV_skel_build, // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (DP_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure +VM_SV_skel_get_numbones, // #265 float(float skel) skel_get_numbones = #265; // (DP_SKELETONOBJECTS) returns how many bones exist in the created skeleton +VM_SV_skel_get_bonename, // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (DP_SKELETONOBJECTS) returns name of bone (as a tempstring) +VM_SV_skel_get_boneparent, // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (DP_SKELETONOBJECTS) returns parent num for supplied bonenum, -1 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) +VM_SV_skel_find_bone, // #268 float(float skel, string tagname) skel_find_bone = #268; // (DP_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex +VM_SV_skel_get_bonerel, // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (DP_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) +VM_SV_skel_get_boneabs, // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (DP_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) +VM_SV_skel_set_bone, // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (DP_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +VM_SV_skel_mul_bone, // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (DP_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) +VM_SV_skel_mul_bones, // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (DP_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) +VM_SV_skel_copybones, // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (DP_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse +VM_SV_skel_delete, // #275 void(float skel) skel_delete = #275; // (DP_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) +VM_SV_frameforname, // #276 float(float modlindex, string framename) frameforname = #276; // (DP_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found +VM_SV_frameduration, // #277 float(float modlindex, float framenum) frameduration = #277; // (DP_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. +NULL, // #278 +NULL, // #279 +NULL, // #280 +NULL, // #281 +NULL, // #282 +NULL, // #283 +NULL, // #284 +NULL, // #285 +NULL, // #286 +NULL, // #287 +NULL, // #288 +NULL, // #289 +NULL, // #290 +NULL, // #291 +NULL, // #292 +NULL, // #293 +NULL, // #294 +NULL, // #295 +NULL, // #296 +NULL, // #297 +NULL, // #298 +NULL, // #299 +// CSQC range #300-#399 +NULL, // #300 void() clearscene (EXT_CSQC) +NULL, // #301 void(float mask) addentities (EXT_CSQC) +NULL, // #302 void(entity ent) addentity (EXT_CSQC) +NULL, // #303 float(float property, ...) setproperty (EXT_CSQC) +NULL, // #304 void() renderscene (EXT_CSQC) +NULL, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (EXT_CSQC) +NULL, // #306 void(string texturename, float flag[, float is2d, float lines]) R_BeginPolygon +NULL, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex +NULL, // #308 void() R_EndPolygon +NULL, // #309 +NULL, // #310 vector (vector v) cs_unproject (EXT_CSQC) +NULL, // #311 vector (vector v) cs_project (EXT_CSQC) +NULL, // #312 +NULL, // #313 +NULL, // #314 +NULL, // #315 void(float width, vector pos1, vector pos2, float flag) drawline (EXT_CSQC) +NULL, // #316 float(string name) iscachedpic (EXT_CSQC) +NULL, // #317 string(string name, float trywad) precache_pic (EXT_CSQC) +NULL, // #318 vector(string picname) draw_getimagesize (EXT_CSQC) +NULL, // #319 void(string name) freepic (EXT_CSQC) +NULL, // #320 float(vector position, float character, vector scale, vector rgb, float alpha, float flag) drawcharacter (EXT_CSQC) +NULL, // #321 float(vector position, string text, vector scale, vector rgb, float alpha, float flag) drawstring (EXT_CSQC) +NULL, // #322 float(vector position, string pic, vector size, vector rgb, float alpha, float flag) drawpic (EXT_CSQC) +NULL, // #323 float(vector position, vector size, vector rgb, float alpha, float flag) drawfill (EXT_CSQC) +NULL, // #324 void(float x, float y, float width, float height) drawsetcliparea +NULL, // #325 void(void) drawresetcliparea +NULL, // #326 +NULL, // #327 +NULL, // #328 +NULL, // #329 +NULL, // #330 float(float stnum) getstatf (EXT_CSQC) +NULL, // #331 float(float stnum) getstati (EXT_CSQC) +NULL, // #332 string(float firststnum) getstats (EXT_CSQC) +VM_SV_setmodelindex, // #333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) +VM_SV_modelnameforindex, // #334 string(float mdlindex) modelnameforindex (EXT_CSQC) +VM_SV_particleeffectnum, // #335 float(string effectname) particleeffectnum (EXT_CSQC) +VM_SV_trailparticles, // #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) +VM_SV_pointparticles, // #337 void(float effectnum, vector origin [, vector dir, float count]) pointparticles (EXT_CSQC) +NULL, // #338 void(string s, ...) centerprint (EXT_CSQC) +VM_print, // #339 void(string s, ...) print (EXT_CSQC, DP_SV_PRINT) +NULL, // #340 string(float keynum) keynumtostring (EXT_CSQC) +NULL, // #341 float(string keyname) stringtokeynum (EXT_CSQC) +NULL, // #342 string(float keynum) getkeybind (EXT_CSQC) +NULL, // #343 void(float usecursor) setcursormode (EXT_CSQC) +NULL, // #344 vector() getmousepos (EXT_CSQC) +NULL, // #345 float(float framenum) getinputstate (EXT_CSQC) +NULL, // #346 void(float sens) setsensitivityscaler (EXT_CSQC) +NULL, // #347 void() runstandardplayerphysics (EXT_CSQC) +NULL, // #348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) +NULL, // #349 float() isdemo (EXT_CSQC) +VM_isserver, // #350 float() isserver (EXT_CSQC) +NULL, // #351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) +NULL, // #352 void(string cmdname) registercommand (EXT_CSQC) +VM_wasfreed, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too) +VM_SV_serverkey, // #354 string(string key) serverkey (EXT_CSQC) +NULL, // #355 +NULL, // #356 +NULL, // #357 +NULL, // #358 +NULL, // #359 +NULL, // #360 float() readbyte (EXT_CSQC) +NULL, // #361 float() readchar (EXT_CSQC) +NULL, // #362 float() readshort (EXT_CSQC) +NULL, // #363 float() readlong (EXT_CSQC) +NULL, // #364 float() readcoord (EXT_CSQC) +NULL, // #365 float() readangle (EXT_CSQC) +NULL, // #366 string() readstring (EXT_CSQC) +NULL, // #367 float() readfloat (EXT_CSQC) +NULL, // #368 +NULL, // #369 +NULL, // #370 +NULL, // #371 +NULL, // #372 +NULL, // #373 +NULL, // #374 +NULL, // #375 +NULL, // #376 +NULL, // #377 +NULL, // #378 +NULL, // #379 +NULL, // #380 +NULL, // #381 +NULL, // #382 +NULL, // #383 +NULL, // #384 +NULL, // #385 +NULL, // #386 +NULL, // #387 +NULL, // #388 +NULL, // #389 +NULL, // #390 +NULL, // #391 +NULL, // #392 +NULL, // #393 +NULL, // #394 +NULL, // #395 +NULL, // #396 +NULL, // #397 +NULL, // #398 +NULL, // #399 +// LordHavoc's range #400-#499 +VM_SV_copyentity, // #400 void(entity from, entity to) copyentity (DP_QC_COPYENTITY) +VM_SV_setcolor, // #401 void(entity ent, float colors) setcolor (DP_QC_SETCOLOR) +VM_findchain, // #402 entity(.string fld, string match) findchain (DP_QC_FINDCHAIN) +VM_findchainfloat, // #403 entity(.float fld, float match) findchainfloat (DP_QC_FINDCHAINFLOAT) +VM_SV_effect, // #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) +VM_SV_te_blood, // #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) +VM_SV_te_bloodshower, // #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) +VM_SV_te_explosionrgb, // #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) +VM_SV_te_particlecube, // #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) +VM_SV_te_particlerain, // #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) +VM_SV_te_particlesnow, // #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) +VM_SV_te_spark, // #411 void(vector org, vector vel, float howmany) te_spark (DP_TE_SPARK) +VM_SV_te_gunshotquad, // #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) +VM_SV_te_spikequad, // #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) +VM_SV_te_superspikequad, // #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) +VM_SV_te_explosionquad, // #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) +VM_SV_te_smallflash, // #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) +VM_SV_te_customflash, // #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) +VM_SV_te_gunshot, // #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_spike, // #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_superspike, // #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_explosion, // #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_tarexplosion, // #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_wizspike, // #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_knightspike, // #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_lavasplash, // #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_teleport, // #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_explosion2, // #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_lightning1, // #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_lightning2, // #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_lightning3, // #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) +VM_SV_te_beam, // #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) +VM_vectorvectors, // #432 void(vector dir) vectorvectors (DP_QC_VECTORVECTORS) +VM_SV_te_plasmaburn, // #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) +VM_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE) +VM_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE) +VM_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE) +VM_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE) +VM_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE) +VM_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE) +VM_SV_clientcommand, // #440 void(entity e, string s) clientcommand (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_tokenize, // #441 float(string s) tokenize (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_argv, // #442 string(float n) argv (KRIMZON_SV_PARSECLIENTCOMMAND) +VM_SV_setattachment, // #443 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) +VM_search_begin, // #444 float(string pattern, float caseinsensitive, float quiet) search_begin (DP_QC_FS_SEARCH) +VM_search_end, // #445 void(float handle) search_end (DP_QC_FS_SEARCH) +VM_search_getsize, // #446 float(float handle) search_getsize (DP_QC_FS_SEARCH) +VM_search_getfilename, // #447 string(float handle, float num) search_getfilename (DP_QC_FS_SEARCH) +VM_cvar_string, // #448 string(string s) cvar_string (DP_QC_CVAR_STRING) +VM_findflags, // #449 entity(entity start, .float fld, float match) findflags (DP_QC_FINDFLAGS) +VM_findchainflags, // #450 entity(.float fld, float match) findchainflags (DP_QC_FINDCHAINFLAGS) +VM_SV_gettagindex, // #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) +VM_SV_gettaginfo, // #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) +VM_SV_dropclient, // #453 void(entity clent) dropclient (DP_SV_DROPCLIENT) +VM_SV_spawnclient, // #454 entity() spawnclient (DP_SV_BOTCLIENT) +VM_SV_clienttype, // #455 float(entity clent) clienttype (DP_SV_BOTCLIENT) +VM_SV_WriteUnterminatedString, // #456 void(float to, string s) WriteUnterminatedString (DP_SV_WRITEUNTERMINATEDSTRING) +VM_SV_te_flamejet, // #457 void(vector org, vector vel, float howmany) te_flamejet = #457 (DP_TE_FLAMEJET) +NULL, // #458 +VM_ftoe, // #459 entity(float num) entitybyindex (DP_QC_EDICT_NUM) +VM_buf_create, // #460 float() buf_create (DP_QC_STRINGBUFFERS) +VM_buf_del, // #461 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) +VM_buf_getsize, // #462 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) +VM_buf_copy, // #463 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) +VM_buf_sort, // #464 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) +VM_buf_implode, // #465 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) +VM_bufstr_get, // #466 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) +VM_bufstr_set, // #467 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) +VM_bufstr_add, // #468 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) +VM_bufstr_free, // #469 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) +NULL, // #470 +VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) +VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) +VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) +VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) +VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) +VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) +VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_SV_STRINGCOLORFUNCTIONS) +VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) +VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) +VM_strtolower, // #480 string(string s) VM_strtolower (DP_QC_STRING_CASE_FUNCTIONS) +VM_strtoupper, // #481 string(string s) VM_strtoupper (DP_QC_STRING_CASE_FUNCTIONS) +VM_cvar_defstring, // #482 string(string s) cvar_defstring (DP_QC_CVAR_DEFSTRING) +VM_SV_pointsound, // #483 void(vector origin, string sample, float volume, float attenuation) (DP_SV_POINTSOUND) +VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) +VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) +VM_getsurfacepointattribute,// #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; +NULL, // #487 +NULL, // #488 +NULL, // #489 +NULL, // #490 +NULL, // #491 +NULL, // #492 +NULL, // #493 +VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) +VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) +VM_numentityfields, // #496 float() numentityfields = #496; (DP_QC_ENTITYDATA) +VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (DP_QC_ENTITYDATA) +VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) +VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) +VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) +VM_SV_WritePicture, // #501 +NULL, // #502 +VM_whichpack, // #503 string(string) whichpack = #503; +NULL, // #504 +NULL, // #505 +NULL, // #506 +NULL, // #507 +NULL, // #508 +NULL, // #509 +VM_uri_escape, // #510 string(string in) uri_escape = #510; +VM_uri_unescape, // #511 string(string in) uri_unescape = #511; +VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) +VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) +VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) +VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) +VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) +VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) +VM_gettime, // #519 float(float timer) gettime = #519; (DP_QC_GETTIME) +NULL, // #520 +NULL, // #521 +NULL, // #522 +NULL, // #523 +NULL, // #524 +NULL, // #525 +NULL, // #526 +NULL, // #527 +NULL, // #528 +VM_loadfromdata, // #529 +VM_loadfromfile, // #530 +VM_SV_setpause, // #531 void(float pause) setpause = #531; +VM_log, // #532 +VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) +VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) +VM_buf_loadfile, // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_buf_writefile, // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_bufstr_find, // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP) +VM_matchpattern, // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP) +NULL, // #539 +VM_physics_enable, // #540 void(entity e, float physics_enabled) physics_enable = #540; (DP_PHYSICS_ODE) +VM_physics_addforce, // #541 void(entity e, vector force, vector relative_ofs) physics_addforce = #541; (DP_PHYSICS_ODE) +VM_physics_addtorque, // #542 void(entity e, vector torque) physics_addtorque = #542; (DP_PHYSICS_ODE) +NULL, // #543 +NULL, // #544 +NULL, // #545 +NULL, // #546 +NULL, // #547 +NULL, // #548 +NULL, // #549 +NULL, // #550 +NULL, // #551 +NULL, // #552 +NULL, // #553 +NULL, // #554 +NULL, // #555 +NULL, // #556 +NULL, // #557 +NULL, // #558 +NULL, // #559 +NULL, // #560 +NULL, // #561 +NULL, // #562 +NULL, // #563 +NULL, // #564 +NULL, // #565 +NULL, // #566 +NULL, // #567 +NULL, // #568 +NULL, // #569 +NULL, // #570 +NULL, // #571 +NULL, // #572 +NULL, // #573 +NULL, // #574 +NULL, // #575 +NULL, // #576 +NULL, // #577 +NULL, // #578 +NULL, // #579 +NULL, // #580 +NULL, // #581 +NULL, // #582 +NULL, // #583 +NULL, // #584 +NULL, // #585 +NULL, // #586 +NULL, // #587 +NULL, // #588 +NULL, // #589 +NULL, // #590 +NULL, // #591 +NULL, // #592 +NULL, // #593 +NULL, // #594 +NULL, // #595 +NULL, // #596 +NULL, // #597 +NULL, // #598 +NULL, // #599 +NULL, // #600 +NULL, // #601 +NULL, // #602 +NULL, // #603 +NULL, // #604 +VM_callfunction, // #605 +VM_writetofile, // #606 +VM_isfunction, // #607 +NULL, // #608 +NULL, // #609 +NULL, // #610 +NULL, // #611 +NULL, // #612 +VM_parseentitydata, // #613 +NULL, // #614 +NULL, // #615 +NULL, // #616 +NULL, // #617 +NULL, // #618 +NULL, // #619 +NULL, // #620 +NULL, // #621 +NULL, // #622 +NULL, // #623 +VM_SV_getextresponse, // #624 string getextresponse(void) +NULL, // #625 +NULL, // #626 +VM_sprintf, // #627 string sprintf(string format, ...) +VM_getsurfacenumtriangles, // #628 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACETRIANGLE) +VM_getsurfacetriangle, // #629 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACETRIANGLE) +NULL, // #630 +NULL, // #631 +NULL, // #632 +NULL, // #633 +NULL, // #634 +NULL, // #635 +NULL, // #636 +NULL, // #637 +NULL, // #638 +VM_digest_hex, // #639 +NULL, // #640 +}; + +const int vm_sv_numbuiltins = sizeof(vm_sv_builtins) / sizeof(prvm_builtin_t); + +void SVVM_init_cmd(prvm_prog_t *prog) +{ + VM_Cmd_Init(prog); +} + +void SVVM_reset_cmd(prvm_prog_t *prog) +{ + World_End(&sv.world); + if(PRVM_serverfunction(SV_Shutdown)) + { + func_t s = PRVM_serverfunction(SV_Shutdown); + PRVM_serverglobalfloat(time) = sv.time; + PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again + prog->ExecuteProgram(prog, s,"SV_Shutdown() required"); + } + + VM_Cmd_Reset(prog); +} diff --git a/app/jni/sys.h b/app/jni/sys.h new file mode 100644 index 0000000..e5247d3 --- /dev/null +++ b/app/jni/sys.h @@ -0,0 +1,119 @@ +/* +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. + +*/ +// sys.h -- non-portable functions + +#ifndef SYS_H +#define SYS_H + +extern cvar_t sys_usenoclockbutbenchmark; + +// +// DLL management +// + +// Win32 specific +#ifdef WIN32 +# include +typedef HMODULE dllhandle_t; + +// Other platforms +#else + typedef void* dllhandle_t; +#endif + +typedef struct dllfunction_s +{ + const char *name; + void **funcvariable; +} +dllfunction_t; + +/*! Loads a library. + * \param dllnames a NULL terminated array of possible names for the DLL you want to load. + * \param handle + * \param fcts + */ +qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts); +void Sys_UnloadLibrary (dllhandle_t* handle); +void* Sys_GetProcAddress (dllhandle_t handle, const char* name); + +/// called early in Host_Init +void Sys_InitConsole (void); +/// called after command system is initialized but before first Con_Print +void Sys_Init_Commands (void); + + +/// \returns current timestamp +char *Sys_TimeString(const char *timeformat); + +// +// system IO interface (these are the sys functions that need to be implemented in a new driver atm) +// + +/// an error will cause the entire program to exit +void Sys_Error (const char *error, ...) DP_FUNC_PRINTF(1) DP_FUNC_NORETURN; + +/// (may) output text to terminal which launched program +void Sys_PrintToTerminal(const char *text); +void Sys_PrintfToTerminal(const char *fmt, ...); + +/// INFO: This is only called by Host_Shutdown so we dont need testing for recursion +void Sys_Shutdown (void); +void Sys_Quit (int returnvalue); + +/*! on some build/platform combinations (such as Linux gcc with the -pg + * profiling option) this can turn on/off profiling, used primarily to limit + * profiling to certain areas of the code, such as ingame performance without + * regard for loading/shutdown performance (-profilegameonly on commandline) + */ +void Sys_AllowProfiling (qboolean enable); + +typedef struct sys_cleantime_s +{ + double dirtytime; // last value gotten from Sys_DirtyTime() + double cleantime; // sanitized linearly increasing time since app start +} +sys_cleantime_t; + +double Sys_DirtyTime(void); + +void Sys_ProvideSelfFD (void); + +char *Sys_ConsoleInput (void); + +/// called to yield for a little bit so as not to hog cpu when paused or debugging +void Sys_Sleep(int microseconds); + +/// Perform Key_Event () callbacks until the input que is empty +void Sys_SendKeyEvents (void); + +char *Sys_GetClipboardData (void); + +extern qboolean sys_supportsdlgetticks; +unsigned int Sys_SDL_GetTicks (void); // wrapper to call SDL_GetTicks +void Sys_SDL_Delay (unsigned int milliseconds); // wrapper to call SDL_Delay + +/// called to set process priority for dedicated servers +void Sys_InitProcessNice (void); +void Sys_MakeProcessNice (void); +void Sys_MakeProcessMean (void); + +#endif + diff --git a/app/jni/sys_linux.c b/app/jni/sys_linux.c new file mode 100644 index 0000000..63c5f90 --- /dev/null +++ b/app/jni/sys_linux.c @@ -0,0 +1,200 @@ + +#ifdef WIN32 +#include +#include +#include +#include "conio.h" +#else +#include +#include +#include +#endif + +#include + +#include "quakedef.h" + +// ======================================================================= +// General routines +// ======================================================================= +void Sys_Shutdown (void) +{ +#ifdef FNDELAY + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); +#endif + fflush(stdout); + exit(0); +} + +void Sys_Error (const char *error, ...) +{ + va_list argptr; + char string[MAX_INPUTLINE]; + +// change stdin to non blocking +#ifdef FNDELAY + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); +#endif + + va_start (argptr,error); + dpvsnprintf (string, sizeof (string), error, argptr); + va_end (argptr); + + Con_Printf ("Quake Error: %s\n", string); + + Host_Shutdown (); + exit (1); +} + +static int outfd = 1; +void Sys_PrintToTerminal(const char *text) +{ + if(outfd < 0) + return; +#ifdef FNDELAY + // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0). + // this is because both go to /dev/tty by default! + { + int origflags = fcntl (outfd, F_GETFL, 0); + fcntl (outfd, F_SETFL, origflags & ~FNDELAY); +#endif +#ifdef WIN32 +#define write _write +#endif + while(*text) + { + fs_offset_t written = (fs_offset_t)write(outfd, text, strlen(text)); + if(written <= 0) + break; // sorry, I cannot do anything about this error - without an output + text += written; + } +#ifdef FNDELAY + fcntl (outfd, F_SETFL, origflags); + } +#endif + //fprintf(stdout, "%s", text); +} + +char *Sys_ConsoleInput(void) +{ + //if (cls.state == ca_dedicated) + { + static char text[MAX_INPUTLINE]; + static unsigned int len = 0; +#ifdef WIN32 + int c; + + // read a line out + while (_kbhit ()) + { + c = _getch (); + if (c == '\r') + { + text[len] = '\0'; + _putch ('\n'); + len = 0; + return text; + } + if (c == '\b') + { + if (len) + { + _putch (c); + _putch (' '); + _putch (c); + len--; + } + continue; + } + if (len < sizeof (text) - 1) + { + _putch (c); + text[len] = c; + len++; + } + } +#else + fd_set fdset; + struct timeval timeout; + FD_ZERO(&fdset); + FD_SET(0, &fdset); // stdin + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select (1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(0, &fdset)) + { + len = read (0, text, sizeof(text) - 1); + if (len >= 1) + { + // rip off the \n and terminate + // div0: WHY? console code can deal with \n just fine + // this caused problems with pasting stuff into a terminal window + // so, not ripping off the \n, but STILL keeping a NUL terminator + text[len] = 0; + return text; + } + } +#endif + } + return NULL; +} + +char *Sys_GetClipboardData (void) +{ + return NULL; +} + +void Sys_InitConsole (void) +{ +} + +int main (int argc, char **argv) +{ + signal(SIGFPE, SIG_IGN); + + com_argc = argc; + int argvsize=0; + int i; + for (i=0; i +# include // timeGetTime +# include // localtime +#ifdef _MSC_VER +#pragma comment(lib, "winmm.lib") +#endif +#else +# include +# include +# include +# include +# ifdef SUPPORTDLL +# include +# endif +#endif + +static char sys_timestring[128]; +char *Sys_TimeString(const char *timeformat) +{ + time_t mytime = time(NULL); +#if _MSC_VER >= 1400 + struct tm mytm; + localtime_s(&mytm, &mytime); + strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm); +#else + strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime)); +#endif + return sys_timestring; +} + +extern void QC_exit(int exitCode); + +extern qboolean host_shuttingdown; +void Sys_Quit (int returnvalue) +{ + if (COM_CheckParm("-profilegameonly")) + Sys_AllowProfiling(false); + + //Inform the main rendering loop we are exiting the game + QC_exit(1); + //host_shuttingdown = true; + //Host_Shutdown(); +} + +#if defined(__linux__) || defined(__FreeBSD__) +#ifdef __cplusplus +extern "C" +#endif +int moncontrol(int); +#endif + +void Sys_AllowProfiling(qboolean enable) +{ +#if defined(__linux__) || defined(__FreeBSD__) + //moncontrol(enable); +#endif +} + + +/* +=============================================================================== + +DLL MANAGEMENT + +=============================================================================== +*/ + +static qboolean Sys_LoadLibraryFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qboolean complain, qboolean has_next) +{ + const dllfunction_t *func; + if(dllhandle) + { + for (func = fcts; func && func->name != NULL; func++) + if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name))) + { + if(complain) + { + Con_DPrintf (" - missing function \"%s\" - broken library!", func->name); + if(has_next) + Con_DPrintf("\nContinuing with"); + } + goto notfound; + } + return true; + + notfound: + for (func = fcts; func && func->name != NULL; func++) + *func->funcvariable = NULL; + } + return false; +} + +qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts) +{ +#ifdef SUPPORTDLL + const dllfunction_t *func; + dllhandle_t dllhandle = 0; + unsigned int i; + + if (handle == NULL) + return false; + +#ifndef WIN32 +#ifdef PREFER_PRELOAD + dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL); + if(Sys_LoadLibraryFunctions(dllhandle, fcts, false, false)) + { + Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]); + *handle = dllhandle; + return true; + } + else + Sys_UnloadLibrary(&dllhandle); +notfound: +#endif +#endif + + // Initializations + for (func = fcts; func && func->name != NULL; func++) + *func->funcvariable = NULL; + + // Try every possible name + Con_DPrintf ("Trying to load library..."); + for (i = 0; dllnames[i] != NULL; i++) + { + Con_DPrintf (" \"%s\"", dllnames[i]); +#ifdef WIN32 +# ifndef DONT_USE_SETDLLDIRECTORY +# ifdef _WIN64 + SetDllDirectory("bin64"); +# else + SetDllDirectory("bin32"); +# endif +# endif + dllhandle = LoadLibrary (dllnames[i]); + // no need to unset this - we want ALL dlls to be loaded from there, anyway +#else + dllhandle = dlopen (dllnames[i], RTLD_LAZY | RTLD_GLOBAL); +#endif + if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(com_argv[0], '/')))) + break; + else + Sys_UnloadLibrary (&dllhandle); + } + + // see if the names can be loaded relative to the executable path + // (this is for Mac OSX which does not check next to the executable) + if (!dllhandle && strrchr(com_argv[0], '/')) + { + char path[MAX_OSPATH]; + strlcpy(path, com_argv[0], sizeof(path)); + strrchr(path, '/')[1] = 0; + for (i = 0; dllnames[i] != NULL; i++) + { + char temp[MAX_OSPATH]; + strlcpy(temp, path, sizeof(temp)); + strlcat(temp, dllnames[i], sizeof(temp)); + Con_DPrintf (" \"%s\"", temp); +#ifdef WIN32 + dllhandle = LoadLibrary (temp); +#else + dllhandle = dlopen (temp, RTLD_LAZY | RTLD_GLOBAL); +#endif + if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, dllnames[i+1] != NULL)) + break; + else + Sys_UnloadLibrary (&dllhandle); + } + } + + // No DLL found + if (! dllhandle) + { + Con_DPrintf(" - failed.\n"); + return false; + } + + Con_DPrintf(" - loaded.\n"); + + *handle = dllhandle; + return true; +#else + return false; +#endif +} + +void Sys_UnloadLibrary (dllhandle_t* handle) +{ +#ifdef SUPPORTDLL + if (handle == NULL || *handle == NULL) + return; + +#ifdef WIN32 + FreeLibrary (*handle); +#else + dlclose (*handle); +#endif + + *handle = NULL; +#endif +} + +void* Sys_GetProcAddress (dllhandle_t handle, const char* name) +{ +#ifdef SUPPORTDLL +#ifdef WIN32 + return (void *)GetProcAddress (handle, name); +#else + return (void *)dlsym (handle, name); +#endif +#else + return NULL; +#endif +} + +#ifdef WIN32 +# define HAVE_TIMEGETTIME 1 +# define HAVE_QUERYPERFORMANCECOUNTER 1 +# define HAVE_Sleep 1 +#endif + +#if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES) +# define HAVE_CLOCKGETTIME 1 +#endif + +#ifndef WIN32 +// FIXME improve this check, manpage hints to DST_NONE +# define HAVE_GETTIMEOFDAY 1 +#endif + +#ifndef WIN32 +// on Win32, select() cannot be used with all three FD list args being NULL according to MSDN +// (so much for POSIX...) +# ifdef FD_SET +# define HAVE_SELECT 1 +# endif +#endif + +#ifndef WIN32 +// FIXME improve this check +# define HAVE_USLEEP 1 +#endif + +// this one is referenced elsewhere +cvar_t sys_usenoclockbutbenchmark = {CVAR_SAVE, "sys_usenoclockbutbenchmark", "0", "don't use ANY real timing, and simulate a clock (for benchmarking); the game then runs as fast as possible. Run a QC mod with bots that does some stuff, then does a quit at the end, to benchmark a server. NEVER do this on a public server."}; + +// these are not +static cvar_t sys_debugsleep = {0, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"}; +static cvar_t sys_usesdlgetticks = {CVAR_SAVE, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"}; +static cvar_t sys_usesdldelay = {CVAR_SAVE, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"}; +#if HAVE_QUERYPERFORMANCECOUNTER +static cvar_t sys_usequeryperformancecounter = {CVAR_SAVE, "sys_usequeryperformancecounter", "0", "use windows QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) for timing rather than timeGetTime function (which has issues on some motherboards)"}; +#endif +#if HAVE_CLOCKGETTIME +static cvar_t sys_useclockgettime = {CVAR_SAVE, "sys_useclockgettime", "0", "use POSIX clock_gettime function (which has issues if the system clock speed is far off, as it can't get fixed by NTP) for timing rather than gettimeofday (which has issues if the system time is stepped by ntpdate, or apparently on some Xen installations)"}; +#endif + +static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow" + +void Sys_Init_Commands (void) +{ + Cvar_RegisterVariable(&sys_debugsleep); + Cvar_RegisterVariable(&sys_usenoclockbutbenchmark); +#if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY + if(sys_supportsdlgetticks) + { + Cvar_RegisterVariable(&sys_usesdlgetticks); + Cvar_RegisterVariable(&sys_usesdldelay); + } +#endif +#if HAVE_QUERYPERFORMANCECOUNTER + Cvar_RegisterVariable(&sys_usequeryperformancecounter); +#endif +#if HAVE_CLOCKGETTIME + Cvar_RegisterVariable(&sys_useclockgettime); +#endif +} + +double Sys_DirtyTime(void) +{ + // first all the OPTIONAL timers + + // benchmark timer (fake clock) + if(sys_usenoclockbutbenchmark.integer) + { + double old_benchmark_time = benchmark_time; + benchmark_time += 1; + if(benchmark_time == old_benchmark_time) + Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry"); + return benchmark_time * 0.000001; + } +#if HAVE_QUERYPERFORMANCECOUNTER + if (sys_usequeryperformancecounter.integer) + { + // LordHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine. + // QueryPerformanceCounter + // platform: + // Windows 95/98/ME/NT/2000/XP + // features: + // very accurate (CPU cycles) + // known issues: + // does not necessarily match realtime too well (tends to get faster and faster in win98) + // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors) + double timescale; + LARGE_INTEGER PerformanceFreq; + LARGE_INTEGER PerformanceCount; + + if (QueryPerformanceFrequency (&PerformanceFreq)) + { + QueryPerformanceCounter (&PerformanceCount); + + #ifdef __BORLANDC__ + timescale = 1.0 / ((double) PerformanceFreq.u.LowPart + (double) PerformanceFreq.u.HighPart * 65536.0 * 65536.0); + return ((double) PerformanceCount.u.LowPart + (double) PerformanceCount.u.HighPart * 65536.0 * 65536.0) * timescale; + #else + timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0); + return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale; + #endif + } + else + { + Con_Printf("No hardware timer available\n"); + // fall back to other clock sources + Cvar_SetValueQuick(&sys_usequeryperformancecounter, false); + } + } +#endif + +#if HAVE_CLOCKGETTIME + if (sys_useclockgettime.integer) + { + struct timespec ts; +# ifdef CLOCK_MONOTONIC + // linux + clock_gettime(CLOCK_MONOTONIC, &ts); +# else + // sunos + clock_gettime(CLOCK_HIGHRES, &ts); +# endif + return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0; + } +#endif + + // now all the FALLBACK timers + if(sys_supportsdlgetticks && sys_usesdlgetticks.integer) + return (double) Sys_SDL_GetTicks() / 1000.0; +#if HAVE_GETTIMEOFDAY + { + struct timeval tp; + gettimeofday(&tp, NULL); + return (double) tp.tv_sec + tp.tv_usec / 1000000.0; + } +#elif HAVE_TIMEGETTIME + { + static int firsttimegettime = true; + // timeGetTime + // platform: + // Windows 95/98/ME/NT/2000/XP + // features: + // reasonable accuracy (millisecond) + // issues: + // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter) + + // make sure the timer is high precision, otherwise different versions of windows have varying accuracy + if (firsttimegettime) + { + timeBeginPeriod(1); + firsttimegettime = false; + } + + return (double) timeGetTime() / 1000.0; + } +#else + // fallback for using the SDL timer if no other timer is available + // this calls Sys_Error() if not linking against SDL + return (double) Sys_SDL_GetTicks() / 1000.0; +#endif +} + +void Sys_Sleep(int microseconds) +{ + double t = 0; + if(sys_usenoclockbutbenchmark.integer) + { + if(microseconds) + { + double old_benchmark_time = benchmark_time; + benchmark_time += microseconds; + if(benchmark_time == old_benchmark_time) + Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry"); + } + return; + } + if(sys_debugsleep.integer) + { + t = Sys_DirtyTime(); + } + if(sys_supportsdlgetticks && sys_usesdldelay.integer) + { + Sys_SDL_Delay(microseconds / 1000); + } +#if HAVE_SELECT + else + { + struct timeval tv; + tv.tv_sec = microseconds / 1000000; + tv.tv_usec = microseconds % 1000000; + select(0, NULL, NULL, NULL, &tv); + } +#elif HAVE_USLEEP + else + { + usleep(microseconds); + } +#elif HAVE_Sleep + else + { + Sleep(microseconds / 1000); + } +#else + else + { + Sys_SDL_Delay(microseconds / 1000); + } +#endif + if(sys_debugsleep.integer) + { + t = Sys_DirtyTime() - t; + Sys_PrintfToTerminal("%d %d # debugsleep\n", microseconds, (unsigned int)(t * 1000000)); + } +} + +void Sys_PrintfToTerminal(const char *fmt, ...) +{ + va_list argptr; + char msg[MAX_INPUTLINE]; + + va_start(argptr,fmt); + dpvsnprintf(msg,sizeof(msg),fmt,argptr); + va_end(argptr); + + Sys_PrintToTerminal(msg); +} + +#ifndef WIN32 +static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize) +{ + const char *p = PATH; + const char *q; + if(p && name) + { + while((q = strchr(p, ':'))) + { + dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name); + if(FS_SysFileExists(buf)) + return buf; + p = q + 1; + } + if(!q) // none found - try the last item + { + dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name); + if(FS_SysFileExists(buf)) + return buf; + } + } + return name; +} +#endif + +static const char *Sys_FindExecutableName(void) +{ +#if defined(WIN32) + return com_argv[0]; +#else + static char exenamebuf[MAX_OSPATH+1]; + ssize_t n = -1; +#if defined(__FreeBSD__) + n = readlink("/proc/curproc/file", exenamebuf, sizeof(exenamebuf)-1); +#elif defined(__linux__) + n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1); +#endif + if(n > 0 && (size_t)(n) < sizeof(exenamebuf)) + { + exenamebuf[n] = 0; + return exenamebuf; + } + if(strchr(com_argv[0], '/')) + return com_argv[0]; // possibly a relative path + else + return Sys_FindInPATH(com_argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf)); +#endif +} + +void Sys_ProvideSelfFD(void) +{ + if(com_selffd != -1) + return; + com_selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false); +} + +// for x86 cpus only... (x64 has SSE2_PRESENT) +#if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT) +// code from SDL, shortened as we can expect CPUID to work +static int CPUID_Features(void) +{ + int features = 0; +# if defined(__GNUC__) && defined(__i386__) + __asm__ ( +" movl %%ebx,%%edi\n" +" xorl %%eax,%%eax \n" +" incl %%eax \n" +" cpuid # Get family/model/stepping/features\n" +" movl %%edx,%0 \n" +" movl %%edi,%%ebx\n" + : "=m" (features) + : + : "%eax", "%ecx", "%edx", "%edi" + ); +# elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) + __asm { + xor eax, eax + inc eax + cpuid ; Get family/model/stepping/features + mov features, edx + } +# else +# error SSE_POSSIBLE set but no CPUID implementation +# endif + return features; +} +#endif + +#ifdef SSE_POSSIBLE +qboolean Sys_HaveSSE(void) +{ + // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection + if(COM_CheckParm("-nosse")) + return false; +#ifdef SSE_PRESENT + return true; +#else + // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection + if(COM_CheckParm("-forcesse") || COM_CheckParm("-forcesse2")) + return true; + if(CPUID_Features() & (1 << 25)) + return true; + return false; +#endif +} + +qboolean Sys_HaveSSE2(void) +{ + // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection + if(COM_CheckParm("-nosse") || COM_CheckParm("-nosse2")) + return false; +#ifdef SSE2_PRESENT + return true; +#else + // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection + if(COM_CheckParm("-forcesse2")) + return true; + if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26 + return true; + return false; +#endif +} +#endif + +/// called to set process priority for dedicated servers +#if defined(__linux__) +#include +#include +static int nicelevel; +static qboolean nicepossible; +static qboolean isnice; +void Sys_InitProcessNice (void) +{ + struct rlimit lim; + nicepossible = false; + if(COM_CheckParm("-nonice")) + return; + errno = 0; + nicelevel = getpriority(PRIO_PROCESS, 0); + if(errno) + { + Con_Printf("Kernel does not support reading process priority - cannot use niceness\n"); + return; + } + if(getrlimit(RLIMIT_NICE, &lim)) + { + Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n"); + return; + } + if(lim.rlim_cur != RLIM_INFINITY && nicelevel < (int) (20 - lim.rlim_cur)) + { + Con_Printf("Current nice level is below the soft limit - cannot use niceness\n"); + return; + } + nicepossible = true; + isnice = false; +} +void Sys_MakeProcessNice (void) +{ + if(!nicepossible) + return; + if(isnice) + return; + Con_DPrintf("Process is becoming 'nice'...\n"); + if(setpriority(PRIO_PROCESS, 0, 19)) + Con_Printf("Failed to raise nice level to %d\n", 19); + isnice = true; +} +void Sys_MakeProcessMean (void) +{ + if(!nicepossible) + return; + if(!isnice) + return; + Con_DPrintf("Process is becoming 'mean'...\n"); + if(setpriority(PRIO_PROCESS, 0, nicelevel)) + Con_Printf("Failed to lower nice level to %d\n", nicelevel); + isnice = false; +} +#else +void Sys_InitProcessNice (void) +{ +} +void Sys_MakeProcessNice (void) +{ +} +void Sys_MakeProcessMean (void) +{ +} +#endif diff --git a/app/jni/thread.h b/app/jni/thread.h new file mode 100644 index 0000000..7f590a4 --- /dev/null +++ b/app/jni/thread.h @@ -0,0 +1,42 @@ +#ifndef THREAD_H + +// enable Sys_PrintfToTerminal calls on nearly every threading call +//#define THREADDEBUG +//#define THREADDISABLE +// use recursive mutex (non-posix) extensions in thread_pthread +#define THREADRECURSIVE + +#define Thread_CreateMutex() (_Thread_CreateMutex(__FILE__, __LINE__)) +#define Thread_DestroyMutex(m) (_Thread_DestroyMutex(m, __FILE__, __LINE__)) +#define Thread_LockMutex(m) (_Thread_LockMutex(m, __FILE__, __LINE__)) +#define Thread_UnlockMutex(m) (_Thread_UnlockMutex(m, __FILE__, __LINE__)) +#define Thread_CreateCond() (_Thread_CreateCond(__FILE__, __LINE__)) +#define Thread_DestroyCond(cond) (_Thread_DestroyCond(cond, __FILE__, __LINE__)) +#define Thread_CondSignal(cond) (_Thread_CondSignal(cond, __FILE__, __LINE__)) +#define Thread_CondBroadcast(cond) (_Thread_CondBroadcast(cond, __FILE__, __LINE__)) +#define Thread_CondWait(cond, mutex) (_Thread_CondWait(cond, mutex, __FILE__, __LINE__)) +#define Thread_CreateThread(fn, data) (_Thread_CreateThread(fn, data, __FILE__, __LINE__)) +#define Thread_WaitThread(thread, retval) (_Thread_WaitThread(thread, retval, __FILE__, __LINE__)) +#define Thread_CreateBarrier(count) (_Thread_CreateBarrier(count, __FILE__, __LINE__)) +#define Thread_DestroyBarrier(barrier) (_Thread_DestroyBarrier(barrier, __FILE__, __LINE__)) +#define Thread_WaitBarrier(barrier) (_Thread_WaitBarrier(barrier, __FILE__, __LINE__)) + +int Thread_Init(void); +void Thread_Shutdown(void); +qboolean Thread_HasThreads(void); +void *_Thread_CreateMutex(const char *filename, int fileline); +void _Thread_DestroyMutex(void *mutex, const char *filename, int fileline); +int _Thread_LockMutex(void *mutex, const char *filename, int fileline); +int _Thread_UnlockMutex(void *mutex, const char *filename, int fileline); +void *_Thread_CreateCond(const char *filename, int fileline); +void _Thread_DestroyCond(void *cond, const char *filename, int fileline); +int _Thread_CondSignal(void *cond, const char *filename, int fileline); +int _Thread_CondBroadcast(void *cond, const char *filename, int fileline); +int _Thread_CondWait(void *cond, void *mutex, const char *filename, int fileline); +void *_Thread_CreateThread(int (*fn)(void *), void *data, const char *filename, int fileline); +int _Thread_WaitThread(void *thread, int retval, const char *filename, int fileline); +void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline); +void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline); +void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline); + +#endif diff --git a/app/jni/thread_pthread.c b/app/jni/thread_pthread.c new file mode 100644 index 0000000..7bded93 --- /dev/null +++ b/app/jni/thread_pthread.c @@ -0,0 +1,226 @@ +#include "quakedef.h" +#include "thread.h" +#ifdef THREADRECURSIVE +#define __USE_UNIX98 +#include +#endif +#include + + +int Thread_Init(void) +{ + return 0; +} + +void Thread_Shutdown(void) +{ +} + +qboolean Thread_HasThreads(void) +{ + return true; +} + +void *_Thread_CreateMutex(const char *filename, int fileline) +{ +#ifdef THREADRECURSIVE + pthread_mutexattr_t attr; +#endif + pthread_mutex_t *mutexp = (pthread_mutex_t *) Z_Malloc(sizeof(pthread_mutex_t)); +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p mutex create %s:%i\n" , mutexp, filename, fileline); +#endif +#ifdef THREADRECURSIVE + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(mutexp, &attr); + pthread_mutexattr_destroy(&attr); +#else + pthread_mutex_init(mutexp, NULL); +#endif + return mutexp; +} + +void _Thread_DestroyMutex(void *mutex, const char *filename, int fileline) +{ + pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p mutex destroy %s:%i\n", mutex, filename, fileline); +#endif + pthread_mutex_destroy(mutexp); + Z_Free(mutexp); +} + +int _Thread_LockMutex(void *mutex, const char *filename, int fileline) +{ + pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p mutex lock %s:%i\n" , mutex, filename, fileline); +#endif + return pthread_mutex_lock(mutexp); +} + +int _Thread_UnlockMutex(void *mutex, const char *filename, int fileline) +{ + pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p mutex unlock %s:%i\n" , mutex, filename, fileline); +#endif + return pthread_mutex_unlock(mutexp); +} + +void *_Thread_CreateCond(const char *filename, int fileline) +{ + pthread_cond_t *condp = (pthread_cond_t *) Z_Malloc(sizeof(pthread_cond_t)); + pthread_cond_init(condp, NULL); +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p cond create %s:%i\n" , condp, filename, fileline); +#endif + return condp; +} + +void _Thread_DestroyCond(void *cond, const char *filename, int fileline) +{ + pthread_cond_t *condp = (pthread_cond_t *) cond; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p cond destroy %s:%i\n" , cond, filename, fileline); +#endif + pthread_cond_destroy(condp); + Z_Free(condp); +} + +int _Thread_CondSignal(void *cond, const char *filename, int fileline) +{ + pthread_cond_t *condp = (pthread_cond_t *) cond; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p cond signal %s:%i\n" , cond, filename, fileline); +#endif + return pthread_cond_signal(condp); +} + +int _Thread_CondBroadcast(void *cond, const char *filename, int fileline) +{ + pthread_cond_t *condp = (pthread_cond_t *) cond; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p cond broadcast %s:%i\n" , cond, filename, fileline); +#endif + return pthread_cond_broadcast(condp); +} + +int _Thread_CondWait(void *cond, void *mutex, const char *filename, int fileline) +{ + pthread_cond_t *condp = (pthread_cond_t *) cond; + pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p cond wait %s:%i\n" , cond, filename, fileline); +#endif + return pthread_cond_wait(condp, mutexp); +} + +void *_Thread_CreateThread(int (*fn)(void *), void *data, const char *filename, int fileline) +{ + pthread_t *threadp = (pthread_t *) Z_Malloc(sizeof(pthread_t)); +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p thread create %s:%i\n" , threadp, filename, fileline); +#endif + int r = pthread_create(threadp, NULL, (void * (*) (void *)) fn, data); + if(r) + { + Z_Free(threadp); + return NULL; + } + return threadp; +} + +int _Thread_WaitThread(void *thread, int retval, const char *filename, int fileline) +{ + pthread_t *threadp = (pthread_t *) thread; + void *status = (void *) (intptr_t) retval; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p thread wait %s:%i\n" , thread, filename, fileline); +#endif + pthread_join(*threadp, &status); + Z_Free(threadp); + return (int) (intptr_t) status; +} + +#ifdef PTHREAD_BARRIER_SERIAL_THREAD +void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline) +{ + pthread_barrier_t *b = (pthread_barrier_t *) Z_Malloc(sizeof(pthread_barrier_t)); +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p barrier create(%d) %s:%i\n", b, count, filename, fileline); +#endif + pthread_barrier_init(b, NULL, count); + return (void *) b; +} + +void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline) +{ + pthread_barrier_t *b = (pthread_barrier_t *) barrier; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p barrier destroy %s:%i\n", b, filename, fileline); +#endif + pthread_barrier_destroy(b); +} + +void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline) +{ + pthread_barrier_t *b = (pthread_barrier_t *) barrier; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p barrier wait %s:%i\n", b, filename, fileline); +#endif + pthread_barrier_wait(b); +} +#else +// standard barrier implementation using conds and mutexes +// see: http://www.howforge.com/implementing-barrier-in-pthreads +typedef struct { + unsigned int needed; + unsigned int called; + void *mutex; + void *cond; +} barrier_t; + +void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline) +{ + volatile barrier_t *b = (volatile barrier_t *) Z_Malloc(sizeof(barrier_t)); +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p barrier create(%d) %s:%i\n", b, count, filename, fileline); +#endif + b->needed = count; + b->called = 0; + b->mutex = Thread_CreateMutex(); + b->cond = Thread_CreateCond(); + return (void *) b; +} + +void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline) +{ + volatile barrier_t *b = (volatile barrier_t *) barrier; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p barrier destroy %s:%i\n", b, filename, fileline); +#endif + Thread_DestroyMutex(b->mutex); + Thread_DestroyCond(b->cond); +} + +void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline) +{ + volatile barrier_t *b = (volatile barrier_t *) barrier; +#ifdef THREADDEBUG + Sys_PrintfToTerminal("%p barrier wait %s:%i\n", b, filename, fileline); +#endif + Thread_LockMutex(b->mutex); + b->called++; + if (b->called == b->needed) { + b->called = 0; + Thread_CondBroadcast(b->cond); + } else { + do { + Thread_CondWait(b->cond, b->mutex); + } while(b->called); + } + Thread_UnlockMutex(b->mutex); +} +#endif diff --git a/app/jni/timing.h b/app/jni/timing.h new file mode 100644 index 0000000..33da858 --- /dev/null +++ b/app/jni/timing.h @@ -0,0 +1,58 @@ +/* +Simple helper macros to time blocks or statements. + +Copyright (C) 2007 Frank Richter + +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. + +*/ + +#ifndef __TIMING_H__ +#define __TIMING_H__ + +#if defined(DO_TIMING) + +#define TIMING_BEGIN double _timing_end_, _timing_start_ = Sys_DirtyTime(); +#define TIMING_END_STR(S) \ + _timing_end_ = Sys_DirtyTime(); \ + Con_Printf ("%s: %.3g s\n", S, _timing_end_ - _timing_start_); +#define TIMING_END TIMING_END_STR(__FUNCTION__) + +#define TIMING_INTERMEDIATE(S) \ + { \ + double currentTime = Sys_DirtyTime(); \ + Con_Printf ("%s: %.3g s\n", S, currentTime - _timing_start_); \ + } + +#define TIMING_TIMESTATEMENT(Stmt) \ + { \ + TIMING_BEGIN \ + Stmt; \ + TIMING_END_STR(#Stmt); \ + } + +#else + +#define TIMING_BEGIN +#define TIMING_END_STR(S) +#define TIMING_END +#define TIMING_INTERMEDIATE(S) +#define TIMING_TIMESTATEMENT(Stmt) Stmt + +#endif + +#endif // __TIMING_H__ + diff --git a/app/jni/utf8lib.c b/app/jni/utf8lib.c new file mode 100644 index 0000000..76cc813 --- /dev/null +++ b/app/jni/utf8lib.c @@ -0,0 +1,3055 @@ +#include "quakedef.h" +#include "utf8lib.h" + +/* +================================================================================ +Initialization of UTF-8 support and new cvars. +================================================================================ +*/ +// for compatibility this defaults to 0 +cvar_t utf8_enable = {CVAR_SAVE, "utf8_enable", "0", "Enable UTF-8 support. For compatibility, this is disabled by default in most games."}; + +void u8_Init(void) +{ + Cvar_RegisterVariable(&utf8_enable); +} + +/* +================================================================================ +UTF-8 encoding and decoding functions follow. +================================================================================ +*/ + +unsigned char utf8_lengths[256] = { // 0 = invalid + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // ascii characters + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 - 0xBF are within multibyte sequences + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // they could be interpreted as 2-byte starts but + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // the codepoint would be < 127 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0 and C1 would also result in overlong encodings + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + // with F5 the codepoint is above 0x10FFFF, + // F8-FB would start 5-byte sequences + // FC-FD would start 6-byte sequences + // ... +}; +Uchar utf8_range[5] = { + 1, // invalid - let's not allow the creation of 0-bytes :P + 1, // ascii minimum + 0x80, // 2-byte minimum + 0x800, // 3-byte minimum + 0x10000, // 4-byte minimum +}; + +/** Analyze the next character and return various information if requested. + * @param _s An utf-8 string. + * @param _start Filled with the start byte-offset of the next valid character + * @param _len Fileed with the length of the next valid character + * @param _ch Filled with the unicode value of the next character + * @param _maxlen Maximum number of bytes to read from _s + * @return Whether or not another valid character is in the string + */ +#define U8_ANALYZE_INFINITY 7 +static qboolean u8_analyze(const char *_s, size_t *_start, size_t *_len, Uchar *_ch, size_t _maxlen) +{ + const unsigned char *s = (const unsigned char*)_s; + size_t i, j; + size_t bits = 0; + Uchar ch; + + i = 0; +findchar: + while (i < _maxlen && s[i] && (bits = utf8_lengths[s[i]]) == 0) + ++i; + + if (i >= _maxlen || !s[i]) { + if (_start) *_start = i; + if (_len) *_len = 0; + return false; + } + + if (bits == 1) { // ascii + if (_start) *_start = i; + if (_len) *_len = 1; + if (_ch) *_ch = (Uchar)s[i]; + return true; + } + + ch = (s[i] & (0xFF >> bits)); + for (j = 1; j < bits; ++j) + { + if ( (s[i+j] & 0xC0) != 0x80 ) + { + i += j; + goto findchar; + } + ch = (ch << 6) | (s[i+j] & 0x3F); + } + if (ch < utf8_range[bits] || ch >= 0x10FFFF) + { + i += bits; + goto findchar; + } +#if 0 + // <0xC2 is always an overlong encoding, they're invalid, thus skipped + while (i < _maxlen && s[i] && s[i] >= 0x80 && s[i] < 0xC2) { + //fprintf(stderr, "skipping\n"); + ++i; + } + + // If we hit the end, well, we're out and invalid + if(i >= _maxlen || !s[i]) { + if (_start) *_start = i; + if (_len) *_len = 0; + return false; + } + + // I'll leave that in - if you remove it, also change the part below + // to support 1-byte chars correctly + if (s[i] < 0x80) + { + if (_start) *_start = i; + if (_len) *_len = 1; + if (_ch) *_ch = (Uchar)s[i]; + //fprintf(stderr, "valid ascii\n"); + return true; + } + + // Figure out the next char's length + bc = s[i]; + bits = 1; + // count the 1 bits, they're the # of bytes + for (bt = 0x40; bt && (bc & bt); bt >>= 1, ++bits); + if (!bt) + { + //fprintf(stderr, "superlong\n"); + ++i; + goto findchar; + } + if(i + bits > _maxlen) { + /* + if (_start) *_start = i; + if (_len) *_len = 0; + return false; + */ + ++i; + goto findchar; + } + // turn bt into a mask and give ch a starting value + --bt; + ch = (s[i] & bt); + // check the byte sequence for invalid bytes + for (j = 1; j < bits; ++j) + { + // valid bit value: 10xx xxxx + //if (s[i+j] < 0x80 || s[i+j] >= 0xC0) + if ( (s[i+j] & 0xC0) != 0x80 ) + { + //fprintf(stderr, "sequence of %i f'd at %i by %x\n", bits, j, (unsigned int)s[i+j]); + // this byte sequence is invalid, skip it + i += j; + // find a character after it + goto findchar; + } + // at the same time, decode the character + ch = (ch << 6) | (s[i+j] & 0x3F); + } + + // Now check the decoded byte for an overlong encoding + if ( (bits >= 2 && ch < 0x80) || + (bits >= 3 && ch < 0x800) || + (bits >= 4 && ch < 0x10000) || + ch >= 0x10FFFF // RFC 3629 + ) + { + i += bits; + //fprintf(stderr, "overlong: %i bytes for %x\n", bits, ch); + goto findchar; + } +#endif + + if (_start) + *_start = i; + if (_len) + *_len = bits; + if (_ch) + *_ch = ch; + //fprintf(stderr, "valid utf8\n"); + return true; +} + +/** Get the number of characters in an UTF-8 string. + * @param _s An utf-8 encoded null-terminated string. + * @return The number of unicode characters in the string. + */ +size_t u8_strlen(const char *_s) +{ + size_t st, ln; + size_t len = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) + return strlen(_s); + + while (*s) + { + // ascii char, skip u8_analyze + if (*s < 0x80) + { + ++len; + ++s; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + break; + // valid character, skip after it + s += st + ln; + ++len; + } + return len; +} + +static int colorcode_skipwidth(const unsigned char *s) +{ + if(*s == STRING_COLOR_TAG) + { + if(s[1] <= '9' && s[1] >= '0') // ^[0-9] found + { + return 2; + } + else if(s[1] == STRING_COLOR_RGB_TAG_CHAR && + ((s[2] >= '0' && s[2] <= '9') || (s[2] >= 'a' && s[2] <= 'f') || (s[2] >= 'A' && s[2] <= 'F')) && + ((s[3] >= '0' && s[3] <= '9') || (s[3] >= 'a' && s[3] <= 'f') || (s[3] >= 'A' && s[3] <= 'F')) && + ((s[4] >= '0' && s[4] <= '9') || (s[4] >= 'a' && s[4] <= 'f') || (s[4] >= 'A' && s[4] <= 'F'))) + { + return 5; + } + else if(s[1] == STRING_COLOR_TAG) + { + return 1; // special case, do NOT call colorcode_skipwidth for next char + } + } + return 0; +} + +/** Get the number of characters in a part of an UTF-8 string. + * @param _s An utf-8 encoded null-terminated string. + * @param n The maximum number of bytes. + * @return The number of unicode characters in the string. + */ +size_t u8_strnlen(const char *_s, size_t n) +{ + size_t st, ln; + size_t len = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) + { + len = strlen(_s); + return (len < n) ? len : n; + } + + while (*s && n) + { + // ascii char, skip u8_analyze + if (*s < 0x80) + { + ++len; + ++s; + --n; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + --n; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, n)) + break; + // valid character, see if it's still inside the range specified by n: + if (n < st + ln) + return len; + ++len; + n -= st + ln; + s += st + ln; + } + return len; +} + +static size_t u8_strnlen_colorcodes(const char *_s, size_t n) +{ + size_t st, ln; + size_t len = 0; + const unsigned char *s = (const unsigned char*)_s; + + while (*s && n) + { + int w = colorcode_skipwidth(s); + n -= w; + s += w; + if(w > 1) // == 1 means single caret + continue; + + // ascii char, skip u8_analyze + if (*s < 0x80 || !utf8_enable.integer) + { + ++len; + ++s; + --n; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + --n; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, n)) + break; + // valid character, see if it's still inside the range specified by n: + if (n < st + ln) + return len; + ++len; + n -= st + ln; + s += st + ln; + } + return len; +} + +/** Get the number of bytes used in a string to represent an amount of characters. + * @param _s An utf-8 encoded null-terminated string. + * @param n The number of characters we want to know the byte-size for. + * @return The number of bytes used to represent n characters. + */ +size_t u8_bytelen(const char *_s, size_t n) +{ + size_t st, ln; + size_t len = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) { + len = strlen(_s); + return (len < n) ? len : n; + } + + while (*s && n) + { + // ascii char, skip u8_analyze + if (*s < 0x80) + { + ++len; + ++s; + --n; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + ++len; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + break; + --n; + s += st + ln; + len += st + ln; + } + return len; +} + +static size_t u8_bytelen_colorcodes(const char *_s, size_t n) +{ + size_t st, ln; + size_t len = 0; + const unsigned char *s = (const unsigned char*)_s; + + while (*s && n) + { + int w = colorcode_skipwidth(s); + len += w; + s += w; + if(w > 1) // == 1 means single caret + continue; + + // ascii char, skip u8_analyze + if (*s < 0x80 || !utf8_enable.integer) + { + ++len; + ++s; + --n; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + ++len; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + break; + --n; + s += st + ln; + len += st + ln; + } + return len; +} + +/** Get the byte-index for a character-index. + * @param _s An utf-8 encoded string. + * @param i The character-index for which you want the byte offset. + * @param len If not null, character's length will be stored in there. + * @return The byte-index at which the character begins, or -1 if the string is too short. + */ +int u8_byteofs(const char *_s, size_t i, size_t *len) +{ + size_t st, ln; + size_t ofs = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) + { + if (strlen(_s) < i) + { + if (len) *len = 0; + return -1; + } + + if (len) *len = 1; + return i; + } + + st = ln = 0; + do + { + ofs += ln; + if (!u8_analyze((const char*)s + ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + return -1; + ofs += st; + } while(i-- > 0); + if (len) + *len = ln; + return ofs; +} + +/** Get the char-index for a byte-index. + * @param _s An utf-8 encoded string. + * @param i The byte offset for which you want the character index. + * @param len If not null, the offset within the character is stored here. + * @return The character-index, or -1 if the string is too short. + */ +int u8_charidx(const char *_s, size_t i, size_t *len) +{ + size_t st, ln; + size_t ofs = 0; + size_t pofs = 0; + int idx = 0; + const unsigned char *s = (const unsigned char*)_s; + + if (!utf8_enable.integer) + { + if (len) *len = 0; + return i; + } + + while (ofs < i && s[ofs]) + { + // ascii character, skip u8_analyze + if (s[ofs] < 0x80) + { + pofs = ofs; + ++idx; + ++ofs; + continue; + } + + // invalid, skip u8_analyze + if (s[ofs] < 0xC2) + { + ++ofs; + continue; + } + + if (!u8_analyze((const char*)s+ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + return -1; + // see if next char is after the bytemark + if (ofs + st > i) + { + if (len) + *len = i - pofs; + return idx; + } + ++idx; + pofs = ofs + st; + ofs += st + ln; + // see if bytemark is within the char + if (ofs > i) + { + if (len) + *len = i - pofs; + return idx; + } + } + if (len) *len = 0; + return idx; +} + +/** Get the byte offset of the previous byte. + * The result equals: + * prevchar_pos = u8_byteofs(text, u8_charidx(text, thischar_pos, NULL) - 1, NULL) + * @param _s An utf-8 encoded string. + * @param i The current byte offset. + * @return The byte offset of the previous character + */ +size_t u8_prevbyte(const char *_s, size_t i) +{ + size_t st, ln; + const unsigned char *s = (const unsigned char*)_s; + size_t lastofs = 0; + size_t ofs = 0; + + if (!utf8_enable.integer) + { + if (i > 0) + return i-1; + return 0; + } + + while (ofs < i && s[ofs]) + { + // ascii character, skip u8_analyze + if (s[ofs] < 0x80) + { + lastofs = ofs++; + continue; + } + + // invalid, skip u8_analyze + if (s[ofs] < 0xC2) + { + ++ofs; + continue; + } + + if (!u8_analyze((const char*)s+ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + return lastofs; + if (ofs + st > i) + return lastofs; + if (ofs + st + ln >= i) + return ofs + st; + + lastofs = ofs; + ofs += st + ln; + } + return lastofs; +} + +Uchar u8_quake2utf8map[256] = { + 0xE000, 0xE001, 0xE002, 0xE003, 0xE004, 0xE005, 0xE006, 0xE007, 0xE008, 0xE009, 0xE00A, 0xE00B, 0xE00C, 0xE00D, 0xE00E, 0xE00F, // specials + 0xE010, 0xE011, 0xE012, 0xE013, 0xE014, 0xE015, 0xE016, 0xE017, 0xE018, 0xE019, 0xE01A, 0xE01B, 0xE01C, 0xE01D, 0xE01E, 0xE01F, // specials + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, // shift+digit line + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, // digits + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, // caps + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, // caps + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, // small + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, // small + 0xE080, 0xE081, 0xE082, 0xE083, 0xE084, 0xE085, 0xE086, 0xE087, 0xE088, 0xE089, 0xE08A, 0xE08B, 0xE08C, 0xE08D, 0xE08E, 0xE08F, // specials + 0xE090, 0xE091, 0xE092, 0xE093, 0xE094, 0xE095, 0xE096, 0xE097, 0xE098, 0xE099, 0xE09A, 0xE09B, 0xE09C, 0xE09D, 0xE09E, 0xE09F, // faces + 0xE0A0, 0xE0A1, 0xE0A2, 0xE0A3, 0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, 0xE0A8, 0xE0A9, 0xE0AA, 0xE0AB, 0xE0AC, 0xE0AD, 0xE0AE, 0xE0AF, + 0xE0B0, 0xE0B1, 0xE0B2, 0xE0B3, 0xE0B4, 0xE0B5, 0xE0B6, 0xE0B7, 0xE0B8, 0xE0B9, 0xE0BA, 0xE0BB, 0xE0BC, 0xE0BD, 0xE0BE, 0xE0BF, + 0xE0C0, 0xE0C1, 0xE0C2, 0xE0C3, 0xE0C4, 0xE0C5, 0xE0C6, 0xE0C7, 0xE0C8, 0xE0C9, 0xE0CA, 0xE0CB, 0xE0CC, 0xE0CD, 0xE0CE, 0xE0CF, + 0xE0D0, 0xE0D1, 0xE0D2, 0xE0D3, 0xE0D4, 0xE0D5, 0xE0D6, 0xE0D7, 0xE0D8, 0xE0D9, 0xE0DA, 0xE0DB, 0xE0DC, 0xE0DD, 0xE0DE, 0xE0DF, + 0xE0E0, 0xE0E1, 0xE0E2, 0xE0E3, 0xE0E4, 0xE0E5, 0xE0E6, 0xE0E7, 0xE0E8, 0xE0E9, 0xE0EA, 0xE0EB, 0xE0EC, 0xE0ED, 0xE0EE, 0xE0EF, + 0xE0F0, 0xE0F1, 0xE0F2, 0xE0F3, 0xE0F4, 0xE0F5, 0xE0F6, 0xE0F7, 0xE0F8, 0xE0F9, 0xE0FA, 0xE0FB, 0xE0FC, 0xE0FD, 0xE0FE, 0xE0FF, +}; + +/** Fetch a character from an utf-8 encoded string. + * @param _s The start of an utf-8 encoded multi-byte character. + * @param _end Will point to after the first multi-byte character. + * @return The 32-bit integer representation of the first multi-byte character or 0 for invalid characters. + */ +Uchar u8_getchar_utf8_enabled(const char *_s, const char **_end) +{ + size_t st, ln; + Uchar ch; + + if (!u8_analyze(_s, &st, &ln, &ch, U8_ANALYZE_INFINITY)) + ch = 0; + if (_end) + *_end = _s + st + ln; + return ch; +} + +/** Fetch a character from an utf-8 encoded string. + * @param _s The start of an utf-8 encoded multi-byte character. + * @param _end Will point to after the first multi-byte character. + * @return The 32-bit integer representation of the first multi-byte character or 0 for invalid characters. + */ +Uchar u8_getnchar_utf8_enabled(const char *_s, const char **_end, size_t _maxlen) +{ + size_t st, ln; + Uchar ch; + + if (!u8_analyze(_s, &st, &ln, &ch, _maxlen)) + ch = 0; + if (_end) + *_end = _s + st + ln; + return ch; +} + +/** Encode a wide-character into utf-8. + * @param w The wide character to encode. + * @param to The target buffer the utf-8 encoded string is stored to. + * @param maxlen The maximum number of bytes that fit into the target buffer. + * @return Number of bytes written to the buffer not including the terminating null. + * Less or equal to 0 if the buffer is too small. + */ +int u8_fromchar(Uchar w, char *to, size_t maxlen) +{ + if (maxlen < 1) + return 0; + + if (!w) + return 0; + + if (w >= 0xE000 && !utf8_enable.integer) + w -= 0xE000; + + if (w < 0x80 || !utf8_enable.integer) + { + to[0] = (char)w; + if (maxlen < 2) + return -1; + to[1] = 0; + return 1; + } + // for a little speedup + if (w < 0x800) + { + if (maxlen < 3) + { + to[0] = 0; + return -1; + } + to[2] = 0; + to[1] = 0x80 | (w & 0x3F); w >>= 6; + to[0] = 0xC0 | w; + return 2; + } + if (w < 0x10000) + { + if (maxlen < 4) + { + to[0] = 0; + return -1; + } + to[3] = 0; + to[2] = 0x80 | (w & 0x3F); w >>= 6; + to[1] = 0x80 | (w & 0x3F); w >>= 6; + to[0] = 0xE0 | w; + return 3; + } + + // RFC 3629 + if (w <= 0x10FFFF) + { + if (maxlen < 5) + { + to[0] = 0; + return -1; + } + to[4] = 0; + to[3] = 0x80 | (w & 0x3F); w >>= 6; + to[2] = 0x80 | (w & 0x3F); w >>= 6; + to[1] = 0x80 | (w & 0x3F); w >>= 6; + to[0] = 0xF0 | w; + return 4; + } + return 0; +} + +/** uses u8_fromchar on a static buffer + * @param ch The unicode character to convert to encode + * @param l The number of bytes without the terminating null. + * @return A statically allocated buffer containing the character's utf8 representation, or NULL if it fails. + */ +char *u8_encodech(Uchar ch, size_t *l, char *buf16) +{ + size_t len; + len = u8_fromchar(ch, buf16, 16); + if (len > 0) + { + if (l) *l = len; + return buf16; + } + return NULL; +} + +/** Convert a utf-8 multibyte string to a wide character string. + * @param wcs The target wide-character buffer. + * @param mb The utf-8 encoded multibyte string to convert. + * @param maxlen The maximum number of wide-characters that fit into the target buffer. + * @return The number of characters written to the target buffer. + */ +size_t u8_mbstowcs(Uchar *wcs, const char *mb, size_t maxlen) +{ + size_t i; + Uchar ch; + if (maxlen < 1) + return 0; + for (i = 0; *mb && i < maxlen-1; ++i) + { + ch = u8_getchar(mb, &mb); + if (!ch) + break; + wcs[i] = ch; + } + wcs[i] = 0; + return i; +} + +/** Convert a wide-character string to a utf-8 multibyte string. + * @param mb The target buffer the utf-8 string is written to. + * @param wcs The wide-character string to convert. + * @param maxlen The number bytes that fit into the multibyte target buffer. + * @return The number of bytes written, not including the terminating \0 + */ +size_t u8_wcstombs(char *mb, const Uchar *wcs, size_t maxlen) +{ + size_t i; + const char *start = mb; + if (maxlen < 2) + return 0; + for (i = 0; wcs[i] && i < maxlen-1; ++i) + { + /* + int len; + if ( (len = u8_fromchar(wcs[i], mb, maxlen - i)) < 0) + return (mb - start); + mb += len; + */ + mb += u8_fromchar(wcs[i], mb, maxlen - i); + } + *mb = 0; + return (mb - start); +} + +/* +============ +UTF-8 aware COM_StringLengthNoColors + +calculates the visible width of a color coded string. + +*valid is filled with TRUE if the string is a valid colored string (that is, if +it does not end with an unfinished color code). If it gets filled with FALSE, a +fix would be adding a STRING_COLOR_TAG at the end of the string. + +valid can be set to NULL if the caller doesn't care. + +For size_s, specify the maximum number of characters from s to use, or 0 to use +all characters until the zero terminator. +============ +*/ +size_t +COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); +size_t +u8_COM_StringLengthNoColors(const char *_s, size_t size_s, qboolean *valid) +{ + const unsigned char *s = (const unsigned char*)_s; + const unsigned char *end; + size_t len = 0; + size_t st, ln; + + if (!utf8_enable.integer) + return COM_StringLengthNoColors(_s, size_s, valid); + + end = size_s ? (s + size_s) : NULL; + + for(;;) + { + switch((s == end) ? 0 : *s) + { + case 0: + if(valid) + *valid = TRUE; + return len; + case STRING_COLOR_TAG: + ++s; + switch((s == end) ? 0 : *s) + { + case STRING_COLOR_RGB_TAG_CHAR: + if (s+1 != end && isxdigit(s[1]) && + s+2 != end && isxdigit(s[2]) && + s+3 != end && isxdigit(s[3]) ) + { + s+=3; + break; + } + ++len; // STRING_COLOR_TAG + ++len; // STRING_COLOR_RGB_TAG_CHAR + break; + case 0: // ends with unfinished color code! + ++len; + if(valid) + *valid = FALSE; + return len; + case STRING_COLOR_TAG: // escaped ^ + ++len; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': // color code + break; + default: // not a color code + ++len; // STRING_COLOR_TAG + ++len; // the character + break; + } + ++s; + continue; + default: + break; + } + + // ascii char, skip u8_analyze + if (*s < 0x80) + { + ++len; + ++s; + continue; + } + + // invalid, skip u8_analyze + if (*s < 0xC2) + { + ++s; + continue; + } + + if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) + { + // we CAN end up here, if an invalid char is between this one and the end of the string + if(valid) + *valid = TRUE; + return len; + } + + if(end && s + st + ln > end) + { + // string length exceeded by new character + if(valid) + *valid = TRUE; + return len; + } + + // valid character, skip after it + s += st + ln; + ++len; + } + // never get here +} + +/** Pads a utf-8 string + * @param out The target buffer the utf-8 string is written to. + * @param outsize The size of the target buffer, including the final NUL + * @param in The input utf-8 buffer + * @param leftalign Left align the output string (by default right alignment is done) + * @param minwidth The minimum output width + * @param maxwidth The maximum output width + * @return The number of bytes written, not including the terminating \0 + */ +size_t u8_strpad(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth) +{ + if(!utf8_enable.integer) + { + return dpsnprintf(out, outsize, "%*.*s", leftalign ? -(int) minwidth : (int) minwidth, (int) maxwidth, in); + } + else + { + size_t l = u8_bytelen(in, maxwidth); + size_t actual_width = u8_strnlen(in, l); + int pad = (actual_width >= minwidth) ? 0 : (minwidth - actual_width); + int prec = l; + int lpad = leftalign ? 0 : pad; + int rpad = leftalign ? pad : 0; + return dpsnprintf(out, outsize, "%*s%.*s%*s", lpad, "", prec, in, rpad, ""); + } +} + +size_t u8_strpad_colorcodes(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth) +{ + size_t l = u8_bytelen_colorcodes(in, maxwidth); + size_t actual_width = u8_strnlen_colorcodes(in, l); + int pad = (actual_width >= minwidth) ? 0 : (minwidth - actual_width); + int prec = l; + int lpad = leftalign ? 0 : pad; + int rpad = leftalign ? pad : 0; + return dpsnprintf(out, outsize, "%*s%.*s%*s", lpad, "", prec, in, rpad, ""); +} + + +/* +The two following functions (u8_toupper, u8_tolower) are derived from +ftp://ftp.unicode.org/Public/UNIDATA/UnicodeData.txt and the following license +holds for these: + +Copyright © 1991-2011 Unicode, Inc. All rights reserved. Distributed under the +Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +the Unicode data files and any associated documentation (the "Data Files") or +Unicode software and any associated documentation (the "Software") to deal in +the Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell copies +of the Data Files or Software, and to permit persons to whom the Data Files or +Software are furnished to do so, provided that (a) the above copyright +notice(s) and this permission notice appear with all copies of the Data Files +or Software, (b) both the above copyright notice(s) and this permission notice +appear in associated documentation, and (c) there is clear notice in each +modified Data File or in the Software as well as in the documentation +associated with the Data File(s) or Software that the data or software has been +modified. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD +PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN +THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING +OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR +SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +these Data Files or Software without prior written authorization of the +copyright holder. +*/ + +Uchar u8_toupper(Uchar ch) +{ + switch(ch) + { + case 0x0061: return 0x0041; + case 0x0062: return 0x0042; + case 0x0063: return 0x0043; + case 0x0064: return 0x0044; + case 0x0065: return 0x0045; + case 0x0066: return 0x0046; + case 0x0067: return 0x0047; + case 0x0068: return 0x0048; + case 0x0069: return 0x0049; + case 0x006A: return 0x004A; + case 0x006B: return 0x004B; + case 0x006C: return 0x004C; + case 0x006D: return 0x004D; + case 0x006E: return 0x004E; + case 0x006F: return 0x004F; + case 0x0070: return 0x0050; + case 0x0071: return 0x0051; + case 0x0072: return 0x0052; + case 0x0073: return 0x0053; + case 0x0074: return 0x0054; + case 0x0075: return 0x0055; + case 0x0076: return 0x0056; + case 0x0077: return 0x0057; + case 0x0078: return 0x0058; + case 0x0079: return 0x0059; + case 0x007A: return 0x005A; + case 0x00B5: return 0x039C; + case 0x00E0: return 0x00C0; + case 0x00E1: return 0x00C1; + case 0x00E2: return 0x00C2; + case 0x00E3: return 0x00C3; + case 0x00E4: return 0x00C4; + case 0x00E5: return 0x00C5; + case 0x00E6: return 0x00C6; + case 0x00E7: return 0x00C7; + case 0x00E8: return 0x00C8; + case 0x00E9: return 0x00C9; + case 0x00EA: return 0x00CA; + case 0x00EB: return 0x00CB; + case 0x00EC: return 0x00CC; + case 0x00ED: return 0x00CD; + case 0x00EE: return 0x00CE; + case 0x00EF: return 0x00CF; + case 0x00F0: return 0x00D0; + case 0x00F1: return 0x00D1; + case 0x00F2: return 0x00D2; + case 0x00F3: return 0x00D3; + case 0x00F4: return 0x00D4; + case 0x00F5: return 0x00D5; + case 0x00F6: return 0x00D6; + case 0x00F8: return 0x00D8; + case 0x00F9: return 0x00D9; + case 0x00FA: return 0x00DA; + case 0x00FB: return 0x00DB; + case 0x00FC: return 0x00DC; + case 0x00FD: return 0x00DD; + case 0x00FE: return 0x00DE; + case 0x00FF: return 0x0178; + case 0x0101: return 0x0100; + case 0x0103: return 0x0102; + case 0x0105: return 0x0104; + case 0x0107: return 0x0106; + case 0x0109: return 0x0108; + case 0x010B: return 0x010A; + case 0x010D: return 0x010C; + case 0x010F: return 0x010E; + case 0x0111: return 0x0110; + case 0x0113: return 0x0112; + case 0x0115: return 0x0114; + case 0x0117: return 0x0116; + case 0x0119: return 0x0118; + case 0x011B: return 0x011A; + case 0x011D: return 0x011C; + case 0x011F: return 0x011E; + case 0x0121: return 0x0120; + case 0x0123: return 0x0122; + case 0x0125: return 0x0124; + case 0x0127: return 0x0126; + case 0x0129: return 0x0128; + case 0x012B: return 0x012A; + case 0x012D: return 0x012C; + case 0x012F: return 0x012E; + case 0x0131: return 0x0049; + case 0x0133: return 0x0132; + case 0x0135: return 0x0134; + case 0x0137: return 0x0136; + case 0x013A: return 0x0139; + case 0x013C: return 0x013B; + case 0x013E: return 0x013D; + case 0x0140: return 0x013F; + case 0x0142: return 0x0141; + case 0x0144: return 0x0143; + case 0x0146: return 0x0145; + case 0x0148: return 0x0147; + case 0x014B: return 0x014A; + case 0x014D: return 0x014C; + case 0x014F: return 0x014E; + case 0x0151: return 0x0150; + case 0x0153: return 0x0152; + case 0x0155: return 0x0154; + case 0x0157: return 0x0156; + case 0x0159: return 0x0158; + case 0x015B: return 0x015A; + case 0x015D: return 0x015C; + case 0x015F: return 0x015E; + case 0x0161: return 0x0160; + case 0x0163: return 0x0162; + case 0x0165: return 0x0164; + case 0x0167: return 0x0166; + case 0x0169: return 0x0168; + case 0x016B: return 0x016A; + case 0x016D: return 0x016C; + case 0x016F: return 0x016E; + case 0x0171: return 0x0170; + case 0x0173: return 0x0172; + case 0x0175: return 0x0174; + case 0x0177: return 0x0176; + case 0x017A: return 0x0179; + case 0x017C: return 0x017B; + case 0x017E: return 0x017D; + case 0x017F: return 0x0053; + case 0x0180: return 0x0243; + case 0x0183: return 0x0182; + case 0x0185: return 0x0184; + case 0x0188: return 0x0187; + case 0x018C: return 0x018B; + case 0x0192: return 0x0191; + case 0x0195: return 0x01F6; + case 0x0199: return 0x0198; + case 0x019A: return 0x023D; + case 0x019E: return 0x0220; + case 0x01A1: return 0x01A0; + case 0x01A3: return 0x01A2; + case 0x01A5: return 0x01A4; + case 0x01A8: return 0x01A7; + case 0x01AD: return 0x01AC; + case 0x01B0: return 0x01AF; + case 0x01B4: return 0x01B3; + case 0x01B6: return 0x01B5; + case 0x01B9: return 0x01B8; + case 0x01BD: return 0x01BC; + case 0x01BF: return 0x01F7; + case 0x01C5: return 0x01C4; + case 0x01C6: return 0x01C4; + case 0x01C8: return 0x01C7; + case 0x01C9: return 0x01C7; + case 0x01CB: return 0x01CA; + case 0x01CC: return 0x01CA; + case 0x01CE: return 0x01CD; + case 0x01D0: return 0x01CF; + case 0x01D2: return 0x01D1; + case 0x01D4: return 0x01D3; + case 0x01D6: return 0x01D5; + case 0x01D8: return 0x01D7; + case 0x01DA: return 0x01D9; + case 0x01DC: return 0x01DB; + case 0x01DD: return 0x018E; + case 0x01DF: return 0x01DE; + case 0x01E1: return 0x01E0; + case 0x01E3: return 0x01E2; + case 0x01E5: return 0x01E4; + case 0x01E7: return 0x01E6; + case 0x01E9: return 0x01E8; + case 0x01EB: return 0x01EA; + case 0x01ED: return 0x01EC; + case 0x01EF: return 0x01EE; + case 0x01F2: return 0x01F1; + case 0x01F3: return 0x01F1; + case 0x01F5: return 0x01F4; + case 0x01F9: return 0x01F8; + case 0x01FB: return 0x01FA; + case 0x01FD: return 0x01FC; + case 0x01FF: return 0x01FE; + case 0x0201: return 0x0200; + case 0x0203: return 0x0202; + case 0x0205: return 0x0204; + case 0x0207: return 0x0206; + case 0x0209: return 0x0208; + case 0x020B: return 0x020A; + case 0x020D: return 0x020C; + case 0x020F: return 0x020E; + case 0x0211: return 0x0210; + case 0x0213: return 0x0212; + case 0x0215: return 0x0214; + case 0x0217: return 0x0216; + case 0x0219: return 0x0218; + case 0x021B: return 0x021A; + case 0x021D: return 0x021C; + case 0x021F: return 0x021E; + case 0x0223: return 0x0222; + case 0x0225: return 0x0224; + case 0x0227: return 0x0226; + case 0x0229: return 0x0228; + case 0x022B: return 0x022A; + case 0x022D: return 0x022C; + case 0x022F: return 0x022E; + case 0x0231: return 0x0230; + case 0x0233: return 0x0232; + case 0x023C: return 0x023B; + case 0x023F: return 0x2C7E; + case 0x0240: return 0x2C7F; + case 0x0242: return 0x0241; + case 0x0247: return 0x0246; + case 0x0249: return 0x0248; + case 0x024B: return 0x024A; + case 0x024D: return 0x024C; + case 0x024F: return 0x024E; + case 0x0250: return 0x2C6F; + case 0x0251: return 0x2C6D; + case 0x0252: return 0x2C70; + case 0x0253: return 0x0181; + case 0x0254: return 0x0186; + case 0x0256: return 0x0189; + case 0x0257: return 0x018A; + case 0x0259: return 0x018F; + case 0x025B: return 0x0190; + case 0x0260: return 0x0193; + case 0x0263: return 0x0194; + case 0x0265: return 0xA78D; + case 0x0268: return 0x0197; + case 0x0269: return 0x0196; + case 0x026B: return 0x2C62; + case 0x026F: return 0x019C; + case 0x0271: return 0x2C6E; + case 0x0272: return 0x019D; + case 0x0275: return 0x019F; + case 0x027D: return 0x2C64; + case 0x0280: return 0x01A6; + case 0x0283: return 0x01A9; + case 0x0288: return 0x01AE; + case 0x0289: return 0x0244; + case 0x028A: return 0x01B1; + case 0x028B: return 0x01B2; + case 0x028C: return 0x0245; + case 0x0292: return 0x01B7; + case 0x0345: return 0x0399; + case 0x0371: return 0x0370; + case 0x0373: return 0x0372; + case 0x0377: return 0x0376; + case 0x037B: return 0x03FD; + case 0x037C: return 0x03FE; + case 0x037D: return 0x03FF; + case 0x03AC: return 0x0386; + case 0x03AD: return 0x0388; + case 0x03AE: return 0x0389; + case 0x03AF: return 0x038A; + case 0x03B1: return 0x0391; + case 0x03B2: return 0x0392; + case 0x03B3: return 0x0393; + case 0x03B4: return 0x0394; + case 0x03B5: return 0x0395; + case 0x03B6: return 0x0396; + case 0x03B7: return 0x0397; + case 0x03B8: return 0x0398; + case 0x03B9: return 0x0399; + case 0x03BA: return 0x039A; + case 0x03BB: return 0x039B; + case 0x03BC: return 0x039C; + case 0x03BD: return 0x039D; + case 0x03BE: return 0x039E; + case 0x03BF: return 0x039F; + case 0x03C0: return 0x03A0; + case 0x03C1: return 0x03A1; + case 0x03C2: return 0x03A3; + case 0x03C3: return 0x03A3; + case 0x03C4: return 0x03A4; + case 0x03C5: return 0x03A5; + case 0x03C6: return 0x03A6; + case 0x03C7: return 0x03A7; + case 0x03C8: return 0x03A8; + case 0x03C9: return 0x03A9; + case 0x03CA: return 0x03AA; + case 0x03CB: return 0x03AB; + case 0x03CC: return 0x038C; + case 0x03CD: return 0x038E; + case 0x03CE: return 0x038F; + case 0x03D0: return 0x0392; + case 0x03D1: return 0x0398; + case 0x03D5: return 0x03A6; + case 0x03D6: return 0x03A0; + case 0x03D7: return 0x03CF; + case 0x03D9: return 0x03D8; + case 0x03DB: return 0x03DA; + case 0x03DD: return 0x03DC; + case 0x03DF: return 0x03DE; + case 0x03E1: return 0x03E0; + case 0x03E3: return 0x03E2; + case 0x03E5: return 0x03E4; + case 0x03E7: return 0x03E6; + case 0x03E9: return 0x03E8; + case 0x03EB: return 0x03EA; + case 0x03ED: return 0x03EC; + case 0x03EF: return 0x03EE; + case 0x03F0: return 0x039A; + case 0x03F1: return 0x03A1; + case 0x03F2: return 0x03F9; + case 0x03F5: return 0x0395; + case 0x03F8: return 0x03F7; + case 0x03FB: return 0x03FA; + case 0x0430: return 0x0410; + case 0x0431: return 0x0411; + case 0x0432: return 0x0412; + case 0x0433: return 0x0413; + case 0x0434: return 0x0414; + case 0x0435: return 0x0415; + case 0x0436: return 0x0416; + case 0x0437: return 0x0417; + case 0x0438: return 0x0418; + case 0x0439: return 0x0419; + case 0x043A: return 0x041A; + case 0x043B: return 0x041B; + case 0x043C: return 0x041C; + case 0x043D: return 0x041D; + case 0x043E: return 0x041E; + case 0x043F: return 0x041F; + case 0x0440: return 0x0420; + case 0x0441: return 0x0421; + case 0x0442: return 0x0422; + case 0x0443: return 0x0423; + case 0x0444: return 0x0424; + case 0x0445: return 0x0425; + case 0x0446: return 0x0426; + case 0x0447: return 0x0427; + case 0x0448: return 0x0428; + case 0x0449: return 0x0429; + case 0x044A: return 0x042A; + case 0x044B: return 0x042B; + case 0x044C: return 0x042C; + case 0x044D: return 0x042D; + case 0x044E: return 0x042E; + case 0x044F: return 0x042F; + case 0x0450: return 0x0400; + case 0x0451: return 0x0401; + case 0x0452: return 0x0402; + case 0x0453: return 0x0403; + case 0x0454: return 0x0404; + case 0x0455: return 0x0405; + case 0x0456: return 0x0406; + case 0x0457: return 0x0407; + case 0x0458: return 0x0408; + case 0x0459: return 0x0409; + case 0x045A: return 0x040A; + case 0x045B: return 0x040B; + case 0x045C: return 0x040C; + case 0x045D: return 0x040D; + case 0x045E: return 0x040E; + case 0x045F: return 0x040F; + case 0x0461: return 0x0460; + case 0x0463: return 0x0462; + case 0x0465: return 0x0464; + case 0x0467: return 0x0466; + case 0x0469: return 0x0468; + case 0x046B: return 0x046A; + case 0x046D: return 0x046C; + case 0x046F: return 0x046E; + case 0x0471: return 0x0470; + case 0x0473: return 0x0472; + case 0x0475: return 0x0474; + case 0x0477: return 0x0476; + case 0x0479: return 0x0478; + case 0x047B: return 0x047A; + case 0x047D: return 0x047C; + case 0x047F: return 0x047E; + case 0x0481: return 0x0480; + case 0x048B: return 0x048A; + case 0x048D: return 0x048C; + case 0x048F: return 0x048E; + case 0x0491: return 0x0490; + case 0x0493: return 0x0492; + case 0x0495: return 0x0494; + case 0x0497: return 0x0496; + case 0x0499: return 0x0498; + case 0x049B: return 0x049A; + case 0x049D: return 0x049C; + case 0x049F: return 0x049E; + case 0x04A1: return 0x04A0; + case 0x04A3: return 0x04A2; + case 0x04A5: return 0x04A4; + case 0x04A7: return 0x04A6; + case 0x04A9: return 0x04A8; + case 0x04AB: return 0x04AA; + case 0x04AD: return 0x04AC; + case 0x04AF: return 0x04AE; + case 0x04B1: return 0x04B0; + case 0x04B3: return 0x04B2; + case 0x04B5: return 0x04B4; + case 0x04B7: return 0x04B6; + case 0x04B9: return 0x04B8; + case 0x04BB: return 0x04BA; + case 0x04BD: return 0x04BC; + case 0x04BF: return 0x04BE; + case 0x04C2: return 0x04C1; + case 0x04C4: return 0x04C3; + case 0x04C6: return 0x04C5; + case 0x04C8: return 0x04C7; + case 0x04CA: return 0x04C9; + case 0x04CC: return 0x04CB; + case 0x04CE: return 0x04CD; + case 0x04CF: return 0x04C0; + case 0x04D1: return 0x04D0; + case 0x04D3: return 0x04D2; + case 0x04D5: return 0x04D4; + case 0x04D7: return 0x04D6; + case 0x04D9: return 0x04D8; + case 0x04DB: return 0x04DA; + case 0x04DD: return 0x04DC; + case 0x04DF: return 0x04DE; + case 0x04E1: return 0x04E0; + case 0x04E3: return 0x04E2; + case 0x04E5: return 0x04E4; + case 0x04E7: return 0x04E6; + case 0x04E9: return 0x04E8; + case 0x04EB: return 0x04EA; + case 0x04ED: return 0x04EC; + case 0x04EF: return 0x04EE; + case 0x04F1: return 0x04F0; + case 0x04F3: return 0x04F2; + case 0x04F5: return 0x04F4; + case 0x04F7: return 0x04F6; + case 0x04F9: return 0x04F8; + case 0x04FB: return 0x04FA; + case 0x04FD: return 0x04FC; + case 0x04FF: return 0x04FE; + case 0x0501: return 0x0500; + case 0x0503: return 0x0502; + case 0x0505: return 0x0504; + case 0x0507: return 0x0506; + case 0x0509: return 0x0508; + case 0x050B: return 0x050A; + case 0x050D: return 0x050C; + case 0x050F: return 0x050E; + case 0x0511: return 0x0510; + case 0x0513: return 0x0512; + case 0x0515: return 0x0514; + case 0x0517: return 0x0516; + case 0x0519: return 0x0518; + case 0x051B: return 0x051A; + case 0x051D: return 0x051C; + case 0x051F: return 0x051E; + case 0x0521: return 0x0520; + case 0x0523: return 0x0522; + case 0x0525: return 0x0524; + case 0x0527: return 0x0526; + case 0x0561: return 0x0531; + case 0x0562: return 0x0532; + case 0x0563: return 0x0533; + case 0x0564: return 0x0534; + case 0x0565: return 0x0535; + case 0x0566: return 0x0536; + case 0x0567: return 0x0537; + case 0x0568: return 0x0538; + case 0x0569: return 0x0539; + case 0x056A: return 0x053A; + case 0x056B: return 0x053B; + case 0x056C: return 0x053C; + case 0x056D: return 0x053D; + case 0x056E: return 0x053E; + case 0x056F: return 0x053F; + case 0x0570: return 0x0540; + case 0x0571: return 0x0541; + case 0x0572: return 0x0542; + case 0x0573: return 0x0543; + case 0x0574: return 0x0544; + case 0x0575: return 0x0545; + case 0x0576: return 0x0546; + case 0x0577: return 0x0547; + case 0x0578: return 0x0548; + case 0x0579: return 0x0549; + case 0x057A: return 0x054A; + case 0x057B: return 0x054B; + case 0x057C: return 0x054C; + case 0x057D: return 0x054D; + case 0x057E: return 0x054E; + case 0x057F: return 0x054F; + case 0x0580: return 0x0550; + case 0x0581: return 0x0551; + case 0x0582: return 0x0552; + case 0x0583: return 0x0553; + case 0x0584: return 0x0554; + case 0x0585: return 0x0555; + case 0x0586: return 0x0556; + case 0x1D79: return 0xA77D; + case 0x1D7D: return 0x2C63; + case 0x1E01: return 0x1E00; + case 0x1E03: return 0x1E02; + case 0x1E05: return 0x1E04; + case 0x1E07: return 0x1E06; + case 0x1E09: return 0x1E08; + case 0x1E0B: return 0x1E0A; + case 0x1E0D: return 0x1E0C; + case 0x1E0F: return 0x1E0E; + case 0x1E11: return 0x1E10; + case 0x1E13: return 0x1E12; + case 0x1E15: return 0x1E14; + case 0x1E17: return 0x1E16; + case 0x1E19: return 0x1E18; + case 0x1E1B: return 0x1E1A; + case 0x1E1D: return 0x1E1C; + case 0x1E1F: return 0x1E1E; + case 0x1E21: return 0x1E20; + case 0x1E23: return 0x1E22; + case 0x1E25: return 0x1E24; + case 0x1E27: return 0x1E26; + case 0x1E29: return 0x1E28; + case 0x1E2B: return 0x1E2A; + case 0x1E2D: return 0x1E2C; + case 0x1E2F: return 0x1E2E; + case 0x1E31: return 0x1E30; + case 0x1E33: return 0x1E32; + case 0x1E35: return 0x1E34; + case 0x1E37: return 0x1E36; + case 0x1E39: return 0x1E38; + case 0x1E3B: return 0x1E3A; + case 0x1E3D: return 0x1E3C; + case 0x1E3F: return 0x1E3E; + case 0x1E41: return 0x1E40; + case 0x1E43: return 0x1E42; + case 0x1E45: return 0x1E44; + case 0x1E47: return 0x1E46; + case 0x1E49: return 0x1E48; + case 0x1E4B: return 0x1E4A; + case 0x1E4D: return 0x1E4C; + case 0x1E4F: return 0x1E4E; + case 0x1E51: return 0x1E50; + case 0x1E53: return 0x1E52; + case 0x1E55: return 0x1E54; + case 0x1E57: return 0x1E56; + case 0x1E59: return 0x1E58; + case 0x1E5B: return 0x1E5A; + case 0x1E5D: return 0x1E5C; + case 0x1E5F: return 0x1E5E; + case 0x1E61: return 0x1E60; + case 0x1E63: return 0x1E62; + case 0x1E65: return 0x1E64; + case 0x1E67: return 0x1E66; + case 0x1E69: return 0x1E68; + case 0x1E6B: return 0x1E6A; + case 0x1E6D: return 0x1E6C; + case 0x1E6F: return 0x1E6E; + case 0x1E71: return 0x1E70; + case 0x1E73: return 0x1E72; + case 0x1E75: return 0x1E74; + case 0x1E77: return 0x1E76; + case 0x1E79: return 0x1E78; + case 0x1E7B: return 0x1E7A; + case 0x1E7D: return 0x1E7C; + case 0x1E7F: return 0x1E7E; + case 0x1E81: return 0x1E80; + case 0x1E83: return 0x1E82; + case 0x1E85: return 0x1E84; + case 0x1E87: return 0x1E86; + case 0x1E89: return 0x1E88; + case 0x1E8B: return 0x1E8A; + case 0x1E8D: return 0x1E8C; + case 0x1E8F: return 0x1E8E; + case 0x1E91: return 0x1E90; + case 0x1E93: return 0x1E92; + case 0x1E95: return 0x1E94; + case 0x1E9B: return 0x1E60; + case 0x1EA1: return 0x1EA0; + case 0x1EA3: return 0x1EA2; + case 0x1EA5: return 0x1EA4; + case 0x1EA7: return 0x1EA6; + case 0x1EA9: return 0x1EA8; + case 0x1EAB: return 0x1EAA; + case 0x1EAD: return 0x1EAC; + case 0x1EAF: return 0x1EAE; + case 0x1EB1: return 0x1EB0; + case 0x1EB3: return 0x1EB2; + case 0x1EB5: return 0x1EB4; + case 0x1EB7: return 0x1EB6; + case 0x1EB9: return 0x1EB8; + case 0x1EBB: return 0x1EBA; + case 0x1EBD: return 0x1EBC; + case 0x1EBF: return 0x1EBE; + case 0x1EC1: return 0x1EC0; + case 0x1EC3: return 0x1EC2; + case 0x1EC5: return 0x1EC4; + case 0x1EC7: return 0x1EC6; + case 0x1EC9: return 0x1EC8; + case 0x1ECB: return 0x1ECA; + case 0x1ECD: return 0x1ECC; + case 0x1ECF: return 0x1ECE; + case 0x1ED1: return 0x1ED0; + case 0x1ED3: return 0x1ED2; + case 0x1ED5: return 0x1ED4; + case 0x1ED7: return 0x1ED6; + case 0x1ED9: return 0x1ED8; + case 0x1EDB: return 0x1EDA; + case 0x1EDD: return 0x1EDC; + case 0x1EDF: return 0x1EDE; + case 0x1EE1: return 0x1EE0; + case 0x1EE3: return 0x1EE2; + case 0x1EE5: return 0x1EE4; + case 0x1EE7: return 0x1EE6; + case 0x1EE9: return 0x1EE8; + case 0x1EEB: return 0x1EEA; + case 0x1EED: return 0x1EEC; + case 0x1EEF: return 0x1EEE; + case 0x1EF1: return 0x1EF0; + case 0x1EF3: return 0x1EF2; + case 0x1EF5: return 0x1EF4; + case 0x1EF7: return 0x1EF6; + case 0x1EF9: return 0x1EF8; + case 0x1EFB: return 0x1EFA; + case 0x1EFD: return 0x1EFC; + case 0x1EFF: return 0x1EFE; + case 0x1F00: return 0x1F08; + case 0x1F01: return 0x1F09; + case 0x1F02: return 0x1F0A; + case 0x1F03: return 0x1F0B; + case 0x1F04: return 0x1F0C; + case 0x1F05: return 0x1F0D; + case 0x1F06: return 0x1F0E; + case 0x1F07: return 0x1F0F; + case 0x1F10: return 0x1F18; + case 0x1F11: return 0x1F19; + case 0x1F12: return 0x1F1A; + case 0x1F13: return 0x1F1B; + case 0x1F14: return 0x1F1C; + case 0x1F15: return 0x1F1D; + case 0x1F20: return 0x1F28; + case 0x1F21: return 0x1F29; + case 0x1F22: return 0x1F2A; + case 0x1F23: return 0x1F2B; + case 0x1F24: return 0x1F2C; + case 0x1F25: return 0x1F2D; + case 0x1F26: return 0x1F2E; + case 0x1F27: return 0x1F2F; + case 0x1F30: return 0x1F38; + case 0x1F31: return 0x1F39; + case 0x1F32: return 0x1F3A; + case 0x1F33: return 0x1F3B; + case 0x1F34: return 0x1F3C; + case 0x1F35: return 0x1F3D; + case 0x1F36: return 0x1F3E; + case 0x1F37: return 0x1F3F; + case 0x1F40: return 0x1F48; + case 0x1F41: return 0x1F49; + case 0x1F42: return 0x1F4A; + case 0x1F43: return 0x1F4B; + case 0x1F44: return 0x1F4C; + case 0x1F45: return 0x1F4D; + case 0x1F51: return 0x1F59; + case 0x1F53: return 0x1F5B; + case 0x1F55: return 0x1F5D; + case 0x1F57: return 0x1F5F; + case 0x1F60: return 0x1F68; + case 0x1F61: return 0x1F69; + case 0x1F62: return 0x1F6A; + case 0x1F63: return 0x1F6B; + case 0x1F64: return 0x1F6C; + case 0x1F65: return 0x1F6D; + case 0x1F66: return 0x1F6E; + case 0x1F67: return 0x1F6F; + case 0x1F70: return 0x1FBA; + case 0x1F71: return 0x1FBB; + case 0x1F72: return 0x1FC8; + case 0x1F73: return 0x1FC9; + case 0x1F74: return 0x1FCA; + case 0x1F75: return 0x1FCB; + case 0x1F76: return 0x1FDA; + case 0x1F77: return 0x1FDB; + case 0x1F78: return 0x1FF8; + case 0x1F79: return 0x1FF9; + case 0x1F7A: return 0x1FEA; + case 0x1F7B: return 0x1FEB; + case 0x1F7C: return 0x1FFA; + case 0x1F7D: return 0x1FFB; + case 0x1F80: return 0x1F88; + case 0x1F81: return 0x1F89; + case 0x1F82: return 0x1F8A; + case 0x1F83: return 0x1F8B; + case 0x1F84: return 0x1F8C; + case 0x1F85: return 0x1F8D; + case 0x1F86: return 0x1F8E; + case 0x1F87: return 0x1F8F; + case 0x1F90: return 0x1F98; + case 0x1F91: return 0x1F99; + case 0x1F92: return 0x1F9A; + case 0x1F93: return 0x1F9B; + case 0x1F94: return 0x1F9C; + case 0x1F95: return 0x1F9D; + case 0x1F96: return 0x1F9E; + case 0x1F97: return 0x1F9F; + case 0x1FA0: return 0x1FA8; + case 0x1FA1: return 0x1FA9; + case 0x1FA2: return 0x1FAA; + case 0x1FA3: return 0x1FAB; + case 0x1FA4: return 0x1FAC; + case 0x1FA5: return 0x1FAD; + case 0x1FA6: return 0x1FAE; + case 0x1FA7: return 0x1FAF; + case 0x1FB0: return 0x1FB8; + case 0x1FB1: return 0x1FB9; + case 0x1FB3: return 0x1FBC; + case 0x1FBE: return 0x0399; + case 0x1FC3: return 0x1FCC; + case 0x1FD0: return 0x1FD8; + case 0x1FD1: return 0x1FD9; + case 0x1FE0: return 0x1FE8; + case 0x1FE1: return 0x1FE9; + case 0x1FE5: return 0x1FEC; + case 0x1FF3: return 0x1FFC; + case 0x214E: return 0x2132; + case 0x2170: return 0x2160; + case 0x2171: return 0x2161; + case 0x2172: return 0x2162; + case 0x2173: return 0x2163; + case 0x2174: return 0x2164; + case 0x2175: return 0x2165; + case 0x2176: return 0x2166; + case 0x2177: return 0x2167; + case 0x2178: return 0x2168; + case 0x2179: return 0x2169; + case 0x217A: return 0x216A; + case 0x217B: return 0x216B; + case 0x217C: return 0x216C; + case 0x217D: return 0x216D; + case 0x217E: return 0x216E; + case 0x217F: return 0x216F; + case 0x2184: return 0x2183; + case 0x24D0: return 0x24B6; + case 0x24D1: return 0x24B7; + case 0x24D2: return 0x24B8; + case 0x24D3: return 0x24B9; + case 0x24D4: return 0x24BA; + case 0x24D5: return 0x24BB; + case 0x24D6: return 0x24BC; + case 0x24D7: return 0x24BD; + case 0x24D8: return 0x24BE; + case 0x24D9: return 0x24BF; + case 0x24DA: return 0x24C0; + case 0x24DB: return 0x24C1; + case 0x24DC: return 0x24C2; + case 0x24DD: return 0x24C3; + case 0x24DE: return 0x24C4; + case 0x24DF: return 0x24C5; + case 0x24E0: return 0x24C6; + case 0x24E1: return 0x24C7; + case 0x24E2: return 0x24C8; + case 0x24E3: return 0x24C9; + case 0x24E4: return 0x24CA; + case 0x24E5: return 0x24CB; + case 0x24E6: return 0x24CC; + case 0x24E7: return 0x24CD; + case 0x24E8: return 0x24CE; + case 0x24E9: return 0x24CF; + case 0x2C30: return 0x2C00; + case 0x2C31: return 0x2C01; + case 0x2C32: return 0x2C02; + case 0x2C33: return 0x2C03; + case 0x2C34: return 0x2C04; + case 0x2C35: return 0x2C05; + case 0x2C36: return 0x2C06; + case 0x2C37: return 0x2C07; + case 0x2C38: return 0x2C08; + case 0x2C39: return 0x2C09; + case 0x2C3A: return 0x2C0A; + case 0x2C3B: return 0x2C0B; + case 0x2C3C: return 0x2C0C; + case 0x2C3D: return 0x2C0D; + case 0x2C3E: return 0x2C0E; + case 0x2C3F: return 0x2C0F; + case 0x2C40: return 0x2C10; + case 0x2C41: return 0x2C11; + case 0x2C42: return 0x2C12; + case 0x2C43: return 0x2C13; + case 0x2C44: return 0x2C14; + case 0x2C45: return 0x2C15; + case 0x2C46: return 0x2C16; + case 0x2C47: return 0x2C17; + case 0x2C48: return 0x2C18; + case 0x2C49: return 0x2C19; + case 0x2C4A: return 0x2C1A; + case 0x2C4B: return 0x2C1B; + case 0x2C4C: return 0x2C1C; + case 0x2C4D: return 0x2C1D; + case 0x2C4E: return 0x2C1E; + case 0x2C4F: return 0x2C1F; + case 0x2C50: return 0x2C20; + case 0x2C51: return 0x2C21; + case 0x2C52: return 0x2C22; + case 0x2C53: return 0x2C23; + case 0x2C54: return 0x2C24; + case 0x2C55: return 0x2C25; + case 0x2C56: return 0x2C26; + case 0x2C57: return 0x2C27; + case 0x2C58: return 0x2C28; + case 0x2C59: return 0x2C29; + case 0x2C5A: return 0x2C2A; + case 0x2C5B: return 0x2C2B; + case 0x2C5C: return 0x2C2C; + case 0x2C5D: return 0x2C2D; + case 0x2C5E: return 0x2C2E; + case 0x2C61: return 0x2C60; + case 0x2C65: return 0x023A; + case 0x2C66: return 0x023E; + case 0x2C68: return 0x2C67; + case 0x2C6A: return 0x2C69; + case 0x2C6C: return 0x2C6B; + case 0x2C73: return 0x2C72; + case 0x2C76: return 0x2C75; + case 0x2C81: return 0x2C80; + case 0x2C83: return 0x2C82; + case 0x2C85: return 0x2C84; + case 0x2C87: return 0x2C86; + case 0x2C89: return 0x2C88; + case 0x2C8B: return 0x2C8A; + case 0x2C8D: return 0x2C8C; + case 0x2C8F: return 0x2C8E; + case 0x2C91: return 0x2C90; + case 0x2C93: return 0x2C92; + case 0x2C95: return 0x2C94; + case 0x2C97: return 0x2C96; + case 0x2C99: return 0x2C98; + case 0x2C9B: return 0x2C9A; + case 0x2C9D: return 0x2C9C; + case 0x2C9F: return 0x2C9E; + case 0x2CA1: return 0x2CA0; + case 0x2CA3: return 0x2CA2; + case 0x2CA5: return 0x2CA4; + case 0x2CA7: return 0x2CA6; + case 0x2CA9: return 0x2CA8; + case 0x2CAB: return 0x2CAA; + case 0x2CAD: return 0x2CAC; + case 0x2CAF: return 0x2CAE; + case 0x2CB1: return 0x2CB0; + case 0x2CB3: return 0x2CB2; + case 0x2CB5: return 0x2CB4; + case 0x2CB7: return 0x2CB6; + case 0x2CB9: return 0x2CB8; + case 0x2CBB: return 0x2CBA; + case 0x2CBD: return 0x2CBC; + case 0x2CBF: return 0x2CBE; + case 0x2CC1: return 0x2CC0; + case 0x2CC3: return 0x2CC2; + case 0x2CC5: return 0x2CC4; + case 0x2CC7: return 0x2CC6; + case 0x2CC9: return 0x2CC8; + case 0x2CCB: return 0x2CCA; + case 0x2CCD: return 0x2CCC; + case 0x2CCF: return 0x2CCE; + case 0x2CD1: return 0x2CD0; + case 0x2CD3: return 0x2CD2; + case 0x2CD5: return 0x2CD4; + case 0x2CD7: return 0x2CD6; + case 0x2CD9: return 0x2CD8; + case 0x2CDB: return 0x2CDA; + case 0x2CDD: return 0x2CDC; + case 0x2CDF: return 0x2CDE; + case 0x2CE1: return 0x2CE0; + case 0x2CE3: return 0x2CE2; + case 0x2CEC: return 0x2CEB; + case 0x2CEE: return 0x2CED; + case 0x2D00: return 0x10A0; + case 0x2D01: return 0x10A1; + case 0x2D02: return 0x10A2; + case 0x2D03: return 0x10A3; + case 0x2D04: return 0x10A4; + case 0x2D05: return 0x10A5; + case 0x2D06: return 0x10A6; + case 0x2D07: return 0x10A7; + case 0x2D08: return 0x10A8; + case 0x2D09: return 0x10A9; + case 0x2D0A: return 0x10AA; + case 0x2D0B: return 0x10AB; + case 0x2D0C: return 0x10AC; + case 0x2D0D: return 0x10AD; + case 0x2D0E: return 0x10AE; + case 0x2D0F: return 0x10AF; + case 0x2D10: return 0x10B0; + case 0x2D11: return 0x10B1; + case 0x2D12: return 0x10B2; + case 0x2D13: return 0x10B3; + case 0x2D14: return 0x10B4; + case 0x2D15: return 0x10B5; + case 0x2D16: return 0x10B6; + case 0x2D17: return 0x10B7; + case 0x2D18: return 0x10B8; + case 0x2D19: return 0x10B9; + case 0x2D1A: return 0x10BA; + case 0x2D1B: return 0x10BB; + case 0x2D1C: return 0x10BC; + case 0x2D1D: return 0x10BD; + case 0x2D1E: return 0x10BE; + case 0x2D1F: return 0x10BF; + case 0x2D20: return 0x10C0; + case 0x2D21: return 0x10C1; + case 0x2D22: return 0x10C2; + case 0x2D23: return 0x10C3; + case 0x2D24: return 0x10C4; + case 0x2D25: return 0x10C5; + case 0xA641: return 0xA640; + case 0xA643: return 0xA642; + case 0xA645: return 0xA644; + case 0xA647: return 0xA646; + case 0xA649: return 0xA648; + case 0xA64B: return 0xA64A; + case 0xA64D: return 0xA64C; + case 0xA64F: return 0xA64E; + case 0xA651: return 0xA650; + case 0xA653: return 0xA652; + case 0xA655: return 0xA654; + case 0xA657: return 0xA656; + case 0xA659: return 0xA658; + case 0xA65B: return 0xA65A; + case 0xA65D: return 0xA65C; + case 0xA65F: return 0xA65E; + case 0xA661: return 0xA660; + case 0xA663: return 0xA662; + case 0xA665: return 0xA664; + case 0xA667: return 0xA666; + case 0xA669: return 0xA668; + case 0xA66B: return 0xA66A; + case 0xA66D: return 0xA66C; + case 0xA681: return 0xA680; + case 0xA683: return 0xA682; + case 0xA685: return 0xA684; + case 0xA687: return 0xA686; + case 0xA689: return 0xA688; + case 0xA68B: return 0xA68A; + case 0xA68D: return 0xA68C; + case 0xA68F: return 0xA68E; + case 0xA691: return 0xA690; + case 0xA693: return 0xA692; + case 0xA695: return 0xA694; + case 0xA697: return 0xA696; + case 0xA723: return 0xA722; + case 0xA725: return 0xA724; + case 0xA727: return 0xA726; + case 0xA729: return 0xA728; + case 0xA72B: return 0xA72A; + case 0xA72D: return 0xA72C; + case 0xA72F: return 0xA72E; + case 0xA733: return 0xA732; + case 0xA735: return 0xA734; + case 0xA737: return 0xA736; + case 0xA739: return 0xA738; + case 0xA73B: return 0xA73A; + case 0xA73D: return 0xA73C; + case 0xA73F: return 0xA73E; + case 0xA741: return 0xA740; + case 0xA743: return 0xA742; + case 0xA745: return 0xA744; + case 0xA747: return 0xA746; + case 0xA749: return 0xA748; + case 0xA74B: return 0xA74A; + case 0xA74D: return 0xA74C; + case 0xA74F: return 0xA74E; + case 0xA751: return 0xA750; + case 0xA753: return 0xA752; + case 0xA755: return 0xA754; + case 0xA757: return 0xA756; + case 0xA759: return 0xA758; + case 0xA75B: return 0xA75A; + case 0xA75D: return 0xA75C; + case 0xA75F: return 0xA75E; + case 0xA761: return 0xA760; + case 0xA763: return 0xA762; + case 0xA765: return 0xA764; + case 0xA767: return 0xA766; + case 0xA769: return 0xA768; + case 0xA76B: return 0xA76A; + case 0xA76D: return 0xA76C; + case 0xA76F: return 0xA76E; + case 0xA77A: return 0xA779; + case 0xA77C: return 0xA77B; + case 0xA77F: return 0xA77E; + case 0xA781: return 0xA780; + case 0xA783: return 0xA782; + case 0xA785: return 0xA784; + case 0xA787: return 0xA786; + case 0xA78C: return 0xA78B; + case 0xA791: return 0xA790; + case 0xA7A1: return 0xA7A0; + case 0xA7A3: return 0xA7A2; + case 0xA7A5: return 0xA7A4; + case 0xA7A7: return 0xA7A6; + case 0xA7A9: return 0xA7A8; + case 0xFF41: return 0xFF21; + case 0xFF42: return 0xFF22; + case 0xFF43: return 0xFF23; + case 0xFF44: return 0xFF24; + case 0xFF45: return 0xFF25; + case 0xFF46: return 0xFF26; + case 0xFF47: return 0xFF27; + case 0xFF48: return 0xFF28; + case 0xFF49: return 0xFF29; + case 0xFF4A: return 0xFF2A; + case 0xFF4B: return 0xFF2B; + case 0xFF4C: return 0xFF2C; + case 0xFF4D: return 0xFF2D; + case 0xFF4E: return 0xFF2E; + case 0xFF4F: return 0xFF2F; + case 0xFF50: return 0xFF30; + case 0xFF51: return 0xFF31; + case 0xFF52: return 0xFF32; + case 0xFF53: return 0xFF33; + case 0xFF54: return 0xFF34; + case 0xFF55: return 0xFF35; + case 0xFF56: return 0xFF36; + case 0xFF57: return 0xFF37; + case 0xFF58: return 0xFF38; + case 0xFF59: return 0xFF39; + case 0xFF5A: return 0xFF3A; + case 0x10428: return 0x10400; + case 0x10429: return 0x10401; + case 0x1042A: return 0x10402; + case 0x1042B: return 0x10403; + case 0x1042C: return 0x10404; + case 0x1042D: return 0x10405; + case 0x1042E: return 0x10406; + case 0x1042F: return 0x10407; + case 0x10430: return 0x10408; + case 0x10431: return 0x10409; + case 0x10432: return 0x1040A; + case 0x10433: return 0x1040B; + case 0x10434: return 0x1040C; + case 0x10435: return 0x1040D; + case 0x10436: return 0x1040E; + case 0x10437: return 0x1040F; + case 0x10438: return 0x10410; + case 0x10439: return 0x10411; + case 0x1043A: return 0x10412; + case 0x1043B: return 0x10413; + case 0x1043C: return 0x10414; + case 0x1043D: return 0x10415; + case 0x1043E: return 0x10416; + case 0x1043F: return 0x10417; + case 0x10440: return 0x10418; + case 0x10441: return 0x10419; + case 0x10442: return 0x1041A; + case 0x10443: return 0x1041B; + case 0x10444: return 0x1041C; + case 0x10445: return 0x1041D; + case 0x10446: return 0x1041E; + case 0x10447: return 0x1041F; + case 0x10448: return 0x10420; + case 0x10449: return 0x10421; + case 0x1044A: return 0x10422; + case 0x1044B: return 0x10423; + case 0x1044C: return 0x10424; + case 0x1044D: return 0x10425; + case 0x1044E: return 0x10426; + case 0x1044F: return 0x10427; + default: return ch; + } +} + +Uchar u8_tolower(Uchar ch) +{ + switch(ch) + { + case 0x0041: return 0x0061; + case 0x0042: return 0x0062; + case 0x0043: return 0x0063; + case 0x0044: return 0x0064; + case 0x0045: return 0x0065; + case 0x0046: return 0x0066; + case 0x0047: return 0x0067; + case 0x0048: return 0x0068; + case 0x0049: return 0x0069; + case 0x004A: return 0x006A; + case 0x004B: return 0x006B; + case 0x004C: return 0x006C; + case 0x004D: return 0x006D; + case 0x004E: return 0x006E; + case 0x004F: return 0x006F; + case 0x0050: return 0x0070; + case 0x0051: return 0x0071; + case 0x0052: return 0x0072; + case 0x0053: return 0x0073; + case 0x0054: return 0x0074; + case 0x0055: return 0x0075; + case 0x0056: return 0x0076; + case 0x0057: return 0x0077; + case 0x0058: return 0x0078; + case 0x0059: return 0x0079; + case 0x005A: return 0x007A; + case 0x00C0: return 0x00E0; + case 0x00C1: return 0x00E1; + case 0x00C2: return 0x00E2; + case 0x00C3: return 0x00E3; + case 0x00C4: return 0x00E4; + case 0x00C5: return 0x00E5; + case 0x00C6: return 0x00E6; + case 0x00C7: return 0x00E7; + case 0x00C8: return 0x00E8; + case 0x00C9: return 0x00E9; + case 0x00CA: return 0x00EA; + case 0x00CB: return 0x00EB; + case 0x00CC: return 0x00EC; + case 0x00CD: return 0x00ED; + case 0x00CE: return 0x00EE; + case 0x00CF: return 0x00EF; + case 0x00D0: return 0x00F0; + case 0x00D1: return 0x00F1; + case 0x00D2: return 0x00F2; + case 0x00D3: return 0x00F3; + case 0x00D4: return 0x00F4; + case 0x00D5: return 0x00F5; + case 0x00D6: return 0x00F6; + case 0x00D8: return 0x00F8; + case 0x00D9: return 0x00F9; + case 0x00DA: return 0x00FA; + case 0x00DB: return 0x00FB; + case 0x00DC: return 0x00FC; + case 0x00DD: return 0x00FD; + case 0x00DE: return 0x00FE; + case 0x0100: return 0x0101; + case 0x0102: return 0x0103; + case 0x0104: return 0x0105; + case 0x0106: return 0x0107; + case 0x0108: return 0x0109; + case 0x010A: return 0x010B; + case 0x010C: return 0x010D; + case 0x010E: return 0x010F; + case 0x0110: return 0x0111; + case 0x0112: return 0x0113; + case 0x0114: return 0x0115; + case 0x0116: return 0x0117; + case 0x0118: return 0x0119; + case 0x011A: return 0x011B; + case 0x011C: return 0x011D; + case 0x011E: return 0x011F; + case 0x0120: return 0x0121; + case 0x0122: return 0x0123; + case 0x0124: return 0x0125; + case 0x0126: return 0x0127; + case 0x0128: return 0x0129; + case 0x012A: return 0x012B; + case 0x012C: return 0x012D; + case 0x012E: return 0x012F; + case 0x0130: return 0x0069; + case 0x0132: return 0x0133; + case 0x0134: return 0x0135; + case 0x0136: return 0x0137; + case 0x0139: return 0x013A; + case 0x013B: return 0x013C; + case 0x013D: return 0x013E; + case 0x013F: return 0x0140; + case 0x0141: return 0x0142; + case 0x0143: return 0x0144; + case 0x0145: return 0x0146; + case 0x0147: return 0x0148; + case 0x014A: return 0x014B; + case 0x014C: return 0x014D; + case 0x014E: return 0x014F; + case 0x0150: return 0x0151; + case 0x0152: return 0x0153; + case 0x0154: return 0x0155; + case 0x0156: return 0x0157; + case 0x0158: return 0x0159; + case 0x015A: return 0x015B; + case 0x015C: return 0x015D; + case 0x015E: return 0x015F; + case 0x0160: return 0x0161; + case 0x0162: return 0x0163; + case 0x0164: return 0x0165; + case 0x0166: return 0x0167; + case 0x0168: return 0x0169; + case 0x016A: return 0x016B; + case 0x016C: return 0x016D; + case 0x016E: return 0x016F; + case 0x0170: return 0x0171; + case 0x0172: return 0x0173; + case 0x0174: return 0x0175; + case 0x0176: return 0x0177; + case 0x0178: return 0x00FF; + case 0x0179: return 0x017A; + case 0x017B: return 0x017C; + case 0x017D: return 0x017E; + case 0x0181: return 0x0253; + case 0x0182: return 0x0183; + case 0x0184: return 0x0185; + case 0x0186: return 0x0254; + case 0x0187: return 0x0188; + case 0x0189: return 0x0256; + case 0x018A: return 0x0257; + case 0x018B: return 0x018C; + case 0x018E: return 0x01DD; + case 0x018F: return 0x0259; + case 0x0190: return 0x025B; + case 0x0191: return 0x0192; + case 0x0193: return 0x0260; + case 0x0194: return 0x0263; + case 0x0196: return 0x0269; + case 0x0197: return 0x0268; + case 0x0198: return 0x0199; + case 0x019C: return 0x026F; + case 0x019D: return 0x0272; + case 0x019F: return 0x0275; + case 0x01A0: return 0x01A1; + case 0x01A2: return 0x01A3; + case 0x01A4: return 0x01A5; + case 0x01A6: return 0x0280; + case 0x01A7: return 0x01A8; + case 0x01A9: return 0x0283; + case 0x01AC: return 0x01AD; + case 0x01AE: return 0x0288; + case 0x01AF: return 0x01B0; + case 0x01B1: return 0x028A; + case 0x01B2: return 0x028B; + case 0x01B3: return 0x01B4; + case 0x01B5: return 0x01B6; + case 0x01B7: return 0x0292; + case 0x01B8: return 0x01B9; + case 0x01BC: return 0x01BD; + case 0x01C4: return 0x01C6; + case 0x01C5: return 0x01C6; + case 0x01C7: return 0x01C9; + case 0x01C8: return 0x01C9; + case 0x01CA: return 0x01CC; + case 0x01CB: return 0x01CC; + case 0x01CD: return 0x01CE; + case 0x01CF: return 0x01D0; + case 0x01D1: return 0x01D2; + case 0x01D3: return 0x01D4; + case 0x01D5: return 0x01D6; + case 0x01D7: return 0x01D8; + case 0x01D9: return 0x01DA; + case 0x01DB: return 0x01DC; + case 0x01DE: return 0x01DF; + case 0x01E0: return 0x01E1; + case 0x01E2: return 0x01E3; + case 0x01E4: return 0x01E5; + case 0x01E6: return 0x01E7; + case 0x01E8: return 0x01E9; + case 0x01EA: return 0x01EB; + case 0x01EC: return 0x01ED; + case 0x01EE: return 0x01EF; + case 0x01F1: return 0x01F3; + case 0x01F2: return 0x01F3; + case 0x01F4: return 0x01F5; + case 0x01F6: return 0x0195; + case 0x01F7: return 0x01BF; + case 0x01F8: return 0x01F9; + case 0x01FA: return 0x01FB; + case 0x01FC: return 0x01FD; + case 0x01FE: return 0x01FF; + case 0x0200: return 0x0201; + case 0x0202: return 0x0203; + case 0x0204: return 0x0205; + case 0x0206: return 0x0207; + case 0x0208: return 0x0209; + case 0x020A: return 0x020B; + case 0x020C: return 0x020D; + case 0x020E: return 0x020F; + case 0x0210: return 0x0211; + case 0x0212: return 0x0213; + case 0x0214: return 0x0215; + case 0x0216: return 0x0217; + case 0x0218: return 0x0219; + case 0x021A: return 0x021B; + case 0x021C: return 0x021D; + case 0x021E: return 0x021F; + case 0x0220: return 0x019E; + case 0x0222: return 0x0223; + case 0x0224: return 0x0225; + case 0x0226: return 0x0227; + case 0x0228: return 0x0229; + case 0x022A: return 0x022B; + case 0x022C: return 0x022D; + case 0x022E: return 0x022F; + case 0x0230: return 0x0231; + case 0x0232: return 0x0233; + case 0x023A: return 0x2C65; + case 0x023B: return 0x023C; + case 0x023D: return 0x019A; + case 0x023E: return 0x2C66; + case 0x0241: return 0x0242; + case 0x0243: return 0x0180; + case 0x0244: return 0x0289; + case 0x0245: return 0x028C; + case 0x0246: return 0x0247; + case 0x0248: return 0x0249; + case 0x024A: return 0x024B; + case 0x024C: return 0x024D; + case 0x024E: return 0x024F; + case 0x0370: return 0x0371; + case 0x0372: return 0x0373; + case 0x0376: return 0x0377; + case 0x0386: return 0x03AC; + case 0x0388: return 0x03AD; + case 0x0389: return 0x03AE; + case 0x038A: return 0x03AF; + case 0x038C: return 0x03CC; + case 0x038E: return 0x03CD; + case 0x038F: return 0x03CE; + case 0x0391: return 0x03B1; + case 0x0392: return 0x03B2; + case 0x0393: return 0x03B3; + case 0x0394: return 0x03B4; + case 0x0395: return 0x03B5; + case 0x0396: return 0x03B6; + case 0x0397: return 0x03B7; + case 0x0398: return 0x03B8; + case 0x0399: return 0x03B9; + case 0x039A: return 0x03BA; + case 0x039B: return 0x03BB; + case 0x039C: return 0x03BC; + case 0x039D: return 0x03BD; + case 0x039E: return 0x03BE; + case 0x039F: return 0x03BF; + case 0x03A0: return 0x03C0; + case 0x03A1: return 0x03C1; + case 0x03A3: return 0x03C3; + case 0x03A4: return 0x03C4; + case 0x03A5: return 0x03C5; + case 0x03A6: return 0x03C6; + case 0x03A7: return 0x03C7; + case 0x03A8: return 0x03C8; + case 0x03A9: return 0x03C9; + case 0x03AA: return 0x03CA; + case 0x03AB: return 0x03CB; + case 0x03CF: return 0x03D7; + case 0x03D8: return 0x03D9; + case 0x03DA: return 0x03DB; + case 0x03DC: return 0x03DD; + case 0x03DE: return 0x03DF; + case 0x03E0: return 0x03E1; + case 0x03E2: return 0x03E3; + case 0x03E4: return 0x03E5; + case 0x03E6: return 0x03E7; + case 0x03E8: return 0x03E9; + case 0x03EA: return 0x03EB; + case 0x03EC: return 0x03ED; + case 0x03EE: return 0x03EF; + case 0x03F4: return 0x03B8; + case 0x03F7: return 0x03F8; + case 0x03F9: return 0x03F2; + case 0x03FA: return 0x03FB; + case 0x03FD: return 0x037B; + case 0x03FE: return 0x037C; + case 0x03FF: return 0x037D; + case 0x0400: return 0x0450; + case 0x0401: return 0x0451; + case 0x0402: return 0x0452; + case 0x0403: return 0x0453; + case 0x0404: return 0x0454; + case 0x0405: return 0x0455; + case 0x0406: return 0x0456; + case 0x0407: return 0x0457; + case 0x0408: return 0x0458; + case 0x0409: return 0x0459; + case 0x040A: return 0x045A; + case 0x040B: return 0x045B; + case 0x040C: return 0x045C; + case 0x040D: return 0x045D; + case 0x040E: return 0x045E; + case 0x040F: return 0x045F; + case 0x0410: return 0x0430; + case 0x0411: return 0x0431; + case 0x0412: return 0x0432; + case 0x0413: return 0x0433; + case 0x0414: return 0x0434; + case 0x0415: return 0x0435; + case 0x0416: return 0x0436; + case 0x0417: return 0x0437; + case 0x0418: return 0x0438; + case 0x0419: return 0x0439; + case 0x041A: return 0x043A; + case 0x041B: return 0x043B; + case 0x041C: return 0x043C; + case 0x041D: return 0x043D; + case 0x041E: return 0x043E; + case 0x041F: return 0x043F; + case 0x0420: return 0x0440; + case 0x0421: return 0x0441; + case 0x0422: return 0x0442; + case 0x0423: return 0x0443; + case 0x0424: return 0x0444; + case 0x0425: return 0x0445; + case 0x0426: return 0x0446; + case 0x0427: return 0x0447; + case 0x0428: return 0x0448; + case 0x0429: return 0x0449; + case 0x042A: return 0x044A; + case 0x042B: return 0x044B; + case 0x042C: return 0x044C; + case 0x042D: return 0x044D; + case 0x042E: return 0x044E; + case 0x042F: return 0x044F; + case 0x0460: return 0x0461; + case 0x0462: return 0x0463; + case 0x0464: return 0x0465; + case 0x0466: return 0x0467; + case 0x0468: return 0x0469; + case 0x046A: return 0x046B; + case 0x046C: return 0x046D; + case 0x046E: return 0x046F; + case 0x0470: return 0x0471; + case 0x0472: return 0x0473; + case 0x0474: return 0x0475; + case 0x0476: return 0x0477; + case 0x0478: return 0x0479; + case 0x047A: return 0x047B; + case 0x047C: return 0x047D; + case 0x047E: return 0x047F; + case 0x0480: return 0x0481; + case 0x048A: return 0x048B; + case 0x048C: return 0x048D; + case 0x048E: return 0x048F; + case 0x0490: return 0x0491; + case 0x0492: return 0x0493; + case 0x0494: return 0x0495; + case 0x0496: return 0x0497; + case 0x0498: return 0x0499; + case 0x049A: return 0x049B; + case 0x049C: return 0x049D; + case 0x049E: return 0x049F; + case 0x04A0: return 0x04A1; + case 0x04A2: return 0x04A3; + case 0x04A4: return 0x04A5; + case 0x04A6: return 0x04A7; + case 0x04A8: return 0x04A9; + case 0x04AA: return 0x04AB; + case 0x04AC: return 0x04AD; + case 0x04AE: return 0x04AF; + case 0x04B0: return 0x04B1; + case 0x04B2: return 0x04B3; + case 0x04B4: return 0x04B5; + case 0x04B6: return 0x04B7; + case 0x04B8: return 0x04B9; + case 0x04BA: return 0x04BB; + case 0x04BC: return 0x04BD; + case 0x04BE: return 0x04BF; + case 0x04C0: return 0x04CF; + case 0x04C1: return 0x04C2; + case 0x04C3: return 0x04C4; + case 0x04C5: return 0x04C6; + case 0x04C7: return 0x04C8; + case 0x04C9: return 0x04CA; + case 0x04CB: return 0x04CC; + case 0x04CD: return 0x04CE; + case 0x04D0: return 0x04D1; + case 0x04D2: return 0x04D3; + case 0x04D4: return 0x04D5; + case 0x04D6: return 0x04D7; + case 0x04D8: return 0x04D9; + case 0x04DA: return 0x04DB; + case 0x04DC: return 0x04DD; + case 0x04DE: return 0x04DF; + case 0x04E0: return 0x04E1; + case 0x04E2: return 0x04E3; + case 0x04E4: return 0x04E5; + case 0x04E6: return 0x04E7; + case 0x04E8: return 0x04E9; + case 0x04EA: return 0x04EB; + case 0x04EC: return 0x04ED; + case 0x04EE: return 0x04EF; + case 0x04F0: return 0x04F1; + case 0x04F2: return 0x04F3; + case 0x04F4: return 0x04F5; + case 0x04F6: return 0x04F7; + case 0x04F8: return 0x04F9; + case 0x04FA: return 0x04FB; + case 0x04FC: return 0x04FD; + case 0x04FE: return 0x04FF; + case 0x0500: return 0x0501; + case 0x0502: return 0x0503; + case 0x0504: return 0x0505; + case 0x0506: return 0x0507; + case 0x0508: return 0x0509; + case 0x050A: return 0x050B; + case 0x050C: return 0x050D; + case 0x050E: return 0x050F; + case 0x0510: return 0x0511; + case 0x0512: return 0x0513; + case 0x0514: return 0x0515; + case 0x0516: return 0x0517; + case 0x0518: return 0x0519; + case 0x051A: return 0x051B; + case 0x051C: return 0x051D; + case 0x051E: return 0x051F; + case 0x0520: return 0x0521; + case 0x0522: return 0x0523; + case 0x0524: return 0x0525; + case 0x0526: return 0x0527; + case 0x0531: return 0x0561; + case 0x0532: return 0x0562; + case 0x0533: return 0x0563; + case 0x0534: return 0x0564; + case 0x0535: return 0x0565; + case 0x0536: return 0x0566; + case 0x0537: return 0x0567; + case 0x0538: return 0x0568; + case 0x0539: return 0x0569; + case 0x053A: return 0x056A; + case 0x053B: return 0x056B; + case 0x053C: return 0x056C; + case 0x053D: return 0x056D; + case 0x053E: return 0x056E; + case 0x053F: return 0x056F; + case 0x0540: return 0x0570; + case 0x0541: return 0x0571; + case 0x0542: return 0x0572; + case 0x0543: return 0x0573; + case 0x0544: return 0x0574; + case 0x0545: return 0x0575; + case 0x0546: return 0x0576; + case 0x0547: return 0x0577; + case 0x0548: return 0x0578; + case 0x0549: return 0x0579; + case 0x054A: return 0x057A; + case 0x054B: return 0x057B; + case 0x054C: return 0x057C; + case 0x054D: return 0x057D; + case 0x054E: return 0x057E; + case 0x054F: return 0x057F; + case 0x0550: return 0x0580; + case 0x0551: return 0x0581; + case 0x0552: return 0x0582; + case 0x0553: return 0x0583; + case 0x0554: return 0x0584; + case 0x0555: return 0x0585; + case 0x0556: return 0x0586; + case 0x10A0: return 0x2D00; + case 0x10A1: return 0x2D01; + case 0x10A2: return 0x2D02; + case 0x10A3: return 0x2D03; + case 0x10A4: return 0x2D04; + case 0x10A5: return 0x2D05; + case 0x10A6: return 0x2D06; + case 0x10A7: return 0x2D07; + case 0x10A8: return 0x2D08; + case 0x10A9: return 0x2D09; + case 0x10AA: return 0x2D0A; + case 0x10AB: return 0x2D0B; + case 0x10AC: return 0x2D0C; + case 0x10AD: return 0x2D0D; + case 0x10AE: return 0x2D0E; + case 0x10AF: return 0x2D0F; + case 0x10B0: return 0x2D10; + case 0x10B1: return 0x2D11; + case 0x10B2: return 0x2D12; + case 0x10B3: return 0x2D13; + case 0x10B4: return 0x2D14; + case 0x10B5: return 0x2D15; + case 0x10B6: return 0x2D16; + case 0x10B7: return 0x2D17; + case 0x10B8: return 0x2D18; + case 0x10B9: return 0x2D19; + case 0x10BA: return 0x2D1A; + case 0x10BB: return 0x2D1B; + case 0x10BC: return 0x2D1C; + case 0x10BD: return 0x2D1D; + case 0x10BE: return 0x2D1E; + case 0x10BF: return 0x2D1F; + case 0x10C0: return 0x2D20; + case 0x10C1: return 0x2D21; + case 0x10C2: return 0x2D22; + case 0x10C3: return 0x2D23; + case 0x10C4: return 0x2D24; + case 0x10C5: return 0x2D25; + case 0x1E00: return 0x1E01; + case 0x1E02: return 0x1E03; + case 0x1E04: return 0x1E05; + case 0x1E06: return 0x1E07; + case 0x1E08: return 0x1E09; + case 0x1E0A: return 0x1E0B; + case 0x1E0C: return 0x1E0D; + case 0x1E0E: return 0x1E0F; + case 0x1E10: return 0x1E11; + case 0x1E12: return 0x1E13; + case 0x1E14: return 0x1E15; + case 0x1E16: return 0x1E17; + case 0x1E18: return 0x1E19; + case 0x1E1A: return 0x1E1B; + case 0x1E1C: return 0x1E1D; + case 0x1E1E: return 0x1E1F; + case 0x1E20: return 0x1E21; + case 0x1E22: return 0x1E23; + case 0x1E24: return 0x1E25; + case 0x1E26: return 0x1E27; + case 0x1E28: return 0x1E29; + case 0x1E2A: return 0x1E2B; + case 0x1E2C: return 0x1E2D; + case 0x1E2E: return 0x1E2F; + case 0x1E30: return 0x1E31; + case 0x1E32: return 0x1E33; + case 0x1E34: return 0x1E35; + case 0x1E36: return 0x1E37; + case 0x1E38: return 0x1E39; + case 0x1E3A: return 0x1E3B; + case 0x1E3C: return 0x1E3D; + case 0x1E3E: return 0x1E3F; + case 0x1E40: return 0x1E41; + case 0x1E42: return 0x1E43; + case 0x1E44: return 0x1E45; + case 0x1E46: return 0x1E47; + case 0x1E48: return 0x1E49; + case 0x1E4A: return 0x1E4B; + case 0x1E4C: return 0x1E4D; + case 0x1E4E: return 0x1E4F; + case 0x1E50: return 0x1E51; + case 0x1E52: return 0x1E53; + case 0x1E54: return 0x1E55; + case 0x1E56: return 0x1E57; + case 0x1E58: return 0x1E59; + case 0x1E5A: return 0x1E5B; + case 0x1E5C: return 0x1E5D; + case 0x1E5E: return 0x1E5F; + case 0x1E60: return 0x1E61; + case 0x1E62: return 0x1E63; + case 0x1E64: return 0x1E65; + case 0x1E66: return 0x1E67; + case 0x1E68: return 0x1E69; + case 0x1E6A: return 0x1E6B; + case 0x1E6C: return 0x1E6D; + case 0x1E6E: return 0x1E6F; + case 0x1E70: return 0x1E71; + case 0x1E72: return 0x1E73; + case 0x1E74: return 0x1E75; + case 0x1E76: return 0x1E77; + case 0x1E78: return 0x1E79; + case 0x1E7A: return 0x1E7B; + case 0x1E7C: return 0x1E7D; + case 0x1E7E: return 0x1E7F; + case 0x1E80: return 0x1E81; + case 0x1E82: return 0x1E83; + case 0x1E84: return 0x1E85; + case 0x1E86: return 0x1E87; + case 0x1E88: return 0x1E89; + case 0x1E8A: return 0x1E8B; + case 0x1E8C: return 0x1E8D; + case 0x1E8E: return 0x1E8F; + case 0x1E90: return 0x1E91; + case 0x1E92: return 0x1E93; + case 0x1E94: return 0x1E95; + case 0x1E9E: return 0x00DF; + case 0x1EA0: return 0x1EA1; + case 0x1EA2: return 0x1EA3; + case 0x1EA4: return 0x1EA5; + case 0x1EA6: return 0x1EA7; + case 0x1EA8: return 0x1EA9; + case 0x1EAA: return 0x1EAB; + case 0x1EAC: return 0x1EAD; + case 0x1EAE: return 0x1EAF; + case 0x1EB0: return 0x1EB1; + case 0x1EB2: return 0x1EB3; + case 0x1EB4: return 0x1EB5; + case 0x1EB6: return 0x1EB7; + case 0x1EB8: return 0x1EB9; + case 0x1EBA: return 0x1EBB; + case 0x1EBC: return 0x1EBD; + case 0x1EBE: return 0x1EBF; + case 0x1EC0: return 0x1EC1; + case 0x1EC2: return 0x1EC3; + case 0x1EC4: return 0x1EC5; + case 0x1EC6: return 0x1EC7; + case 0x1EC8: return 0x1EC9; + case 0x1ECA: return 0x1ECB; + case 0x1ECC: return 0x1ECD; + case 0x1ECE: return 0x1ECF; + case 0x1ED0: return 0x1ED1; + case 0x1ED2: return 0x1ED3; + case 0x1ED4: return 0x1ED5; + case 0x1ED6: return 0x1ED7; + case 0x1ED8: return 0x1ED9; + case 0x1EDA: return 0x1EDB; + case 0x1EDC: return 0x1EDD; + case 0x1EDE: return 0x1EDF; + case 0x1EE0: return 0x1EE1; + case 0x1EE2: return 0x1EE3; + case 0x1EE4: return 0x1EE5; + case 0x1EE6: return 0x1EE7; + case 0x1EE8: return 0x1EE9; + case 0x1EEA: return 0x1EEB; + case 0x1EEC: return 0x1EED; + case 0x1EEE: return 0x1EEF; + case 0x1EF0: return 0x1EF1; + case 0x1EF2: return 0x1EF3; + case 0x1EF4: return 0x1EF5; + case 0x1EF6: return 0x1EF7; + case 0x1EF8: return 0x1EF9; + case 0x1EFA: return 0x1EFB; + case 0x1EFC: return 0x1EFD; + case 0x1EFE: return 0x1EFF; + case 0x1F08: return 0x1F00; + case 0x1F09: return 0x1F01; + case 0x1F0A: return 0x1F02; + case 0x1F0B: return 0x1F03; + case 0x1F0C: return 0x1F04; + case 0x1F0D: return 0x1F05; + case 0x1F0E: return 0x1F06; + case 0x1F0F: return 0x1F07; + case 0x1F18: return 0x1F10; + case 0x1F19: return 0x1F11; + case 0x1F1A: return 0x1F12; + case 0x1F1B: return 0x1F13; + case 0x1F1C: return 0x1F14; + case 0x1F1D: return 0x1F15; + case 0x1F28: return 0x1F20; + case 0x1F29: return 0x1F21; + case 0x1F2A: return 0x1F22; + case 0x1F2B: return 0x1F23; + case 0x1F2C: return 0x1F24; + case 0x1F2D: return 0x1F25; + case 0x1F2E: return 0x1F26; + case 0x1F2F: return 0x1F27; + case 0x1F38: return 0x1F30; + case 0x1F39: return 0x1F31; + case 0x1F3A: return 0x1F32; + case 0x1F3B: return 0x1F33; + case 0x1F3C: return 0x1F34; + case 0x1F3D: return 0x1F35; + case 0x1F3E: return 0x1F36; + case 0x1F3F: return 0x1F37; + case 0x1F48: return 0x1F40; + case 0x1F49: return 0x1F41; + case 0x1F4A: return 0x1F42; + case 0x1F4B: return 0x1F43; + case 0x1F4C: return 0x1F44; + case 0x1F4D: return 0x1F45; + case 0x1F59: return 0x1F51; + case 0x1F5B: return 0x1F53; + case 0x1F5D: return 0x1F55; + case 0x1F5F: return 0x1F57; + case 0x1F68: return 0x1F60; + case 0x1F69: return 0x1F61; + case 0x1F6A: return 0x1F62; + case 0x1F6B: return 0x1F63; + case 0x1F6C: return 0x1F64; + case 0x1F6D: return 0x1F65; + case 0x1F6E: return 0x1F66; + case 0x1F6F: return 0x1F67; + case 0x1F88: return 0x1F80; + case 0x1F89: return 0x1F81; + case 0x1F8A: return 0x1F82; + case 0x1F8B: return 0x1F83; + case 0x1F8C: return 0x1F84; + case 0x1F8D: return 0x1F85; + case 0x1F8E: return 0x1F86; + case 0x1F8F: return 0x1F87; + case 0x1F98: return 0x1F90; + case 0x1F99: return 0x1F91; + case 0x1F9A: return 0x1F92; + case 0x1F9B: return 0x1F93; + case 0x1F9C: return 0x1F94; + case 0x1F9D: return 0x1F95; + case 0x1F9E: return 0x1F96; + case 0x1F9F: return 0x1F97; + case 0x1FA8: return 0x1FA0; + case 0x1FA9: return 0x1FA1; + case 0x1FAA: return 0x1FA2; + case 0x1FAB: return 0x1FA3; + case 0x1FAC: return 0x1FA4; + case 0x1FAD: return 0x1FA5; + case 0x1FAE: return 0x1FA6; + case 0x1FAF: return 0x1FA7; + case 0x1FB8: return 0x1FB0; + case 0x1FB9: return 0x1FB1; + case 0x1FBA: return 0x1F70; + case 0x1FBB: return 0x1F71; + case 0x1FBC: return 0x1FB3; + case 0x1FC8: return 0x1F72; + case 0x1FC9: return 0x1F73; + case 0x1FCA: return 0x1F74; + case 0x1FCB: return 0x1F75; + case 0x1FCC: return 0x1FC3; + case 0x1FD8: return 0x1FD0; + case 0x1FD9: return 0x1FD1; + case 0x1FDA: return 0x1F76; + case 0x1FDB: return 0x1F77; + case 0x1FE8: return 0x1FE0; + case 0x1FE9: return 0x1FE1; + case 0x1FEA: return 0x1F7A; + case 0x1FEB: return 0x1F7B; + case 0x1FEC: return 0x1FE5; + case 0x1FF8: return 0x1F78; + case 0x1FF9: return 0x1F79; + case 0x1FFA: return 0x1F7C; + case 0x1FFB: return 0x1F7D; + case 0x1FFC: return 0x1FF3; + case 0x2126: return 0x03C9; + case 0x212A: return 0x006B; + case 0x212B: return 0x00E5; + case 0x2132: return 0x214E; + case 0x2160: return 0x2170; + case 0x2161: return 0x2171; + case 0x2162: return 0x2172; + case 0x2163: return 0x2173; + case 0x2164: return 0x2174; + case 0x2165: return 0x2175; + case 0x2166: return 0x2176; + case 0x2167: return 0x2177; + case 0x2168: return 0x2178; + case 0x2169: return 0x2179; + case 0x216A: return 0x217A; + case 0x216B: return 0x217B; + case 0x216C: return 0x217C; + case 0x216D: return 0x217D; + case 0x216E: return 0x217E; + case 0x216F: return 0x217F; + case 0x2183: return 0x2184; + case 0x24B6: return 0x24D0; + case 0x24B7: return 0x24D1; + case 0x24B8: return 0x24D2; + case 0x24B9: return 0x24D3; + case 0x24BA: return 0x24D4; + case 0x24BB: return 0x24D5; + case 0x24BC: return 0x24D6; + case 0x24BD: return 0x24D7; + case 0x24BE: return 0x24D8; + case 0x24BF: return 0x24D9; + case 0x24C0: return 0x24DA; + case 0x24C1: return 0x24DB; + case 0x24C2: return 0x24DC; + case 0x24C3: return 0x24DD; + case 0x24C4: return 0x24DE; + case 0x24C5: return 0x24DF; + case 0x24C6: return 0x24E0; + case 0x24C7: return 0x24E1; + case 0x24C8: return 0x24E2; + case 0x24C9: return 0x24E3; + case 0x24CA: return 0x24E4; + case 0x24CB: return 0x24E5; + case 0x24CC: return 0x24E6; + case 0x24CD: return 0x24E7; + case 0x24CE: return 0x24E8; + case 0x24CF: return 0x24E9; + case 0x2C00: return 0x2C30; + case 0x2C01: return 0x2C31; + case 0x2C02: return 0x2C32; + case 0x2C03: return 0x2C33; + case 0x2C04: return 0x2C34; + case 0x2C05: return 0x2C35; + case 0x2C06: return 0x2C36; + case 0x2C07: return 0x2C37; + case 0x2C08: return 0x2C38; + case 0x2C09: return 0x2C39; + case 0x2C0A: return 0x2C3A; + case 0x2C0B: return 0x2C3B; + case 0x2C0C: return 0x2C3C; + case 0x2C0D: return 0x2C3D; + case 0x2C0E: return 0x2C3E; + case 0x2C0F: return 0x2C3F; + case 0x2C10: return 0x2C40; + case 0x2C11: return 0x2C41; + case 0x2C12: return 0x2C42; + case 0x2C13: return 0x2C43; + case 0x2C14: return 0x2C44; + case 0x2C15: return 0x2C45; + case 0x2C16: return 0x2C46; + case 0x2C17: return 0x2C47; + case 0x2C18: return 0x2C48; + case 0x2C19: return 0x2C49; + case 0x2C1A: return 0x2C4A; + case 0x2C1B: return 0x2C4B; + case 0x2C1C: return 0x2C4C; + case 0x2C1D: return 0x2C4D; + case 0x2C1E: return 0x2C4E; + case 0x2C1F: return 0x2C4F; + case 0x2C20: return 0x2C50; + case 0x2C21: return 0x2C51; + case 0x2C22: return 0x2C52; + case 0x2C23: return 0x2C53; + case 0x2C24: return 0x2C54; + case 0x2C25: return 0x2C55; + case 0x2C26: return 0x2C56; + case 0x2C27: return 0x2C57; + case 0x2C28: return 0x2C58; + case 0x2C29: return 0x2C59; + case 0x2C2A: return 0x2C5A; + case 0x2C2B: return 0x2C5B; + case 0x2C2C: return 0x2C5C; + case 0x2C2D: return 0x2C5D; + case 0x2C2E: return 0x2C5E; + case 0x2C60: return 0x2C61; + case 0x2C62: return 0x026B; + case 0x2C63: return 0x1D7D; + case 0x2C64: return 0x027D; + case 0x2C67: return 0x2C68; + case 0x2C69: return 0x2C6A; + case 0x2C6B: return 0x2C6C; + case 0x2C6D: return 0x0251; + case 0x2C6E: return 0x0271; + case 0x2C6F: return 0x0250; + case 0x2C70: return 0x0252; + case 0x2C72: return 0x2C73; + case 0x2C75: return 0x2C76; + case 0x2C7E: return 0x023F; + case 0x2C7F: return 0x0240; + case 0x2C80: return 0x2C81; + case 0x2C82: return 0x2C83; + case 0x2C84: return 0x2C85; + case 0x2C86: return 0x2C87; + case 0x2C88: return 0x2C89; + case 0x2C8A: return 0x2C8B; + case 0x2C8C: return 0x2C8D; + case 0x2C8E: return 0x2C8F; + case 0x2C90: return 0x2C91; + case 0x2C92: return 0x2C93; + case 0x2C94: return 0x2C95; + case 0x2C96: return 0x2C97; + case 0x2C98: return 0x2C99; + case 0x2C9A: return 0x2C9B; + case 0x2C9C: return 0x2C9D; + case 0x2C9E: return 0x2C9F; + case 0x2CA0: return 0x2CA1; + case 0x2CA2: return 0x2CA3; + case 0x2CA4: return 0x2CA5; + case 0x2CA6: return 0x2CA7; + case 0x2CA8: return 0x2CA9; + case 0x2CAA: return 0x2CAB; + case 0x2CAC: return 0x2CAD; + case 0x2CAE: return 0x2CAF; + case 0x2CB0: return 0x2CB1; + case 0x2CB2: return 0x2CB3; + case 0x2CB4: return 0x2CB5; + case 0x2CB6: return 0x2CB7; + case 0x2CB8: return 0x2CB9; + case 0x2CBA: return 0x2CBB; + case 0x2CBC: return 0x2CBD; + case 0x2CBE: return 0x2CBF; + case 0x2CC0: return 0x2CC1; + case 0x2CC2: return 0x2CC3; + case 0x2CC4: return 0x2CC5; + case 0x2CC6: return 0x2CC7; + case 0x2CC8: return 0x2CC9; + case 0x2CCA: return 0x2CCB; + case 0x2CCC: return 0x2CCD; + case 0x2CCE: return 0x2CCF; + case 0x2CD0: return 0x2CD1; + case 0x2CD2: return 0x2CD3; + case 0x2CD4: return 0x2CD5; + case 0x2CD6: return 0x2CD7; + case 0x2CD8: return 0x2CD9; + case 0x2CDA: return 0x2CDB; + case 0x2CDC: return 0x2CDD; + case 0x2CDE: return 0x2CDF; + case 0x2CE0: return 0x2CE1; + case 0x2CE2: return 0x2CE3; + case 0x2CEB: return 0x2CEC; + case 0x2CED: return 0x2CEE; + case 0xA640: return 0xA641; + case 0xA642: return 0xA643; + case 0xA644: return 0xA645; + case 0xA646: return 0xA647; + case 0xA648: return 0xA649; + case 0xA64A: return 0xA64B; + case 0xA64C: return 0xA64D; + case 0xA64E: return 0xA64F; + case 0xA650: return 0xA651; + case 0xA652: return 0xA653; + case 0xA654: return 0xA655; + case 0xA656: return 0xA657; + case 0xA658: return 0xA659; + case 0xA65A: return 0xA65B; + case 0xA65C: return 0xA65D; + case 0xA65E: return 0xA65F; + case 0xA660: return 0xA661; + case 0xA662: return 0xA663; + case 0xA664: return 0xA665; + case 0xA666: return 0xA667; + case 0xA668: return 0xA669; + case 0xA66A: return 0xA66B; + case 0xA66C: return 0xA66D; + case 0xA680: return 0xA681; + case 0xA682: return 0xA683; + case 0xA684: return 0xA685; + case 0xA686: return 0xA687; + case 0xA688: return 0xA689; + case 0xA68A: return 0xA68B; + case 0xA68C: return 0xA68D; + case 0xA68E: return 0xA68F; + case 0xA690: return 0xA691; + case 0xA692: return 0xA693; + case 0xA694: return 0xA695; + case 0xA696: return 0xA697; + case 0xA722: return 0xA723; + case 0xA724: return 0xA725; + case 0xA726: return 0xA727; + case 0xA728: return 0xA729; + case 0xA72A: return 0xA72B; + case 0xA72C: return 0xA72D; + case 0xA72E: return 0xA72F; + case 0xA732: return 0xA733; + case 0xA734: return 0xA735; + case 0xA736: return 0xA737; + case 0xA738: return 0xA739; + case 0xA73A: return 0xA73B; + case 0xA73C: return 0xA73D; + case 0xA73E: return 0xA73F; + case 0xA740: return 0xA741; + case 0xA742: return 0xA743; + case 0xA744: return 0xA745; + case 0xA746: return 0xA747; + case 0xA748: return 0xA749; + case 0xA74A: return 0xA74B; + case 0xA74C: return 0xA74D; + case 0xA74E: return 0xA74F; + case 0xA750: return 0xA751; + case 0xA752: return 0xA753; + case 0xA754: return 0xA755; + case 0xA756: return 0xA757; + case 0xA758: return 0xA759; + case 0xA75A: return 0xA75B; + case 0xA75C: return 0xA75D; + case 0xA75E: return 0xA75F; + case 0xA760: return 0xA761; + case 0xA762: return 0xA763; + case 0xA764: return 0xA765; + case 0xA766: return 0xA767; + case 0xA768: return 0xA769; + case 0xA76A: return 0xA76B; + case 0xA76C: return 0xA76D; + case 0xA76E: return 0xA76F; + case 0xA779: return 0xA77A; + case 0xA77B: return 0xA77C; + case 0xA77D: return 0x1D79; + case 0xA77E: return 0xA77F; + case 0xA780: return 0xA781; + case 0xA782: return 0xA783; + case 0xA784: return 0xA785; + case 0xA786: return 0xA787; + case 0xA78B: return 0xA78C; + case 0xA78D: return 0x0265; + case 0xA790: return 0xA791; + case 0xA7A0: return 0xA7A1; + case 0xA7A2: return 0xA7A3; + case 0xA7A4: return 0xA7A5; + case 0xA7A6: return 0xA7A7; + case 0xA7A8: return 0xA7A9; + case 0xFF21: return 0xFF41; + case 0xFF22: return 0xFF42; + case 0xFF23: return 0xFF43; + case 0xFF24: return 0xFF44; + case 0xFF25: return 0xFF45; + case 0xFF26: return 0xFF46; + case 0xFF27: return 0xFF47; + case 0xFF28: return 0xFF48; + case 0xFF29: return 0xFF49; + case 0xFF2A: return 0xFF4A; + case 0xFF2B: return 0xFF4B; + case 0xFF2C: return 0xFF4C; + case 0xFF2D: return 0xFF4D; + case 0xFF2E: return 0xFF4E; + case 0xFF2F: return 0xFF4F; + case 0xFF30: return 0xFF50; + case 0xFF31: return 0xFF51; + case 0xFF32: return 0xFF52; + case 0xFF33: return 0xFF53; + case 0xFF34: return 0xFF54; + case 0xFF35: return 0xFF55; + case 0xFF36: return 0xFF56; + case 0xFF37: return 0xFF57; + case 0xFF38: return 0xFF58; + case 0xFF39: return 0xFF59; + case 0xFF3A: return 0xFF5A; + case 0x10400: return 0x10428; + case 0x10401: return 0x10429; + case 0x10402: return 0x1042A; + case 0x10403: return 0x1042B; + case 0x10404: return 0x1042C; + case 0x10405: return 0x1042D; + case 0x10406: return 0x1042E; + case 0x10407: return 0x1042F; + case 0x10408: return 0x10430; + case 0x10409: return 0x10431; + case 0x1040A: return 0x10432; + case 0x1040B: return 0x10433; + case 0x1040C: return 0x10434; + case 0x1040D: return 0x10435; + case 0x1040E: return 0x10436; + case 0x1040F: return 0x10437; + case 0x10410: return 0x10438; + case 0x10411: return 0x10439; + case 0x10412: return 0x1043A; + case 0x10413: return 0x1043B; + case 0x10414: return 0x1043C; + case 0x10415: return 0x1043D; + case 0x10416: return 0x1043E; + case 0x10417: return 0x1043F; + case 0x10418: return 0x10440; + case 0x10419: return 0x10441; + case 0x1041A: return 0x10442; + case 0x1041B: return 0x10443; + case 0x1041C: return 0x10444; + case 0x1041D: return 0x10445; + case 0x1041E: return 0x10446; + case 0x1041F: return 0x10447; + case 0x10420: return 0x10448; + case 0x10421: return 0x10449; + case 0x10422: return 0x1044A; + case 0x10423: return 0x1044B; + case 0x10424: return 0x1044C; + case 0x10425: return 0x1044D; + case 0x10426: return 0x1044E; + case 0x10427: return 0x1044F; + default: return ch; + } +} diff --git a/app/jni/utf8lib.h b/app/jni/utf8lib.h new file mode 100644 index 0000000..543fbfc --- /dev/null +++ b/app/jni/utf8lib.h @@ -0,0 +1,67 @@ +/* + * UTF-8 utility functions for DarkPlaces + */ +#ifndef UTF8LIB_H__ +#define UTF8LIB_H__ + +#include "qtypes.h" + +// types for unicode strings +// let them be 32 bit for now +// normally, whcar_t is 16 or 32 bit, 16 on linux I think, 32 on haiku and maybe windows +#ifdef _MSC_VER +typedef __int32 U_int32; +#else +#include +#include +typedef int32_t U_int32; +#endif + +// Uchar, a wide character +typedef U_int32 Uchar; + +// Initialize UTF8, this registers cvars which allows for UTF8 to be disabled +// completely. +// When UTF8 is disabled, every u8_ function will work exactly as you'd expect +// a non-utf8 version to work: u8_strlen() will wrap to strlen() +// u8_byteofs() and u8_charidx() will simply return whatever is passed as index parameter +// u8_getchar() will will just return the next byte, u8_fromchar will write one byte, ... +extern cvar_t utf8_enable; +void u8_Init(void); + +size_t u8_strlen(const char*); +size_t u8_strnlen(const char*, size_t); +int u8_byteofs(const char*, size_t, size_t*); +int u8_charidx(const char*, size_t, size_t*); +size_t u8_bytelen(const char*, size_t); +size_t u8_prevbyte(const char*, size_t); +Uchar u8_getchar_utf8_enabled(const char*, const char**); +Uchar u8_getnchar_utf8_enabled(const char*, const char**, size_t); +int u8_fromchar(Uchar, char*, size_t); +size_t u8_mbstowcs(Uchar *, const char *, size_t); +size_t u8_wcstombs(char*, const Uchar*, size_t); +size_t u8_COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); + +// returns a static buffer, use this for inlining +char *u8_encodech(Uchar ch, size_t*, char*buf16); + +size_t u8_strpad(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth); +size_t u8_strpad_colorcodes(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth); + +/* Careful: if we disable utf8 but not freetype, we wish to see freetype chars + * for normal letters. So use E000+x for special chars, but leave the freetype stuff for the + * rest: + */ +extern Uchar u8_quake2utf8map[256]; +// these defines get a bit tricky, as c and e may be aliased to the same variable +#define u8_getchar(c,e) (utf8_enable.integer ? u8_getchar_utf8_enabled(c,e) : (u8_quake2utf8map[(unsigned char)(*(e) = (c) + 1)[-1]])) +#define u8_getchar_noendptr(c) (utf8_enable.integer ? u8_getchar_utf8_enabled(c,NULL) : (u8_quake2utf8map[(unsigned char)*(c)])) +#define u8_getchar_check(c,e) ((e) ? u8_getchar((c),(e)) : u8_getchar_noendptr((c))) +#define u8_getnchar(c,e,n) (utf8_enable.integer ? u8_getnchar_utf8_enabled(c,e,n) : ((n) <= 0 ? ((*(e) = c), 0) : (u8_quake2utf8map[(unsigned char)(*(e) = (c) + 1)[-1]]))) +#define u8_getnchar_noendptr(c,n) (utf8_enable.integer ? u8_getnchar_utf8_enabled(c,NULL,n) : ((n) <= 0 ? 0 : (u8_quake2utf8map[(unsigned char)*(c)]))) +#define u8_getnchar_check(c,e,n) ((e) ? u8_getchar((c),(e),(n)) : u8_getchar_noendptr((c),(n))) + +Uchar u8_toupper(Uchar ch); +Uchar u8_tolower(Uchar ch); + +#endif // UTF8LIB_H__ diff --git a/app/jni/vid.h b/app/jni/vid.h new file mode 100644 index 0000000..1b960d4 --- /dev/null +++ b/app/jni/vid.h @@ -0,0 +1,296 @@ +/* +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. + +*/ +// vid.h -- video driver defs + +#ifndef VID_H +#define VID_H + +#define ENGINE_ICON ( (gamemode == GAME_NEXUIZ) ? nexuiz_xpm : darkplaces_xpm ) + +extern int cl_available; + +#define MAX_TEXTUREUNITS 16 + +typedef enum renderpath_e +{ + RENDERPATH_GL11, + RENDERPATH_GL13, + RENDERPATH_GL20, + RENDERPATH_D3D9, + RENDERPATH_D3D10, + RENDERPATH_D3D11, + RENDERPATH_SOFT, + RENDERPATH_GLES1, + RENDERPATH_GLES2 +} +renderpath_t; + +typedef struct viddef_support_s +{ + qboolean gl20shaders; + qboolean gl20shaders130; // indicates glBindFragDataLocation is available + int glshaderversion; // typical values: 100 110 120 130 140 ... + qboolean amd_texture_texture4; + qboolean arb_depth_texture; + qboolean arb_draw_buffers; + qboolean arb_framebuffer_object; + qboolean arb_multitexture; + qboolean arb_occlusion_query; + qboolean arb_shadow; + qboolean arb_texture_compression; + qboolean arb_texture_cube_map; + qboolean arb_texture_env_combine; + qboolean arb_texture_gather; + qboolean arb_texture_non_power_of_two; + qboolean arb_vertex_buffer_object; + qboolean arb_uniform_buffer_object; + qboolean ati_separate_stencil; + qboolean ext_blend_minmax; + qboolean ext_blend_subtract; + qboolean ext_draw_range_elements; + qboolean ext_framebuffer_object; + qboolean ext_packed_depth_stencil; + qboolean ext_stencil_two_side; + qboolean ext_texture_3d; + qboolean ext_texture_compression_s3tc; + qboolean ext_texture_edge_clamp; + qboolean ext_texture_filter_anisotropic; + qboolean ext_texture_srgb; + qboolean arb_multisample; +} +viddef_support_t; + +typedef struct viddef_mode_s +{ + int width; + int height; + int bitsperpixel; + qboolean fullscreen; + float refreshrate; + qboolean userefreshrate; + qboolean stereobuffer; + int samples; +} +viddef_mode_t; + +typedef struct viddef_s +{ + // these are set by VID_Mode + viddef_mode_t mode; + // used in many locations in the renderer + int width; + int height; + int bitsperpixel; + qboolean fullscreen; + float refreshrate; + qboolean userefreshrate; + qboolean stereobuffer; + int samples; + qboolean stencil; + qboolean sRGB2D; // whether 2D rendering is sRGB corrected (based on sRGBcapable2D) + qboolean sRGB3D; // whether 3D rendering is sRGB corrected (based on sRGBcapable3D) + qboolean sRGBcapable2D; // whether 2D rendering can be sRGB corrected (renderpath, v_hwgamma) + qboolean sRGBcapable3D; // whether 3D rendering can be sRGB corrected (renderpath, v_hwgamma) + + renderpath_t renderpath; + qboolean forcevbo; // some renderpaths can not operate without it + qboolean useinterleavedarrays; // required by some renderpaths + qboolean allowalphatocoverage; // indicates the GL_AlphaToCoverage function works on this renderpath and framebuffer + + unsigned int texunits; + unsigned int teximageunits; + unsigned int texarrayunits; + unsigned int drawrangeelements_maxvertices; + unsigned int drawrangeelements_maxindices; + + unsigned int maxtexturesize_2d; + unsigned int maxtexturesize_3d; + unsigned int maxtexturesize_cubemap; + unsigned int max_anisotropy; + unsigned int maxdrawbuffers; + + viddef_support_t support; + + // in RENDERPATH_SOFT this is a 32bpp native-endian ARGB framebuffer + // (native-endian ARGB meaning that in little endian it is BGRA bytes, + // in big endian it is ARGB byte order, the format is converted during + // blit to the window) + unsigned int *softpixels; + unsigned int *softdepthpixels; + + int forcetextype; // always use GL_BGRA for D3D, always use GL_RGBA for GLES, etc +} viddef_t; + +// global video state +extern viddef_t vid; +extern void (*vid_menudrawfn)(void); +extern void (*vid_menukeyfn)(int key); + +#define MAXJOYAXIS 16 +// if this is changed, the corresponding code in vid_shared.c must be updated +#define MAXJOYBUTTON 36 +typedef struct vid_joystate_s +{ + float axis[MAXJOYAXIS]; // -1 to +1 + unsigned char button[MAXJOYBUTTON]; // 0 or 1 + qboolean is360; // indicates this joystick is a Microsoft Xbox 360 Controller For Windows +} +vid_joystate_t; + +extern vid_joystate_t vid_joystate; + +extern cvar_t joy_index; +extern cvar_t joy_enable; +extern cvar_t joy_detected; +extern cvar_t joy_active; + +float VID_JoyState_GetAxis(const vid_joystate_t *joystate, int axis, float sensitivity, float deadzone); +void VID_ApplyJoyState(vid_joystate_t *joystate); +void VID_BuildJoyState(vid_joystate_t *joystate); +void VID_Shared_BuildJoyState_Begin(vid_joystate_t *joystate); +void VID_Shared_BuildJoyState_Finish(vid_joystate_t *joystate); +int VID_Shared_SetJoystick(int index); +qboolean VID_JoyBlockEmulatedKeys(int keycode); +void VID_EnableJoystick(qboolean enable); + +extern qboolean vid_hidden; +extern qboolean vid_activewindow; +extern cvar_t vid_hardwaregammasupported; +extern qboolean vid_usinghwgamma; +extern qboolean vid_supportrefreshrate; + +extern cvar_t vid_soft; +extern cvar_t vid_soft_threads; +extern cvar_t vid_soft_interlace; + +extern cvar_t vid_fullscreen; +extern cvar_t vid_width; +extern cvar_t vid_height; +extern cvar_t vid_bitsperpixel; +extern cvar_t vid_samples; +extern cvar_t vid_refreshrate; +extern cvar_t vid_userefreshrate; +extern cvar_t vid_vsync; +extern cvar_t vid_mouse; +extern cvar_t vid_grabkeyboard; +extern cvar_t vid_touchscreen; +extern cvar_t vid_stick_mouse; +extern cvar_t vid_resizable; +extern cvar_t vid_minwidth; +extern cvar_t vid_minheight; +extern cvar_t vid_sRGB; +extern cvar_t vid_sRGB_fallback; + +extern cvar_t gl_finish; + +extern cvar_t v_gamma; +extern cvar_t v_contrast; +extern cvar_t v_brightness; +extern cvar_t v_color_enable; +extern cvar_t v_color_black_r; +extern cvar_t v_color_black_g; +extern cvar_t v_color_black_b; +extern cvar_t v_color_grey_r; +extern cvar_t v_color_grey_g; +extern cvar_t v_color_grey_b; +extern cvar_t v_color_white_r; +extern cvar_t v_color_white_g; +extern cvar_t v_color_white_b; +extern cvar_t v_hwgamma; + +// brand of graphics chip +extern const char *gl_vendor; +// graphics chip model and other information +extern const char *gl_renderer; +// begins with 1.0.0, 1.1.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, or 1.4.0 +extern const char *gl_version; +// extensions list, space separated +extern const char *gl_extensions; +// WGL, GLX, or AGL +extern const char *gl_platform; +// another extensions list, containing platform-specific extensions that are +// not in the main list +extern const char *gl_platformextensions; +// name of driver library (opengl32.dll, libGL.so.1, or whatever) +extern char gl_driver[256]; + +void *GL_GetProcAddress(const char *name); +qboolean GL_CheckExtension(const char *minglver_or_ext, const dllfunction_t *funcs, const char *disableparm, int silent); + +void VID_Shared_Init(void); + +void GL_Init (void); + +void VID_ClearExtensions(void); +void VID_CheckExtensions(void); + +void VID_Init (void); +// Called at startup + +void VID_Shutdown (void); +// Called at shutdown + +int VID_SetMode (int modenum); +// sets the mode; only used by the Quake engine for resetting to mode 0 (the +// base mode) on memory allocation failures + +qboolean VID_InitMode(viddef_mode_t *mode); +// allocates and opens an appropriate OpenGL context (and its window) + + +// sets hardware gamma correction, returns false if the device does not +// support gamma control +// (ONLY called by VID_UpdateGamma and VID_RestoreSystemGamma) +int VID_SetGamma(unsigned short *ramps, int rampsize); +// gets hardware gamma correction, returns false if the device does not +// support gamma control +// (ONLY called by VID_UpdateGamma and VID_RestoreSystemGamma) +int VID_GetGamma(unsigned short *ramps, int rampsize); +// makes sure ramp arrays are big enough and calls VID_GetGamma/VID_SetGamma +// (ONLY to be called from VID_Finish!) +void VID_UpdateGamma(qboolean force, int rampsize); +// turns off hardware gamma ramps immediately +// (called from various shutdown/deactivation functions) +void VID_RestoreSystemGamma(void); + +void VID_SetMouse (qboolean fullscreengrab, qboolean relative, qboolean hidecursor); +void VID_Finish (void); + +void VID_Restart_f(void); + +void VID_Start(void); +void VID_Stop(void); + +extern unsigned int vid_gammatables_serial; // so other subsystems can poll if gamma parameters have changed; this starts with 0 and gets increased by 1 each time the gamma parameters get changed and VID_BuildGammaTables should be called again +extern qboolean vid_gammatables_trivial; // this is set to true if all color control values are at default setting, and it therefore would make no sense to use the gamma table +void VID_BuildGammaTables(unsigned short *ramps, int rampsize); // builds the current gamma tables into an array (needs 3*rampsize items) + +typedef struct +{ + int width, height, bpp, refreshrate; + int pixelheight_num, pixelheight_denom; +} +vid_mode_t; +size_t VID_ListModes(vid_mode_t *modes, size_t maxcount); +size_t VID_SortModes(vid_mode_t *modes, size_t count, qboolean usebpp, qboolean userefreshrate, qboolean useaspect); +void VID_Soft_SharedSetup(void); + +#endif + diff --git a/app/jni/vid_android.c b/app/jni/vid_android.c new file mode 100644 index 0000000..57e6e45 --- /dev/null +++ b/app/jni/vid_android.c @@ -0,0 +1,592 @@ +/* +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 +//#include +#include + +int cl_available = true; + +static long oldtime=0; + +qboolean vid_supportrefreshrate = false; +extern int vrMode; + + +void VID_Shutdown(void) +{ +} + +static void signal_handler(int sig) +{ + Con_Printf("Received signal %d, exiting...\n", sig); + Sys_Quit(1); +} + +static void InitSig(void) +{ + //Nope, I want a logcat backtrace. + /*signal(SIGHUP, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGQUIT, signal_handler); + signal(SIGILL, signal_handler); + signal(SIGTRAP, signal_handler); + signal(SIGIOT, signal_handler); + signal(SIGBUS, signal_handler); + signal(SIGFPE, signal_handler); + signal(SIGSEGV, signal_handler); + signal(SIGTERM, signal_handler);*/ +} + +void qglBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) +{ +//Nope.avi +} + +void qglUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding) +{ +//Nope.avi +} + +GLuint qglGetUniformBlockIndex(GLuint program, const GLchar *uniformBlockName) +{ +//Nope.avi +return 0; +} + +void glLoadMatrixf(const GLfloat *m) +{ +//Nope.avi +} + +void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ +//Nope.avi +} + +void glClientActiveTexture(GLenum target) +{ +//Nope.avi +} + +void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) +{ +//Nope.avi +} + +void qglBindFramebuffer(GLenum target, GLuint framebuffer) +{ +glBindFramebuffer(target, framebuffer); +} + +void qglBindRenderbuffer(GLenum target, GLuint renderbuffer) +{ +glBindRenderbuffer(target, renderbuffer); +} + +void qglDeleteRenderbuffers(GLsizei n, const GLuint *renderbuffers) +{ +glDeleteRenderbuffers(n, renderbuffers); +} + +void qglDeleteFramebuffers(GLsizei n, const GLuint *framebuffers) +{ +glDeleteFramebuffers(n, framebuffers); +} + +void qglGenFramebuffers(GLsizei n, GLuint *framebuffers) +{ +glGenFramebuffers(n, framebuffers); +} + +GLenum qglCheckFramebufferStatus(GLenum target) +{ +return glCheckFramebufferStatus(target); +} + +void qglGenRenderbuffers(GLsizei n, GLuint *renderbuffers) +{ +glGenRenderbuffers(n, renderbuffers); +} + +void qglRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height) +{ +glRenderbufferStorage(target, internalformat, width, height); +} + +void VID_SetMouse (qboolean fullscreengrab, qboolean relative, qboolean hidecursor) +{ +} + +bool scndswp=0; +void VID_Finish (void) +{ +//if (scndswp) eglSwapBuffers(eglGetCurrentDisplay(), eglGetCurrentSurface(EGL_DRAW)); +scndswp=1; +} + +int VID_SetGamma(unsigned short *ramps, int rampsize) +{ + return FALSE; +} + +int VID_GetGamma(unsigned short *ramps, int rampsize) +{ + return FALSE; +} + +void VID_Init(void) +{ + InitSig(); // trap evil signals +} + +#define SDL_GL_ExtensionSupported(x) (strstr(gl_extensions, x) || strstr(gl_platformextensions, x)) + +int is32bit=0; + +void GLES_Init(void) +{ + gl_renderer = (const char *)qglGetString(GL_RENDERER); + gl_vendor = (const char *)qglGetString(GL_VENDOR); + gl_version = (const char *)qglGetString(GL_VERSION); + gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); + + if (!gl_extensions) + gl_extensions = ""; + if (!gl_platformextensions) + gl_platformextensions = ""; + + Con_Printf("GL_VENDOR: %s\n", gl_vendor); + Con_Printf("GL_RENDERER: %s\n", gl_renderer); + Con_Printf("GL_VERSION: %s\n", gl_version); + Con_Printf("GL_EXTENSIONS: %s\n", gl_extensions); + Con_Printf("%s_EXTENSIONS: %s\n", gl_platform, gl_platformextensions); + + // LordHavoc: report supported extensions + Con_Printf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); + + // GLES devices in general do not like GL_BGRA, so use GL_RGBA + vid.forcetextype = TEXTYPE_RGBA; + + vid.support.gl20shaders = true; + vid.support.amd_texture_texture4 = false; + vid.support.arb_depth_texture = false; + vid.support.arb_draw_buffers = false; + vid.support.arb_multitexture = false; + vid.support.arb_occlusion_query = false; + vid.support.arb_shadow = false; + vid.support.arb_texture_compression = SDL_GL_ExtensionSupported("GL_EXT_texture_compression_s3tc"); + vid.support.arb_texture_cube_map = true; + vid.support.arb_texture_env_combine = false; + vid.support.arb_texture_gather = false; + vid.support.arb_texture_non_power_of_two = strstr(gl_extensions, "GL_OES_texture_npot") != NULL; + vid.support.arb_vertex_buffer_object = true; + vid.support.arb_uniform_buffer_object = false; + vid.support.ati_separate_stencil = false; + vid.support.ext_blend_minmax = false; + vid.support.ext_blend_subtract = true; + vid.support.ext_draw_range_elements = true; + vid.support.ext_framebuffer_object = false; + vid.support.ext_packed_depth_stencil = false; + vid.support.ext_stencil_two_side = false; + vid.support.ext_texture_3d = SDL_GL_ExtensionSupported("GL_OES_texture_3D"); + vid.support.ext_texture_compression_s3tc = SDL_GL_ExtensionSupported("GL_EXT_texture_compression_s3tc"); + vid.support.ext_texture_edge_clamp = true; + vid.support.ext_texture_filter_anisotropic = false; // probably don't want to use it... + vid.support.ext_texture_srgb = false; + + qglGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_2d); + if (vid.support.ext_texture_filter_anisotropic) + qglGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint*)&vid.max_anisotropy); + if (vid.support.arb_texture_cube_map) + qglGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_cubemap); + Con_Printf("GL_MAX_CUBE_MAP_TEXTURE_SIZE = %i\n", vid.maxtexturesize_cubemap); + Con_Printf("GL_MAX_3D_TEXTURE_SIZE = %i\n", vid.maxtexturesize_3d); + { +#define GL_ALPHA_BITS 0x0D55 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 + int fb_r = -1, fb_g = -1, fb_b = -1, fb_a = -1, fb_d = -1, fb_s = -1; + qglGetIntegerv(GL_RED_BITS , &fb_r); + qglGetIntegerv(GL_GREEN_BITS , &fb_g); + qglGetIntegerv(GL_BLUE_BITS , &fb_b); + qglGetIntegerv(GL_ALPHA_BITS , &fb_a); + qglGetIntegerv(GL_DEPTH_BITS , &fb_d); + qglGetIntegerv(GL_STENCIL_BITS, &fb_s); + if ((fb_r+fb_g+fb_b+fb_a)>=24) is32bit=1; + Con_Printf("Framebuffer depth is R%iG%iB%iA%iD%iS%i\n", fb_r, fb_g, fb_b, fb_a, fb_d, fb_s); + } + + // verify that cubemap textures are really supported + if (vid.support.arb_texture_cube_map && vid.maxtexturesize_cubemap < 256) + vid.support.arb_texture_cube_map = false; + + // verify that 3d textures are really supported + if (vid.support.ext_texture_3d && vid.maxtexturesize_3d < 32) + { + vid.support.ext_texture_3d = false; + Con_Printf("GL_OES_texture_3d reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n"); + } + + vid.texunits = 4; + vid.teximageunits = 8; + vid.texarrayunits = 5; + vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = bound(1, vid.teximageunits, MAX_TEXTUREUNITS); + vid.texarrayunits = bound(1, vid.texarrayunits, MAX_TEXTUREUNITS); + Con_DPrintf("Using GLES2.0 rendering path - %i texture matrix, %i texture images, %i texcoords%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.support.ext_framebuffer_object ? ", shadowmapping supported" : ""); + vid.renderpath = RENDERPATH_GLES2; + vid.useinterleavedarrays = false; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + + // VorteX: set other info (maybe place them in VID_InitMode?) + extern cvar_t gl_info_vendor; + extern cvar_t gl_info_renderer; + extern cvar_t gl_info_version; + extern cvar_t gl_info_platform; + extern cvar_t gl_info_driver; + Cvar_SetQuick(&gl_info_vendor, gl_vendor); + Cvar_SetQuick(&gl_info_renderer, gl_renderer); + Cvar_SetQuick(&gl_info_version, gl_version); + Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); + Cvar_SetQuick(&gl_info_driver, gl_driver); +} + +int andrw=640; +int andrh=480; + +qboolean VID_InitMode(viddef_mode_t *mode) +{ + mode->width = andrw; + mode->height = andrh; + mode->fullscreen = true; + mode->refreshrate=60.0f; + vid.softpixels = NULL; + vid_hidden=false; + gl_platform = "Android"; + gl_platformextensions = ""; + GLES_Init(); + if (is32bit) + mode->bitsperpixel=32;else mode->bitsperpixel=16; + return true; +} + +void *GL_GetProcAddress(const char *name) +{ + return NULL; +} + +void Sys_SendKeyEvents(void) +{ +} + +void VID_BuildJoyState(vid_joystate_t *joystate) +{ +} + +size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) +{ + return 0; +} + +typedef struct r_glsl_permutation_s +{ + /// hash lookup data + struct r_glsl_permutation_s *hashnext; + unsigned int mode; + unsigned int permutation; + + /// indicates if we have tried compiling this permutation already + qboolean compiled; + /// 0 if compilation failed + int program; + // texture units assigned to each detected uniform + int tex_Texture_First; + int tex_Texture_Second; + int tex_Texture_GammaRamps; + int tex_Texture_Normal; + int tex_Texture_Color; + int tex_Texture_Gloss; + int tex_Texture_Glow; + int tex_Texture_SecondaryNormal; + int tex_Texture_SecondaryColor; + int tex_Texture_SecondaryGloss; + int tex_Texture_SecondaryGlow; + int tex_Texture_Pants; + int tex_Texture_Shirt; + int tex_Texture_FogHeightTexture; + int tex_Texture_FogMask; + int tex_Texture_Lightmap; + int tex_Texture_Deluxemap; + int tex_Texture_Attenuation; + int tex_Texture_Cube; + int tex_Texture_Refraction; + int tex_Texture_Reflection; + int tex_Texture_ShadowMap2D; + int tex_Texture_CubeProjection; + int tex_Texture_ScreenNormalMap; + int tex_Texture_ScreenDiffuse; + int tex_Texture_ScreenSpecular; + int tex_Texture_ReflectMask; + int tex_Texture_ReflectCube; + int tex_Texture_BounceGrid; + /// locations of detected uniforms in program object, or -1 if not found + int loc_Texture_First; + int loc_Texture_Second; + int loc_Texture_GammaRamps; + int loc_Texture_Normal; + int loc_Texture_Color; + int loc_Texture_Gloss; + int loc_Texture_Glow; + int loc_Texture_SecondaryNormal; + int loc_Texture_SecondaryColor; + int loc_Texture_SecondaryGloss; + int loc_Texture_SecondaryGlow; + int loc_Texture_Pants; + int loc_Texture_Shirt; + int loc_Texture_FogHeightTexture; + int loc_Texture_FogMask; + int loc_Texture_Lightmap; + int loc_Texture_Deluxemap; + int loc_Texture_Attenuation; + int loc_Texture_Cube; + int loc_Texture_Refraction; + int loc_Texture_Reflection; + int loc_Texture_ShadowMap2D; + int loc_Texture_CubeProjection; + int loc_Texture_ScreenNormalMap; + int loc_Texture_ScreenDiffuse; + int loc_Texture_ScreenSpecular; + int loc_Texture_ReflectMask; + int loc_Texture_ReflectCube; + int loc_Texture_BounceGrid; + int loc_Alpha; + int loc_BloomBlur_Parameters; + int loc_ClientTime; + int loc_Color_Ambient; + int loc_Color_Diffuse; + int loc_Color_Specular; + int loc_Color_Glow; + int loc_Color_Pants; + int loc_Color_Shirt; + int loc_DeferredColor_Ambient; + int loc_DeferredColor_Diffuse; + int loc_DeferredColor_Specular; + int loc_DeferredMod_Diffuse; + int loc_DeferredMod_Specular; + int loc_DistortScaleRefractReflect; + int loc_EyePosition; + int loc_FogColor; + int loc_FogHeightFade; + int loc_FogPlane; + int loc_FogPlaneViewDist; + int loc_FogRangeRecip; + int loc_LightColor; + int loc_LightDir; + int loc_LightPosition; + int loc_OffsetMapping_ScaleSteps; + int loc_OffsetMapping_LodDistance; + int loc_OffsetMapping_Bias; + int loc_PixelSize; + int loc_ReflectColor; + int loc_ReflectFactor; + int loc_ReflectOffset; + int loc_RefractColor; + int loc_Saturation; + int loc_ScreenCenterRefractReflect; + int loc_ScreenScaleRefractReflect; + int loc_ScreenToDepth; + int loc_ShadowMap_Parameters; + int loc_ShadowMap_TextureScale; + int loc_SpecularPower; + int loc_UserVec1; + int loc_UserVec2; + int loc_UserVec3; + int loc_UserVec4; + int loc_ViewTintColor; + int loc_ViewToLight; + int loc_ModelToLight; + int loc_TexMatrix; + int loc_BackgroundTexMatrix; + int loc_ModelViewProjectionMatrix; + int loc_ModelViewMatrix; + int loc_PixelToScreenTexCoord; + int loc_ModelToReflectCube; + int loc_ShadowMapMatrix; + int loc_BloomColorSubtract; + int loc_NormalmapScrollBlend; + int loc_BounceGridMatrix; + int loc_BounceGridIntensity; +} +r_glsl_permutation_t; + +extern r_glsl_permutation_t *r_glsl_permutation; +extern void android_kostyl(); + +void QC_BeginFrame() +{ + scndswp=0; + if (r_glsl_permutation!=0) + { +// glUseProgram(r_glsl_permutation->program); +// R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First,0); +// android_kostyl();//from ЯUSSIAИ "КоÑ�тыль" - "A dirty hack" + } + Host_BeginFrame(); +} + +void QC_DrawFrame(int eye, int x, int y) +{ + Host_Frame(eye, x, y); +} + +void QC_EndFrame() +{ + Host_EndFrame(); +} + +void QC_KeyEvent(int state,int key,int character) +{ + Key_Event(key, character, state); +} + +float analogx=0.0f; +float analogy=0.0f; +int analogenabled=0; +void QC_Analog(int enable,float x,float y) +{ + analogenabled=enable; + analogx=x; + analogy=y; +} + +void QC_MotionEvent(float delta, float dx, float dy) +{ + static bool canAdjust = true; + + //Pitch lock? + if (cl_pitchmode.integer != 0 || vrMode == 0) { + + float dir = 1.0f; + if (cl_pitchmode.integer == 1) + dir = -1.0f; + + in_mouse_y += (dy * delta * dir); + in_windowmouse_y += (dy * delta * dir); + if (in_windowmouse_y < 0) in_windowmouse_y = 0; + if (in_windowmouse_y > andrh - 1) in_windowmouse_y = andrh - 1; + } + + //If not in vr mode, then always use yaw stick control + if (cl_yawmode.integer == 2) + { + in_mouse_x+=(dx*delta); + in_windowmouse_x += (dx*delta); + if (in_windowmouse_x<0) in_windowmouse_x=0; + if (in_windowmouse_x>andrw-1) in_windowmouse_x=andrw-1; + } + else if (cl_yawmode.integer == 1) { + if (fabs(dx) > 0.4 && canAdjust && delta != -1.0f) { + if (dx > 0.0) + cl.comfortInc--; + else + cl.comfortInc++; + + int max = (360.f / cl_comfort.value); + + if (cl.comfortInc >= max) + cl.comfortInc = 0; + if (cl.comfortInc < 0) + cl.comfortInc = max - 1; + + canAdjust = false; + } + + if (fabs(dx) < 0.3) + canAdjust = true; + } +} + +static struct { + float pitch, previous_pitch, yaw, previous_yaw, roll; +} move_event; + + +void IN_Move(void) +{ + if (cl_pitchmode.integer != 0) { + cl.viewangles[PITCH] -= move_event.previous_pitch; + cl.viewangles[PITCH] += move_event.pitch; + } + else { + cl.viewangles[PITCH] = move_event.pitch; + } + + if (cl_yawmode.integer != 1) + cl.viewangles[YAW] -= move_event.previous_yaw; + cl.viewangles[YAW] += move_event.yaw ; + cl.viewangles[ROLL] = move_event.roll ; +} + +void QC_MoveEvent(float yaw, float pitch, float roll) +{ + move_event.previous_yaw = move_event.yaw; + move_event.previous_pitch = move_event.pitch; + move_event.yaw = yaw * cl_yawmult.value; + move_event.pitch = pitch * cl_pitchmult.value; + move_event.roll = roll; + + if (cl_yawmode.integer == 3) + { + long t=Sys_Milliseconds(); + long delta=t-oldtime; + oldtime=t; + if (delta>1000) + delta=1000; + + float engage_angle = 45.0f; + + float dx = yaw; + if (yaw > engage_angle || yaw < -engage_angle) { + dx = (yaw > engage_angle) ? dx-engage_angle : dx+engage_angle; + dx /= -10.0f; + } + else { + dx = 0.0f; + } + + in_mouse_x+=(dx*delta); + in_windowmouse_x += (dx*delta); + if (in_windowmouse_x<0) in_windowmouse_x=0; + if (in_windowmouse_x>andrw-1) in_windowmouse_x=andrw-1; + } +} + +void QC_SetResolution(int width, int height) +{ + andrw=width; + andrh=height; + VID_Restart_f(); +} diff --git a/app/jni/vid_shared.c b/app/jni/vid_shared.c new file mode 100644 index 0000000..4b4b1e1 --- /dev/null +++ b/app/jni/vid_shared.c @@ -0,0 +1,2125 @@ + +#include "quakedef.h" +#include "cdaudio.h" +#include "image.h" + +#ifdef SUPPORTD3D +#include +#ifdef _MSC_VER +#pragma comment(lib, "d3d9.lib") +#endif + +LPDIRECT3DDEVICE9 vid_d3d9dev; +#endif + +#ifdef WIN32 +//#include +#define XINPUT_GAMEPAD_DPAD_UP 0x0001 +#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002 +#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004 +#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008 +#define XINPUT_GAMEPAD_START 0x0010 +#define XINPUT_GAMEPAD_BACK 0x0020 +#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040 +#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080 +#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 +#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 +#define XINPUT_GAMEPAD_A 0x1000 +#define XINPUT_GAMEPAD_B 0x2000 +#define XINPUT_GAMEPAD_X 0x4000 +#define XINPUT_GAMEPAD_Y 0x8000 +#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 +#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 +#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30 +#define XUSER_INDEX_ANY 0x000000FF + +typedef struct xinput_gamepad_s +{ + WORD wButtons; + BYTE bLeftTrigger; + BYTE bRightTrigger; + SHORT sThumbLX; + SHORT sThumbLY; + SHORT sThumbRX; + SHORT sThumbRY; +} +xinput_gamepad_t; + +typedef struct xinput_state_s +{ + DWORD dwPacketNumber; + xinput_gamepad_t Gamepad; +} +xinput_state_t; + +typedef struct xinput_keystroke_s +{ + WORD VirtualKey; + WCHAR Unicode; + WORD Flags; + BYTE UserIndex; + BYTE HidCode; +} +xinput_keystroke_t; + +DWORD (WINAPI *qXInputGetState)(DWORD index, xinput_state_t *state); +DWORD (WINAPI *qXInputGetKeystroke)(DWORD index, DWORD reserved, xinput_keystroke_t *keystroke); + +qboolean vid_xinputinitialized = false; +int vid_xinputindex = -1; +#endif + +// global video state +viddef_t vid; + +// AK FIXME -> input_dest +qboolean in_client_mouse = true; + +// AK where should it be placed ? +float in_mouse_x, in_mouse_y; +float in_windowmouse_x, in_windowmouse_y; + +// LordHavoc: if window is hidden, don't update screen +qboolean vid_hidden = true; +// LordHavoc: if window is not the active window, don't hog as much CPU time, +// let go of the mouse, turn off sound, and restore system gamma ramps... +qboolean vid_activewindow = true; + +vid_joystate_t vid_joystate; + +#ifdef WIN32 +cvar_t joy_xinputavailable = {CVAR_READONLY, "joy_xinputavailable", "0", "indicates which devices are being reported by the Windows XInput API (first controller = 1, second = 2, third = 4, fourth = 8, added together)"}; +#endif +cvar_t joy_active = {CVAR_READONLY, "joy_active", "0", "indicates that a joystick is active (detected and enabled)"}; +cvar_t joy_detected = {CVAR_READONLY, "joy_detected", "0", "number of joysticks detected by engine"}; +cvar_t joy_enable = {CVAR_SAVE, "joy_enable", "0", "enables joystick support"}; +cvar_t joy_index = {0, "joy_index", "0", "selects which joystick to use if you have multiple (0 uses the first controller, 1 uses the second, ...)"}; +cvar_t joy_axisforward = {0, "joy_axisforward", "1", "which joystick axis to query for forward/backward movement"}; +cvar_t joy_axisside = {0, "joy_axisside", "0", "which joystick axis to query for right/left movement"}; +cvar_t joy_axisup = {0, "joy_axisup", "-1", "which joystick axis to query for up/down movement"}; +cvar_t joy_axispitch = {0, "joy_axispitch", "3", "which joystick axis to query for looking up/down"}; +cvar_t joy_axisyaw = {0, "joy_axisyaw", "2", "which joystick axis to query for looking right/left"}; +cvar_t joy_axisroll = {0, "joy_axisroll", "-1", "which joystick axis to query for tilting head right/left"}; +cvar_t joy_deadzoneforward = {0, "joy_deadzoneforward", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzoneside = {0, "joy_deadzoneside", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzoneup = {0, "joy_deadzoneup", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzonepitch = {0, "joy_deadzonepitch", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzoneyaw = {0, "joy_deadzoneyaw", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_deadzoneroll = {0, "joy_deadzoneroll", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_sensitivityforward = {0, "joy_sensitivityforward", "-1", "movement multiplier"}; +cvar_t joy_sensitivityside = {0, "joy_sensitivityside", "1", "movement multiplier"}; +cvar_t joy_sensitivityup = {0, "joy_sensitivityup", "1", "movement multiplier"}; +cvar_t joy_sensitivitypitch = {0, "joy_sensitivitypitch", "1", "movement multiplier"}; +cvar_t joy_sensitivityyaw = {0, "joy_sensitivityyaw", "-1", "movement multiplier"}; +cvar_t joy_sensitivityroll = {0, "joy_sensitivityroll", "1", "movement multiplier"}; +cvar_t joy_axiskeyevents = {CVAR_SAVE, "joy_axiskeyevents", "0", "generate uparrow/leftarrow etc. keyevents for joystick axes, use if your joystick driver is not generating them"}; +cvar_t joy_axiskeyevents_deadzone = {CVAR_SAVE, "joy_axiskeyevents_deadzone", "0.5", "deadzone value for axes"}; +cvar_t joy_x360_axisforward = {0, "joy_x360_axisforward", "1", "which joystick axis to query for forward/backward movement"}; +cvar_t joy_x360_axisside = {0, "joy_x360_axisside", "0", "which joystick axis to query for right/left movement"}; +cvar_t joy_x360_axisup = {0, "joy_x360_axisup", "-1", "which joystick axis to query for up/down movement"}; +cvar_t joy_x360_axispitch = {0, "joy_x360_axispitch", "3", "which joystick axis to query for looking up/down"}; +cvar_t joy_x360_axisyaw = {0, "joy_x360_axisyaw", "2", "which joystick axis to query for looking right/left"}; +cvar_t joy_x360_axisroll = {0, "joy_x360_axisroll", "-1", "which joystick axis to query for tilting head right/left"}; +cvar_t joy_x360_deadzoneforward = {0, "joy_x360_deadzoneforward", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzoneside = {0, "joy_x360_deadzoneside", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzoneup = {0, "joy_x360_deadzoneup", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzonepitch = {0, "joy_x360_deadzonepitch", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzoneyaw = {0, "joy_x360_deadzoneyaw", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_deadzoneroll = {0, "joy_x360_deadzoneroll", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; +cvar_t joy_x360_sensitivityforward = {0, "joy_x360_sensitivityforward", "1", "movement multiplier"}; +cvar_t joy_x360_sensitivityside = {0, "joy_x360_sensitivityside", "1", "movement multiplier"}; +cvar_t joy_x360_sensitivityup = {0, "joy_x360_sensitivityup", "1", "movement multiplier"}; +cvar_t joy_x360_sensitivitypitch = {0, "joy_x360_sensitivitypitch", "-1", "movement multiplier"}; +cvar_t joy_x360_sensitivityyaw = {0, "joy_x360_sensitivityyaw", "-1", "movement multiplier"}; +cvar_t joy_x360_sensitivityroll = {0, "joy_x360_sensitivityroll", "1", "movement multiplier"}; + +// cvars for DPSOFTRAST +cvar_t vid_soft = {CVAR_SAVE, "vid_soft", "0", "enables use of the DarkPlaces Software Rasterizer rather than OpenGL or Direct3D"}; +cvar_t vid_soft_threads = {CVAR_SAVE, "vid_soft_threads", "8", "the number of threads the DarkPlaces Software Rasterizer should use"}; +cvar_t vid_soft_interlace = {CVAR_SAVE, "vid_soft_interlace", "1", "whether the DarkPlaces Software Rasterizer should interlace the screen bands occupied by each thread"}; + +// we don't know until we try it! +cvar_t vid_hardwaregammasupported = {CVAR_READONLY,"vid_hardwaregammasupported","1", "indicates whether hardware gamma is supported (updated by attempts to set hardware gamma ramps)"}; + +// VorteX: more info cvars, mostly set in VID_CheckExtensions +cvar_t gl_info_vendor = {CVAR_READONLY, "gl_info_vendor", "", "indicates brand of graphics chip"}; +cvar_t gl_info_renderer = {CVAR_READONLY, "gl_info_renderer", "", "indicates graphics chip model and other information"}; +cvar_t gl_info_version = {CVAR_READONLY, "gl_info_version", "", "indicates version of current renderer. begins with 1.0.0, 1.1.0, 1.2.0, 1.3.1 etc."}; +cvar_t gl_info_extensions = {CVAR_READONLY, "gl_info_extensions", "", "indicates extension list found by engine, space separated."}; +cvar_t gl_info_platform = {CVAR_READONLY, "gl_info_platform", "", "indicates GL platform: WGL, GLX, or AGL."}; +cvar_t gl_info_driver = {CVAR_READONLY, "gl_info_driver", "", "name of driver library (opengl32.dll, libGL.so.1, or whatever)."}; + +// whether hardware gamma ramps are currently in effect +qboolean vid_usinghwgamma = false; + +int vid_gammarampsize = 0; +unsigned short *vid_gammaramps = NULL; +unsigned short *vid_systemgammaramps = NULL; + +cvar_t vid_fullscreen = {CVAR_SAVE, "vid_fullscreen", "1", "use fullscreen (1) or windowed (0)"}; +cvar_t vid_width = {CVAR_SAVE, "vid_width", "640", "resolution"}; +cvar_t vid_height = {CVAR_SAVE, "vid_height", "480", "resolution"}; +cvar_t vid_bitsperpixel = {CVAR_SAVE, "vid_bitsperpixel", "32", "how many bits per pixel to render at (32 or 16, 32 is recommended)"}; +cvar_t vid_samples = {CVAR_SAVE, "vid_samples", "1", "how many anti-aliasing samples per pixel to request from the graphics driver (4 is recommended, 1 is faster)"}; +cvar_t vid_refreshrate = {CVAR_SAVE, "vid_refreshrate", "60", "refresh rate to use, in hz (higher values flicker less, if supported by your monitor)"}; +cvar_t vid_userefreshrate = {CVAR_SAVE, "vid_userefreshrate", "0", "set this to 1 to make vid_refreshrate used, or to 0 to let the engine choose a sane default"}; +cvar_t vid_stereobuffer = {CVAR_SAVE, "vid_stereobuffer", "0", "enables 'quad-buffered' stereo rendering for stereo shutterglasses, HMD (head mounted display) devices, or polarized stereo LCDs, if supported by your drivers"}; + +cvar_t vid_vsync = {CVAR_SAVE, "vid_vsync", "0", "sync to vertical blank, prevents 'tearing' (seeing part of one frame and part of another on the screen at the same time), automatically disabled when doing timedemo benchmarks"}; +cvar_t vid_mouse = {CVAR_SAVE, "vid_mouse", "1", "whether to use the mouse in windowed mode (fullscreen always does)"}; +cvar_t vid_grabkeyboard = {CVAR_SAVE, "vid_grabkeyboard", "0", "whether to grab the keyboard when mouse is active (prevents use of volume control keys, music player keys, etc on some keyboards)"}; +cvar_t vid_minwidth = {0, "vid_minwidth", "0", "minimum vid_width that is acceptable (to be set in default.cfg in mods)"}; +cvar_t vid_minheight = {0, "vid_minheight", "0", "minimum vid_height that is acceptable (to be set in default.cfg in mods)"}; +cvar_t vid_gl13 = {0, "vid_gl13", "1", "enables faster rendering using OpenGL 1.3 features (such as GL_ARB_texture_env_combine extension)"}; +cvar_t vid_gl20 = {0, "vid_gl20", "1", "enables faster rendering using OpenGL 2.0 features (such as GL_ARB_fragment_shader extension)"}; +cvar_t gl_finish = {0, "gl_finish", "0", "make the cpu wait for the graphics processor at the end of each rendered frame (can help with strange input or video lag problems on some machines)"}; +cvar_t vid_sRGB = {CVAR_SAVE, "vid_sRGB", "0", "if hardware is capable, modify rendering to be gamma corrected for the sRGB color standard (computer monitors, TVs), recommended"}; +cvar_t vid_sRGB_fallback = {CVAR_SAVE, "vid_sRGB_fallback", "0", "do an approximate sRGB fallback if not properly supported by hardware (2: also use the fallback if framebuffer is 8bit, 3: always use the fallback even if sRGB is supported)"}; + +cvar_t vid_touchscreen = {0, "vid_touchscreen", "0", "Use touchscreen-style input (no mouse grab, track mouse motion only while button is down, screen areas for mimicing joystick axes and buttons"}; +cvar_t vid_stick_mouse = {CVAR_SAVE, "vid_stick_mouse", "0", "have the mouse stuck in the center of the screen" }; +cvar_t vid_resizable = {CVAR_SAVE, "vid_resizable", "0", "0: window not resizable, 1: resizable, 2: window can be resized but the framebuffer isn't adjusted" }; + +cvar_t v_gamma = {CVAR_SAVE, "v_gamma", "1", "inverse gamma correction value, a brightness effect that does not affect white or black, and tends to make the image grey and dull"}; +cvar_t v_contrast = {CVAR_SAVE, "v_contrast", "1", "brightness of white (values above 1 give a brighter image with increased color saturation, unlike v_gamma)"}; +cvar_t v_brightness = {CVAR_SAVE, "v_brightness", "0", "brightness of black, useful for monitors that are too dark"}; +cvar_t v_contrastboost = {CVAR_SAVE, "v_contrastboost", "1", "by how much to multiply the contrast in dark areas (1 is no change)"}; +cvar_t v_color_enable = {CVAR_SAVE, "v_color_enable", "0", "enables black-grey-white color correction curve controls"}; +cvar_t v_color_black_r = {CVAR_SAVE, "v_color_black_r", "0", "desired color of black"}; +cvar_t v_color_black_g = {CVAR_SAVE, "v_color_black_g", "0", "desired color of black"}; +cvar_t v_color_black_b = {CVAR_SAVE, "v_color_black_b", "0", "desired color of black"}; +cvar_t v_color_grey_r = {CVAR_SAVE, "v_color_grey_r", "0.5", "desired color of grey"}; +cvar_t v_color_grey_g = {CVAR_SAVE, "v_color_grey_g", "0.5", "desired color of grey"}; +cvar_t v_color_grey_b = {CVAR_SAVE, "v_color_grey_b", "0.5", "desired color of grey"}; +cvar_t v_color_white_r = {CVAR_SAVE, "v_color_white_r", "1", "desired color of white"}; +cvar_t v_color_white_g = {CVAR_SAVE, "v_color_white_g", "1", "desired color of white"}; +cvar_t v_color_white_b = {CVAR_SAVE, "v_color_white_b", "1", "desired color of white"}; +cvar_t v_hwgamma = {CVAR_SAVE, "v_hwgamma", "0", "enables use of hardware gamma correction ramps if available (note: does not work very well on Windows2000 and above), values are 0 = off, 1 = attempt to use hardware gamma, 2 = use hardware gamma whether it works or not"}; +cvar_t v_glslgamma = {CVAR_SAVE, "v_glslgamma", "1", "enables use of GLSL to apply gamma correction ramps if available (note: overrides v_hwgamma)"}; +cvar_t v_glslgamma_2d = {CVAR_SAVE, "v_glslgamma_2d", "0", "applies GLSL gamma to 2d pictures (HUD, fonts)"}; +cvar_t v_psycho = {0, "v_psycho", "0", "easter egg"}; + +// brand of graphics chip +const char *gl_vendor; +// graphics chip model and other information +const char *gl_renderer; +// begins with 1.0.0, 1.1.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, or 1.4.0 +const char *gl_version; +// extensions list, space separated +const char *gl_extensions; +// WGL, GLX, or AGL +const char *gl_platform; +// another extensions list, containing platform-specific extensions that are +// not in the main list +const char *gl_platformextensions; +// name of driver library (opengl32.dll, libGL.so.1, or whatever) +char gl_driver[256]; + +#ifndef USE_GLES2 +// GL_ARB_multitexture +void (GLAPIENTRY *qglMultiTexCoord1f) (GLenum, GLfloat); +void (GLAPIENTRY *qglMultiTexCoord2f) (GLenum, GLfloat, GLfloat); +void (GLAPIENTRY *qglMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); +void (GLAPIENTRY *qglMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +void (GLAPIENTRY *qglActiveTexture) (GLenum); +void (GLAPIENTRY *qglClientActiveTexture) (GLenum); + +// general GL functions + +void (GLAPIENTRY *qglClearColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + +void (GLAPIENTRY *qglClear)(GLbitfield mask); + +void (GLAPIENTRY *qglAlphaFunc)(GLenum func, GLclampf ref); +void (GLAPIENTRY *qglBlendFunc)(GLenum sfactor, GLenum dfactor); +void (GLAPIENTRY *qglCullFace)(GLenum mode); + +void (GLAPIENTRY *qglDrawBuffer)(GLenum mode); +void (GLAPIENTRY *qglReadBuffer)(GLenum mode); +void (GLAPIENTRY *qglEnable)(GLenum cap); +void (GLAPIENTRY *qglDisable)(GLenum cap); +GLboolean (GLAPIENTRY *qglIsEnabled)(GLenum cap); + +void (GLAPIENTRY *qglEnableClientState)(GLenum cap); +void (GLAPIENTRY *qglDisableClientState)(GLenum cap); + +void (GLAPIENTRY *qglGetBooleanv)(GLenum pname, GLboolean *params); +void (GLAPIENTRY *qglGetDoublev)(GLenum pname, GLdouble *params); +void (GLAPIENTRY *qglGetFloatv)(GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglGetIntegerv)(GLenum pname, GLint *params); + +GLenum (GLAPIENTRY *qglGetError)(void); +const GLubyte* (GLAPIENTRY *qglGetString)(GLenum name); +void (GLAPIENTRY *qglFinish)(void); +void (GLAPIENTRY *qglFlush)(void); + +void (GLAPIENTRY *qglClearDepth)(GLclampd depth); +void (GLAPIENTRY *qglDepthFunc)(GLenum func); +void (GLAPIENTRY *qglDepthMask)(GLboolean flag); +void (GLAPIENTRY *qglDepthRange)(GLclampd near_val, GLclampd far_val); +void (GLAPIENTRY *qglDepthRangef)(GLclampf near_val, GLclampf far_val); +void (GLAPIENTRY *qglColorMask)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); + +void (GLAPIENTRY *qglDrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +void (GLAPIENTRY *qglDrawElements)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void (GLAPIENTRY *qglDrawArrays)(GLenum mode, GLint first, GLsizei count); +void (GLAPIENTRY *qglVertexPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void (GLAPIENTRY *qglNormalPointer)(GLenum type, GLsizei stride, const GLvoid *ptr); +void (GLAPIENTRY *qglColorPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void (GLAPIENTRY *qglTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void (GLAPIENTRY *qglArrayElement)(GLint i); + +void (GLAPIENTRY *qglColor4ub)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +void (GLAPIENTRY *qglColor4f)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void (GLAPIENTRY *qglTexCoord1f)(GLfloat s); +void (GLAPIENTRY *qglTexCoord2f)(GLfloat s, GLfloat t); +void (GLAPIENTRY *qglTexCoord3f)(GLfloat s, GLfloat t, GLfloat r); +void (GLAPIENTRY *qglTexCoord4f)(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +void (GLAPIENTRY *qglVertex2f)(GLfloat x, GLfloat y); +void (GLAPIENTRY *qglVertex3f)(GLfloat x, GLfloat y, GLfloat z); +void (GLAPIENTRY *qglVertex4f)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void (GLAPIENTRY *qglBegin)(GLenum mode); +void (GLAPIENTRY *qglEnd)(void); + +void (GLAPIENTRY *qglMatrixMode)(GLenum mode); +//void (GLAPIENTRY *qglOrtho)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); +//void (GLAPIENTRY *qglFrustum)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); +void (GLAPIENTRY *qglViewport)(GLint x, GLint y, GLsizei width, GLsizei height); +//void (GLAPIENTRY *qglPushMatrix)(void); +//void (GLAPIENTRY *qglPopMatrix)(void); +void (GLAPIENTRY *qglLoadIdentity)(void); +//void (GLAPIENTRY *qglLoadMatrixd)(const GLdouble *m); +void (GLAPIENTRY *qglLoadMatrixf)(const GLfloat *m); +//void (GLAPIENTRY *qglMultMatrixd)(const GLdouble *m); +//void (GLAPIENTRY *qglMultMatrixf)(const GLfloat *m); +//void (GLAPIENTRY *qglRotated)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +//void (GLAPIENTRY *qglRotatef)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +//void (GLAPIENTRY *qglScaled)(GLdouble x, GLdouble y, GLdouble z); +//void (GLAPIENTRY *qglScalef)(GLfloat x, GLfloat y, GLfloat z); +//void (GLAPIENTRY *qglTranslated)(GLdouble x, GLdouble y, GLdouble z); +//void (GLAPIENTRY *qglTranslatef)(GLfloat x, GLfloat y, GLfloat z); + +void (GLAPIENTRY *qglReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); + +void (GLAPIENTRY *qglStencilFunc)(GLenum func, GLint ref, GLuint mask); +void (GLAPIENTRY *qglStencilMask)(GLuint mask); +void (GLAPIENTRY *qglStencilOp)(GLenum fail, GLenum zfail, GLenum zpass); +void (GLAPIENTRY *qglClearStencil)(GLint s); + +void (GLAPIENTRY *qglTexEnvf)(GLenum target, GLenum pname, GLfloat param); +void (GLAPIENTRY *qglTexEnvfv)(GLenum target, GLenum pname, const GLfloat *params); +void (GLAPIENTRY *qglTexEnvi)(GLenum target, GLenum pname, GLint param); +void (GLAPIENTRY *qglTexParameterf)(GLenum target, GLenum pname, GLfloat param); +void (GLAPIENTRY *qglTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglTexParameteri)(GLenum target, GLenum pname, GLint param); +void (GLAPIENTRY *qglGetTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglGetTexParameteriv)(GLenum target, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetTexLevelParameterfv)(GLenum target, GLint level, GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglGetTexLevelParameteriv)(GLenum target, GLint level, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +void (GLAPIENTRY *qglHint)(GLenum target, GLenum mode); + +void (GLAPIENTRY *qglGenTextures)(GLsizei n, GLuint *textures); +void (GLAPIENTRY *qglDeleteTextures)(GLsizei n, const GLuint *textures); +void (GLAPIENTRY *qglBindTexture)(GLenum target, GLuint texture); +//void (GLAPIENTRY *qglPrioritizeTextures)(GLsizei n, const GLuint *textures, const GLclampf *priorities); +//GLboolean (GLAPIENTRY *qglAreTexturesResident)(GLsizei n, const GLuint *textures, GLboolean *residences); +//GLboolean (GLAPIENTRY *qglIsTexture)(GLuint texture); +//void (GLAPIENTRY *qglPixelStoref)(GLenum pname, GLfloat param); +void (GLAPIENTRY *qglPixelStorei)(GLenum pname, GLint param); + +//void (GLAPIENTRY *qglTexImage1D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void (GLAPIENTRY *qglTexImage2D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +//void (GLAPIENTRY *qglTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +void (GLAPIENTRY *qglTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +//void (GLAPIENTRY *qglCopyTexImage1D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +void (GLAPIENTRY *qglCopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +//void (GLAPIENTRY *qglCopyTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +void (GLAPIENTRY *qglCopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + + +void (GLAPIENTRY *qglDrawRangeElementsEXT)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); + +//void (GLAPIENTRY *qglColorTableEXT)(int, int, int, int, int, const void *); + +void (GLAPIENTRY *qglTexImage3D)(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void (GLAPIENTRY *qglTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +void (GLAPIENTRY *qglCopyTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); + +void (GLAPIENTRY *qglScissor)(GLint x, GLint y, GLsizei width, GLsizei height); + +void (GLAPIENTRY *qglPolygonOffset)(GLfloat factor, GLfloat units); +void (GLAPIENTRY *qglPolygonMode)(GLenum face, GLenum mode); +void (GLAPIENTRY *qglPolygonStipple)(const GLubyte *mask); + +//void (GLAPIENTRY *qglClipPlane)(GLenum plane, const GLdouble *equation); +//void (GLAPIENTRY *qglGetClipPlane)(GLenum plane, GLdouble *equation); + +//[515]: added on 29.07.2005 +void (GLAPIENTRY *qglLineWidth)(GLfloat width); +void (GLAPIENTRY *qglPointSize)(GLfloat size); + +void (GLAPIENTRY *qglBlendEquationEXT)(GLenum); + +void (GLAPIENTRY *qglStencilOpSeparate)(GLenum, GLenum, GLenum, GLenum); +void (GLAPIENTRY *qglStencilFuncSeparate)(GLenum, GLenum, GLint, GLuint); +void (GLAPIENTRY *qglActiveStencilFaceEXT)(GLenum); + +void (GLAPIENTRY *qglDeleteShader)(GLuint obj); +void (GLAPIENTRY *qglDeleteProgram)(GLuint obj); +//GLuint (GLAPIENTRY *qglGetHandle)(GLenum pname); +void (GLAPIENTRY *qglDetachShader)(GLuint containerObj, GLuint attachedObj); +GLuint (GLAPIENTRY *qglCreateShader)(GLenum shaderType); +void (GLAPIENTRY *qglShaderSource)(GLuint shaderObj, GLsizei count, const GLchar **string, const GLint *length); +void (GLAPIENTRY *qglCompileShader)(GLuint shaderObj); +GLuint (GLAPIENTRY *qglCreateProgram)(void); +void (GLAPIENTRY *qglAttachShader)(GLuint containerObj, GLuint obj); +void (GLAPIENTRY *qglLinkProgram)(GLuint programObj); +void (GLAPIENTRY *qglUseProgram)(GLuint programObj); +void (GLAPIENTRY *qglValidateProgram)(GLuint programObj); +void (GLAPIENTRY *qglUniform1f)(GLint location, GLfloat v0); +void (GLAPIENTRY *qglUniform2f)(GLint location, GLfloat v0, GLfloat v1); +void (GLAPIENTRY *qglUniform3f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +void (GLAPIENTRY *qglUniform4f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +void (GLAPIENTRY *qglUniform1i)(GLint location, GLint v0); +void (GLAPIENTRY *qglUniform2i)(GLint location, GLint v0, GLint v1); +void (GLAPIENTRY *qglUniform3i)(GLint location, GLint v0, GLint v1, GLint v2); +void (GLAPIENTRY *qglUniform4i)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +void (GLAPIENTRY *qglUniform1fv)(GLint location, GLsizei count, const GLfloat *value); +void (GLAPIENTRY *qglUniform2fv)(GLint location, GLsizei count, const GLfloat *value); +void (GLAPIENTRY *qglUniform3fv)(GLint location, GLsizei count, const GLfloat *value); +void (GLAPIENTRY *qglUniform4fv)(GLint location, GLsizei count, const GLfloat *value); +void (GLAPIENTRY *qglUniform1iv)(GLint location, GLsizei count, const GLint *value); +void (GLAPIENTRY *qglUniform2iv)(GLint location, GLsizei count, const GLint *value); +void (GLAPIENTRY *qglUniform3iv)(GLint location, GLsizei count, const GLint *value); +void (GLAPIENTRY *qglUniform4iv)(GLint location, GLsizei count, const GLint *value); +void (GLAPIENTRY *qglUniformMatrix2fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void (GLAPIENTRY *qglUniformMatrix3fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void (GLAPIENTRY *qglUniformMatrix4fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void (GLAPIENTRY *qglGetShaderiv)(GLuint obj, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetProgramiv)(GLuint obj, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetShaderInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); +void (GLAPIENTRY *qglGetProgramInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); +void (GLAPIENTRY *qglGetAttachedShaders)(GLuint containerObj, GLsizei maxCount, GLsizei *count, GLuint *obj); +GLint (GLAPIENTRY *qglGetUniformLocation)(GLuint programObj, const GLchar *name); +void (GLAPIENTRY *qglGetActiveUniform)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +void (GLAPIENTRY *qglGetUniformfv)(GLuint programObj, GLint location, GLfloat *params); +void (GLAPIENTRY *qglGetUniformiv)(GLuint programObj, GLint location, GLint *params); +void (GLAPIENTRY *qglGetShaderSource)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *source); + +void (GLAPIENTRY *qglVertexAttrib1f)(GLuint index, GLfloat v0); +void (GLAPIENTRY *qglVertexAttrib1s)(GLuint index, GLshort v0); +void (GLAPIENTRY *qglVertexAttrib1d)(GLuint index, GLdouble v0); +void (GLAPIENTRY *qglVertexAttrib2f)(GLuint index, GLfloat v0, GLfloat v1); +void (GLAPIENTRY *qglVertexAttrib2s)(GLuint index, GLshort v0, GLshort v1); +void (GLAPIENTRY *qglVertexAttrib2d)(GLuint index, GLdouble v0, GLdouble v1); +void (GLAPIENTRY *qglVertexAttrib3f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); +void (GLAPIENTRY *qglVertexAttrib3s)(GLuint index, GLshort v0, GLshort v1, GLshort v2); +void (GLAPIENTRY *qglVertexAttrib3d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2); +void (GLAPIENTRY *qglVertexAttrib4f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +void (GLAPIENTRY *qglVertexAttrib4s)(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3); +void (GLAPIENTRY *qglVertexAttrib4d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +void (GLAPIENTRY *qglVertexAttrib4Nub)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +void (GLAPIENTRY *qglVertexAttrib1fv)(GLuint index, const GLfloat *v); +void (GLAPIENTRY *qglVertexAttrib1sv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib1dv)(GLuint index, const GLdouble *v); +void (GLAPIENTRY *qglVertexAttrib2fv)(GLuint index, const GLfloat *v); +void (GLAPIENTRY *qglVertexAttrib2sv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib2dv)(GLuint index, const GLdouble *v); +void (GLAPIENTRY *qglVertexAttrib3fv)(GLuint index, const GLfloat *v); +void (GLAPIENTRY *qglVertexAttrib3sv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib3dv)(GLuint index, const GLdouble *v); +void (GLAPIENTRY *qglVertexAttrib4fv)(GLuint index, const GLfloat *v); +void (GLAPIENTRY *qglVertexAttrib4sv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib4dv)(GLuint index, const GLdouble *v); +void (GLAPIENTRY *qglVertexAttrib4iv)(GLuint index, const GLint *v); +void (GLAPIENTRY *qglVertexAttrib4bv)(GLuint index, const GLbyte *v); +void (GLAPIENTRY *qglVertexAttrib4ubv)(GLuint index, const GLubyte *v); +void (GLAPIENTRY *qglVertexAttrib4usv)(GLuint index, const GLushort *v); +void (GLAPIENTRY *qglVertexAttrib4uiv)(GLuint index, const GLuint *v); +void (GLAPIENTRY *qglVertexAttrib4Nbv)(GLuint index, const GLbyte *v); +void (GLAPIENTRY *qglVertexAttrib4Nsv)(GLuint index, const GLshort *v); +void (GLAPIENTRY *qglVertexAttrib4Niv)(GLuint index, const GLint *v); +void (GLAPIENTRY *qglVertexAttrib4Nubv)(GLuint index, const GLubyte *v); +void (GLAPIENTRY *qglVertexAttrib4Nusv)(GLuint index, const GLushort *v); +void (GLAPIENTRY *qglVertexAttrib4Nuiv)(GLuint index, const GLuint *v); +void (GLAPIENTRY *qglVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +void (GLAPIENTRY *qglEnableVertexAttribArray)(GLuint index); +void (GLAPIENTRY *qglDisableVertexAttribArray)(GLuint index); +void (GLAPIENTRY *qglBindAttribLocation)(GLuint programObj, GLuint index, const GLchar *name); +void (GLAPIENTRY *qglBindFragDataLocation)(GLuint programObj, GLuint index, const GLchar *name); +void (GLAPIENTRY *qglGetActiveAttrib)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLint (GLAPIENTRY *qglGetAttribLocation)(GLuint programObj, const GLchar *name); +void (GLAPIENTRY *qglGetVertexAttribdv)(GLuint index, GLenum pname, GLdouble *params); +void (GLAPIENTRY *qglGetVertexAttribfv)(GLuint index, GLenum pname, GLfloat *params); +void (GLAPIENTRY *qglGetVertexAttribiv)(GLuint index, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetVertexAttribPointerv)(GLuint index, GLenum pname, GLvoid **pointer); + +//GL_ARB_vertex_buffer_object +void (GLAPIENTRY *qglBindBufferARB) (GLenum target, GLuint buffer); +void (GLAPIENTRY *qglDeleteBuffersARB) (GLsizei n, const GLuint *buffers); +void (GLAPIENTRY *qglGenBuffersARB) (GLsizei n, GLuint *buffers); +GLboolean (GLAPIENTRY *qglIsBufferARB) (GLuint buffer); +GLvoid* (GLAPIENTRY *qglMapBufferARB) (GLenum target, GLenum access); +GLboolean (GLAPIENTRY *qglUnmapBufferARB) (GLenum target); +void (GLAPIENTRY *qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +void (GLAPIENTRY *qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); + +//GL_ARB_framebuffer_object +GLboolean (GLAPIENTRY *qglIsRenderbuffer)(GLuint renderbuffer); +GLvoid (GLAPIENTRY *qglBindRenderbuffer)(GLenum target, GLuint renderbuffer); +GLvoid (GLAPIENTRY *qglDeleteRenderbuffers)(GLsizei n, const GLuint *renderbuffers); +GLvoid (GLAPIENTRY *qglGenRenderbuffers)(GLsizei n, GLuint *renderbuffers); +GLvoid (GLAPIENTRY *qglRenderbufferStorage)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLvoid (GLAPIENTRY *qglRenderbufferStorageMultisample)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLvoid (GLAPIENTRY *qglGetRenderbufferParameteriv)(GLenum target, GLenum pname, GLint *params); +GLboolean (GLAPIENTRY *qglIsFramebuffer)(GLuint framebuffer); +GLvoid (GLAPIENTRY *qglBindFramebuffer)(GLenum target, GLuint framebuffer); +GLvoid (GLAPIENTRY *qglDeleteFramebuffers)(GLsizei n, const GLuint *framebuffers); +GLvoid (GLAPIENTRY *qglGenFramebuffers)(GLsizei n, GLuint *framebuffers); +GLenum (GLAPIENTRY *qglCheckFramebufferStatus)(GLenum target); +GLvoid (GLAPIENTRY *qglFramebufferTexture1D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLvoid (GLAPIENTRY *qglFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLvoid (GLAPIENTRY *qglFramebufferTexture3D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer); +GLvoid (GLAPIENTRY *qglFramebufferTextureLayer)(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLvoid (GLAPIENTRY *qglFramebufferRenderbuffer)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLvoid (GLAPIENTRY *qglGetFramebufferAttachmentParameteriv)(GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLvoid (GLAPIENTRY *qglBlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLvoid (GLAPIENTRY *qglGenerateMipmap)(GLenum target); + +void (GLAPIENTRY *qglDrawBuffersARB)(GLsizei n, const GLenum *bufs); + +void (GLAPIENTRY *qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +void (GLAPIENTRY *qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +//void (GLAPIENTRY *qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +void (GLAPIENTRY *qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +void (GLAPIENTRY *qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +//void (GLAPIENTRY *qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +void (GLAPIENTRY *qglGetCompressedTexImageARB)(GLenum target, GLint lod, void *img); + +void (GLAPIENTRY *qglGenQueriesARB)(GLsizei n, GLuint *ids); +void (GLAPIENTRY *qglDeleteQueriesARB)(GLsizei n, const GLuint *ids); +GLboolean (GLAPIENTRY *qglIsQueryARB)(GLuint qid); +void (GLAPIENTRY *qglBeginQueryARB)(GLenum target, GLuint qid); +void (GLAPIENTRY *qglEndQueryARB)(GLenum target); +void (GLAPIENTRY *qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetQueryObjectivARB)(GLuint qid, GLenum pname, GLint *params); +void (GLAPIENTRY *qglGetQueryObjectuivARB)(GLuint qid, GLenum pname, GLuint *params); + +void (GLAPIENTRY *qglSampleCoverageARB)(GLclampf value, GLboolean invert); + +void (GLAPIENTRY *qglGetUniformIndices)(GLuint program, GLsizei uniformCount, const GLchar** uniformNames, GLuint* uniformIndices); +void (GLAPIENTRY *qglGetActiveUniformsiv)(GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params); +void (GLAPIENTRY *qglGetActiveUniformName)(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformName); +GLuint (GLAPIENTRY *qglGetUniformBlockIndex)(GLuint program, const GLchar* uniformBlockName); +void (GLAPIENTRY *qglGetActiveUniformBlockiv)(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params); +void (GLAPIENTRY *qglGetActiveUniformBlockName)(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformBlockName); +void (GLAPIENTRY *qglBindBufferRange)(GLenum target, GLuint index, GLuint buffer, GLintptrARB offset, GLsizeiptrARB size); +void (GLAPIENTRY *qglBindBufferBase)(GLenum target, GLuint index, GLuint buffer); +void (GLAPIENTRY *qglGetIntegeri_v)(GLenum target, GLuint index, GLint* data); +void (GLAPIENTRY *qglUniformBlockBinding)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#endif + +#if _MSC_VER >= 1400 +#define sscanf sscanf_s +#endif + +qboolean GL_CheckExtension(const char *minglver_or_ext, const dllfunction_t *funcs, const char *disableparm, int silent) +{ + int failed = false; + const dllfunction_t *func; + struct { int major, minor; } min_version, curr_version; + char extstr[MAX_INPUTLINE]; + int ext; + + if(sscanf(minglver_or_ext, "%d.%d", &min_version.major, &min_version.minor) == 2) + ext = 0; // opengl version + else if(minglver_or_ext[0] != toupper(minglver_or_ext[0])) + ext = -1; // pseudo name + else + ext = 1; // extension name + + if (ext) + Con_DPrintf("checking for %s... ", minglver_or_ext); + else + Con_DPrintf("checking for OpenGL %s core features... ", minglver_or_ext); + + for (func = funcs;func && func->name;func++) + *func->funcvariable = NULL; + + if (disableparm && (COM_CheckParm(disableparm) || COM_CheckParm("-safe"))) + { + Con_DPrint("disabled by commandline\n"); + return false; + } + + if (ext == 1) // opengl extension + { + if (!strstr(gl_extensions ? gl_extensions : "", minglver_or_ext) && !strstr(gl_platformextensions ? gl_platformextensions : "", minglver_or_ext)) + { + Con_DPrint("not detected\n"); + return false; + } + } + + if(ext == 0) // opengl version + { + if (sscanf(gl_version, "%d.%d", &curr_version.major, &curr_version.minor) < 2) + curr_version.major = curr_version.minor = 1; + + if (curr_version.major < min_version.major || (curr_version.major == min_version.major && curr_version.minor < min_version.minor)) + { + Con_DPrintf("not detected (OpenGL %d.%d loaded)\n", curr_version.major, curr_version.minor); + return false; + } + } + + for (func = funcs;func && func->name != NULL;func++) + { + // Con_DPrintf("\n %s... ", func->name); + + // functions are cleared before all the extensions are evaluated + if (!(*func->funcvariable = (void *) GL_GetProcAddress(func->name))) + { + if (ext && !silent) + Con_DPrintf("%s is missing function \"%s\" - broken driver!\n", minglver_or_ext, func->name); + if (!ext) + Con_Printf("OpenGL %s core features are missing function \"%s\" - broken driver!\n", minglver_or_ext, func->name); + failed = true; + } + } + // delay the return so it prints all missing functions + if (failed) + return false; + // VorteX: add to found extension list + dpsnprintf(extstr, sizeof(extstr), "%s %s ", gl_info_extensions.string, minglver_or_ext); + Cvar_SetQuick(&gl_info_extensions, extstr); + + Con_DPrint("enabled\n"); + return true; +} + +#ifndef USE_GLES2 +static dllfunction_t opengl110funcs[] = +{ + {"glClearColor", (void **) &qglClearColor}, + {"glClear", (void **) &qglClear}, + {"glAlphaFunc", (void **) &qglAlphaFunc}, + {"glBlendFunc", (void **) &qglBlendFunc}, + {"glCullFace", (void **) &qglCullFace}, + {"glDrawBuffer", (void **) &qglDrawBuffer}, + {"glReadBuffer", (void **) &qglReadBuffer}, + {"glEnable", (void **) &qglEnable}, + {"glDisable", (void **) &qglDisable}, + {"glIsEnabled", (void **) &qglIsEnabled}, + {"glEnableClientState", (void **) &qglEnableClientState}, + {"glDisableClientState", (void **) &qglDisableClientState}, + {"glGetBooleanv", (void **) &qglGetBooleanv}, + {"glGetDoublev", (void **) &qglGetDoublev}, + {"glGetFloatv", (void **) &qglGetFloatv}, + {"glGetIntegerv", (void **) &qglGetIntegerv}, + {"glGetError", (void **) &qglGetError}, + {"glGetString", (void **) &qglGetString}, + {"glFinish", (void **) &qglFinish}, + {"glFlush", (void **) &qglFlush}, + {"glClearDepth", (void **) &qglClearDepth}, + {"glDepthFunc", (void **) &qglDepthFunc}, + {"glDepthMask", (void **) &qglDepthMask}, + {"glDepthRange", (void **) &qglDepthRange}, + {"glDrawElements", (void **) &qglDrawElements}, + {"glDrawArrays", (void **) &qglDrawArrays}, + {"glColorMask", (void **) &qglColorMask}, + {"glVertexPointer", (void **) &qglVertexPointer}, + {"glNormalPointer", (void **) &qglNormalPointer}, + {"glColorPointer", (void **) &qglColorPointer}, + {"glTexCoordPointer", (void **) &qglTexCoordPointer}, + {"glArrayElement", (void **) &qglArrayElement}, + {"glColor4ub", (void **) &qglColor4ub}, + {"glColor4f", (void **) &qglColor4f}, + {"glTexCoord1f", (void **) &qglTexCoord1f}, + {"glTexCoord2f", (void **) &qglTexCoord2f}, + {"glTexCoord3f", (void **) &qglTexCoord3f}, + {"glTexCoord4f", (void **) &qglTexCoord4f}, + {"glVertex2f", (void **) &qglVertex2f}, + {"glVertex3f", (void **) &qglVertex3f}, + {"glVertex4f", (void **) &qglVertex4f}, + {"glBegin", (void **) &qglBegin}, + {"glEnd", (void **) &qglEnd}, +//[515]: added on 29.07.2005 + {"glLineWidth", (void**) &qglLineWidth}, + {"glPointSize", (void**) &qglPointSize}, +// + {"glMatrixMode", (void **) &qglMatrixMode}, +// {"glOrtho", (void **) &qglOrtho}, +// {"glFrustum", (void **) &qglFrustum}, + {"glViewport", (void **) &qglViewport}, +// {"glPushMatrix", (void **) &qglPushMatrix}, +// {"glPopMatrix", (void **) &qglPopMatrix}, + {"glLoadIdentity", (void **) &qglLoadIdentity}, +// {"glLoadMatrixd", (void **) &qglLoadMatrixd}, + {"glLoadMatrixf", (void **) &qglLoadMatrixf}, +// {"glMultMatrixd", (void **) &qglMultMatrixd}, +// {"glMultMatrixf", (void **) &qglMultMatrixf}, +// {"glRotated", (void **) &qglRotated}, +// {"glRotatef", (void **) &qglRotatef}, +// {"glScaled", (void **) &qglScaled}, +// {"glScalef", (void **) &qglScalef}, +// {"glTranslated", (void **) &qglTranslated}, +// {"glTranslatef", (void **) &qglTranslatef}, + {"glReadPixels", (void **) &qglReadPixels}, + {"glStencilFunc", (void **) &qglStencilFunc}, + {"glStencilMask", (void **) &qglStencilMask}, + {"glStencilOp", (void **) &qglStencilOp}, + {"glClearStencil", (void **) &qglClearStencil}, + {"glTexEnvf", (void **) &qglTexEnvf}, + {"glTexEnvfv", (void **) &qglTexEnvfv}, + {"glTexEnvi", (void **) &qglTexEnvi}, + {"glTexParameterf", (void **) &qglTexParameterf}, + {"glTexParameterfv", (void **) &qglTexParameterfv}, + {"glTexParameteri", (void **) &qglTexParameteri}, + {"glGetTexImage", (void **) &qglGetTexImage}, + {"glGetTexParameterfv", (void **) &qglGetTexParameterfv}, + {"glGetTexParameteriv", (void **) &qglGetTexParameteriv}, + {"glGetTexLevelParameterfv", (void **) &qglGetTexLevelParameterfv}, + {"glGetTexLevelParameteriv", (void **) &qglGetTexLevelParameteriv}, + {"glHint", (void **) &qglHint}, +// {"glPixelStoref", (void **) &qglPixelStoref}, + {"glPixelStorei", (void **) &qglPixelStorei}, + {"glGenTextures", (void **) &qglGenTextures}, + {"glDeleteTextures", (void **) &qglDeleteTextures}, + {"glBindTexture", (void **) &qglBindTexture}, +// {"glPrioritizeTextures", (void **) &qglPrioritizeTextures}, +// {"glAreTexturesResident", (void **) &qglAreTexturesResident}, +// {"glIsTexture", (void **) &qglIsTexture}, +// {"glTexImage1D", (void **) &qglTexImage1D}, + {"glTexImage2D", (void **) &qglTexImage2D}, +// {"glTexSubImage1D", (void **) &qglTexSubImage1D}, + {"glTexSubImage2D", (void **) &qglTexSubImage2D}, +// {"glCopyTexImage1D", (void **) &qglCopyTexImage1D}, + {"glCopyTexImage2D", (void **) &qglCopyTexImage2D}, +// {"glCopyTexSubImage1D", (void **) &qglCopyTexSubImage1D}, + {"glCopyTexSubImage2D", (void **) &qglCopyTexSubImage2D}, + {"glScissor", (void **) &qglScissor}, + {"glPolygonOffset", (void **) &qglPolygonOffset}, + {"glPolygonMode", (void **) &qglPolygonMode}, + {"glPolygonStipple", (void **) &qglPolygonStipple}, +// {"glClipPlane", (void **) &qglClipPlane}, +// {"glGetClipPlane", (void **) &qglGetClipPlane}, + {NULL, NULL} +}; + +static dllfunction_t drawrangeelementsfuncs[] = +{ + {"glDrawRangeElements", (void **) &qglDrawRangeElements}, + {NULL, NULL} +}; + +static dllfunction_t drawrangeelementsextfuncs[] = +{ + {"glDrawRangeElementsEXT", (void **) &qglDrawRangeElementsEXT}, + {NULL, NULL} +}; + +static dllfunction_t multitexturefuncs[] = +{ + {"glMultiTexCoord1fARB", (void **) &qglMultiTexCoord1f}, + {"glMultiTexCoord2fARB", (void **) &qglMultiTexCoord2f}, + {"glMultiTexCoord3fARB", (void **) &qglMultiTexCoord3f}, + {"glMultiTexCoord4fARB", (void **) &qglMultiTexCoord4f}, + {"glActiveTextureARB", (void **) &qglActiveTexture}, + {"glClientActiveTextureARB", (void **) &qglClientActiveTexture}, + {NULL, NULL} +}; + +static dllfunction_t texture3dextfuncs[] = +{ + {"glTexImage3DEXT", (void **) &qglTexImage3D}, + {"glTexSubImage3DEXT", (void **) &qglTexSubImage3D}, + {"glCopyTexSubImage3DEXT", (void **) &qglCopyTexSubImage3D}, + {NULL, NULL} +}; + +static dllfunction_t atiseparatestencilfuncs[] = +{ + {"glStencilOpSeparateATI", (void **) &qglStencilOpSeparate}, + {"glStencilFuncSeparateATI", (void **) &qglStencilFuncSeparate}, + {NULL, NULL} +}; + +static dllfunction_t gl2separatestencilfuncs[] = +{ + {"glStencilOpSeparate", (void **) &qglStencilOpSeparate}, + {"glStencilFuncSeparate", (void **) &qglStencilFuncSeparate}, + {NULL, NULL} +}; + +static dllfunction_t stenciltwosidefuncs[] = +{ + {"glActiveStencilFaceEXT", (void **) &qglActiveStencilFaceEXT}, + {NULL, NULL} +}; + +static dllfunction_t blendequationfuncs[] = +{ + {"glBlendEquationEXT", (void **) &qglBlendEquationEXT}, + {NULL, NULL} +}; + +static dllfunction_t gl20shaderfuncs[] = +{ + {"glDeleteShader", (void **) &qglDeleteShader}, + {"glDeleteProgram", (void **) &qglDeleteProgram}, +// {"glGetHandle", (void **) &qglGetHandle}, + {"glDetachShader", (void **) &qglDetachShader}, + {"glCreateShader", (void **) &qglCreateShader}, + {"glShaderSource", (void **) &qglShaderSource}, + {"glCompileShader", (void **) &qglCompileShader}, + {"glCreateProgram", (void **) &qglCreateProgram}, + {"glAttachShader", (void **) &qglAttachShader}, + {"glLinkProgram", (void **) &qglLinkProgram}, + {"glUseProgram", (void **) &qglUseProgram}, + {"glValidateProgram", (void **) &qglValidateProgram}, + {"glUniform1f", (void **) &qglUniform1f}, + {"glUniform2f", (void **) &qglUniform2f}, + {"glUniform3f", (void **) &qglUniform3f}, + {"glUniform4f", (void **) &qglUniform4f}, + {"glUniform1i", (void **) &qglUniform1i}, + {"glUniform2i", (void **) &qglUniform2i}, + {"glUniform3i", (void **) &qglUniform3i}, + {"glUniform4i", (void **) &qglUniform4i}, + {"glUniform1fv", (void **) &qglUniform1fv}, + {"glUniform2fv", (void **) &qglUniform2fv}, + {"glUniform3fv", (void **) &qglUniform3fv}, + {"glUniform4fv", (void **) &qglUniform4fv}, + {"glUniform1iv", (void **) &qglUniform1iv}, + {"glUniform2iv", (void **) &qglUniform2iv}, + {"glUniform3iv", (void **) &qglUniform3iv}, + {"glUniform4iv", (void **) &qglUniform4iv}, + {"glUniformMatrix2fv", (void **) &qglUniformMatrix2fv}, + {"glUniformMatrix3fv", (void **) &qglUniformMatrix3fv}, + {"glUniformMatrix4fv", (void **) &qglUniformMatrix4fv}, + {"glGetShaderiv", (void **) &qglGetShaderiv}, + {"glGetProgramiv", (void **) &qglGetProgramiv}, + {"glGetShaderInfoLog", (void **) &qglGetShaderInfoLog}, + {"glGetProgramInfoLog", (void **) &qglGetProgramInfoLog}, + {"glGetAttachedShaders", (void **) &qglGetAttachedShaders}, + {"glGetUniformLocation", (void **) &qglGetUniformLocation}, + {"glGetActiveUniform", (void **) &qglGetActiveUniform}, + {"glGetUniformfv", (void **) &qglGetUniformfv}, + {"glGetUniformiv", (void **) &qglGetUniformiv}, + {"glGetShaderSource", (void **) &qglGetShaderSource}, + {"glVertexAttrib1f", (void **) &qglVertexAttrib1f}, + {"glVertexAttrib1s", (void **) &qglVertexAttrib1s}, + {"glVertexAttrib1d", (void **) &qglVertexAttrib1d}, + {"glVertexAttrib2f", (void **) &qglVertexAttrib2f}, + {"glVertexAttrib2s", (void **) &qglVertexAttrib2s}, + {"glVertexAttrib2d", (void **) &qglVertexAttrib2d}, + {"glVertexAttrib3f", (void **) &qglVertexAttrib3f}, + {"glVertexAttrib3s", (void **) &qglVertexAttrib3s}, + {"glVertexAttrib3d", (void **) &qglVertexAttrib3d}, + {"glVertexAttrib4f", (void **) &qglVertexAttrib4f}, + {"glVertexAttrib4s", (void **) &qglVertexAttrib4s}, + {"glVertexAttrib4d", (void **) &qglVertexAttrib4d}, + {"glVertexAttrib4Nub", (void **) &qglVertexAttrib4Nub}, + {"glVertexAttrib1fv", (void **) &qglVertexAttrib1fv}, + {"glVertexAttrib1sv", (void **) &qglVertexAttrib1sv}, + {"glVertexAttrib1dv", (void **) &qglVertexAttrib1dv}, + {"glVertexAttrib2fv", (void **) &qglVertexAttrib1fv}, + {"glVertexAttrib2sv", (void **) &qglVertexAttrib1sv}, + {"glVertexAttrib2dv", (void **) &qglVertexAttrib1dv}, + {"glVertexAttrib3fv", (void **) &qglVertexAttrib1fv}, + {"glVertexAttrib3sv", (void **) &qglVertexAttrib1sv}, + {"glVertexAttrib3dv", (void **) &qglVertexAttrib1dv}, + {"glVertexAttrib4fv", (void **) &qglVertexAttrib1fv}, + {"glVertexAttrib4sv", (void **) &qglVertexAttrib1sv}, + {"glVertexAttrib4dv", (void **) &qglVertexAttrib1dv}, +// {"glVertexAttrib4iv", (void **) &qglVertexAttrib1iv}, +// {"glVertexAttrib4bv", (void **) &qglVertexAttrib1bv}, +// {"glVertexAttrib4ubv", (void **) &qglVertexAttrib1ubv}, +// {"glVertexAttrib4usv", (void **) &qglVertexAttrib1usv}, +// {"glVertexAttrib4uiv", (void **) &qglVertexAttrib1uiv}, +// {"glVertexAttrib4Nbv", (void **) &qglVertexAttrib1Nbv}, +// {"glVertexAttrib4Nsv", (void **) &qglVertexAttrib1Nsv}, +// {"glVertexAttrib4Niv", (void **) &qglVertexAttrib1Niv}, +// {"glVertexAttrib4Nubv", (void **) &qglVertexAttrib1Nubv}, +// {"glVertexAttrib4Nusv", (void **) &qglVertexAttrib1Nusv}, +// {"glVertexAttrib4Nuiv", (void **) &qglVertexAttrib1Nuiv}, + {"glVertexAttribPointer", (void **) &qglVertexAttribPointer}, + {"glEnableVertexAttribArray", (void **) &qglEnableVertexAttribArray}, + {"glDisableVertexAttribArray", (void **) &qglDisableVertexAttribArray}, + {"glBindAttribLocation", (void **) &qglBindAttribLocation}, + {"glGetActiveAttrib", (void **) &qglGetActiveAttrib}, + {"glGetAttribLocation", (void **) &qglGetAttribLocation}, + {"glGetVertexAttribdv", (void **) &qglGetVertexAttribdv}, + {"glGetVertexAttribfv", (void **) &qglGetVertexAttribfv}, + {"glGetVertexAttribiv", (void **) &qglGetVertexAttribiv}, + {"glGetVertexAttribPointerv", (void **) &qglGetVertexAttribPointerv}, + {NULL, NULL} +}; + +static dllfunction_t glsl130funcs[] = +{ + {"glBindFragDataLocation", (void **) &qglBindFragDataLocation}, + {NULL, NULL} +}; + +static dllfunction_t vbofuncs[] = +{ + {"glBindBufferARB" , (void **) &qglBindBufferARB}, + {"glDeleteBuffersARB" , (void **) &qglDeleteBuffersARB}, + {"glGenBuffersARB" , (void **) &qglGenBuffersARB}, + {"glIsBufferARB" , (void **) &qglIsBufferARB}, + {"glMapBufferARB" , (void **) &qglMapBufferARB}, + {"glUnmapBufferARB" , (void **) &qglUnmapBufferARB}, + {"glBufferDataARB" , (void **) &qglBufferDataARB}, + {"glBufferSubDataARB" , (void **) &qglBufferSubDataARB}, + {NULL, NULL} +}; + +static dllfunction_t ubofuncs[] = +{ + {"glGetUniformIndices" , (void **) &qglGetUniformIndices}, + {"glGetActiveUniformsiv" , (void **) &qglGetActiveUniformsiv}, + {"glGetActiveUniformName" , (void **) &qglGetActiveUniformName}, + {"glGetUniformBlockIndex" , (void **) &qglGetUniformBlockIndex}, + {"glGetActiveUniformBlockiv" , (void **) &qglGetActiveUniformBlockiv}, + {"glGetActiveUniformBlockName", (void **) &qglGetActiveUniformBlockName}, + {"glBindBufferRange" , (void **) &qglBindBufferRange}, + {"glBindBufferBase" , (void **) &qglBindBufferBase}, + {"glGetIntegeri_v" , (void **) &qglGetIntegeri_v}, + {"glUniformBlockBinding" , (void **) &qglUniformBlockBinding}, + {NULL, NULL} +}; + +static dllfunction_t arbfbofuncs[] = +{ + {"glIsRenderbuffer" , (void **) &qglIsRenderbuffer}, + {"glBindRenderbuffer" , (void **) &qglBindRenderbuffer}, + {"glDeleteRenderbuffers" , (void **) &qglDeleteRenderbuffers}, + {"glGenRenderbuffers" , (void **) &qglGenRenderbuffers}, + {"glRenderbufferStorage" , (void **) &qglRenderbufferStorage}, + {"glRenderbufferStorageMultisample" , (void **) &qglRenderbufferStorageMultisample}, // not in GL_EXT_framebuffer_object + {"glGetRenderbufferParameteriv" , (void **) &qglGetRenderbufferParameteriv}, + {"glIsFramebuffer" , (void **) &qglIsFramebuffer}, + {"glBindFramebuffer" , (void **) &qglBindFramebuffer}, + {"glDeleteFramebuffers" , (void **) &qglDeleteFramebuffers}, + {"glGenFramebuffers" , (void **) &qglGenFramebuffers}, + {"glCheckFramebufferStatus" , (void **) &qglCheckFramebufferStatus}, + {"glFramebufferTexture1D" , (void **) &qglFramebufferTexture1D}, + {"glFramebufferTexture2D" , (void **) &qglFramebufferTexture2D}, + {"glFramebufferTexture3D" , (void **) &qglFramebufferTexture3D}, + {"glFramebufferTextureLayer" , (void **) &qglFramebufferTextureLayer}, // not in GL_EXT_framebuffer_object + {"glFramebufferRenderbuffer" , (void **) &qglFramebufferRenderbuffer}, + {"glGetFramebufferAttachmentParameteriv" , (void **) &qglGetFramebufferAttachmentParameteriv}, + {"glBlitFramebuffer" , (void **) &qglBlitFramebuffer}, // not in GL_EXT_framebuffer_object + {"glGenerateMipmap" , (void **) &qglGenerateMipmap}, + {NULL, NULL} +}; + +static dllfunction_t extfbofuncs[] = +{ + {"glIsRenderbufferEXT" , (void **) &qglIsRenderbuffer}, + {"glBindRenderbufferEXT" , (void **) &qglBindRenderbuffer}, + {"glDeleteRenderbuffersEXT" , (void **) &qglDeleteRenderbuffers}, + {"glGenRenderbuffersEXT" , (void **) &qglGenRenderbuffers}, + {"glRenderbufferStorageEXT" , (void **) &qglRenderbufferStorage}, + {"glGetRenderbufferParameterivEXT" , (void **) &qglGetRenderbufferParameteriv}, + {"glIsFramebufferEXT" , (void **) &qglIsFramebuffer}, + {"glBindFramebufferEXT" , (void **) &qglBindFramebuffer}, + {"glDeleteFramebuffersEXT" , (void **) &qglDeleteFramebuffers}, + {"glGenFramebuffersEXT" , (void **) &qglGenFramebuffers}, + {"glCheckFramebufferStatusEXT" , (void **) &qglCheckFramebufferStatus}, + {"glFramebufferTexture1DEXT" , (void **) &qglFramebufferTexture1D}, + {"glFramebufferTexture2DEXT" , (void **) &qglFramebufferTexture2D}, + {"glFramebufferTexture3DEXT" , (void **) &qglFramebufferTexture3D}, + {"glFramebufferRenderbufferEXT" , (void **) &qglFramebufferRenderbuffer}, + {"glGetFramebufferAttachmentParameterivEXT" , (void **) &qglGetFramebufferAttachmentParameteriv}, + {"glGenerateMipmapEXT" , (void **) &qglGenerateMipmap}, + {NULL, NULL} +}; + +static dllfunction_t texturecompressionfuncs[] = +{ + {"glCompressedTexImage3DARB", (void **) &qglCompressedTexImage3DARB}, + {"glCompressedTexImage2DARB", (void **) &qglCompressedTexImage2DARB}, +// {"glCompressedTexImage1DARB", (void **) &qglCompressedTexImage1DARB}, + {"glCompressedTexSubImage3DARB", (void **) &qglCompressedTexSubImage3DARB}, + {"glCompressedTexSubImage2DARB", (void **) &qglCompressedTexSubImage2DARB}, +// {"glCompressedTexSubImage1DARB", (void **) &qglCompressedTexSubImage1DARB}, + {"glGetCompressedTexImageARB", (void **) &qglGetCompressedTexImageARB}, + {NULL, NULL} +}; + +static dllfunction_t occlusionqueryfuncs[] = +{ + {"glGenQueriesARB", (void **) &qglGenQueriesARB}, + {"glDeleteQueriesARB", (void **) &qglDeleteQueriesARB}, + {"glIsQueryARB", (void **) &qglIsQueryARB}, + {"glBeginQueryARB", (void **) &qglBeginQueryARB}, + {"glEndQueryARB", (void **) &qglEndQueryARB}, + {"glGetQueryivARB", (void **) &qglGetQueryivARB}, + {"glGetQueryObjectivARB", (void **) &qglGetQueryObjectivARB}, + {"glGetQueryObjectuivARB", (void **) &qglGetQueryObjectuivARB}, + {NULL, NULL} +}; + +static dllfunction_t drawbuffersfuncs[] = +{ + {"glDrawBuffersARB", (void **) &qglDrawBuffersARB}, + {NULL, NULL} +}; + +static dllfunction_t multisamplefuncs[] = +{ + {"glSampleCoverageARB", (void **) &qglSampleCoverageARB}, + {NULL, NULL} +}; +#endif + +void VID_ClearExtensions(void) +{ + // VorteX: reset extensions info cvar, it got filled by GL_CheckExtension + Cvar_SetQuick(&gl_info_extensions, ""); + + // clear the extension flags + memset(&vid.support, 0, sizeof(vid.support)); + vid.renderpath = RENDERPATH_GL11; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + vid.useinterleavedarrays = false; + vid.forcevbo = false; + vid.maxtexturesize_2d = 0; + vid.maxtexturesize_3d = 0; + vid.maxtexturesize_cubemap = 0; + vid.texunits = 1; + vid.teximageunits = 1; + vid.texarrayunits = 1; + vid.max_anisotropy = 1; + vid.maxdrawbuffers = 1; + +#ifndef USE_GLES2 + // this is a complete list of all functions that are directly checked in the renderer + qglDrawRangeElements = NULL; + qglDrawBuffer = NULL; + qglPolygonStipple = NULL; + qglFlush = NULL; + qglActiveTexture = NULL; + qglGetCompressedTexImageARB = NULL; + qglFramebufferTexture2D = NULL; + qglDrawBuffersARB = NULL; +#endif +} + +#ifndef USE_GLES2 +void VID_CheckExtensions(void) +{ + if (!GL_CheckExtension("glbase", opengl110funcs, NULL, false)) + Sys_Error("OpenGL 1.1.0 functions not found"); + vid.support.gl20shaders = GL_CheckExtension("2.0", gl20shaderfuncs, "-noshaders", true); + + CHECKGLERROR + + Con_DPrint("Checking OpenGL extensions...\n"); + + if (vid.support.gl20shaders) + { + char *s; + // detect what GLSL version is available, to enable features like r_glsl_skeletal and higher quality reliefmapping + vid.support.glshaderversion = 100; + s = (char *) qglGetString(GL_SHADING_LANGUAGE_VERSION); + if (s) + vid.support.glshaderversion = (int)(atof(s) * 100.0f + 0.5f); + if (vid.support.glshaderversion < 100) + vid.support.glshaderversion = 100; + Con_DPrintf("Detected GLSL #version %i\n", vid.support.glshaderversion); + // get the glBindFragDataLocation function + if (vid.support.glshaderversion >= 130) + vid.support.gl20shaders130 = GL_CheckExtension("glshaders130", glsl130funcs, "-noglsl130", true); + } + + // GL drivers generally prefer GL_BGRA + vid.forcetextype = GL_BGRA; + + vid.support.amd_texture_texture4 = GL_CheckExtension("GL_AMD_texture_texture4", NULL, "-notexture4", false); + vid.support.arb_depth_texture = GL_CheckExtension("GL_ARB_depth_texture", NULL, "-nodepthtexture", false); + vid.support.arb_draw_buffers = GL_CheckExtension("GL_ARB_draw_buffers", drawbuffersfuncs, "-nodrawbuffers", false); + vid.support.arb_multitexture = GL_CheckExtension("GL_ARB_multitexture", multitexturefuncs, "-nomtex", false); + vid.support.arb_occlusion_query = GL_CheckExtension("GL_ARB_occlusion_query", occlusionqueryfuncs, "-noocclusionquery", false); + vid.support.arb_shadow = GL_CheckExtension("GL_ARB_shadow", NULL, "-noshadow", false); + vid.support.arb_texture_compression = GL_CheckExtension("GL_ARB_texture_compression", texturecompressionfuncs, "-notexturecompression", false); + vid.support.arb_texture_cube_map = GL_CheckExtension("GL_ARB_texture_cube_map", NULL, "-nocubemap", false); + vid.support.arb_texture_env_combine = GL_CheckExtension("GL_ARB_texture_env_combine", NULL, "-nocombine", false) || GL_CheckExtension("GL_EXT_texture_env_combine", NULL, "-nocombine", false); + vid.support.arb_texture_gather = GL_CheckExtension("GL_ARB_texture_gather", NULL, "-notexturegather", false); +#ifndef __APPLE__ + // LordHavoc: too many bugs on OSX! + vid.support.arb_texture_non_power_of_two = GL_CheckExtension("GL_ARB_texture_non_power_of_two", NULL, "-notexturenonpoweroftwo", false); +#endif + vid.support.arb_vertex_buffer_object = GL_CheckExtension("GL_ARB_vertex_buffer_object", vbofuncs, "-novbo", false); + vid.support.arb_uniform_buffer_object = GL_CheckExtension("GL_ARB_uniform_buffer_object", ubofuncs, "-noubo", false); + vid.support.ati_separate_stencil = GL_CheckExtension("separatestencil", gl2separatestencilfuncs, "-noseparatestencil", true) || GL_CheckExtension("GL_ATI_separate_stencil", atiseparatestencilfuncs, "-noseparatestencil", false); + vid.support.ext_blend_minmax = GL_CheckExtension("GL_EXT_blend_minmax", blendequationfuncs, "-noblendminmax", false); + vid.support.ext_blend_subtract = GL_CheckExtension("GL_EXT_blend_subtract", blendequationfuncs, "-noblendsubtract", false); + vid.support.ext_draw_range_elements = GL_CheckExtension("drawrangeelements", drawrangeelementsfuncs, "-nodrawrangeelements", true) || GL_CheckExtension("GL_EXT_draw_range_elements", drawrangeelementsextfuncs, "-nodrawrangeelements", false); + vid.support.arb_framebuffer_object = GL_CheckExtension("GL_ARB_framebuffer_object", arbfbofuncs, "-nofbo", false); + if (vid.support.arb_framebuffer_object) + vid.support.ext_framebuffer_object = true; + else + vid.support.ext_framebuffer_object = GL_CheckExtension("GL_EXT_framebuffer_object", extfbofuncs, "-nofbo", false); + + vid.support.ext_packed_depth_stencil = GL_CheckExtension("GL_EXT_packed_depth_stencil", NULL, "-nopackeddepthstencil", false); + vid.support.ext_stencil_two_side = GL_CheckExtension("GL_EXT_stencil_two_side", stenciltwosidefuncs, "-nostenciltwoside", false); + vid.support.ext_texture_3d = GL_CheckExtension("GL_EXT_texture3D", texture3dextfuncs, "-notexture3d", false); + vid.support.ext_texture_compression_s3tc = GL_CheckExtension("GL_EXT_texture_compression_s3tc", NULL, "-nos3tc", false); + vid.support.ext_texture_edge_clamp = GL_CheckExtension("GL_EXT_texture_edge_clamp", NULL, "-noedgeclamp", false) || GL_CheckExtension("GL_SGIS_texture_edge_clamp", NULL, "-noedgeclamp", false); + vid.support.ext_texture_filter_anisotropic = GL_CheckExtension("GL_EXT_texture_filter_anisotropic", NULL, "-noanisotropy", false); + vid.support.ext_texture_srgb = GL_CheckExtension("GL_EXT_texture_sRGB", NULL, "-nosrgb", false); + vid.support.arb_multisample = GL_CheckExtension("GL_ARB_multisample", multisamplefuncs, "-nomultisample", false); + vid.allowalphatocoverage = false; + +// COMMANDLINEOPTION: GL: -noshaders disables use of OpenGL 2.0 shaders (which allow pixel shader effects, can improve per pixel lighting performance and capabilities) +// COMMANDLINEOPTION: GL: -noanisotropy disables GL_EXT_texture_filter_anisotropic (allows higher quality texturing) +// COMMANDLINEOPTION: GL: -noblendminmax disables GL_EXT_blend_minmax +// COMMANDLINEOPTION: GL: -noblendsubtract disables GL_EXT_blend_subtract +// COMMANDLINEOPTION: GL: -nocombine disables GL_ARB_texture_env_combine or GL_EXT_texture_env_combine (required for bumpmapping and faster map rendering) +// COMMANDLINEOPTION: GL: -nocubemap disables GL_ARB_texture_cube_map (required for bumpmapping) +// COMMANDLINEOPTION: GL: -nodepthtexture disables use of GL_ARB_depth_texture (required for shadowmapping) +// COMMANDLINEOPTION: GL: -nodrawbuffers disables use of GL_ARB_draw_buffers (required for r_shadow_deferredprepass) +// COMMANDLINEOPTION: GL: -nodrawrangeelements disables GL_EXT_draw_range_elements (renders faster) +// COMMANDLINEOPTION: GL: -noedgeclamp disables GL_EXT_texture_edge_clamp or GL_SGIS_texture_edge_clamp (recommended, some cards do not support the other texture clamp method) +// COMMANDLINEOPTION: GL: -nofbo disables GL_EXT_framebuffer_object (which accelerates rendering), only used if GL_ARB_fragment_shader is also available +// COMMANDLINEOPTION: GL: -nomtex disables GL_ARB_multitexture (required for faster map rendering) +// COMMANDLINEOPTION: GL: -noocclusionquery disables GL_ARB_occlusion_query (which allows coronas to fade according to visibility, and potentially used for rendering optimizations) +// COMMANDLINEOPTION: GL: -nos3tc disables GL_EXT_texture_compression_s3tc (which allows use of .dds texture caching) +// COMMANDLINEOPTION: GL: -noseparatestencil disables use of OpenGL2.0 glStencilOpSeparate and GL_ATI_separate_stencil extensions (which accelerate shadow rendering) +// COMMANDLINEOPTION: GL: -noshadow disables use of GL_ARB_shadow (required for hardware shadowmap filtering) +// COMMANDLINEOPTION: GL: -nostenciltwoside disables GL_EXT_stencil_two_side (which accelerate shadow rendering) +// COMMANDLINEOPTION: GL: -notexture3d disables GL_EXT_texture3D (required for spherical lights, otherwise they render as a column) +// COMMANDLINEOPTION: GL: -notexture4 disables GL_AMD_texture_texture4 (which provides fetch4 sampling) +// COMMANDLINEOPTION: GL: -notexturecompression disables GL_ARB_texture_compression (which saves video memory if it is supported, but can also degrade image quality, see gl_texturecompression cvar documentation for more information) +// COMMANDLINEOPTION: GL: -notexturegather disables GL_ARB_texture_gather (which provides fetch4 sampling) +// COMMANDLINEOPTION: GL: -notexturenonpoweroftwo disables GL_ARB_texture_non_power_of_two (which saves video memory if it is supported, but crashes on some buggy drivers) +// COMMANDLINEOPTION: GL: -novbo disables GL_ARB_vertex_buffer_object (which accelerates rendering) +// COMMANDLINEOPTION: GL: -nosrgb disables GL_EXT_texture_sRGB (which is used for higher quality non-linear texture gamma) +// COMMANDLINEOPTION: GL: -nomultisample disables GL_ARB_multisample + + if (vid.support.arb_draw_buffers) + qglGetIntegerv(GL_MAX_DRAW_BUFFERS_ARB, (GLint*)&vid.maxdrawbuffers); + + // disable non-power-of-two textures on Radeon X1600 and other cards that do not accelerate it with some filtering modes / repeat modes that we use + // we detect these cards by checking if the hardware supports vertex texture fetch (Geforce6 does, Radeon X1600 does not, all GL3-class hardware does) + if(vid.support.arb_texture_non_power_of_two && vid.support.gl20shaders) + { + int val = 0; + qglGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &val);CHECKGLERROR + if (val < 1) + vid.support.arb_texture_non_power_of_two = false; + } + + // we don't care if it's an extension or not, they are identical functions, so keep it simple in the rendering code + if (qglDrawRangeElements == NULL) + qglDrawRangeElements = qglDrawRangeElementsEXT; + + qglGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_2d); + if (vid.support.ext_texture_filter_anisotropic) + qglGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint*)&vid.max_anisotropy); + if (vid.support.arb_texture_cube_map) + qglGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_cubemap); + if (vid.support.ext_texture_3d) + qglGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_3d); + + // verify that 3d textures are really supported + if (vid.support.ext_texture_3d && vid.maxtexturesize_3d < 32) + { + vid.support.ext_texture_3d = false; + Con_Printf("GL_EXT_texture3D reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n"); + } + + vid.texunits = vid.teximageunits = vid.texarrayunits = 1; + if (vid.support.arb_multitexture) + qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits); + if (vid_gl20.integer && vid.support.gl20shaders) + { + qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits); + qglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, (int *)&vid.teximageunits);CHECKGLERROR + qglGetIntegerv(GL_MAX_TEXTURE_COORDS, (int *)&vid.texarrayunits);CHECKGLERROR + vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS); + vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS); + Con_DPrintf("Using GL2.0 rendering path - %i texture matrix, %i texture images, %i texcoords%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.support.ext_framebuffer_object ? ", shadowmapping supported" : ""); + vid.renderpath = RENDERPATH_GL20; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = true; + vid.useinterleavedarrays = false; + Con_Printf("vid.support.arb_multisample %i\n", vid.support.arb_multisample); + Con_Printf("vid.support.gl20shaders %i\n", vid.support.gl20shaders); + vid.allowalphatocoverage = true; // but see below, it may get turned to false again if GL_SAMPLES_ARB is <= 1 + } + else if (vid.support.arb_texture_env_combine && vid.texunits >= 2 && vid_gl13.integer) + { + qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits); + vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = vid.texunits; + vid.texarrayunits = vid.texunits; + Con_DPrintf("Using GL1.3 rendering path - %i texture units, single pass rendering\n", vid.texunits); + vid.renderpath = RENDERPATH_GL13; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + vid.useinterleavedarrays = false; + } + else + { + vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = vid.texunits; + vid.texarrayunits = vid.texunits; + Con_DPrintf("Using GL1.1 rendering path - %i texture units, two pass rendering\n", vid.texunits); + vid.renderpath = RENDERPATH_GL11; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + vid.useinterleavedarrays = false; + } + + // enable multisample antialiasing if possible + if(vid.support.arb_multisample) + { + int samples = 0; + qglGetIntegerv(GL_SAMPLES_ARB, &samples); + vid.samples = samples; + if (samples > 1) + qglEnable(GL_MULTISAMPLE_ARB); + else + vid.allowalphatocoverage = false; + } + else + { + vid.allowalphatocoverage = false; + vid.samples = 1; + } + + // VorteX: set other info (maybe place them in VID_InitMode?) + Cvar_SetQuick(&gl_info_vendor, gl_vendor); + Cvar_SetQuick(&gl_info_renderer, gl_renderer); + Cvar_SetQuick(&gl_info_version, gl_version); + Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); + Cvar_SetQuick(&gl_info_driver, gl_driver); +} +#endif + +float VID_JoyState_GetAxis(const vid_joystate_t *joystate, int axis, float sensitivity, float deadzone) +{ + float value; + value = (axis >= 0 && axis < MAXJOYAXIS) ? joystate->axis[axis] : 0.0f; + value = value > deadzone ? (value - deadzone) : (value < -deadzone ? (value + deadzone) : 0.0f); + value *= deadzone > 0 ? (1.0f / (1.0f - deadzone)) : 1.0f; + value = bound(-1, value, 1); + return value * sensitivity; +} + +qboolean VID_JoyBlockEmulatedKeys(int keycode) +{ + int j; + vid_joystate_t joystate; + + if (!joy_axiskeyevents.integer) + return false; + if (vid_joystate.is360) + return false; + if (keycode != K_UPARROW && keycode != K_DOWNARROW && keycode != K_RIGHTARROW && keycode != K_LEFTARROW) + return false; + + // block system-generated key events for arrow keys if we're emulating the arrow keys ourselves + VID_BuildJoyState(&joystate); + for (j = 32;j < 36;j++) + if (vid_joystate.button[j] || joystate.button[j]) + return true; + + return false; +} + +void VID_Shared_BuildJoyState_Begin(vid_joystate_t *joystate) +{ +#ifdef WIN32 + xinput_state_t xinputstate; +#endif + memset(joystate, 0, sizeof(*joystate)); +#ifdef WIN32 + if (vid_xinputindex >= 0 && qXInputGetState && qXInputGetState(vid_xinputindex, &xinputstate) == S_OK) + { + joystate->is360 = true; + joystate->button[ 0] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0; + joystate->button[ 1] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0; + joystate->button[ 2] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0; + joystate->button[ 3] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0; + joystate->button[ 4] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_START) != 0; + joystate->button[ 5] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) != 0; + joystate->button[ 6] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0; + joystate->button[ 7] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0; + joystate->button[ 8] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0; + joystate->button[ 9] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0; + joystate->button[10] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0; + joystate->button[11] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_B) != 0; + joystate->button[12] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_X) != 0; + joystate->button[13] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y) != 0; + joystate->button[14] = xinputstate.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; + joystate->button[15] = xinputstate.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; + joystate->button[16] = xinputstate.Gamepad.sThumbLY < -16384; + joystate->button[17] = xinputstate.Gamepad.sThumbLY > 16384; + joystate->button[18] = xinputstate.Gamepad.sThumbLX < -16384; + joystate->button[19] = xinputstate.Gamepad.sThumbLX > 16384; + joystate->button[20] = xinputstate.Gamepad.sThumbRY < -16384; + joystate->button[21] = xinputstate.Gamepad.sThumbRY > 16384; + joystate->button[22] = xinputstate.Gamepad.sThumbRX < -16384; + joystate->button[23] = xinputstate.Gamepad.sThumbRX > 16384; + joystate->axis[ 4] = xinputstate.Gamepad.bLeftTrigger * (1.0f / 255.0f); + joystate->axis[ 5] = xinputstate.Gamepad.bRightTrigger * (1.0f / 255.0f); + joystate->axis[ 0] = xinputstate.Gamepad.sThumbLX * (1.0f / 32767.0f); + joystate->axis[ 1] = xinputstate.Gamepad.sThumbLY * (1.0f / 32767.0f); + joystate->axis[ 2] = xinputstate.Gamepad.sThumbRX * (1.0f / 32767.0f); + joystate->axis[ 3] = xinputstate.Gamepad.sThumbRY * (1.0f / 32767.0f); + } +#endif +} + +void VID_Shared_BuildJoyState_Finish(vid_joystate_t *joystate) +{ + float f, r; + if (joystate->is360) + return; + // emulate key events for thumbstick + f = VID_JoyState_GetAxis(joystate, joy_axisforward.integer, 1, joy_axiskeyevents_deadzone.value) * joy_sensitivityforward.value; + r = VID_JoyState_GetAxis(joystate, joy_axisside.integer , 1, joy_axiskeyevents_deadzone.value) * joy_sensitivityside.value; +#if MAXJOYBUTTON != 36 +#error this code must be updated if MAXJOYBUTTON changes! +#endif + joystate->button[32] = f > 0.0f; + joystate->button[33] = f < 0.0f; + joystate->button[34] = r > 0.0f; + joystate->button[35] = r < 0.0f; +} + +static void VID_KeyEventForButton(qboolean oldbutton, qboolean newbutton, int key, double *timer) +{ + if (oldbutton) + { + if (newbutton) + { + if (realtime >= *timer) + { + Key_Event(key, 0, true); + *timer = realtime + 0.1; + } + } + else + { + Key_Event(key, 0, false); + *timer = 0; + } + } + else + { + if (newbutton) + { + Key_Event(key, 0, true); + *timer = realtime + 0.5; + } + } +} + +#if MAXJOYBUTTON != 36 +#error this code must be updated if MAXJOYBUTTON changes! +#endif +static int joybuttonkey[MAXJOYBUTTON][2] = +{ + {K_JOY1, K_ENTER}, {K_JOY2, K_ESCAPE}, {K_JOY3, 0}, {K_JOY4, 0}, {K_JOY5, 0}, {K_JOY6, 0}, {K_JOY7, 0}, {K_JOY8, 0}, {K_JOY9, 0}, {K_JOY10, 0}, {K_JOY11, 0}, {K_JOY12, 0}, {K_JOY13, 0}, {K_JOY14, 0}, {K_JOY15, 0}, {K_JOY16, 0}, + {K_AUX1, 0}, {K_AUX2, 0}, {K_AUX3, 0}, {K_AUX4, 0}, {K_AUX5, 0}, {K_AUX6, 0}, {K_AUX7, 0}, {K_AUX8, 0}, {K_AUX9, 0}, {K_AUX10, 0}, {K_AUX11, 0}, {K_AUX12, 0}, {K_AUX13, 0}, {K_AUX14, 0}, {K_AUX15, 0}, {K_AUX16, 0}, + {K_JOY_UP, K_UPARROW}, {K_JOY_DOWN, K_DOWNARROW}, {K_JOY_RIGHT, K_RIGHTARROW}, {K_JOY_LEFT, K_LEFTARROW}, +}; + +static int joybuttonkey360[][2] = +{ + {K_X360_DPAD_UP, K_UPARROW}, + {K_X360_DPAD_DOWN, K_DOWNARROW}, + {K_X360_DPAD_LEFT, K_LEFTARROW}, + {K_X360_DPAD_RIGHT, K_RIGHTARROW}, + {K_X360_START, K_ESCAPE}, + {K_X360_BACK, K_ESCAPE}, + {K_X360_LEFT_THUMB, 0}, + {K_X360_RIGHT_THUMB, 0}, + {K_X360_LEFT_SHOULDER, 0}, + {K_X360_RIGHT_SHOULDER, 0}, + {K_X360_A, K_ENTER}, + {K_X360_B, K_ESCAPE}, + {K_X360_X, 0}, + {K_X360_Y, 0}, + {K_X360_LEFT_TRIGGER, 0}, + {K_X360_RIGHT_TRIGGER, 0}, + {K_X360_LEFT_THUMB_DOWN, K_DOWNARROW}, + {K_X360_LEFT_THUMB_UP, K_UPARROW}, + {K_X360_LEFT_THUMB_LEFT, K_LEFTARROW}, + {K_X360_LEFT_THUMB_RIGHT, K_RIGHTARROW}, + {K_X360_RIGHT_THUMB_DOWN, 0}, + {K_X360_RIGHT_THUMB_UP, 0}, + {K_X360_RIGHT_THUMB_LEFT, 0}, + {K_X360_RIGHT_THUMB_RIGHT, 0}, +}; + +double vid_joybuttontimer[MAXJOYBUTTON]; +void VID_ApplyJoyState(vid_joystate_t *joystate) +{ + int j; + int c = joy_axiskeyevents.integer != 0; + if (joystate->is360) + { +#if 0 + // keystrokes (chatpad) + // DOES NOT WORK - no driver support in xinput1_3.dll :( + xinput_keystroke_t keystroke; + while (qXInputGetKeystroke && qXInputGetKeystroke(XUSER_INDEX_ANY, 0, &keystroke) == S_OK) + Con_Printf("XInput KeyStroke: VirtualKey %i, Unicode %i, Flags %x, UserIndex %i, HidCode %i\n", keystroke.VirtualKey, keystroke.Unicode, keystroke.Flags, keystroke.UserIndex, keystroke.HidCode); +#endif + + // emit key events for buttons + for (j = 0;j < (int)(sizeof(joybuttonkey360)/sizeof(joybuttonkey360[0]));j++) + VID_KeyEventForButton(vid_joystate.button[j] != 0, joystate->button[j] != 0, joybuttonkey360[j][c], &vid_joybuttontimer[j]); + + // axes + cl.cmd.forwardmove += VID_JoyState_GetAxis(joystate, joy_x360_axisforward.integer, joy_x360_sensitivityforward.value, joy_x360_deadzoneforward.value) * cl_forwardspeed.value; + cl.cmd.sidemove += VID_JoyState_GetAxis(joystate, joy_x360_axisside.integer, joy_x360_sensitivityside.value, joy_x360_deadzoneside.value) * cl_sidespeed.value; + cl.cmd.upmove += VID_JoyState_GetAxis(joystate, joy_x360_axisup.integer, joy_x360_sensitivityup.value, joy_x360_deadzoneup.value) * cl_upspeed.value; + cl.viewangles[0] += VID_JoyState_GetAxis(joystate, joy_x360_axispitch.integer, joy_x360_sensitivitypitch.value, joy_x360_deadzonepitch.value) * cl.realframetime * cl_pitchspeed.value; + cl.viewangles[1] += VID_JoyState_GetAxis(joystate, joy_x360_axisyaw.integer, joy_x360_sensitivityyaw.value, joy_x360_deadzoneyaw.value) * cl.realframetime * cl_yawspeed.value; + //cl.viewangles[2] += VID_JoyState_GetAxis(joystate, joy_x360_axisroll.integer, joy_x360_sensitivityroll.value, joy_x360_deadzoneroll.value) * cl.realframetime * cl_rollspeed.value; + } + else + { + // emit key events for buttons + for (j = 0;j < MAXJOYBUTTON;j++) + VID_KeyEventForButton(vid_joystate.button[j] != 0, joystate->button[j] != 0, joybuttonkey[j][c], &vid_joybuttontimer[j]); + + // axes + cl.cmd.forwardmove += VID_JoyState_GetAxis(joystate, joy_axisforward.integer, joy_sensitivityforward.value, joy_deadzoneforward.value) * cl_forwardspeed.value; + cl.cmd.sidemove += VID_JoyState_GetAxis(joystate, joy_axisside.integer, joy_sensitivityside.value, joy_deadzoneside.value) * cl_sidespeed.value; + cl.cmd.upmove += VID_JoyState_GetAxis(joystate, joy_axisup.integer, joy_sensitivityup.value, joy_deadzoneup.value) * cl_upspeed.value; + cl.viewangles[0] += VID_JoyState_GetAxis(joystate, joy_axispitch.integer, joy_sensitivitypitch.value, joy_deadzonepitch.value) * cl.realframetime * cl_pitchspeed.value; + cl.viewangles[1] += VID_JoyState_GetAxis(joystate, joy_axisyaw.integer, joy_sensitivityyaw.value, joy_deadzoneyaw.value) * cl.realframetime * cl_yawspeed.value; + //cl.viewangles[2] += VID_JoyState_GetAxis(joystate, joy_axisroll.integer, joy_sensitivityroll.value, joy_deadzoneroll.value) * cl.realframetime * cl_rollspeed.value; + } + + vid_joystate = *joystate; +} + +int VID_Shared_SetJoystick(int index) +{ +#ifdef WIN32 + int i; + int xinputcount = 0; + int xinputindex = -1; + int xinputavailable = 0; + xinput_state_t state; + // detect available XInput controllers + for (i = 0;i < 4;i++) + { + if (qXInputGetState && qXInputGetState(i, &state) == S_OK) + { + xinputavailable |= 1<= 0) + Con_Printf("Joystick %i opened (XInput Device %i)\n", index, xinputindex); + } + return xinputcount; +#else + return 0; +#endif +} + + +static void Force_CenterView_f (void) +{ + cl.viewangles[PITCH] = 0; +} + +static int gamma_forcenextframe = false; +static float cachegamma, cachebrightness, cachecontrast, cacheblack[3], cachegrey[3], cachewhite[3], cachecontrastboost; +static int cachecolorenable, cachehwgamma; + +unsigned int vid_gammatables_serial = 0; // so other subsystems can poll if gamma parameters have changed +qboolean vid_gammatables_trivial = true; +void VID_BuildGammaTables(unsigned short *ramps, int rampsize) +{ + if (cachecolorenable) + { + BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[0]), cachewhite[0], cacheblack[0], cachecontrastboost, ramps, rampsize); + BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[1]), cachewhite[1], cacheblack[1], cachecontrastboost, ramps + rampsize, rampsize); + BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[2]), cachewhite[2], cacheblack[2], cachecontrastboost, ramps + rampsize*2, rampsize); + } + else + { + BuildGammaTable16(1.0f, cachegamma, cachecontrast, cachebrightness, cachecontrastboost, ramps, rampsize); + BuildGammaTable16(1.0f, cachegamma, cachecontrast, cachebrightness, cachecontrastboost, ramps + rampsize, rampsize); + BuildGammaTable16(1.0f, cachegamma, cachecontrast, cachebrightness, cachecontrastboost, ramps + rampsize*2, rampsize); + } + + if(vid.sRGB2D || vid.sRGB3D) + { + int i; + for(i = 0; i < 3*rampsize; ++i) + ramps[i] = (int)floor(bound(0.0f, Image_sRGBFloatFromLinearFloat(ramps[i] / 65535.0f), 1.0f) * 65535.0f + 0.5f); + } + + // LordHavoc: this code came from Ben Winslow and Zinx Verituse, I have + // immensely butchered it to work with variable framerates and fit in with + // the rest of darkplaces. + if (v_psycho.integer) + { + int x, y; + float t; + static float n[3], nd[3], nt[3]; + static int init = true; + unsigned short *ramp; + gamma_forcenextframe = true; + if (init) + { + init = false; + for (x = 0;x < 3;x++) + { + n[x] = lhrandom(0, 1); + nd[x] = (rand()&1)?-0.25:0.25; + nt[x] = lhrandom(1, 8.2); + } + } + + for (x = 0;x < 3;x++) + { + nt[x] -= cl.realframetime; + if (nt[x] < 0) + { + nd[x] = -nd[x]; + nt[x] += lhrandom(1, 8.2); + } + n[x] += nd[x] * cl.realframetime; + n[x] -= floor(n[x]); + } + + for (x = 0, ramp = ramps;x < 3;x++) + for (y = 0, t = n[x] - 0.75f;y < rampsize;y++, t += 0.75f * (2.0f / rampsize)) + *ramp++ = (unsigned short)(cos(t*(M_PI*2.0)) * 32767.0f + 32767.0f); + } +} + +void VID_UpdateGamma(qboolean force, int rampsize) +{ + cvar_t *c; + float f; + int wantgamma; + qboolean gamma_changed = false; + + // LordHavoc: don't mess with gamma tables if running dedicated + if (cls.state == ca_dedicated) + return; + + wantgamma = v_hwgamma.integer; + switch(vid.renderpath) + { + case RENDERPATH_GL20: + case RENDERPATH_D3D9: + case RENDERPATH_D3D10: + case RENDERPATH_D3D11: + case RENDERPATH_SOFT: + case RENDERPATH_GLES2: + if (v_glslgamma.integer) + wantgamma = 0; + break; + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GLES1: + break; + } + if(!vid_activewindow) + wantgamma = 0; +#define BOUNDCVAR(cvar, m1, m2) c = &(cvar);f = bound(m1, c->value, m2);if (c->value != f) Cvar_SetValueQuick(c, f); + BOUNDCVAR(v_gamma, 0.1, 5); + BOUNDCVAR(v_contrast, 0.2, 5); + BOUNDCVAR(v_brightness, -v_contrast.value * 0.8, 0.8); + //BOUNDCVAR(v_contrastboost, 0.0625, 16); + BOUNDCVAR(v_color_black_r, 0, 0.8); + BOUNDCVAR(v_color_black_g, 0, 0.8); + BOUNDCVAR(v_color_black_b, 0, 0.8); + BOUNDCVAR(v_color_grey_r, 0, 0.95); + BOUNDCVAR(v_color_grey_g, 0, 0.95); + BOUNDCVAR(v_color_grey_b, 0, 0.95); + BOUNDCVAR(v_color_white_r, 1, 5); + BOUNDCVAR(v_color_white_g, 1, 5); + BOUNDCVAR(v_color_white_b, 1, 5); +#undef BOUNDCVAR + + // set vid_gammatables_trivial to true if the current settings would generate the identity gamma table + vid_gammatables_trivial = false; + if(v_psycho.integer == 0) + if(v_contrastboost.value == 1) + if(!vid.sRGB2D) + if(!vid.sRGB3D) + { + if(v_color_enable.integer) + { + if(v_color_black_r.value == 0) + if(v_color_black_g.value == 0) + if(v_color_black_b.value == 0) + if(fabs(v_color_grey_r.value - 0.5) < 1e-6) + if(fabs(v_color_grey_g.value - 0.5) < 1e-6) + if(fabs(v_color_grey_b.value - 0.5) < 1e-6) + if(v_color_white_r.value == 1) + if(v_color_white_g.value == 1) + if(v_color_white_b.value == 1) + vid_gammatables_trivial = true; + } + else + { + if(v_gamma.value == 1) + if(v_contrast.value == 1) + if(v_brightness.value == 0) + vid_gammatables_trivial = true; + } + } + +#define GAMMACHECK(cache, value) if (cache != (value)) gamma_changed = true;cache = (value) + if(v_psycho.integer) + gamma_changed = true; + GAMMACHECK(cachegamma , v_gamma.value); + GAMMACHECK(cachecontrast , v_contrast.value); + GAMMACHECK(cachebrightness , v_brightness.value); + GAMMACHECK(cachecontrastboost, v_contrastboost.value); + GAMMACHECK(cachecolorenable, v_color_enable.integer); + GAMMACHECK(cacheblack[0] , v_color_black_r.value); + GAMMACHECK(cacheblack[1] , v_color_black_g.value); + GAMMACHECK(cacheblack[2] , v_color_black_b.value); + GAMMACHECK(cachegrey[0] , v_color_grey_r.value); + GAMMACHECK(cachegrey[1] , v_color_grey_g.value); + GAMMACHECK(cachegrey[2] , v_color_grey_b.value); + GAMMACHECK(cachewhite[0] , v_color_white_r.value); + GAMMACHECK(cachewhite[1] , v_color_white_g.value); + GAMMACHECK(cachewhite[2] , v_color_white_b.value); + + if(gamma_changed) + ++vid_gammatables_serial; + + GAMMACHECK(cachehwgamma , wantgamma); +#undef GAMMACHECK + + if (!force && !gamma_forcenextframe && !gamma_changed) + return; + + gamma_forcenextframe = false; + + if (cachehwgamma) + { + if (!vid_usinghwgamma) + { + vid_usinghwgamma = true; + if (vid_gammarampsize != rampsize || !vid_gammaramps) + { + vid_gammarampsize = rampsize; + if (vid_gammaramps) + Z_Free(vid_gammaramps); + vid_gammaramps = (unsigned short *)Z_Malloc(6 * vid_gammarampsize * sizeof(unsigned short)); + vid_systemgammaramps = vid_gammaramps + 3 * vid_gammarampsize; + } + VID_GetGamma(vid_systemgammaramps, vid_gammarampsize); + } + + VID_BuildGammaTables(vid_gammaramps, vid_gammarampsize); + + // set vid_hardwaregammasupported to true if VID_SetGamma succeeds, OR if vid_hwgamma is >= 2 (forced gamma - ignores driver return value) + Cvar_SetValueQuick(&vid_hardwaregammasupported, VID_SetGamma(vid_gammaramps, vid_gammarampsize) || cachehwgamma >= 2); + // if custom gamma ramps failed (Windows stupidity), restore to system gamma + if(!vid_hardwaregammasupported.integer) + { + if (vid_usinghwgamma) + { + vid_usinghwgamma = false; + VID_SetGamma(vid_systemgammaramps, vid_gammarampsize); + } + } + } + else + { + if (vid_usinghwgamma) + { + vid_usinghwgamma = false; + VID_SetGamma(vid_systemgammaramps, vid_gammarampsize); + } + } +} + +void VID_RestoreSystemGamma(void) +{ + if (vid_usinghwgamma) + { + vid_usinghwgamma = false; + Cvar_SetValueQuick(&vid_hardwaregammasupported, VID_SetGamma(vid_systemgammaramps, vid_gammarampsize)); + // force gamma situation to be reexamined next frame + gamma_forcenextframe = true; + } +} + +#ifdef WIN32 +static dllfunction_t xinputdllfuncs[] = +{ + {"XInputGetState", (void **) &qXInputGetState}, + {"XInputGetKeystroke", (void **) &qXInputGetKeystroke}, + {NULL, NULL} +}; +static const char* xinputdllnames [] = +{ + "xinput1_3.dll", + "xinput1_2.dll", + "xinput1_1.dll", + NULL +}; +static dllhandle_t xinputdll_dll = NULL; +#endif + +void VID_Shared_Init(void) +{ +#ifdef SSE_POSSIBLE + if (Sys_HaveSSE2()) + { + Con_Printf("DPSOFTRAST available (SSE2 instructions detected)\n"); + Cvar_RegisterVariable(&vid_soft); + Cvar_RegisterVariable(&vid_soft_threads); + Cvar_RegisterVariable(&vid_soft_interlace); + } + else + Con_Printf("DPSOFTRAST not available (SSE2 disabled or not detected)\n"); +#else + Con_Printf("DPSOFTRAST not available (SSE2 not compiled in)\n"); +#endif + + Cvar_RegisterVariable(&vid_hardwaregammasupported); + Cvar_RegisterVariable(&gl_info_vendor); + Cvar_RegisterVariable(&gl_info_renderer); + Cvar_RegisterVariable(&gl_info_version); + Cvar_RegisterVariable(&gl_info_extensions); + Cvar_RegisterVariable(&gl_info_platform); + Cvar_RegisterVariable(&gl_info_driver); + Cvar_RegisterVariable(&v_gamma); + Cvar_RegisterVariable(&v_brightness); + Cvar_RegisterVariable(&v_contrastboost); + Cvar_RegisterVariable(&v_contrast); + + Cvar_RegisterVariable(&v_color_enable); + Cvar_RegisterVariable(&v_color_black_r); + Cvar_RegisterVariable(&v_color_black_g); + Cvar_RegisterVariable(&v_color_black_b); + Cvar_RegisterVariable(&v_color_grey_r); + Cvar_RegisterVariable(&v_color_grey_g); + Cvar_RegisterVariable(&v_color_grey_b); + Cvar_RegisterVariable(&v_color_white_r); + Cvar_RegisterVariable(&v_color_white_g); + Cvar_RegisterVariable(&v_color_white_b); + + Cvar_RegisterVariable(&v_hwgamma); + Cvar_RegisterVariable(&v_glslgamma); + Cvar_RegisterVariable(&v_glslgamma_2d); + + Cvar_RegisterVariable(&v_psycho); + + Cvar_RegisterVariable(&vid_fullscreen); + Cvar_RegisterVariable(&vid_width); + Cvar_RegisterVariable(&vid_height); + Cvar_RegisterVariable(&vid_bitsperpixel); + Cvar_RegisterVariable(&vid_samples); + Cvar_RegisterVariable(&vid_refreshrate); + Cvar_RegisterVariable(&vid_userefreshrate); + Cvar_RegisterVariable(&vid_stereobuffer); + Cvar_RegisterVariable(&vid_vsync); + Cvar_RegisterVariable(&vid_mouse); + Cvar_RegisterVariable(&vid_grabkeyboard); + Cvar_RegisterVariable(&vid_touchscreen); + Cvar_RegisterVariable(&vid_stick_mouse); + Cvar_RegisterVariable(&vid_resizable); + Cvar_RegisterVariable(&vid_minwidth); + Cvar_RegisterVariable(&vid_minheight); + Cvar_RegisterVariable(&vid_gl13); + Cvar_RegisterVariable(&vid_gl20); + Cvar_RegisterVariable(&gl_finish); + Cvar_RegisterVariable(&vid_sRGB); + Cvar_RegisterVariable(&vid_sRGB_fallback); + + Cvar_RegisterVariable(&joy_active); +#ifdef WIN32 + Cvar_RegisterVariable(&joy_xinputavailable); +#endif + Cvar_RegisterVariable(&joy_detected); + Cvar_RegisterVariable(&joy_enable); + Cvar_RegisterVariable(&joy_index); + Cvar_RegisterVariable(&joy_axisforward); + Cvar_RegisterVariable(&joy_axisside); + Cvar_RegisterVariable(&joy_axisup); + Cvar_RegisterVariable(&joy_axispitch); + Cvar_RegisterVariable(&joy_axisyaw); + //Cvar_RegisterVariable(&joy_axisroll); + Cvar_RegisterVariable(&joy_deadzoneforward); + Cvar_RegisterVariable(&joy_deadzoneside); + Cvar_RegisterVariable(&joy_deadzoneup); + Cvar_RegisterVariable(&joy_deadzonepitch); + Cvar_RegisterVariable(&joy_deadzoneyaw); + //Cvar_RegisterVariable(&joy_deadzoneroll); + Cvar_RegisterVariable(&joy_sensitivityforward); + Cvar_RegisterVariable(&joy_sensitivityside); + Cvar_RegisterVariable(&joy_sensitivityup); + Cvar_RegisterVariable(&joy_sensitivitypitch); + Cvar_RegisterVariable(&joy_sensitivityyaw); + //Cvar_RegisterVariable(&joy_sensitivityroll); + Cvar_RegisterVariable(&joy_axiskeyevents); + Cvar_RegisterVariable(&joy_axiskeyevents_deadzone); + Cvar_RegisterVariable(&joy_x360_axisforward); + Cvar_RegisterVariable(&joy_x360_axisside); + Cvar_RegisterVariable(&joy_x360_axisup); + Cvar_RegisterVariable(&joy_x360_axispitch); + Cvar_RegisterVariable(&joy_x360_axisyaw); + //Cvar_RegisterVariable(&joy_x360_axisroll); + Cvar_RegisterVariable(&joy_x360_deadzoneforward); + Cvar_RegisterVariable(&joy_x360_deadzoneside); + Cvar_RegisterVariable(&joy_x360_deadzoneup); + Cvar_RegisterVariable(&joy_x360_deadzonepitch); + Cvar_RegisterVariable(&joy_x360_deadzoneyaw); + //Cvar_RegisterVariable(&joy_x360_deadzoneroll); + Cvar_RegisterVariable(&joy_x360_sensitivityforward); + Cvar_RegisterVariable(&joy_x360_sensitivityside); + Cvar_RegisterVariable(&joy_x360_sensitivityup); + Cvar_RegisterVariable(&joy_x360_sensitivitypitch); + Cvar_RegisterVariable(&joy_x360_sensitivityyaw); + //Cvar_RegisterVariable(&joy_x360_sensitivityroll); + +#ifdef WIN32 + Sys_LoadLibrary(xinputdllnames, &xinputdll_dll, xinputdllfuncs); +#endif + + Cmd_AddCommand("force_centerview", Force_CenterView_f, "recenters view (stops looking up/down)"); + Cmd_AddCommand("vid_restart", VID_Restart_f, "restarts video system (closes and reopens the window, restarts renderer)"); +} + +static int VID_Mode(int fullscreen, int width, int height, int bpp, float refreshrate, int stereobuffer, int samples) +{ + viddef_mode_t mode; + char vabuf[1024]; + + memset(&mode, 0, sizeof(mode)); + mode.fullscreen = fullscreen != 0; + mode.width = width; + mode.height = height; + mode.bitsperpixel = bpp; + mode.refreshrate = vid_userefreshrate.integer ? max(1, refreshrate) : 0; + mode.userefreshrate = vid_userefreshrate.integer != 0; + mode.stereobuffer = stereobuffer != 0; + mode.samples = samples; + cl_ignoremousemoves = 2; + VID_ClearExtensions(); + + vid.samples = vid.mode.samples; + if (VID_InitMode(&mode)) + { + // accept the (possibly modified) mode + vid.mode = mode; + vid.fullscreen = vid.mode.fullscreen; + vid.width = vid.mode.width; + vid.height = vid.mode.height; + vid.bitsperpixel = vid.mode.bitsperpixel; + vid.refreshrate = vid.mode.refreshrate; + vid.userefreshrate = vid.mode.userefreshrate; + vid.stereobuffer = vid.mode.stereobuffer; + vid.stencil = vid.mode.bitsperpixel >= 16; + vid.sRGB2D = vid_sRGB.integer >= 1 && vid.sRGBcapable2D; + vid.sRGB3D = vid_sRGB.integer >= 1 && vid.sRGBcapable3D; + + switch(vid.renderpath) + { + case RENDERPATH_GL11: + case RENDERPATH_GL13: + case RENDERPATH_GL20: + { + GLboolean stereo; + qglGetBooleanv(GL_STEREO, &stereo); + vid.stereobuffer = stereo != 0; + } + break; + default: + vid.stereobuffer = false; + break; + } + + if( + (vid_sRGB_fallback.integer >= 3) // force fallback + || + (vid_sRGB_fallback.integer >= 2 && // fallback if framebuffer is 8bit + !(r_viewfbo.integer >= 2 && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2)) + ) + vid.sRGB2D = vid.sRGB3D = false; + + if(vid.samples != vid.mode.samples) + Con_Printf("NOTE: requested %dx AA, got %dx AA\n", vid.mode.samples, vid.samples); + + Con_Printf("Video Mode: %s %dx%dx%dx%.2fhz%s%s\n", mode.fullscreen ? "fullscreen" : "window", mode.width, mode.height, mode.bitsperpixel, mode.refreshrate, mode.stereobuffer ? " stereo" : "", mode.samples > 1 ? va(vabuf, sizeof(vabuf), " (%ix AA)", mode.samples) : ""); + + Cvar_SetValueQuick(&vid_fullscreen, vid.mode.fullscreen); + Cvar_SetValueQuick(&vid_width, vid.mode.width); + Cvar_SetValueQuick(&vid_height, vid.mode.height); + Cvar_SetValueQuick(&vid_bitsperpixel, vid.mode.bitsperpixel); + Cvar_SetValueQuick(&vid_samples, vid.mode.samples); + if(vid_userefreshrate.integer) + Cvar_SetValueQuick(&vid_refreshrate, vid.mode.refreshrate); + Cvar_SetValueQuick(&vid_stereobuffer, vid.stereobuffer ? 1 : 0); + + return true; + } + else + return false; +} + +static void VID_OpenSystems(void) +{ + R_Modules_Start(); + S_Startup(); +} + +static void VID_CloseSystems(void) +{ + S_Shutdown(); + R_Modules_Shutdown(); +} + +qboolean vid_commandlinecheck = true; +extern qboolean vid_opened; + +void VID_Restart_f(void) +{ + char vabuf[1024]; + char vabuf2[1024]; + // don't crash if video hasn't started yet + if (vid_commandlinecheck) + return; + + if (!vid_opened) + { + SCR_BeginLoadingPlaque(false); + return; + } + + Con_Printf("VID_Restart: changing from %s %dx%dx%dbpp%s%s, to %s %dx%dx%dbpp%s%s.\n", + vid.mode.fullscreen ? "fullscreen" : "window", vid.mode.width, vid.mode.height, vid.mode.bitsperpixel, vid.mode.fullscreen && vid.mode.userefreshrate ? va(vabuf, sizeof(vabuf), "x%.2fhz", vid.mode.refreshrate) : "", vid.mode.samples > 1 ? va(vabuf2, sizeof(vabuf2), " (%ix AA)", vid.mode.samples) : "", + vid_fullscreen.integer ? "fullscreen" : "window", vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_fullscreen.integer && vid_userefreshrate.integer ? va(vabuf, sizeof(vabuf), "x%.2fhz", vid_refreshrate.value) : "", vid_samples.integer > 1 ? va(vabuf2, sizeof(vabuf2), " (%ix AA)", vid_samples.integer) : ""); + VID_CloseSystems(); + VID_Shutdown(); + if (!VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer)) + { + Con_Print("Video mode change failed\n"); + if (!VID_Mode(vid.mode.fullscreen, vid.mode.width, vid.mode.height, vid.mode.bitsperpixel, vid.mode.refreshrate, vid.mode.stereobuffer, vid.mode.samples)) + Sys_Error("Unable to restore to last working video mode"); + } + VID_OpenSystems(); +} + +const char *vidfallbacks[][2] = +{ + {"vid_stereobuffer", "0"}, + {"vid_samples", "1"}, + {"vid_userefreshrate", "0"}, + {"vid_width", "640"}, + {"vid_height", "480"}, + {"vid_bitsperpixel", "16"}, + {NULL, NULL} +}; + +// this is only called once by Host_StartVideo and again on each FS_GameDir_f +void VID_Start(void) +{ + int i, width, height, success; + if (vid_commandlinecheck) + { + // interpret command-line parameters + vid_commandlinecheck = false; +// COMMANDLINEOPTION: Video: -window performs +vid_fullscreen 0 + if (COM_CheckParm("-window") || COM_CheckParm("-safe")) + Cvar_SetValueQuick(&vid_fullscreen, false); +// COMMANDLINEOPTION: Video: -fullscreen performs +vid_fullscreen 1 + if (COM_CheckParm("-fullscreen")) + Cvar_SetValueQuick(&vid_fullscreen, true); + width = 0; + height = 0; +// COMMANDLINEOPTION: Video: -width performs +vid_width and also +vid_height if only -width is specified (example: -width 1024 sets 1024x768 mode) + if ((i = COM_CheckParm("-width")) != 0) + width = atoi(com_argv[i+1]); +// COMMANDLINEOPTION: Video: -height performs +vid_height and also +vid_width if only -height is specified (example: -height 768 sets 1024x768 mode) + if ((i = COM_CheckParm("-height")) != 0) + height = atoi(com_argv[i+1]); + if (width == 0) + width = height * 4 / 3; + if (height == 0) + height = width * 3 / 4; + if (width) + Cvar_SetValueQuick(&vid_width, width); + if (height) + Cvar_SetValueQuick(&vid_height, height); +// COMMANDLINEOPTION: Video: -bpp performs +vid_bitsperpixel (example -bpp 32 or -bpp 16) + if ((i = COM_CheckParm("-bpp")) != 0) + Cvar_SetQuick(&vid_bitsperpixel, com_argv[i+1]); + } + + success = VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer); + if (!success) + { + Con_Print("Desired video mode fail, trying fallbacks...\n"); + for (i = 0;!success && vidfallbacks[i][0] != NULL;i++) + { + Cvar_Set(vidfallbacks[i][0], vidfallbacks[i][1]); + success = VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer); + } + if (!success) + Sys_Error("Video modes failed"); + } + VID_OpenSystems(); +} + +void VID_Stop(void) +{ + VID_CloseSystems(); + VID_Shutdown(); +} + +static int VID_SortModes_Compare(const void *a_, const void *b_) +{ + vid_mode_t *a = (vid_mode_t *) a_; + vid_mode_t *b = (vid_mode_t *) b_; + if(a->width > b->width) + return +1; + if(a->width < b->width) + return -1; + if(a->height > b->height) + return +1; + if(a->height < b->height) + return -1; + if(a->refreshrate > b->refreshrate) + return +1; + if(a->refreshrate < b->refreshrate) + return -1; + if(a->bpp > b->bpp) + return +1; + if(a->bpp < b->bpp) + return -1; + if(a->pixelheight_num * b->pixelheight_denom > a->pixelheight_denom * b->pixelheight_num) + return +1; + if(a->pixelheight_num * b->pixelheight_denom < a->pixelheight_denom * b->pixelheight_num) + return -1; + return 0; +} +size_t VID_SortModes(vid_mode_t *modes, size_t count, qboolean usebpp, qboolean userefreshrate, qboolean useaspect) +{ + size_t i; + if(count == 0) + return 0; + // 1. sort them + qsort(modes, count, sizeof(*modes), VID_SortModes_Compare); + // 2. remove duplicates + for(i = 0; i < count; ++i) + { + if(modes[i].width && modes[i].height) + { + if(i == 0) + continue; + if(modes[i].width != modes[i-1].width) + continue; + if(modes[i].height != modes[i-1].height) + continue; + if(userefreshrate) + if(modes[i].refreshrate != modes[i-1].refreshrate) + continue; + if(usebpp) + if(modes[i].bpp != modes[i-1].bpp) + continue; + if(useaspect) + if(modes[i].pixelheight_num * modes[i-1].pixelheight_denom != modes[i].pixelheight_denom * modes[i-1].pixelheight_num) + continue; + } + // a dupe, or a bogus mode! + if(i < count-1) + memmove(&modes[i], &modes[i+1], sizeof(*modes) * (count-1 - i)); + --i; // check this index again, as mode i+1 is now here + --count; + } + return count; +} + +void VID_Soft_SharedSetup(void) +{ + gl_platform = "DPSOFTRAST"; + gl_platformextensions = ""; + + gl_renderer = "DarkPlaces-Soft"; + gl_vendor = "Forest Hale"; + gl_version = "0.0"; + gl_extensions = ""; + + // clear the extension flags + memset(&vid.support, 0, sizeof(vid.support)); + Cvar_SetQuick(&gl_info_extensions, ""); + + // DPSOFTRAST requires BGRA + vid.forcetextype = TEXTYPE_BGRA; + + vid.forcevbo = false; + vid.support.arb_depth_texture = true; + vid.support.arb_draw_buffers = true; + vid.support.arb_occlusion_query = true; + vid.support.arb_shadow = true; + //vid.support.arb_texture_compression = true; + vid.support.arb_texture_cube_map = true; + vid.support.arb_texture_non_power_of_two = false; + vid.support.arb_vertex_buffer_object = true; + vid.support.ext_blend_subtract = true; + vid.support.ext_draw_range_elements = true; + vid.support.ext_framebuffer_object = true; + + vid.support.ext_texture_3d = true; + //vid.support.ext_texture_compression_s3tc = true; + vid.support.ext_texture_filter_anisotropic = true; + vid.support.ati_separate_stencil = true; + vid.support.ext_texture_srgb = false; + + vid.maxtexturesize_2d = 16384; + vid.maxtexturesize_3d = 512; + vid.maxtexturesize_cubemap = 16384; + vid.texunits = 4; + vid.teximageunits = 32; + vid.texarrayunits = 8; + vid.max_anisotropy = 1; + vid.maxdrawbuffers = 4; + + vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS); + vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS); + vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS); + Con_DPrintf("Using DarkPlaces Software Rasterizer rendering path\n"); + vid.renderpath = RENDERPATH_SOFT; + vid.sRGBcapable2D = false; + vid.sRGBcapable3D = false; + vid.useinterleavedarrays = false; + + Cvar_SetQuick(&gl_info_vendor, gl_vendor); + Cvar_SetQuick(&gl_info_renderer, gl_renderer); + Cvar_SetQuick(&gl_info_version, gl_version); + Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); + Cvar_SetQuick(&gl_info_driver, gl_driver); + + // LordHavoc: report supported extensions + Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); + + // clear to black (loading plaque will be seen over this) + GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128); +} diff --git a/app/jni/view.c b/app/jni/view.c new file mode 100644 index 0000000..17ced7d --- /dev/null +++ b/app/jni/view.c @@ -0,0 +1,1148 @@ +/* +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. + +*/ +// view.c -- player eye positioning + +#include "quakedef.h" +#include "cl_collision.h" +#include "image.h" + +/* + +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. + +*/ + +cvar_t cl_autocentreoffset = {CVAR_SAVE, "cl_autocentreoffset", "0", "Additional lens offset (for difficult headets) from the centre to get images converging ok"}; +cvar_t v_eyebufferresolution = {CVAR_SAVE, "v_eyebufferresolution", "0", "Eye Buffer Resolution"}; + +cvar_t cl_rollspeed = {0, "cl_rollspeed", "200", "how much strafing is necessary to tilt the view"}; +cvar_t cl_rollangle = {0, "cl_rollangle", "0.0", "how much to tilt the view when strafing"}; + +cvar_t cl_bob = {CVAR_SAVE, "cl_bob","0.0", "view bobbing amount"}; +cvar_t cl_bobcycle = {CVAR_SAVE, "cl_bobcycle","0.6", "view bobbing speed"}; +cvar_t cl_bobup = {CVAR_SAVE, "cl_bobup","0.5", "view bobbing adjustment that makes the up or down swing of the bob last longer"}; +cvar_t cl_bob2 = {CVAR_SAVE, "cl_bob2","0", "sideways view bobbing amount"}; +cvar_t cl_bob2cycle = {CVAR_SAVE, "cl_bob2cycle","0.6", "sideways view bobbing speed"}; +cvar_t cl_bob2smooth = {CVAR_SAVE, "cl_bob2smooth","0.05", "how fast the view goes back when you stop touching the ground"}; +cvar_t cl_bobfall = {CVAR_SAVE, "cl_bobfall","0", "how much the view swings down when falling (influenced by the speed you hit the ground with)"}; +cvar_t cl_bobfallcycle = {CVAR_SAVE, "cl_bobfallcycle","3", "speed of the bobfall swing"}; +cvar_t cl_bobfallminspeed = {CVAR_SAVE, "cl_bobfallminspeed","200", "necessary amount of speed for bob-falling to occur"}; +cvar_t cl_bobmodel = {CVAR_SAVE, "cl_bobmodel", "1", "enables gun bobbing"}; +cvar_t cl_bobmodel_side = {CVAR_SAVE, "cl_bobmodel_side", "0.15", "gun bobbing sideways sway amount"}; +cvar_t cl_bobmodel_up = {CVAR_SAVE, "cl_bobmodel_up", "0.06", "gun bobbing upward movement amount"}; +cvar_t cl_bobmodel_speed = {CVAR_SAVE, "cl_bobmodel_speed", "7", "gun bobbing speed"}; + +cvar_t cl_leanmodel = {CVAR_SAVE, "cl_leanmodel", "0", "enables gun leaning"}; +cvar_t cl_leanmodel_side_speed = {CVAR_SAVE, "cl_leanmodel_side_speed", "0.7", "gun leaning sideways speed"}; +cvar_t cl_leanmodel_side_limit = {CVAR_SAVE, "cl_leanmodel_side_limit", "35", "gun leaning sideways limit"}; +cvar_t cl_leanmodel_side_highpass1 = {CVAR_SAVE, "cl_leanmodel_side_highpass1", "30", "gun leaning sideways pre-highpass in 1/s"}; +cvar_t cl_leanmodel_side_highpass = {CVAR_SAVE, "cl_leanmodel_side_highpass", "3", "gun leaning sideways highpass in 1/s"}; +cvar_t cl_leanmodel_side_lowpass = {CVAR_SAVE, "cl_leanmodel_side_lowpass", "20", "gun leaning sideways lowpass in 1/s"}; +cvar_t cl_leanmodel_up_speed = {CVAR_SAVE, "cl_leanmodel_up_speed", "0.65", "gun leaning upward speed"}; +cvar_t cl_leanmodel_up_limit = {CVAR_SAVE, "cl_leanmodel_up_limit", "50", "gun leaning upward limit"}; +cvar_t cl_leanmodel_up_highpass1 = {CVAR_SAVE, "cl_leanmodel_up_highpass1", "5", "gun leaning upward pre-highpass in 1/s"}; +cvar_t cl_leanmodel_up_highpass = {CVAR_SAVE, "cl_leanmodel_up_highpass", "15", "gun leaning upward highpass in 1/s"}; +cvar_t cl_leanmodel_up_lowpass = {CVAR_SAVE, "cl_leanmodel_up_lowpass", "20", "gun leaning upward lowpass in 1/s"}; + +cvar_t cl_followmodel = {CVAR_SAVE, "cl_followmodel", "0", "enables gun following"}; +cvar_t cl_followmodel_side_speed = {CVAR_SAVE, "cl_followmodel_side_speed", "0.25", "gun following sideways speed"}; +cvar_t cl_followmodel_side_limit = {CVAR_SAVE, "cl_followmodel_side_limit", "6", "gun following sideways limit"}; +cvar_t cl_followmodel_side_highpass1 = {CVAR_SAVE, "cl_followmodel_side_highpass1", "30", "gun following sideways pre-highpass in 1/s"}; +cvar_t cl_followmodel_side_highpass = {CVAR_SAVE, "cl_followmodel_side_highpass", "5", "gun following sideways highpass in 1/s"}; +cvar_t cl_followmodel_side_lowpass = {CVAR_SAVE, "cl_followmodel_side_lowpass", "10", "gun following sideways lowpass in 1/s"}; +cvar_t cl_followmodel_up_speed = {CVAR_SAVE, "cl_followmodel_up_speed", "0.5", "gun following upward speed"}; +cvar_t cl_followmodel_up_limit = {CVAR_SAVE, "cl_followmodel_up_limit", "5", "gun following upward limit"}; +cvar_t cl_followmodel_up_highpass1 = {CVAR_SAVE, "cl_followmodel_up_highpass1", "60", "gun following upward pre-highpass in 1/s"}; +cvar_t cl_followmodel_up_highpass = {CVAR_SAVE, "cl_followmodel_up_highpass", "2", "gun following upward highpass in 1/s"}; +cvar_t cl_followmodel_up_lowpass = {CVAR_SAVE, "cl_followmodel_up_lowpass", "10", "gun following upward lowpass in 1/s"}; + +cvar_t cl_viewmodel_scale = {0, "cl_viewmodel_scale", "1", "changes size of gun model, lower values prevent poking into walls but cause strange artifacts on lighting and especially r_stereo/vid_stereobuffer options where the size of the gun becomes visible"}; + +cvar_t v_kicktime = {0, "v_kicktime", "0.5", "how long a view kick from damage lasts"}; +cvar_t v_kickroll = {0, "v_kickroll", "0.6", "how much a view kick from damage rolls your view"}; +cvar_t v_kickpitch = {0, "v_kickpitch", "0.6", "how much a view kick from damage pitches your view"}; + +cvar_t v_iyaw_cycle = {0, "v_iyaw_cycle", "2", "v_idlescale yaw speed"}; +cvar_t v_iroll_cycle = {0, "v_iroll_cycle", "0.5", "v_idlescale roll speed"}; +cvar_t v_ipitch_cycle = {0, "v_ipitch_cycle", "1", "v_idlescale pitch speed"}; +cvar_t v_iyaw_level = {0, "v_iyaw_level", "0.3", "v_idlescale yaw amount"}; +cvar_t v_iroll_level = {0, "v_iroll_level", "0.1", "v_idlescale roll amount"}; +cvar_t v_ipitch_level = {0, "v_ipitch_level", "0.3", "v_idlescale pitch amount"}; + +cvar_t v_idlescale = {0, "v_idlescale", "0", "how much of the quake 'drunken view' effect to use"}; + +cvar_t crosshair = {CVAR_SAVE, "crosshair", "0", "selects crosshair to use (0 is none)"}; + +cvar_t v_centermove = {0, "v_centermove", "0.15", "how long before the view begins to center itself (if freelook/+mlook/+jlook/+klook are off)"}; +cvar_t v_centerspeed = {0, "v_centerspeed","500", "how fast the view centers itself"}; + +cvar_t cl_stairsmoothspeed = {CVAR_SAVE, "cl_stairsmoothspeed", "160", "how fast your view moves upward/downward when running up/down stairs"}; + +cvar_t cl_smoothviewheight = {CVAR_SAVE, "cl_smoothviewheight", "0", "time of the averaging to the viewheight value so that it creates a smooth transition. higher values = longer transition, 0 for instant transition."}; + +cvar_t chase_back = {CVAR_SAVE, "chase_back", "180", "chase cam distance from the player"}; +cvar_t chase_up = {CVAR_SAVE, "chase_up", "20", "chase cam distance from the player"}; +cvar_t chase_active = {CVAR_SAVE, "chase_active", "0", "enables chase cam"}; +cvar_t chase_overhead = {CVAR_SAVE, "chase_overhead", "0", "chase cam looks straight down if this is not zero"}; +// GAME_GOODVSBAD2 +cvar_t chase_stevie = {0, "chase_stevie", "0", "(GOODVSBAD2 only) chase cam view from above"}; + +cvar_t v_deathtilt = {0, "v_deathtilt", "1", "whether to use sideways view when dead"}; +cvar_t v_deathtiltangle = {0, "v_deathtiltangle", "0", "what roll angle to use when tilting the view while dead"}; + +// Prophecy camera pitchangle by Alexander "motorsep" Zubov +cvar_t chase_pitchangle = {CVAR_SAVE, "chase_pitchangle", "55", "chase cam pitch angle"}; + +float v_dmg_time, v_dmg_roll, v_dmg_pitch; + + +/* +=============== +V_CalcRoll + +Used by view and sv_user +=============== +*/ +float V_CalcRoll (const vec3_t angles, const vec3_t velocity) +{ + vec3_t right; + float sign; + float side; + float value; + + AngleVectors (angles, NULL, right, NULL); + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = cl_rollangle.value; + + if (side < cl_rollspeed.value) + side = side * value / cl_rollspeed.value; + else + side = value; + + return side*sign; + +} + +void V_StartPitchDrift (void) +{ + if (cl.laststop == cl.time) + return; // something else is keeping it from drifting + + if (cl.nodrift || !cl.pitchvel) + { + cl.pitchvel = v_centerspeed.value; + cl.nodrift = false; + cl.driftmove = 0; + } +} + +void V_StopPitchDrift (void) +{ + cl.laststop = cl.time; + cl.nodrift = true; + cl.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards cl.idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. + +Drifting is enabled when the center view key is hit, mlook is released and +lookspring is non 0, or when +=============== +*/ +void V_DriftPitch (void) +{ + float delta, move; + + if (noclip_anglehack || !cl.onground || cls.demoplayback ) + { + cl.driftmove = 0; + cl.pitchvel = 0; + return; + } + +// don't count small mouse motion + if (cl.nodrift) + { + if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value) + cl.driftmove = 0; + else + cl.driftmove += cl.realframetime; + + if ( cl.driftmove > v_centermove.value) + { + V_StartPitchDrift (); + } + return; + } + + delta = cl.idealpitch - cl.viewangles[PITCH]; + + if (!delta) + { + cl.pitchvel = 0; + return; + } + + move = cl.realframetime * cl.pitchvel; + cl.pitchvel += cl.realframetime * v_centerspeed.value; + + if (delta > 0) + { + if (move > delta) + { + cl.pitchvel = 0; + move = delta; + } + cl.viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + cl.pitchvel = 0; + move = -delta; + } + cl.viewangles[PITCH] -= move; + } +} + + +/* +============================================================================== + + SCREEN FLASHES + +============================================================================== +*/ + + +/* +=============== +V_ParseDamage +=============== +*/ +void V_ParseDamage (void) +{ + int armor, blood; + vec3_t from; + //vec3_t forward, right; + vec3_t localfrom; + entity_t *ent; + //float side; + float count; + + armor = MSG_ReadByte(&cl_message); + blood = MSG_ReadByte(&cl_message); + MSG_ReadVector(&cl_message, from, cls.protocol); + + // Send the Dmg Globals to CSQC + CL_VM_UpdateDmgGlobals(blood, armor, from); + + count = blood*0.5 + armor*0.5; + if (count < 10) + count = 10; + + cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame + + cl.cshifts[CSHIFT_DAMAGE].percent += 3*count; + cl.cshifts[CSHIFT_DAMAGE].alphafade = 150; + if (cl.cshifts[CSHIFT_DAMAGE].percent < 0) + cl.cshifts[CSHIFT_DAMAGE].percent = 0; + if (cl.cshifts[CSHIFT_DAMAGE].percent > 150) + cl.cshifts[CSHIFT_DAMAGE].percent = 150; + + if (armor > blood) + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100; + } + else if (armor) + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50; + } + else + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0; + } + + // calculate view angle kicks + if (cl.entities[cl.viewentity].state_current.active) + { + ent = &cl.entities[cl.viewentity]; + Matrix4x4_Transform(&ent->render.inversematrix, from, localfrom); + VectorNormalize(localfrom); + v_dmg_pitch = count * localfrom[0] * v_kickpitch.value; + v_dmg_roll = count * localfrom[1] * v_kickroll.value; + v_dmg_time = v_kicktime.value; + } +} + +static cshift_t v_cshift; + +/* +================== +V_cshift_f +================== +*/ +static void V_cshift_f (void) +{ + v_cshift.destcolor[0] = atof(Cmd_Argv(1)); + v_cshift.destcolor[1] = atof(Cmd_Argv(2)); + v_cshift.destcolor[2] = atof(Cmd_Argv(3)); + v_cshift.percent = atof(Cmd_Argv(4)); +} + + +/* +================== +V_BonusFlash_f + +When you run over an item, the server sends this command +================== +*/ +static void V_BonusFlash_f (void) +{ + if(Cmd_Argc() == 1) + { + cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215; + cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186; + cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69; + cl.cshifts[CSHIFT_BONUS].percent = 50; + cl.cshifts[CSHIFT_BONUS].alphafade = 100; + } + else if(Cmd_Argc() >= 4 && Cmd_Argc() <= 6) + { + cl.cshifts[CSHIFT_BONUS].destcolor[0] = atof(Cmd_Argv(1)) * 255; + cl.cshifts[CSHIFT_BONUS].destcolor[1] = atof(Cmd_Argv(2)) * 255; + cl.cshifts[CSHIFT_BONUS].destcolor[2] = atof(Cmd_Argv(3)) * 255; + if(Cmd_Argc() >= 5) + cl.cshifts[CSHIFT_BONUS].percent = atof(Cmd_Argv(4)) * 255; // yes, these are HEXADECIMAL percent ;) + else + cl.cshifts[CSHIFT_BONUS].percent = 50; + if(Cmd_Argc() >= 6) + cl.cshifts[CSHIFT_BONUS].alphafade = atof(Cmd_Argv(5)) * 255; + else + cl.cshifts[CSHIFT_BONUS].alphafade = 100; + } + else + Con_Printf("usage:\nbf, or bf R G B [A [alphafade]]\n"); +} + +/* +============================================================================== + + VIEW RENDERING + +============================================================================== +*/ + +extern matrix4x4_t viewmodelmatrix_nobob; +extern matrix4x4_t viewmodelmatrix_withbob; + +#include "cl_collision.h" +#include "csprogs.h" + +/* +================== +V_CalcRefdef + +================== +*/ +#if 0 +static vec3_t eyeboxmins = {-16, -16, -24}; +static vec3_t eyeboxmaxs = { 16, 16, 32}; +#endif + +static vec_t lowpass(vec_t value, vec_t frac, vec_t *store) +{ + frac = bound(0, frac, 1); + return (*store = *store * (1 - frac) + value * frac); +} + +static vec_t lowpass_limited(vec_t value, vec_t frac, vec_t limit, vec_t *store) +{ + lowpass(value, frac, store); + return (*store = bound(value - limit, *store, value + limit)); +} + +static vec_t highpass(vec_t value, vec_t frac, vec_t *store) +{ + return value - lowpass(value, frac, store); +} + +static vec_t highpass_limited(vec_t value, vec_t frac, vec_t limit, vec_t *store) +{ + return value - lowpass_limited(value, frac, limit, store); +} + +static void lowpass3(vec3_t value, vec_t fracx, vec_t fracy, vec_t fracz, vec3_t store, vec3_t out) +{ + out[0] = lowpass(value[0], fracx, &store[0]); + out[1] = lowpass(value[1], fracy, &store[1]); + out[2] = lowpass(value[2], fracz, &store[2]); +} + +static void highpass3(vec3_t value, vec_t fracx, vec_t fracy, vec_t fracz, vec3_t store, vec3_t out) +{ + out[0] = highpass(value[0], fracx, &store[0]); + out[1] = highpass(value[1], fracy, &store[1]); + out[2] = highpass(value[2], fracz, &store[2]); +} + +static void highpass3_limited(vec3_t value, vec_t fracx, vec_t limitx, vec_t fracy, vec_t limity, vec_t fracz, vec_t limitz, vec3_t store, vec3_t out) +{ + out[0] = highpass_limited(value[0], fracx, limitx, &store[0]); + out[1] = highpass_limited(value[1], fracy, limity, &store[1]); + out[2] = highpass_limited(value[2], fracz, limitz, &store[2]); +} + +/* + * State: + * cl.bob2_smooth + * cl.bobfall_speed + * cl.bobfall_swing + * cl.gunangles_adjustment_highpass + * cl.gunangles_adjustment_lowpass + * cl.gunangles_highpass + * cl.gunangles_prev + * cl.gunorg_adjustment_highpass + * cl.gunorg_adjustment_lowpass + * cl.gunorg_highpass + * cl.gunorg_prev + * cl.hitgroundtime + * cl.lastongroundtime + * cl.oldongrounbd + * cl.stairsmoothtime + * cl.stairsmoothz + * cl.calcrefdef_prevtime + * Extra input: + * cl.movecmd[0].time + * cl.movevars_stepheight + * cl.movevars_timescale + * cl.oldtime + * cl.punchangle + * cl.punchvector + * cl.qw_intermission_angles + * cl.qw_intermission_origin + * cl.qw_weaponkick + * cls.protocol + * cl.time + * Output: + * cl.csqc_viewanglesfromengine + * cl.csqc_viewmodelmatrixfromengine + * cl.csqc_vieworiginfromengine + * r_refdef.view.matrix + * viewmodelmatrix_nobob + * viewmodelmatrix_withbob + */ +void V_CalcRefdefUsing (const matrix4x4_t *entrendermatrix, const vec3_t clviewangles, qboolean teleported, qboolean clonground, qboolean clcmdjump, float clstatsviewheight, qboolean cldead, qboolean clintermission, const vec3_t clvelocity) +{ + float vieworg[3], viewangles[3], smoothtime; + float gunorg[3], gunangles[3]; + matrix4x4_t tmpmatrix; + + static float viewheightavg; + float viewheight; +#if 0 +// begin of chase camera bounding box size for proper collisions by Alexander Zubov + vec3_t camboxmins = {-3, -3, -3}; + vec3_t camboxmaxs = {3, 3, 3}; +// end of chase camera bounding box size for proper collisions by Alexander Zubov +#endif + trace_t trace; + + // react to clonground state changes (for gun bob) + if (clonground) + { + if (!cl.oldonground) + cl.hitgroundtime = cl.movecmd[0].time; + cl.lastongroundtime = cl.movecmd[0].time; + } + cl.oldonground = clonground; + cl.calcrefdef_prevtime = max(cl.calcrefdef_prevtime, cl.oldtime); + + VectorClear(gunorg); + viewmodelmatrix_nobob = identitymatrix; + viewmodelmatrix_withbob = identitymatrix; + r_refdef.view.matrix = identitymatrix; + + // player can look around, so take the origin from the entity, + // and the angles from the input system + Matrix4x4_OriginFromMatrix(entrendermatrix, vieworg); + VectorCopy(clviewangles, viewangles); + + // calculate how much time has passed since the last V_CalcRefdef + smoothtime = bound(0, cl.time - cl.stairsmoothtime, 0.1); + cl.stairsmoothtime = cl.time; + + // fade damage flash + if (v_dmg_time > 0) + v_dmg_time -= bound(0, smoothtime, 0.1); + + if (clintermission) + { + // entity is a fixed camera, just copy the matrix + if (cls.protocol == PROTOCOL_QUAKEWORLD) + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, cl.qw_intermission_origin[0], cl.qw_intermission_origin[1], cl.qw_intermission_origin[2], cl.qw_intermission_angles[0], cl.qw_intermission_angles[1], cl.qw_intermission_angles[2], 1); + else + { + r_refdef.view.matrix = *entrendermatrix; + Matrix4x4_AdjustOrigin(&r_refdef.view.matrix, 0, 0, clstatsviewheight); + } + Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); + Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); + Matrix4x4_Copy(&viewmodelmatrix_withbob, &viewmodelmatrix_nobob); + + VectorCopy(vieworg, cl.csqc_vieworiginfromengine); + VectorCopy(viewangles, cl.csqc_viewanglesfromengine); + + Matrix4x4_Invert_Simple(&tmpmatrix, &r_refdef.view.matrix); + Matrix4x4_CreateScale(&cl.csqc_viewmodelmatrixfromengine, cl_viewmodel_scale.value); + } + else + { + // smooth stair stepping, but only if clonground and enabled + if (!clonground || cl_stairsmoothspeed.value <= 0 || teleported) + cl.stairsmoothz = vieworg[2]; + else + { + if (cl.stairsmoothz < vieworg[2]) + vieworg[2] = cl.stairsmoothz = bound(vieworg[2] - cl.movevars_stepheight, cl.stairsmoothz + smoothtime * cl_stairsmoothspeed.value, vieworg[2]); + else if (cl.stairsmoothz > vieworg[2]) + vieworg[2] = cl.stairsmoothz = bound(vieworg[2], cl.stairsmoothz - smoothtime * cl_stairsmoothspeed.value, vieworg[2] + cl.movevars_stepheight); + } + + // apply qw weapon recoil effect (this did not work in QW) + // TODO: add a cvar to disable this + viewangles[PITCH] += cl.qw_weaponkick; + + // apply the viewofs (even if chasecam is used) + // Samual: Lets add smoothing for this too so that things like crouching are done with a transition. + viewheight = bound(0, (cl.time - cl.calcrefdef_prevtime) / max(0.0001, cl_smoothviewheight.value), 1); + viewheightavg = viewheightavg * (1 - viewheight) + clstatsviewheight * viewheight; + vieworg[2] += viewheightavg; + + if (chase_active.value) + { + // observing entity from third person. Added "campitch" by Alexander "motorsep" Zubov + vec_t camback, camup, dist, campitch, forward[3], chase_dest[3]; + + camback = chase_back.value; + camup = chase_up.value; + campitch = chase_pitchangle.value; + + AngleVectors(viewangles, forward, NULL, NULL); + + if (chase_overhead.integer) + { +#if 1 + vec3_t offset; + vec3_t bestvieworg; +#endif + vec3_t up; + viewangles[PITCH] = 0; + AngleVectors(viewangles, forward, NULL, up); + // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range) + chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup; + chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup; + chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup; +#if 0 +#if 1 + //trace = CL_TraceLine(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); + trace = CL_TraceLine(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); +#else + //trace = CL_TraceBox(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); + trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); +#endif + VectorCopy(trace.endpos, vieworg); + vieworg[2] -= 8; +#else + // trace from first person view location to our chosen third person view location +#if 1 + trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); +#else + trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); +#endif + VectorCopy(trace.endpos, bestvieworg); + offset[2] = 0; + for (offset[0] = -16;offset[0] <= 16;offset[0] += 8) + { + for (offset[1] = -16;offset[1] <= 16;offset[1] += 8) + { + AngleVectors(viewangles, NULL, NULL, up); + chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup + offset[0]; + chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup + offset[1]; + chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup + offset[2]; +#if 1 + trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); +#else + trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false); +#endif + if (bestvieworg[2] > trace.endpos[2]) + bestvieworg[2] = trace.endpos[2]; + } + } + bestvieworg[2] -= 8; + VectorCopy(bestvieworg, vieworg); +#endif + viewangles[PITCH] = campitch; + } + else + { + if (gamemode == GAME_GOODVSBAD2 && chase_stevie.integer) + { + // look straight down from high above + viewangles[PITCH] = 90; + camback = 2048; + VectorSet(forward, 0, 0, -1); + } + + // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range) + dist = -camback - 8; + chase_dest[0] = vieworg[0] + forward[0] * dist; + chase_dest[1] = vieworg[1] + forward[1] * dist; + chase_dest[2] = vieworg[2] + forward[2] * dist + camup; + trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, true, false, NULL, false, true); + VectorMAMAM(1, trace.endpos, 8, forward, 4, trace.plane.normal, vieworg); + } + } + else + { + // first person view from entity + // angles + if (cldead && v_deathtilt.integer) + viewangles[ROLL] = v_deathtiltangle.value; + VectorAdd(viewangles, cl.punchangle, viewangles); + viewangles[ROLL] += V_CalcRoll(clviewangles, clvelocity); + if (v_dmg_time > 0) + { + viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll; + viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch; + } + // origin + VectorAdd(vieworg, cl.punchvector, vieworg); + if (!cldead) + { + double xyspeed, bob, bobfall; + float cycle; + vec_t frametime; + + frametime = (cl.time - cl.calcrefdef_prevtime) * cl.movevars_timescale; + + // 1. if we teleported, clear the frametime... the lowpass will recover the previous value then + if(teleported) + { + // try to fix the first highpass; result is NOT + // perfect! TODO find a better fix + VectorCopy(viewangles, cl.gunangles_prev); + VectorCopy(vieworg, cl.gunorg_prev); + } + + // 2. for the gun origin, only keep the high frequency (non-DC) parts, which is "somewhat like velocity" + VectorAdd(cl.gunorg_highpass, cl.gunorg_prev, cl.gunorg_highpass); + highpass3_limited(vieworg, frametime*cl_followmodel_side_highpass1.value, cl_followmodel_side_limit.value, frametime*cl_followmodel_side_highpass1.value, cl_followmodel_side_limit.value, frametime*cl_followmodel_up_highpass1.value, cl_followmodel_up_limit.value, cl.gunorg_highpass, gunorg); + VectorCopy(vieworg, cl.gunorg_prev); + VectorSubtract(cl.gunorg_highpass, cl.gunorg_prev, cl.gunorg_highpass); + + // in the highpass, we _store_ the DIFFERENCE to the actual view angles... + VectorAdd(cl.gunangles_highpass, cl.gunangles_prev, cl.gunangles_highpass); + cl.gunangles_highpass[PITCH] += 360 * floor((viewangles[PITCH] - cl.gunangles_highpass[PITCH]) / 360 + 0.5); + cl.gunangles_highpass[YAW] += 360 * floor((viewangles[YAW] - cl.gunangles_highpass[YAW]) / 360 + 0.5); + cl.gunangles_highpass[ROLL] += 360 * floor((viewangles[ROLL] - cl.gunangles_highpass[ROLL]) / 360 + 0.5); + highpass3_limited(viewangles, frametime*cl_leanmodel_up_highpass1.value, cl_leanmodel_up_limit.value, frametime*cl_leanmodel_side_highpass1.value, cl_leanmodel_side_limit.value, 0, 0, cl.gunangles_highpass, gunangles); + VectorCopy(viewangles, cl.gunangles_prev); + VectorSubtract(cl.gunangles_highpass, cl.gunangles_prev, cl.gunangles_highpass); + + // 3. calculate the RAW adjustment vectors + gunorg[0] *= (cl_followmodel.value ? -cl_followmodel_side_speed.value : 0); + gunorg[1] *= (cl_followmodel.value ? -cl_followmodel_side_speed.value : 0); + gunorg[2] *= (cl_followmodel.value ? -cl_followmodel_up_speed.value : 0); + + gunangles[PITCH] *= (cl_leanmodel.value ? -cl_leanmodel_up_speed.value : 0); + gunangles[YAW] *= (cl_leanmodel.value ? -cl_leanmodel_side_speed.value : 0); + gunangles[ROLL] = 0; + + // 4. perform highpass/lowpass on the adjustment vectors (turning velocity into acceleration!) + // trick: we must do the lowpass LAST, so the lowpass vector IS the final vector! + highpass3(gunorg, frametime*cl_followmodel_side_highpass.value, frametime*cl_followmodel_side_highpass.value, frametime*cl_followmodel_up_highpass.value, cl.gunorg_adjustment_highpass, gunorg); + lowpass3(gunorg, frametime*cl_followmodel_side_lowpass.value, frametime*cl_followmodel_side_lowpass.value, frametime*cl_followmodel_up_lowpass.value, cl.gunorg_adjustment_lowpass, gunorg); + // we assume here: PITCH = 0, YAW = 1, ROLL = 2 + highpass3(gunangles, frametime*cl_leanmodel_up_highpass.value, frametime*cl_leanmodel_side_highpass.value, 0, cl.gunangles_adjustment_highpass, gunangles); + lowpass3(gunangles, frametime*cl_leanmodel_up_lowpass.value, frametime*cl_leanmodel_side_lowpass.value, 0, cl.gunangles_adjustment_lowpass, gunangles); + + // 5. use the adjusted vectors + VectorAdd(vieworg, gunorg, gunorg); + VectorAdd(viewangles, gunangles, gunangles); + + // bounded XY speed, used by several effects below + xyspeed = bound (0, sqrt(clvelocity[0]*clvelocity[0] + clvelocity[1]*clvelocity[1]), 400); + + // vertical view bobbing code + if (cl_bob.value && cl_bobcycle.value) + { + // LordHavoc: this code is *weird*, but not replacable (I think it + // should be done in QC on the server, but oh well, quake is quake) + // LordHavoc: figured out bobup: the time at which the sin is at 180 + // degrees (which allows lengthening or squishing the peak or valley) + cycle = cl.time / cl_bobcycle.value; + cycle -= (int) cycle; + if (cycle < cl_bobup.value) + cycle = sin(M_PI * cycle / cl_bobup.value); + else + cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); + // bob is proportional to velocity in the xy plane + // (don't count Z, or jumping messes it up) + bob = xyspeed * bound(0, cl_bob.value, 0.05); + bob = bob*0.3 + bob*0.7*cycle; + vieworg[2] += bob; + // we also need to adjust gunorg, or this appears like pushing the gun! + // In the old code, this was applied to vieworg BEFORE copying to gunorg, + // but this is not viable with the new followmodel code as that would mean + // that followmodel would work on the munged-by-bob vieworg and do feedback + gunorg[2] += bob; + } + + // horizontal view bobbing code + if (cl_bob2.value && cl_bob2cycle.value) + { + vec3_t bob2vel; + vec3_t forward, right, up; + float side, front; + + cycle = cl.time / cl_bob2cycle.value; + cycle -= (int) cycle; + if (cycle < 0.5) + cycle = cos(M_PI * cycle / 0.5); // cos looks better here with the other view bobbing using sin + else + cycle = cos(M_PI + M_PI * (cycle-0.5)/0.5); + bob = bound(0, cl_bob2.value, 0.05) * cycle; + + // this value slowly decreases from 1 to 0 when we stop touching the ground. + // The cycle is later multiplied with it so the view smooths back to normal + if (clonground && !clcmdjump) // also block the effect while the jump button is pressed, to avoid twitches when bunny-hopping + cl.bob2_smooth = 1; + else + { + if(cl.bob2_smooth > 0) + cl.bob2_smooth -= bound(0, cl_bob2smooth.value, 1); + else + cl.bob2_smooth = 0; + } + + // calculate the front and side of the player between the X and Y axes + AngleVectors(viewangles, forward, right, up); + // now get the speed based on those angles. The bounds should match the same value as xyspeed's + side = bound(-400, DotProduct (clvelocity, right) * cl.bob2_smooth, 400); + front = bound(-400, DotProduct (clvelocity, forward) * cl.bob2_smooth, 400); + VectorScale(forward, bob, forward); + VectorScale(right, bob, right); + // we use side with forward and front with right, so the bobbing goes + // to the side when we walk forward and to the front when we strafe + VectorMAMAM(side, forward, front, right, 0, up, bob2vel); + vieworg[0] += bob2vel[0]; + vieworg[1] += bob2vel[1]; + // we also need to adjust gunorg, or this appears like pushing the gun! + // In the old code, this was applied to vieworg BEFORE copying to gunorg, + // but this is not viable with the new followmodel code as that would mean + // that followmodel would work on the munged-by-bob vieworg and do feedback + gunorg[0] += bob2vel[0]; + gunorg[1] += bob2vel[1]; + } + + // fall bobbing code + // causes the view to swing down and back up when touching the ground + if (cl_bobfall.value && cl_bobfallcycle.value) + { + if (!clonground) + { + cl.bobfall_speed = bound(-400, clvelocity[2], 0) * bound(0, cl_bobfall.value, 0.1); + if (clvelocity[2] < -cl_bobfallminspeed.value) + cl.bobfall_swing = 1; + else + cl.bobfall_swing = 0; // TODO really? + } + else + { + cl.bobfall_swing = max(0, cl.bobfall_swing - cl_bobfallcycle.value * frametime); + + bobfall = sin(M_PI * cl.bobfall_swing) * cl.bobfall_speed; + vieworg[2] += bobfall; + gunorg[2] += bobfall; + } + } + + // gun model bobbing code + if (cl_bobmodel.value) + { + // calculate for swinging gun model + // the gun bobs when running on the ground, but doesn't bob when you're in the air. + // Sajt: I tried to smooth out the transitions between bob and no bob, which works + // for the most part, but for some reason when you go through a message trigger or + // pick up an item or anything like that it will momentarily jolt the gun. + vec3_t forward, right, up; + float bspeed; + float s; + float t; + + s = cl.time * cl_bobmodel_speed.value; + if (clonground) + { + if (cl.time - cl.hitgroundtime < 0.2) + { + // just hit the ground, speed the bob back up over the next 0.2 seconds + t = cl.time - cl.hitgroundtime; + t = bound(0, t, 0.2); + t *= 5; + } + else + t = 1; + } + else + { + // recently left the ground, slow the bob down over the next 0.2 seconds + t = cl.time - cl.lastongroundtime; + t = 0.2 - bound(0, t, 0.2); + t *= 5; + } + + bspeed = xyspeed * 0.01f; + AngleVectors (gunangles, forward, right, up); + bob = bspeed * cl_bobmodel_side.value * cl_viewmodel_scale.value * sin (s) * t; + VectorMA (gunorg, bob, right, gunorg); + bob = bspeed * cl_bobmodel_up.value * cl_viewmodel_scale.value * cos (s * 2) * t; + VectorMA (gunorg, bob, up, gunorg); + } + } + } + // calculate a view matrix for rendering the scene + if (v_idlescale.value) + { + viewangles[0] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value; + viewangles[1] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value; + viewangles[2] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value; + } + Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0], viewangles[1], viewangles[2], 1); + + // calculate a viewmodel matrix for use in view-attached entities + Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); + Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); + + Matrix4x4_CreateFromQuakeEntity(&viewmodelmatrix_withbob, gunorg[0], gunorg[1], gunorg[2], gunangles[0], gunangles[1], gunangles[2], cl_viewmodel_scale.value); + VectorCopy(vieworg, cl.csqc_vieworiginfromengine); + VectorCopy(viewangles, cl.csqc_viewanglesfromengine); + + Matrix4x4_Invert_Simple(&tmpmatrix, &r_refdef.view.matrix); + Matrix4x4_Concat(&cl.csqc_viewmodelmatrixfromengine, &tmpmatrix, &viewmodelmatrix_withbob); + } + + cl.calcrefdef_prevtime = cl.time; +} + +void V_CalcRefdef (void) +{ + entity_t *ent; + qboolean cldead; + + if (cls.state == ca_connected && cls.signon == SIGNONS && !cl.csqc_server2csqcentitynumber[cl.viewentity]) + { + // ent is the view entity (visible when out of body) + ent = &cl.entities[cl.viewentity]; + + cldead = (cl.stats[STAT_HEALTH] <= 0 && cl.stats[STAT_HEALTH] != -666 && cl.stats[STAT_HEALTH] != -2342); + V_CalcRefdefUsing(&ent->render.matrix, cl.viewangles, !ent->persistent.trail_allowed, cl.onground, cl.cmd.jump, cl.stats[STAT_VIEWHEIGHT], cldead, false, cl.velocity); // FIXME use a better way to detect teleport/warp than trail_allowed + } + else + { + viewmodelmatrix_nobob = identitymatrix; + viewmodelmatrix_withbob = identitymatrix; + cl.csqc_viewmodelmatrixfromengine = identitymatrix; + r_refdef.view.matrix = identitymatrix; + VectorClear(cl.csqc_vieworiginfromengine); + VectorCopy(cl.viewangles, cl.csqc_viewanglesfromengine); + } +} + +void V_FadeViewFlashs(void) +{ + // don't flash if time steps backwards + if (cl.time <= cl.oldtime) + return; + // drop the damage value + cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*cl.cshifts[CSHIFT_DAMAGE].alphafade; + if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0) + cl.cshifts[CSHIFT_DAMAGE].percent = 0; + // drop the bonus value + cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*cl.cshifts[CSHIFT_BONUS].alphafade; + if (cl.cshifts[CSHIFT_BONUS].percent <= 0) + cl.cshifts[CSHIFT_BONUS].percent = 0; +} + +void V_CalcViewBlend(void) +{ + float a2; + int j; + r_refdef.viewblend[0] = 0; + r_refdef.viewblend[1] = 0; + r_refdef.viewblend[2] = 0; + r_refdef.viewblend[3] = 0; + r_refdef.frustumscale_x = 1; + r_refdef.frustumscale_y = 1; + if (cls.state == ca_connected && cls.signon == SIGNONS) + { + // set contents color + int supercontents; + vec3_t vieworigin; + Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, vieworigin); + supercontents = CL_PointSuperContents(vieworigin); + if (supercontents & SUPERCONTENTS_LIQUIDSMASK) + { + r_refdef.frustumscale_x *= 1 - (((sin(cl.time * 4.7) + 1) * 0.015) * r_waterwarp.value); + r_refdef.frustumscale_y *= 1 - (((sin(cl.time * 3.0) + 1) * 0.015) * r_waterwarp.value); + if (supercontents & SUPERCONTENTS_LAVA) + { + cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255; + cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80; + cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0; + } + else if (supercontents & SUPERCONTENTS_SLIME) + { + cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0; + cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25; + cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5; + } + else + { + cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130; + cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80; + cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50; + } + cl.cshifts[CSHIFT_CONTENTS].percent = 150 * 0.5; + } + else + { + cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0; + cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 0; + cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0; + cl.cshifts[CSHIFT_CONTENTS].percent = 0; + } + + if (gamemode != GAME_TRANSFUSION) + { + if (cl.stats[STAT_ITEMS] & IT_QUAD) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255; + cl.cshifts[CSHIFT_POWERUP].percent = 30; + } + else if (cl.stats[STAT_ITEMS] & IT_SUIT) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; + cl.cshifts[CSHIFT_POWERUP].percent = 20; + } + else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100; + cl.cshifts[CSHIFT_POWERUP].percent = 100; + } + else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; + cl.cshifts[CSHIFT_POWERUP].percent = 30; + } + else + cl.cshifts[CSHIFT_POWERUP].percent = 0; + } + + cl.cshifts[CSHIFT_VCSHIFT].destcolor[0] = v_cshift.destcolor[0]; + cl.cshifts[CSHIFT_VCSHIFT].destcolor[1] = v_cshift.destcolor[1]; + cl.cshifts[CSHIFT_VCSHIFT].destcolor[2] = v_cshift.destcolor[2]; + cl.cshifts[CSHIFT_VCSHIFT].percent = v_cshift.percent; + + // LordHavoc: fixed V_CalcBlend + for (j = 0;j < NUM_CSHIFTS;j++) + { + a2 = bound(0.0f, cl.cshifts[j].percent * (1.0f / 255.0f), 1.0f); + if (a2 > 0) + { + VectorLerp(r_refdef.viewblend, a2, cl.cshifts[j].destcolor, r_refdef.viewblend); + r_refdef.viewblend[3] = (1 - (1 - r_refdef.viewblend[3]) * (1 - a2)); // correct alpha multiply... took a while to find it on the web + } + } + // saturate color (to avoid blending in black) + if (r_refdef.viewblend[3]) + { + a2 = 1 / r_refdef.viewblend[3]; + VectorScale(r_refdef.viewblend, a2, r_refdef.viewblend); + } + r_refdef.viewblend[0] = bound(0.0f, r_refdef.viewblend[0], 255.0f); + r_refdef.viewblend[1] = bound(0.0f, r_refdef.viewblend[1], 255.0f); + r_refdef.viewblend[2] = bound(0.0f, r_refdef.viewblend[2], 255.0f); + r_refdef.viewblend[3] = bound(0.0f, r_refdef.viewblend[3] * gl_polyblend.value, 1.0f); + if (vid.sRGB3D) + { + r_refdef.viewblend[0] = Image_LinearFloatFromsRGB(r_refdef.viewblend[0]); + r_refdef.viewblend[1] = Image_LinearFloatFromsRGB(r_refdef.viewblend[1]); + r_refdef.viewblend[2] = Image_LinearFloatFromsRGB(r_refdef.viewblend[2]); + } + else + { + r_refdef.viewblend[0] *= (1.0f/256.0f); + r_refdef.viewblend[1] *= (1.0f/256.0f); + r_refdef.viewblend[2] *= (1.0f/256.0f); + } + + // Samual: Ugly hack, I know. But it's the best we can do since + // there is no way to detect client states from the engine. + if (cl.stats[STAT_HEALTH] <= 0 && cl.stats[STAT_HEALTH] != -666 && + cl.stats[STAT_HEALTH] != -2342 && cl_deathfade.value > 0) + { + cl.deathfade += cl_deathfade.value * max(0.00001, cl.time - cl.oldtime); + cl.deathfade = bound(0.0f, cl.deathfade, 0.9f); + } + else + cl.deathfade = 0.0f; + + if(cl.deathfade > 0) + { + float a; + float deathfadevec[3] = {0.3f, 0.0f, 0.0f}; + a = r_refdef.viewblend[3] + cl.deathfade - r_refdef.viewblend[3]*cl.deathfade; + if(a > 0) + VectorMAM(r_refdef.viewblend[3] * (1 - cl.deathfade) / a, r_refdef.viewblend, cl.deathfade / a, deathfadevec, r_refdef.viewblend); + r_refdef.viewblend[3] = a; + } + } +} + +//============================================================================ + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + Cmd_AddCommand ("v_cshift", V_cshift_f, "sets tint color of view"); + Cmd_AddCommand ("bf", V_BonusFlash_f, "briefly flashes a bright color tint on view (used when items are picked up); optionally takes R G B [A [alphafade]] arguments to specify how the flash looks"); + Cmd_AddCommand ("centerview", V_StartPitchDrift, "gradually recenter view (stop looking up/down)"); + + Cvar_RegisterVariable (&v_centermove); + Cvar_RegisterVariable (&v_centerspeed); + + Cvar_RegisterVariable (&v_iyaw_cycle); + Cvar_RegisterVariable (&v_iroll_cycle); + Cvar_RegisterVariable (&v_ipitch_cycle); + Cvar_RegisterVariable (&v_iyaw_level); + Cvar_RegisterVariable (&v_iroll_level); + Cvar_RegisterVariable (&v_ipitch_level); + + Cvar_RegisterVariable (&v_idlescale); + Cvar_RegisterVariable (&crosshair); + + Cvar_RegisterVariable (&cl_autocentreoffset); + Cvar_RegisterVariable (&v_eyebufferresolution); + + Cvar_RegisterVariable (&cl_rollspeed); + Cvar_RegisterVariable (&cl_rollangle); + Cvar_RegisterVariable (&cl_bob); + Cvar_RegisterVariable (&cl_bobcycle); + Cvar_RegisterVariable (&cl_bobup); + Cvar_RegisterVariable (&cl_bob2); + Cvar_RegisterVariable (&cl_bob2cycle); + Cvar_RegisterVariable (&cl_bob2smooth); + Cvar_RegisterVariable (&cl_bobfall); + Cvar_RegisterVariable (&cl_bobfallcycle); + Cvar_RegisterVariable (&cl_bobfallminspeed); + Cvar_RegisterVariable (&cl_bobmodel); + Cvar_RegisterVariable (&cl_bobmodel_side); + Cvar_RegisterVariable (&cl_bobmodel_up); + Cvar_RegisterVariable (&cl_bobmodel_speed); + + Cvar_RegisterVariable (&cl_leanmodel); + Cvar_RegisterVariable (&cl_leanmodel_side_speed); + Cvar_RegisterVariable (&cl_leanmodel_side_limit); + Cvar_RegisterVariable (&cl_leanmodel_side_highpass1); + Cvar_RegisterVariable (&cl_leanmodel_side_lowpass); + Cvar_RegisterVariable (&cl_leanmodel_side_highpass); + Cvar_RegisterVariable (&cl_leanmodel_up_speed); + Cvar_RegisterVariable (&cl_leanmodel_up_limit); + Cvar_RegisterVariable (&cl_leanmodel_up_highpass1); + Cvar_RegisterVariable (&cl_leanmodel_up_lowpass); + Cvar_RegisterVariable (&cl_leanmodel_up_highpass); + + Cvar_RegisterVariable (&cl_followmodel); + Cvar_RegisterVariable (&cl_followmodel_side_speed); + Cvar_RegisterVariable (&cl_followmodel_side_limit); + Cvar_RegisterVariable (&cl_followmodel_side_highpass1); + Cvar_RegisterVariable (&cl_followmodel_side_lowpass); + Cvar_RegisterVariable (&cl_followmodel_side_highpass); + Cvar_RegisterVariable (&cl_followmodel_up_speed); + Cvar_RegisterVariable (&cl_followmodel_up_limit); + Cvar_RegisterVariable (&cl_followmodel_up_highpass1); + Cvar_RegisterVariable (&cl_followmodel_up_lowpass); + Cvar_RegisterVariable (&cl_followmodel_up_highpass); + + Cvar_RegisterVariable (&cl_viewmodel_scale); + + Cvar_RegisterVariable (&v_kicktime); + Cvar_RegisterVariable (&v_kickroll); + Cvar_RegisterVariable (&v_kickpitch); + + Cvar_RegisterVariable (&cl_stairsmoothspeed); + + Cvar_RegisterVariable (&cl_smoothviewheight); + + Cvar_RegisterVariable (&chase_back); + Cvar_RegisterVariable (&chase_up); + Cvar_RegisterVariable (&chase_active); + Cvar_RegisterVariable (&chase_overhead); + Cvar_RegisterVariable (&chase_pitchangle); + Cvar_RegisterVariable (&chase_stevie); + + Cvar_RegisterVariable (&v_deathtilt); + Cvar_RegisterVariable (&v_deathtiltangle); +} + diff --git a/app/jni/wad.c b/app/jni/wad.c new file mode 100644 index 0000000..4e714b0 --- /dev/null +++ b/app/jni/wad.c @@ -0,0 +1,312 @@ +/* +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 "image.h" +#include "wad.h" + +typedef struct mwad_s +{ + qfile_t *file; + int numlumps; + lumpinfo_t *lumps; +} +mwad_t; + +typedef struct wadstate_s +{ + unsigned char *gfx_base; + mwad_t gfx; + memexpandablearray_t hlwads; +} +wadstate_t; + +static wadstate_t wad; + +/* +================== +W_CleanupName + +Lowercases name and pads with spaces and a terminating 0 to the length of +lumpinfo_t->name. +Used so lumpname lookups can proceed rapidly by comparing 4 chars at a time +Space padding is so names can be printed nicely in tables. +Can safely be performed in place. +================== +*/ +static void W_CleanupName (const char *in, char *out) +{ + int i; + int c; + + for (i=0 ; i<16 ; i++ ) + { + c = in[i]; + if (!c) + break; + + if (c >= 'A' && c <= 'Z') + c += ('a' - 'A'); + out[i] = c; + } + + for ( ; i< 16 ; i++ ) + out[i] = 0; +} + +static void W_SwapLumps(int numlumps, lumpinfo_t *lumps) +{ + int i; + for (i = 0;i < numlumps;i++) + { + lumps[i].filepos = LittleLong(lumps[i].filepos); + lumps[i].disksize = LittleLong(lumps[i].disksize); + lumps[i].size = LittleLong(lumps[i].size); + W_CleanupName(lumps[i].name, lumps[i].name); + } +} + +void W_UnloadAll(void) +{ + unsigned int i; + mwad_t *w; + // free gfx.wad if it is loaded + if (wad.gfx_base) + Mem_Free(wad.gfx_base); + wad.gfx_base = NULL; + // close all hlwad files and free their lumps data + for (i = 0;i < Mem_ExpandableArray_IndexRange(&wad.hlwads);i++) + { + w = (mwad_t *) Mem_ExpandableArray_RecordAtIndex(&wad.hlwads, i); + if (!w) + continue; + if (w->file) + FS_Close(w->file); + w->file = NULL; + if (w->lumps) + Mem_Free(w->lumps); + w->lumps = NULL; + } + // free the hlwads array + Mem_ExpandableArray_FreeArray(&wad.hlwads); + // clear all state + memset(&wad, 0, sizeof(wad)); +} + +unsigned char *W_GetLumpName(const char *name) +{ + int i; + fs_offset_t filesize; + lumpinfo_t *lump; + char clean[16]; + wadinfo_t *header; + int infotableofs; + + W_CleanupName (name, clean); + + if (!wad.gfx_base) + { + if ((wad.gfx_base = FS_LoadFile ("gfx.wad", cls.permanentmempool, false, &filesize))) + { + if (memcmp(wad.gfx_base, "WAD2", 4)) + { + Con_Print("gfx.wad doesn't have WAD2 id\n"); + Mem_Free(wad.gfx_base); + wad.gfx_base = NULL; + } + else + { + header = (wadinfo_t *)wad.gfx_base; + wad.gfx.numlumps = LittleLong(header->numlumps); + infotableofs = LittleLong(header->infotableofs); + wad.gfx.lumps = (lumpinfo_t *)(wad.gfx_base + infotableofs); + + // byteswap the gfx.wad lumps in place + W_SwapLumps(wad.gfx.numlumps, wad.gfx.lumps); + } + } + } + + for (lump = wad.gfx.lumps, i = 0;i < wad.gfx.numlumps;i++, lump++) + if (!strcmp(clean, lump->name)) + return (wad.gfx_base + lump->filepos); + return NULL; +} + +/* +==================== +W_LoadTextureWadFile +==================== +*/ +void W_LoadTextureWadFile (char *filename, int complain) +{ + wadinfo_t header; + int infotableofs; + qfile_t *file; + int numlumps; + mwad_t *w; + + file = FS_OpenVirtualFile(filename, false); + if (!file) + { + if (complain) + Con_Printf("W_LoadTextureWadFile: couldn't find %s\n", filename); + return; + } + + if (FS_Read(file, &header, sizeof(wadinfo_t)) != sizeof(wadinfo_t)) + {Con_Print("W_LoadTextureWadFile: unable to read wad header\n");FS_Close(file);file = NULL;return;} + + if(memcmp(header.identification, "WAD3", 4)) + {Con_Printf("W_LoadTextureWadFile: Wad file %s doesn't have WAD3 id\n",filename);FS_Close(file);file = NULL;return;} + + numlumps = LittleLong(header.numlumps); + if (numlumps < 1 || numlumps > 65536) + {Con_Printf("W_LoadTextureWadFile: invalid number of lumps (%i)\n", numlumps);FS_Close(file);file = NULL;return;} + infotableofs = LittleLong(header.infotableofs); + if (FS_Seek (file, infotableofs, SEEK_SET)) + {Con_Print("W_LoadTextureWadFile: unable to seek to lump table\n");FS_Close(file);file = NULL;return;} + + if (!wad.hlwads.mempool) + Mem_ExpandableArray_NewArray(&wad.hlwads, cls.permanentmempool, sizeof(mwad_t), 16); + w = (mwad_t *) Mem_ExpandableArray_AllocRecord(&wad.hlwads); + w->file = file; + w->numlumps = numlumps; + w->lumps = (lumpinfo_t *) Mem_Alloc(cls.permanentmempool, w->numlumps * sizeof(lumpinfo_t)); + + if (!w->lumps) + { + Con_Print("W_LoadTextureWadFile: unable to allocate temporary memory for lump table\n"); + FS_Close(w->file); + w->file = NULL; + w->numlumps = 0; + return; + } + + if (FS_Read(file, w->lumps, sizeof(lumpinfo_t) * w->numlumps) != (fs_offset_t)sizeof(lumpinfo_t) * numlumps) + { + Con_Print("W_LoadTextureWadFile: unable to read lump table\n"); + FS_Close(w->file); + w->file = NULL; + w->numlumps = 0; + Mem_Free(w->lumps); + w->lumps = NULL; + return; + } + + W_SwapLumps(w->numlumps, w->lumps); + + // leaves the file open +} + +unsigned char *W_ConvertWAD3TextureBGRA(sizebuf_t *sb) +{ + unsigned char *in, *data, *out, *pal; + int d, p; + unsigned char name[16]; + unsigned int mipoffset[4]; + + MSG_BeginReading(sb); + MSG_ReadBytes(sb, 16, name); + image_width = MSG_ReadLittleLong(sb); + image_height = MSG_ReadLittleLong(sb); + mipoffset[0] = MSG_ReadLittleLong(sb); + mipoffset[1] = MSG_ReadLittleLong(sb); // should be mipoffset[0] + image_width*image_height + mipoffset[2] = MSG_ReadLittleLong(sb); // should be mipoffset[1] + image_width*image_height/4 + mipoffset[3] = MSG_ReadLittleLong(sb); // should be mipoffset[2] + image_width*image_height/16 + pal = sb->data + mipoffset[3] + (image_width / 8 * image_height / 8) + 2; + + // bail if any data looks wrong + if (image_width < 0 + || image_width > 4096 + || image_height < 0 + || image_height > 4096 + || mipoffset[0] != 40 + || mipoffset[1] != mipoffset[0] + image_width * image_height + || mipoffset[2] != mipoffset[1] + image_width / 2 * image_height / 2 + || mipoffset[3] != mipoffset[2] + image_width / 4 * image_height / 4 + || (unsigned int)sb->cursize < (mipoffset[3] + image_width / 8 * image_height / 8 + 2 + 768)) + return NULL; + + in = (unsigned char *)sb->data + mipoffset[0]; + data = out = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); + if (!data) + return NULL; + for (d = 0;d < image_width * image_height;d++) + { + p = *in++; + if (name[0] == '{' && p == 255) + out[0] = out[1] = out[2] = out[3] = 0; + else + { + p *= 3; + out[2] = pal[p]; + out[1] = pal[p+1]; + out[0] = pal[p+2]; + out[3] = 255; + } + out += 4; + } + return data; +} + +unsigned char *W_GetTextureBGRA(char *name) +{ + unsigned int i, k; + sizebuf_t sb; + unsigned char *data; + mwad_t *w; + char texname[17]; + size_t range; + + texname[16] = 0; + W_CleanupName(name, texname); + if (!wad.hlwads.mempool) + Mem_ExpandableArray_NewArray(&wad.hlwads, cls.permanentmempool, sizeof(mwad_t), 16); + range = Mem_ExpandableArray_IndexRange(&wad.hlwads); + for (k = 0;k < range;k++) + { + w = (mwad_t *)Mem_ExpandableArray_RecordAtIndex(&wad.hlwads, k); + if (!w) + continue; + for (i = 0;i < (unsigned int)w->numlumps;i++) + { + if (!strcmp(texname, w->lumps[i].name)) // found it + { + if (FS_Seek(w->file, w->lumps[i].filepos, SEEK_SET)) + {Con_Print("W_GetTexture: corrupt WAD3 file\n");return NULL;} + + MSG_InitReadBuffer(&sb, (unsigned char *)Mem_Alloc(tempmempool, w->lumps[i].disksize), w->lumps[i].disksize); + if (!sb.data) + return NULL; + if (FS_Read(w->file, sb.data, w->lumps[i].size) < w->lumps[i].disksize) + {Con_Print("W_GetTexture: corrupt WAD3 file\n");return NULL;} + + data = W_ConvertWAD3TextureBGRA(&sb); + Mem_Free(sb.data); + return data; + } + } + } + image_width = image_height = 0; + return NULL; +} + diff --git a/app/jni/wad.h b/app/jni/wad.h new file mode 100644 index 0000000..3c4297d --- /dev/null +++ b/app/jni/wad.h @@ -0,0 +1,77 @@ +/* +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. + +*/ +// wad.h + +#ifndef WAD_H +#define WAD_H + +//=============== +// TYPES +//=============== + +#define CMP_NONE 0 +#define CMP_LZSS 1 + +#define TYP_NONE 0 +#define TYP_LABEL 1 + +#define TYP_LUMPY 64 // 64 + grab command number +#define TYP_PALETTE 64 +#define TYP_QTEX 65 +#define TYP_QPIC 66 +#define TYP_SOUND 67 +#define TYP_MIPTEX 68 + +typedef struct qpic_s +{ + int width, height; + unsigned char data[4]; // variably sized +} qpic_t; + + + +typedef struct wadinfo_s +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct lumpinfo_s +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +void W_UnloadAll(void); +unsigned char *W_GetLumpName(const char *name); + +// halflife texture wads +void W_LoadTextureWadFile(char *filename, int complain); +unsigned char *W_GetTextureBGRA(char *name); // returns tempmempool allocated image data, width and height are in image_width and image_height +unsigned char *W_ConvertWAD3TextureBGRA(sizebuf_t *sb); // returns tempmempool allocated image data, width and height are in image_width and image_height + +#endif + diff --git a/app/jni/world.c b/app/jni/world.c new file mode 100644 index 0000000..abacf51 --- /dev/null +++ b/app/jni/world.c @@ -0,0 +1,3123 @@ +/* +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. + +*/ +// world.c -- world query functions + +#include "quakedef.h" +#include "clvm_cmds.h" +#include "cl_collision.h" + +/* + +entities never clip against themselves, or their owner + +line of sight checks trace->inopen and trace->inwater, but bullets don't + +*/ + +static void World_Physics_Init(void); +void World_Init(void) +{ + Collision_Init(); + World_Physics_Init(); +} + +static void World_Physics_Shutdown(void); +void World_Shutdown(void) +{ + World_Physics_Shutdown(); +} + +static void World_Physics_Start(world_t *world); +void World_Start(world_t *world) +{ + World_Physics_Start(world); +} + +static void World_Physics_End(world_t *world); +void World_End(world_t *world) +{ + World_Physics_End(world); +} + +//============================================================================ + +/// World_ClearLink is used for new headnodes +void World_ClearLink (link_t *l) +{ + l->entitynumber = 0; + l->prev = l->next = l; +} + +void World_RemoveLink (link_t *l) +{ + l->next->prev = l->prev; + l->prev->next = l->next; +} + +void World_InsertLinkBefore (link_t *l, link_t *before, int entitynumber) +{ + l->entitynumber = entitynumber; + l->next = before; + l->prev = before->prev; + l->prev->next = l; + l->next->prev = l; +} + +/* +=============================================================================== + +ENTITY AREA CHECKING + +=============================================================================== +*/ + +void World_PrintAreaStats(world_t *world, const char *worldname) +{ + Con_Printf("%s areagrid check stats: %d calls %d nodes (%f per call) %d entities (%f per call)\n", worldname, world->areagrid_stats_calls, world->areagrid_stats_nodechecks, (double) world->areagrid_stats_nodechecks / (double) world->areagrid_stats_calls, world->areagrid_stats_entitychecks, (double) world->areagrid_stats_entitychecks / (double) world->areagrid_stats_calls); + world->areagrid_stats_calls = 0; + world->areagrid_stats_nodechecks = 0; + world->areagrid_stats_entitychecks = 0; +} + +/* +=============== +World_SetSize + +=============== +*/ +void World_SetSize(world_t *world, const char *filename, const vec3_t mins, const vec3_t maxs, prvm_prog_t *prog) +{ + int i; + + strlcpy(world->filename, filename, sizeof(world->filename)); + VectorCopy(mins, world->mins); + VectorCopy(maxs, world->maxs); + world->prog = prog; + + // the areagrid_marknumber is not allowed to be 0 + if (world->areagrid_marknumber < 1) + world->areagrid_marknumber = 1; + // choose either the world box size, or a larger box to ensure the grid isn't too fine + world->areagrid_size[0] = max(world->maxs[0] - world->mins[0], AREA_GRID * sv_areagrid_mingridsize.value); + world->areagrid_size[1] = max(world->maxs[1] - world->mins[1], AREA_GRID * sv_areagrid_mingridsize.value); + world->areagrid_size[2] = max(world->maxs[2] - world->mins[2], AREA_GRID * sv_areagrid_mingridsize.value); + // figure out the corners of such a box, centered at the center of the world box + world->areagrid_mins[0] = (world->mins[0] + world->maxs[0] - world->areagrid_size[0]) * 0.5f; + world->areagrid_mins[1] = (world->mins[1] + world->maxs[1] - world->areagrid_size[1]) * 0.5f; + world->areagrid_mins[2] = (world->mins[2] + world->maxs[2] - world->areagrid_size[2]) * 0.5f; + world->areagrid_maxs[0] = (world->mins[0] + world->maxs[0] + world->areagrid_size[0]) * 0.5f; + world->areagrid_maxs[1] = (world->mins[1] + world->maxs[1] + world->areagrid_size[1]) * 0.5f; + world->areagrid_maxs[2] = (world->mins[2] + world->maxs[2] + world->areagrid_size[2]) * 0.5f; + // now calculate the actual useful info from that + VectorNegate(world->areagrid_mins, world->areagrid_bias); + world->areagrid_scale[0] = AREA_GRID / world->areagrid_size[0]; + world->areagrid_scale[1] = AREA_GRID / world->areagrid_size[1]; + world->areagrid_scale[2] = AREA_GRID / world->areagrid_size[2]; + World_ClearLink(&world->areagrid_outside); + for (i = 0;i < AREA_GRIDNODES;i++) + World_ClearLink(&world->areagrid[i]); + if (developer_extra.integer) + Con_DPrintf("areagrid settings: divisions %ix%ix1 : box %f %f %f : %f %f %f size %f %f %f grid %f %f %f (mingrid %f)\n", AREA_GRID, AREA_GRID, world->areagrid_mins[0], world->areagrid_mins[1], world->areagrid_mins[2], world->areagrid_maxs[0], world->areagrid_maxs[1], world->areagrid_maxs[2], world->areagrid_size[0], world->areagrid_size[1], world->areagrid_size[2], 1.0f / world->areagrid_scale[0], 1.0f / world->areagrid_scale[1], 1.0f / world->areagrid_scale[2], sv_areagrid_mingridsize.value); +} + +/* +=============== +World_UnlinkAll + +=============== +*/ +void World_UnlinkAll(world_t *world) +{ + prvm_prog_t *prog = world->prog; + int i; + link_t *grid; + // unlink all entities one by one + grid = &world->areagrid_outside; + while (grid->next != grid) + World_UnlinkEdict(PRVM_EDICT_NUM(grid->next->entitynumber)); + for (i = 0, grid = world->areagrid;i < AREA_GRIDNODES;i++, grid++) + while (grid->next != grid) + World_UnlinkEdict(PRVM_EDICT_NUM(grid->next->entitynumber)); +} + +/* +=============== + +=============== +*/ +void World_UnlinkEdict(prvm_edict_t *ent) +{ + int i; + for (i = 0;i < ENTITYGRIDAREAS;i++) + { + if (ent->priv.server->areagrid[i].prev) + { + World_RemoveLink (&ent->priv.server->areagrid[i]); + ent->priv.server->areagrid[i].prev = ent->priv.server->areagrid[i].next = NULL; + } + } +} + +int World_EntitiesInBox(world_t *world, const vec3_t requestmins, const vec3_t requestmaxs, int maxlist, prvm_edict_t **list) +{ + prvm_prog_t *prog = world->prog; + int numlist; + link_t *grid; + link_t *l; + prvm_edict_t *ent; + vec3_t paddedmins, paddedmaxs; + int igrid[3], igridmins[3], igridmaxs[3]; + + // LordHavoc: discovered this actually causes its own bugs (dm6 teleporters being too close to info_teleport_destination) + //VectorSet(paddedmins, requestmins[0] - 1.0f, requestmins[1] - 1.0f, requestmins[2] - 1.0f); + //VectorSet(paddedmaxs, requestmaxs[0] + 1.0f, requestmaxs[1] + 1.0f, requestmaxs[2] + 1.0f); + VectorCopy(requestmins, paddedmins); + VectorCopy(requestmaxs, paddedmaxs); + + // FIXME: if areagrid_marknumber wraps, all entities need their + // ent->priv.server->areagridmarknumber reset + world->areagrid_stats_calls++; + world->areagrid_marknumber++; + igridmins[0] = (int) floor((paddedmins[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]); + igridmins[1] = (int) floor((paddedmins[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]); + //igridmins[2] = (int) ((paddedmins[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]); + igridmaxs[0] = (int) floor((paddedmaxs[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]) + 1; + igridmaxs[1] = (int) floor((paddedmaxs[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]) + 1; + //igridmaxs[2] = (int) ((paddedmaxs[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]) + 1; + igridmins[0] = max(0, igridmins[0]); + igridmins[1] = max(0, igridmins[1]); + //igridmins[2] = max(0, igridmins[2]); + igridmaxs[0] = min(AREA_GRID, igridmaxs[0]); + igridmaxs[1] = min(AREA_GRID, igridmaxs[1]); + //igridmaxs[2] = min(AREA_GRID, igridmaxs[2]); + + // paranoid debugging + //VectorSet(igridmins, 0, 0, 0);VectorSet(igridmaxs, AREA_GRID, AREA_GRID, AREA_GRID); + + numlist = 0; + // add entities not linked into areagrid because they are too big or + // outside the grid bounds + if (world->areagrid_outside.next) + { + grid = &world->areagrid_outside; + for (l = grid->next;l != grid;l = l->next) + { + ent = PRVM_EDICT_NUM(l->entitynumber); + if (ent->priv.server->areagridmarknumber != world->areagrid_marknumber) + { + ent->priv.server->areagridmarknumber = world->areagrid_marknumber; + if (!ent->priv.server->free && BoxesOverlap(paddedmins, paddedmaxs, ent->priv.server->areamins, ent->priv.server->areamaxs)) + { + if (numlist < maxlist) + list[numlist] = ent; + numlist++; + } + world->areagrid_stats_entitychecks++; + } + } + } + // add grid linked entities + for (igrid[1] = igridmins[1];igrid[1] < igridmaxs[1];igrid[1]++) + { + grid = world->areagrid + igrid[1] * AREA_GRID + igridmins[0]; + for (igrid[0] = igridmins[0];igrid[0] < igridmaxs[0];igrid[0]++, grid++) + { + if (grid->next) + { + for (l = grid->next;l != grid;l = l->next) + { + ent = PRVM_EDICT_NUM(l->entitynumber); + if (ent->priv.server->areagridmarknumber != world->areagrid_marknumber) + { + ent->priv.server->areagridmarknumber = world->areagrid_marknumber; + if (!ent->priv.server->free && BoxesOverlap(paddedmins, paddedmaxs, ent->priv.server->areamins, ent->priv.server->areamaxs)) + { + if (numlist < maxlist) + list[numlist] = ent; + numlist++; + } + //Con_Printf("%d %f %f %f %f %f %f : %d : %f %f %f %f %f %f\n", BoxesOverlap(mins, maxs, ent->priv.server->areamins, ent->priv.server->areamaxs), ent->priv.server->areamins[0], ent->priv.server->areamins[1], ent->priv.server->areamins[2], ent->priv.server->areamaxs[0], ent->priv.server->areamaxs[1], ent->priv.server->areamaxs[2], PRVM_NUM_FOR_EDICT(ent), mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); + } + world->areagrid_stats_entitychecks++; + } + } + } + } + return numlist; +} + +static void World_LinkEdict_AreaGrid(world_t *world, prvm_edict_t *ent) +{ + prvm_prog_t *prog = world->prog; + link_t *grid; + int igrid[3], igridmins[3], igridmaxs[3], gridnum, entitynumber = PRVM_NUM_FOR_EDICT(ent); + + if (entitynumber <= 0 || entitynumber >= prog->max_edicts || PRVM_EDICT_NUM(entitynumber) != ent) + { + Con_Printf ("World_LinkEdict_AreaGrid: invalid edict %p (edicts is %p, edict compared to prog->edicts is %i)\n", (void *)ent, (void *)prog->edicts, entitynumber); + return; + } + + igridmins[0] = (int) floor((ent->priv.server->areamins[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]); + igridmins[1] = (int) floor((ent->priv.server->areamins[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]); + //igridmins[2] = (int) floor((ent->priv.server->areamins[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]); + igridmaxs[0] = (int) floor((ent->priv.server->areamaxs[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]) + 1; + igridmaxs[1] = (int) floor((ent->priv.server->areamaxs[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]) + 1; + //igridmaxs[2] = (int) floor((ent->priv.server->areamaxs[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]) + 1; + if (igridmins[0] < 0 || igridmaxs[0] > AREA_GRID || igridmins[1] < 0 || igridmaxs[1] > AREA_GRID || ((igridmaxs[0] - igridmins[0]) * (igridmaxs[1] - igridmins[1])) > ENTITYGRIDAREAS) + { + // wow, something outside the grid, store it as such + World_InsertLinkBefore (&ent->priv.server->areagrid[0], &world->areagrid_outside, entitynumber); + return; + } + + gridnum = 0; + for (igrid[1] = igridmins[1];igrid[1] < igridmaxs[1];igrid[1]++) + { + grid = world->areagrid + igrid[1] * AREA_GRID + igridmins[0]; + for (igrid[0] = igridmins[0];igrid[0] < igridmaxs[0];igrid[0]++, grid++, gridnum++) + World_InsertLinkBefore (&ent->priv.server->areagrid[gridnum], grid, entitynumber); + } +} + +/* +=============== +World_LinkEdict + +=============== +*/ +void World_LinkEdict(world_t *world, prvm_edict_t *ent, const vec3_t mins, const vec3_t maxs) +{ + prvm_prog_t *prog = world->prog; + // unlink from old position first + if (ent->priv.server->areagrid[0].prev) + World_UnlinkEdict(ent); + + // don't add the world + if (ent == prog->edicts) + return; + + // don't add free entities + if (ent->priv.server->free) + return; + + VectorCopy(mins, ent->priv.server->areamins); + VectorCopy(maxs, ent->priv.server->areamaxs); + World_LinkEdict_AreaGrid(world, ent); +} + + + + +//============================================================================ +// physics engine support +//============================================================================ + +#ifdef USEODE +cvar_t physics_ode_quadtree_depth = {0, "physics_ode_quadtree_depth","5", "desired subdivision level of quadtree culling space"}; +cvar_t physics_ode_allowconvex = {0, "physics_ode_allowconvex", "0", "allow usage of Convex Hull primitive type on trimeshes that have custom 'collisionconvex' mesh. If disabled, trimesh primitive type are used."}; +cvar_t physics_ode_contactsurfacelayer = {0, "physics_ode_contactsurfacelayer","1", "allows objects to overlap this many units to reduce jitter"}; +cvar_t physics_ode_worldstep_iterations = {0, "physics_ode_worldstep_iterations", "20", "parameter to dWorldQuickStep"}; +cvar_t physics_ode_contact_mu = {0, "physics_ode_contact_mu", "1", "contact solver mu parameter - friction pyramid approximation 1 (see ODE User Guide)"}; +cvar_t physics_ode_contact_erp = {0, "physics_ode_contact_erp", "0.96", "contact solver erp parameter - Error Restitution Percent (see ODE User Guide)"}; +cvar_t physics_ode_contact_cfm = {0, "physics_ode_contact_cfm", "0", "contact solver cfm parameter - Constraint Force Mixing (see ODE User Guide)"}; +cvar_t physics_ode_contact_maxpoints = {0, "physics_ode_contact_maxpoints", "16", "maximal number of contact points between 2 objects, higher = stable (and slower), can be up to 32"}; +cvar_t physics_ode_world_erp = {0, "physics_ode_world_erp", "-1", "world solver erp parameter - Error Restitution Percent (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_cfm = {0, "physics_ode_world_cfm", "-1", "world solver cfm parameter - Constraint Force Mixing (see ODE User Guide); not touched when -1"}; +cvar_t physics_ode_world_damping = {0, "physics_ode_world_damping", "1", "enabled damping scale (see ODE User Guide), this scales all damping values, be aware that behavior depends of step type"}; +cvar_t physics_ode_world_damping_linear = {0, "physics_ode_world_damping_linear", "0.01", "world linear damping scale (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_damping_linear_threshold = {0, "physics_ode_world_damping_linear_threshold", "0.1", "world linear damping threshold (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_damping_angular = {0, "physics_ode_world_damping_angular", "0.05", "world angular damping scale (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_damping_angular_threshold = {0, "physics_ode_world_damping_angular_threshold", "0.1", "world angular damping threshold (see ODE User Guide); use defaults when set to -1"}; +cvar_t physics_ode_world_gravitymod = {0, "physics_ode_world_gravitymod", "1", "multiplies gravity got from sv_gravity, this may be needed to tweak if strong damping is used"}; +cvar_t physics_ode_iterationsperframe = {0, "physics_ode_iterationsperframe", "1", "divisor for time step, runs multiple physics steps per frame"}; +cvar_t physics_ode_constantstep = {0, "physics_ode_constantstep", "0", "use constant step instead of variable step which tends to increase stability, if set to 1 uses sys_ticrate, instead uses it's own value"}; +cvar_t physics_ode_autodisable = {0, "physics_ode_autodisable", "1", "automatic disabling of objects which dont move for long period of time, makes object stacking a lot faster"}; +cvar_t physics_ode_autodisable_steps = {0, "physics_ode_autodisable_steps", "10", "how many steps object should be dormant to be autodisabled"}; +cvar_t physics_ode_autodisable_time = {0, "physics_ode_autodisable_time", "0", "how many seconds object should be dormant to be autodisabled"}; +cvar_t physics_ode_autodisable_threshold_linear = {0, "physics_ode_autodisable_threshold_linear", "0.6", "body will be disabled if it's linear move below this value"}; +cvar_t physics_ode_autodisable_threshold_angular = {0, "physics_ode_autodisable_threshold_angular", "6", "body will be disabled if it's angular move below this value"}; +cvar_t physics_ode_autodisable_threshold_samples = {0, "physics_ode_autodisable_threshold_samples", "5", "average threshold with this number of samples"}; +cvar_t physics_ode_movelimit = {0, "physics_ode_movelimit", "0.5", "clamp velocity if a single move would exceed this percentage of object thickness, to prevent flying through walls, be aware that behavior depends of step type"}; +cvar_t physics_ode_spinlimit = {0, "physics_ode_spinlimit", "10000", "reset spin velocity if it gets too large"}; +cvar_t physics_ode_trick_fixnan = {0, "physics_ode_trick_fixnan", "1", "engine trick that checks and fixes NaN velocity/origin/angles on objects, a value of 2 makes console prints on each fix"}; +cvar_t physics_ode_printstats = {0, "physics_ode_printstats", "0", "print ODE stats each frame"}; + +cvar_t physics_ode = {0, "physics_ode", "0", "run ODE physics (VERY experimental and potentially buggy)"}; + +// LordHavoc: this large chunk of definitions comes from the ODE library +// include files. + +#ifdef ODE_STATIC +#include "ode/ode.h" +#else +#ifdef WINAPI +// ODE does not use WINAPI +#define ODE_API +#else +#define ODE_API +#endif + +// note: dynamic builds of ODE tend to be double precision, this is not used +// for static builds +typedef double dReal; + +typedef dReal dVector3[4]; +typedef dReal dVector4[4]; +typedef dReal dMatrix3[4*3]; +typedef dReal dMatrix4[4*4]; +typedef dReal dMatrix6[8*6]; +typedef dReal dQuaternion[4]; + +struct dxWorld; /* dynamics world */ +struct dxSpace; /* collision space */ +struct dxBody; /* rigid body (dynamics object) */ +struct dxGeom; /* geometry (collision object) */ +struct dxJoint; +struct dxJointNode; +struct dxJointGroup; +struct dxTriMeshData; + +#define dInfinity 3.402823466e+38f + +typedef struct dxWorld *dWorldID; +typedef struct dxSpace *dSpaceID; +typedef struct dxBody *dBodyID; +typedef struct dxGeom *dGeomID; +typedef struct dxJoint *dJointID; +typedef struct dxJointGroup *dJointGroupID; +typedef struct dxTriMeshData *dTriMeshDataID; + +typedef struct dJointFeedback +{ + dVector3 f1; /* force applied to body 1 */ + dVector3 t1; /* torque applied to body 1 */ + dVector3 f2; /* force applied to body 2 */ + dVector3 t2; /* torque applied to body 2 */ +} +dJointFeedback; + +typedef enum dJointType +{ + dJointTypeNone = 0, + dJointTypeBall, + dJointTypeHinge, + dJointTypeSlider, + dJointTypeContact, + dJointTypeUniversal, + dJointTypeHinge2, + dJointTypeFixed, + dJointTypeNull, + dJointTypeAMotor, + dJointTypeLMotor, + dJointTypePlane2D, + dJointTypePR, + dJointTypePU, + dJointTypePiston +} +dJointType; + +#define D_ALL_PARAM_NAMES(start) \ + /* parameters for limits and motors */ \ + dParamLoStop = start, \ + dParamHiStop, \ + dParamVel, \ + dParamFMax, \ + dParamFudgeFactor, \ + dParamBounce, \ + dParamCFM, \ + dParamStopERP, \ + dParamStopCFM, \ + /* parameters for suspension */ \ + dParamSuspensionERP, \ + dParamSuspensionCFM, \ + dParamERP, \ + +#define D_ALL_PARAM_NAMES_X(start,x) \ + /* parameters for limits and motors */ \ + dParamLoStop ## x = start, \ + dParamHiStop ## x, \ + dParamVel ## x, \ + dParamFMax ## x, \ + dParamFudgeFactor ## x, \ + dParamBounce ## x, \ + dParamCFM ## x, \ + dParamStopERP ## x, \ + dParamStopCFM ## x, \ + /* parameters for suspension */ \ + dParamSuspensionERP ## x, \ + dParamSuspensionCFM ## x, \ + dParamERP ## x, + +enum { + D_ALL_PARAM_NAMES(0) + D_ALL_PARAM_NAMES_X(0x100,2) + D_ALL_PARAM_NAMES_X(0x200,3) + + /* add a multiple of this constant to the basic parameter numbers to get + * the parameters for the second, third etc axes. + */ + dParamGroup=0x100 +}; + +typedef struct dMass +{ + dReal mass; + dVector3 c; + dMatrix3 I; +} +dMass; + +enum +{ + dContactMu2 = 0x001, + dContactFDir1 = 0x002, + dContactBounce = 0x004, + dContactSoftERP = 0x008, + dContactSoftCFM = 0x010, + dContactMotion1 = 0x020, + dContactMotion2 = 0x040, + dContactMotionN = 0x080, + dContactSlip1 = 0x100, + dContactSlip2 = 0x200, + + dContactApprox0 = 0x0000, + dContactApprox1_1 = 0x1000, + dContactApprox1_2 = 0x2000, + dContactApprox1 = 0x3000 +}; + +typedef struct dSurfaceParameters +{ + /* must always be defined */ + int mode; + dReal mu; + + /* only defined if the corresponding flag is set in mode */ + dReal mu2; + dReal bounce; + dReal bounce_vel; + dReal soft_erp; + dReal soft_cfm; + dReal motion1,motion2,motionN; + dReal slip1,slip2; +} dSurfaceParameters; + +typedef struct dContactGeom +{ + dVector3 pos; ///< contact position + dVector3 normal; ///< normal vector + dReal depth; ///< penetration depth + dGeomID g1,g2; ///< the colliding geoms + int side1,side2; ///< (to be documented) +} +dContactGeom; + +typedef struct dContact +{ + dSurfaceParameters surface; + dContactGeom geom; + dVector3 fdir1; +} +dContact; + +typedef void dNearCallback (void *data, dGeomID o1, dGeomID o2); + +// SAP +// Order XZY or ZXY usually works best, if your Y is up. +#define dSAP_AXES_XYZ ((0)|(1<<2)|(2<<4)) +#define dSAP_AXES_XZY ((0)|(2<<2)|(1<<4)) +#define dSAP_AXES_YXZ ((1)|(0<<2)|(2<<4)) +#define dSAP_AXES_YZX ((1)|(2<<2)|(0<<4)) +#define dSAP_AXES_ZXY ((2)|(0<<2)|(1<<4)) +#define dSAP_AXES_ZYX ((2)|(1<<2)|(0<<4)) + +const char* (ODE_API *dGetConfiguration)(void); +int (ODE_API *dCheckConfiguration)( const char* token ); +int (ODE_API *dInitODE)(void); +//int (ODE_API *dInitODE2)(unsigned int uiInitFlags); +//int (ODE_API *dAllocateODEDataForThread)(unsigned int uiAllocateFlags); +//void (ODE_API *dCleanupODEAllDataForThread)(void); +void (ODE_API *dCloseODE)(void); + +//int (ODE_API *dMassCheck)(const dMass *m); +//void (ODE_API *dMassSetZero)(dMass *); +//void (ODE_API *dMassSetParameters)(dMass *, dReal themass, dReal cgx, dReal cgy, dReal cgz, dReal I11, dReal I22, dReal I33, dReal I12, dReal I13, dReal I23); +//void (ODE_API *dMassSetSphere)(dMass *, dReal density, dReal radius); +void (ODE_API *dMassSetSphereTotal)(dMass *, dReal total_mass, dReal radius); +//void (ODE_API *dMassSetCapsule)(dMass *, dReal density, int direction, dReal radius, dReal length); +void (ODE_API *dMassSetCapsuleTotal)(dMass *, dReal total_mass, int direction, dReal radius, dReal length); +//void (ODE_API *dMassSetCylinder)(dMass *, dReal density, int direction, dReal radius, dReal length); +void (ODE_API *dMassSetCylinderTotal)(dMass *, dReal total_mass, int direction, dReal radius, dReal length); +//void (ODE_API *dMassSetBox)(dMass *, dReal density, dReal lx, dReal ly, dReal lz); +void (ODE_API *dMassSetBoxTotal)(dMass *, dReal total_mass, dReal lx, dReal ly, dReal lz); +//void (ODE_API *dMassSetTrimesh)(dMass *, dReal density, dGeomID g); +//void (ODE_API *dMassSetTrimeshTotal)(dMass *m, dReal total_mass, dGeomID g); +//void (ODE_API *dMassAdjust)(dMass *, dReal newmass); +//void (ODE_API *dMassTranslate)(dMass *, dReal x, dReal y, dReal z); +//void (ODE_API *dMassRotate)(dMass *, const dMatrix3 R); +//void (ODE_API *dMassAdd)(dMass *a, const dMass *b); +// +dWorldID (ODE_API *dWorldCreate)(void); +void (ODE_API *dWorldDestroy)(dWorldID world); +void (ODE_API *dWorldSetGravity)(dWorldID, dReal x, dReal y, dReal z); +void (ODE_API *dWorldGetGravity)(dWorldID, dVector3 gravity); +void (ODE_API *dWorldSetERP)(dWorldID, dReal erp); +//dReal (ODE_API *dWorldGetERP)(dWorldID); +void (ODE_API *dWorldSetCFM)(dWorldID, dReal cfm); +//dReal (ODE_API *dWorldGetCFM)(dWorldID); +//void (ODE_API *dWorldStep)(dWorldID, dReal stepsize); +//void (ODE_API *dWorldImpulseToForce)(dWorldID, dReal stepsize, dReal ix, dReal iy, dReal iz, dVector3 force); +void (ODE_API *dWorldQuickStep)(dWorldID w, dReal stepsize); +void (ODE_API *dWorldSetQuickStepNumIterations)(dWorldID, int num); +//int (ODE_API *dWorldGetQuickStepNumIterations)(dWorldID); +//void (ODE_API *dWorldSetQuickStepW)(dWorldID, dReal over_relaxation); +//dReal (ODE_API *dWorldGetQuickStepW)(dWorldID); +//void (ODE_API *dWorldSetContactMaxCorrectingVel)(dWorldID, dReal vel); +//dReal (ODE_API *dWorldGetContactMaxCorrectingVel)(dWorldID); +void (ODE_API *dWorldSetContactSurfaceLayer)(dWorldID, dReal depth); +//dReal (ODE_API *dWorldGetContactSurfaceLayer)(dWorldID); +//void (ODE_API *dWorldStepFast1)(dWorldID, dReal stepsize, int maxiterations); +//void (ODE_API *dWorldSetAutoEnableDepthSF1)(dWorldID, int autoEnableDepth); +//int (ODE_API *dWorldGetAutoEnableDepthSF1)(dWorldID); +//dReal (ODE_API *dWorldGetAutoDisableLinearThreshold)(dWorldID); +void (ODE_API *dWorldSetAutoDisableLinearThreshold)(dWorldID, dReal linear_threshold); +//dReal (ODE_API *dWorldGetAutoDisableAngularThreshold)(dWorldID); +void (ODE_API *dWorldSetAutoDisableAngularThreshold)(dWorldID, dReal angular_threshold); +//dReal (ODE_API *dWorldGetAutoDisableLinearAverageThreshold)(dWorldID); +//void (ODE_API *dWorldSetAutoDisableLinearAverageThreshold)(dWorldID, dReal linear_average_threshold); +//dReal (ODE_API *dWorldGetAutoDisableAngularAverageThreshold)(dWorldID); +//void (ODE_API *dWorldSetAutoDisableAngularAverageThreshold)(dWorldID, dReal angular_average_threshold); +//int (ODE_API *dWorldGetAutoDisableAverageSamplesCount)(dWorldID); +void (ODE_API *dWorldSetAutoDisableAverageSamplesCount)(dWorldID, unsigned int average_samples_count ); +//int (ODE_API *dWorldGetAutoDisableSteps)(dWorldID); +void (ODE_API *dWorldSetAutoDisableSteps)(dWorldID, int steps); +//dReal (ODE_API *dWorldGetAutoDisableTime)(dWorldID); +void (ODE_API *dWorldSetAutoDisableTime)(dWorldID, dReal time); +//int (ODE_API *dWorldGetAutoDisableFlag)(dWorldID); +void (ODE_API *dWorldSetAutoDisableFlag)(dWorldID, int do_auto_disable); +//dReal (ODE_API *dWorldGetLinearDampingThreshold)(dWorldID w); +void (ODE_API *dWorldSetLinearDampingThreshold)(dWorldID w, dReal threshold); +//dReal (ODE_API *dWorldGetAngularDampingThreshold)(dWorldID w); +void (ODE_API *dWorldSetAngularDampingThreshold)(dWorldID w, dReal threshold); +//dReal (ODE_API *dWorldGetLinearDamping)(dWorldID w); +void (ODE_API *dWorldSetLinearDamping)(dWorldID w, dReal scale); +//dReal (ODE_API *dWorldGetAngularDamping)(dWorldID w); +void (ODE_API *dWorldSetAngularDamping)(dWorldID w, dReal scale); +//void (ODE_API *dWorldSetDamping)(dWorldID w, dReal linear_scale, dReal angular_scale); +//dReal (ODE_API *dWorldGetMaxAngularSpeed)(dWorldID w); +//void (ODE_API *dWorldSetMaxAngularSpeed)(dWorldID w, dReal max_speed); +//dReal (ODE_API *dBodyGetAutoDisableLinearThreshold)(dBodyID); +//void (ODE_API *dBodySetAutoDisableLinearThreshold)(dBodyID, dReal linear_average_threshold); +//dReal (ODE_API *dBodyGetAutoDisableAngularThreshold)(dBodyID); +//void (ODE_API *dBodySetAutoDisableAngularThreshold)(dBodyID, dReal angular_average_threshold); +//int (ODE_API *dBodyGetAutoDisableAverageSamplesCount)(dBodyID); +//void (ODE_API *dBodySetAutoDisableAverageSamplesCount)(dBodyID, unsigned int average_samples_count); +//int (ODE_API *dBodyGetAutoDisableSteps)(dBodyID); +//void (ODE_API *dBodySetAutoDisableSteps)(dBodyID, int steps); +//dReal (ODE_API *dBodyGetAutoDisableTime)(dBodyID); +//void (ODE_API *dBodySetAutoDisableTime)(dBodyID, dReal time); +//int (ODE_API *dBodyGetAutoDisableFlag)(dBodyID); +//void (ODE_API *dBodySetAutoDisableFlag)(dBodyID, int do_auto_disable); +//void (ODE_API *dBodySetAutoDisableDefaults)(dBodyID); +//dWorldID (ODE_API *dBodyGetWorld)(dBodyID); +dBodyID (ODE_API *dBodyCreate)(dWorldID); +void (ODE_API *dBodyDestroy)(dBodyID); +void (ODE_API *dBodySetData)(dBodyID, void *data); +void * (ODE_API *dBodyGetData)(dBodyID); +void (ODE_API *dBodySetPosition)(dBodyID, dReal x, dReal y, dReal z); +void (ODE_API *dBodySetRotation)(dBodyID, const dMatrix3 R); +//void (ODE_API *dBodySetQuaternion)(dBodyID, const dQuaternion q); +void (ODE_API *dBodySetLinearVel)(dBodyID, dReal x, dReal y, dReal z); +void (ODE_API *dBodySetAngularVel)(dBodyID, dReal x, dReal y, dReal z); +const dReal * (ODE_API *dBodyGetPosition)(dBodyID); +//void (ODE_API *dBodyCopyPosition)(dBodyID body, dVector3 pos); +const dReal * (ODE_API *dBodyGetRotation)(dBodyID); +//void (ODE_API *dBodyCopyRotation)(dBodyID, dMatrix3 R); +//const dReal * (ODE_API *dBodyGetQuaternion)(dBodyID); +//void (ODE_API *dBodyCopyQuaternion)(dBodyID body, dQuaternion quat); +const dReal * (ODE_API *dBodyGetLinearVel)(dBodyID); +const dReal * (ODE_API *dBodyGetAngularVel)(dBodyID); +void (ODE_API *dBodySetMass)(dBodyID, const dMass *mass); +//void (ODE_API *dBodyGetMass)(dBodyID, dMass *mass); +void (ODE_API *dBodyAddForce)(dBodyID, dReal fx, dReal fy, dReal fz); +void (ODE_API *dBodyAddTorque)(dBodyID, dReal fx, dReal fy, dReal fz); +//void (ODE_API *dBodyAddRelForce)(dBodyID, dReal fx, dReal fy, dReal fz); +//void (ODE_API *dBodyAddRelTorque)(dBodyID, dReal fx, dReal fy, dReal fz); +void (ODE_API *dBodyAddForceAtPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); +//void (ODE_API *dBodyAddForceAtRelPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); +//void (ODE_API *dBodyAddRelForceAtPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); +//void (ODE_API *dBodyAddRelForceAtRelPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); +//const dReal * (ODE_API *dBodyGetForce)(dBodyID); +//const dReal * (ODE_API *dBodyGetTorque)(dBodyID); +//void (ODE_API *dBodySetForce)(dBodyID b, dReal x, dReal y, dReal z); +//void (ODE_API *dBodySetTorque)(dBodyID b, dReal x, dReal y, dReal z); +//void (ODE_API *dBodyGetRelPointPos)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyGetRelPointVel)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyGetPointVel)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyGetPosRelPoint)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyVectorToWorld)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodyVectorFromWorld)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); +//void (ODE_API *dBodySetFiniteRotationMode)(dBodyID, int mode); +//void (ODE_API *dBodySetFiniteRotationAxis)(dBodyID, dReal x, dReal y, dReal z); +//int (ODE_API *dBodyGetFiniteRotationMode)(dBodyID); +//void (ODE_API *dBodyGetFiniteRotationAxis)(dBodyID, dVector3 result); +int (ODE_API *dBodyGetNumJoints)(dBodyID b); +dJointID (ODE_API *dBodyGetJoint)(dBodyID, int index); +//void (ODE_API *dBodySetDynamic)(dBodyID); +//void (ODE_API *dBodySetKinematic)(dBodyID); +//int (ODE_API *dBodyIsKinematic)(dBodyID); +void (ODE_API *dBodyEnable)(dBodyID); +void (ODE_API *dBodyDisable)(dBodyID); +int (ODE_API *dBodyIsEnabled)(dBodyID); +void (ODE_API *dBodySetGravityMode)(dBodyID b, int mode); +int (ODE_API *dBodyGetGravityMode)(dBodyID b); +//void (*dBodySetMovedCallback)(dBodyID b, void(ODE_API *callback)(dBodyID)); +//dGeomID (ODE_API *dBodyGetFirstGeom)(dBodyID b); +//dGeomID (ODE_API *dBodyGetNextGeom)(dGeomID g); +//void (ODE_API *dBodySetDampingDefaults)(dBodyID b); +//dReal (ODE_API *dBodyGetLinearDamping)(dBodyID b); +//void (ODE_API *dBodySetLinearDamping)(dBodyID b, dReal scale); +//dReal (ODE_API *dBodyGetAngularDamping)(dBodyID b); +//void (ODE_API *dBodySetAngularDamping)(dBodyID b, dReal scale); +//void (ODE_API *dBodySetDamping)(dBodyID b, dReal linear_scale, dReal angular_scale); +//dReal (ODE_API *dBodyGetLinearDampingThreshold)(dBodyID b); +//void (ODE_API *dBodySetLinearDampingThreshold)(dBodyID b, dReal threshold); +//dReal (ODE_API *dBodyGetAngularDampingThreshold)(dBodyID b); +//void (ODE_API *dBodySetAngularDampingThreshold)(dBodyID b, dReal threshold); +//dReal (ODE_API *dBodyGetMaxAngularSpeed)(dBodyID b); +//void (ODE_API *dBodySetMaxAngularSpeed)(dBodyID b, dReal max_speed); +//int (ODE_API *dBodyGetGyroscopicMode)(dBodyID b); +//void (ODE_API *dBodySetGyroscopicMode)(dBodyID b, int enabled); +dJointID (ODE_API *dJointCreateBall)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateHinge)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateSlider)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateContact)(dWorldID, dJointGroupID, const dContact *); +dJointID (ODE_API *dJointCreateHinge2)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateUniversal)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreatePR)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreatePU)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreatePiston)(dWorldID, dJointGroupID); +dJointID (ODE_API *dJointCreateFixed)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreateNull)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreateAMotor)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreateLMotor)(dWorldID, dJointGroupID); +//dJointID (ODE_API *dJointCreatePlane2D)(dWorldID, dJointGroupID); +void (ODE_API *dJointDestroy)(dJointID); +dJointGroupID (ODE_API *dJointGroupCreate)(int max_size); +void (ODE_API *dJointGroupDestroy)(dJointGroupID); +void (ODE_API *dJointGroupEmpty)(dJointGroupID); +//int (ODE_API *dJointGetNumBodies)(dJointID); +void (ODE_API *dJointAttach)(dJointID, dBodyID body1, dBodyID body2); +//void (ODE_API *dJointEnable)(dJointID); +//void (ODE_API *dJointDisable)(dJointID); +//int (ODE_API *dJointIsEnabled)(dJointID); +void (ODE_API *dJointSetData)(dJointID, void *data); +void * (ODE_API *dJointGetData)(dJointID); +//dJointType (ODE_API *dJointGetType)(dJointID); +dBodyID (ODE_API *dJointGetBody)(dJointID, int index); +//void (ODE_API *dJointSetFeedback)(dJointID, dJointFeedback *); +//dJointFeedback *(ODE_API *dJointGetFeedback)(dJointID); +void (ODE_API *dJointSetBallAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetBallAnchor2)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetBallParam)(dJointID, int parameter, dReal value); +void (ODE_API *dJointSetHingeAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetHingeAnchorDelta)(dJointID, dReal x, dReal y, dReal z, dReal ax, dReal ay, dReal az); +void (ODE_API *dJointSetHingeAxis)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetHingeAxisOffset)(dJointID j, dReal x, dReal y, dReal z, dReal angle); +void (ODE_API *dJointSetHingeParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddHingeTorque)(dJointID joint, dReal torque); +void (ODE_API *dJointSetSliderAxis)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetSliderAxisDelta)(dJointID, dReal x, dReal y, dReal z, dReal ax, dReal ay, dReal az); +void (ODE_API *dJointSetSliderParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddSliderForce)(dJointID joint, dReal force); +void (ODE_API *dJointSetHinge2Anchor)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetHinge2Axis1)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetHinge2Axis2)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetHinge2Param)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddHinge2Torques)(dJointID joint, dReal torque1, dReal torque2); +void (ODE_API *dJointSetUniversalAnchor)(dJointID, dReal x, dReal y, dReal z); +void (ODE_API *dJointSetUniversalAxis1)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetUniversalAxis1Offset)(dJointID, dReal x, dReal y, dReal z, dReal offset1, dReal offset2); +void (ODE_API *dJointSetUniversalAxis2)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetUniversalAxis2Offset)(dJointID, dReal x, dReal y, dReal z, dReal offset1, dReal offset2); +void (ODE_API *dJointSetUniversalParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddUniversalTorques)(dJointID joint, dReal torque1, dReal torque2); +//void (ODE_API *dJointSetPRAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPRAxis1)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPRAxis2)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPRParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddPRTorque)(dJointID j, dReal torque); +//void (ODE_API *dJointSetPUAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUAnchorOffset)(dJointID, dReal x, dReal y, dReal z, dReal dx, dReal dy, dReal dz); +//void (ODE_API *dJointSetPUAxis1)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUAxis2)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUAxis3)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUAxisP)(dJointID id, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPUParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddPUTorque)(dJointID j, dReal torque); +//void (ODE_API *dJointSetPistonAnchor)(dJointID, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetPistonAnchorOffset)(dJointID j, dReal x, dReal y, dReal z, dReal dx, dReal dy, dReal dz); +//void (ODE_API *dJointSetPistonParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointAddPistonForce)(dJointID joint, dReal force); +//void (ODE_API *dJointSetFixed)(dJointID); +//void (ODE_API *dJointSetFixedParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetAMotorNumAxes)(dJointID, int num); +//void (ODE_API *dJointSetAMotorAxis)(dJointID, int anum, int rel, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetAMotorAngle)(dJointID, int anum, dReal angle); +//void (ODE_API *dJointSetAMotorParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetAMotorMode)(dJointID, int mode); +//void (ODE_API *dJointAddAMotorTorques)(dJointID, dReal torque1, dReal torque2, dReal torque3); +//void (ODE_API *dJointSetLMotorNumAxes)(dJointID, int num); +//void (ODE_API *dJointSetLMotorAxis)(dJointID, int anum, int rel, dReal x, dReal y, dReal z); +//void (ODE_API *dJointSetLMotorParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetPlane2DXParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetPlane2DYParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointSetPlane2DAngleParam)(dJointID, int parameter, dReal value); +//void (ODE_API *dJointGetBallAnchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetBallAnchor2)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetBallParam)(dJointID, int parameter); +//void (ODE_API *dJointGetHingeAnchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHingeAnchor2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHingeAxis)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetHingeParam)(dJointID, int parameter); +//dReal (ODE_API *dJointGetHingeAngle)(dJointID); +//dReal (ODE_API *dJointGetHingeAngleRate)(dJointID); +//dReal (ODE_API *dJointGetSliderPosition)(dJointID); +//dReal (ODE_API *dJointGetSliderPositionRate)(dJointID); +//void (ODE_API *dJointGetSliderAxis)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetSliderParam)(dJointID, int parameter); +//void (ODE_API *dJointGetHinge2Anchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHinge2Anchor2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHinge2Axis1)(dJointID, dVector3 result); +//void (ODE_API *dJointGetHinge2Axis2)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetHinge2Param)(dJointID, int parameter); +//dReal (ODE_API *dJointGetHinge2Angle1)(dJointID); +//dReal (ODE_API *dJointGetHinge2Angle1Rate)(dJointID); +//dReal (ODE_API *dJointGetHinge2Angle2Rate)(dJointID); +//void (ODE_API *dJointGetUniversalAnchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetUniversalAnchor2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetUniversalAxis1)(dJointID, dVector3 result); +//void (ODE_API *dJointGetUniversalAxis2)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetUniversalParam)(dJointID, int parameter); +//void (ODE_API *dJointGetUniversalAngles)(dJointID, dReal *angle1, dReal *angle2); +//dReal (ODE_API *dJointGetUniversalAngle1)(dJointID); +//dReal (ODE_API *dJointGetUniversalAngle2)(dJointID); +//dReal (ODE_API *dJointGetUniversalAngle1Rate)(dJointID); +//dReal (ODE_API *dJointGetUniversalAngle2Rate)(dJointID); +//void (ODE_API *dJointGetPRAnchor)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetPRPosition)(dJointID); +//dReal (ODE_API *dJointGetPRPositionRate)(dJointID); +//dReal (ODE_API *dJointGetPRAngle)(dJointID); +//dReal (ODE_API *dJointGetPRAngleRate)(dJointID); +//void (ODE_API *dJointGetPRAxis1)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPRAxis2)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetPRParam)(dJointID, int parameter); +//void (ODE_API *dJointGetPUAnchor)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetPUPosition)(dJointID); +//dReal (ODE_API *dJointGetPUPositionRate)(dJointID); +//void (ODE_API *dJointGetPUAxis1)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPUAxis2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPUAxis3)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPUAxisP)(dJointID id, dVector3 result); +//void (ODE_API *dJointGetPUAngles)(dJointID, dReal *angle1, dReal *angle2); +//dReal (ODE_API *dJointGetPUAngle1)(dJointID); +//dReal (ODE_API *dJointGetPUAngle1Rate)(dJointID); +//dReal (ODE_API *dJointGetPUAngle2)(dJointID); +//dReal (ODE_API *dJointGetPUAngle2Rate)(dJointID); +//dReal (ODE_API *dJointGetPUParam)(dJointID, int parameter); +//dReal (ODE_API *dJointGetPistonPosition)(dJointID); +//dReal (ODE_API *dJointGetPistonPositionRate)(dJointID); +//dReal (ODE_API *dJointGetPistonAngle)(dJointID); +//dReal (ODE_API *dJointGetPistonAngleRate)(dJointID); +//void (ODE_API *dJointGetPistonAnchor)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPistonAnchor2)(dJointID, dVector3 result); +//void (ODE_API *dJointGetPistonAxis)(dJointID, dVector3 result); +//dReal (ODE_API *dJointGetPistonParam)(dJointID, int parameter); +//int (ODE_API *dJointGetAMotorNumAxes)(dJointID); +//void (ODE_API *dJointGetAMotorAxis)(dJointID, int anum, dVector3 result); +//int (ODE_API *dJointGetAMotorAxisRel)(dJointID, int anum); +//dReal (ODE_API *dJointGetAMotorAngle)(dJointID, int anum); +//dReal (ODE_API *dJointGetAMotorAngleRate)(dJointID, int anum); +//dReal (ODE_API *dJointGetAMotorParam)(dJointID, int parameter); +//int (ODE_API *dJointGetAMotorMode)(dJointID); +//int (ODE_API *dJointGetLMotorNumAxes)(dJointID); +//void (ODE_API *dJointGetLMotorAxis)(dJointID, int anum, dVector3 result); +//dReal (ODE_API *dJointGetLMotorParam)(dJointID, int parameter); +//dReal (ODE_API *dJointGetFixedParam)(dJointID, int parameter); +//dJointID (ODE_API *dConnectingJoint)(dBodyID, dBodyID); +//int (ODE_API *dConnectingJointList)(dBodyID, dBodyID, dJointID*); +int (ODE_API *dAreConnected)(dBodyID, dBodyID); +int (ODE_API *dAreConnectedExcluding)(dBodyID body1, dBodyID body2, int joint_type); +// +dSpaceID (ODE_API *dSimpleSpaceCreate)(dSpaceID space); +dSpaceID (ODE_API *dHashSpaceCreate)(dSpaceID space); +dSpaceID (ODE_API *dQuadTreeSpaceCreate)(dSpaceID space, const dVector3 Center, const dVector3 Extents, int Depth); +//dSpaceID (ODE_API *dSweepAndPruneSpaceCreate)( dSpaceID space, int axisorder ); +void (ODE_API *dSpaceDestroy)(dSpaceID); +//void (ODE_API *dHashSpaceSetLevels)(dSpaceID space, int minlevel, int maxlevel); +//void (ODE_API *dHashSpaceGetLevels)(dSpaceID space, int *minlevel, int *maxlevel); +//void (ODE_API *dSpaceSetCleanup)(dSpaceID space, int mode); +//int (ODE_API *dSpaceGetCleanup)(dSpaceID space); +//void (ODE_API *dSpaceSetSublevel)(dSpaceID space, int sublevel); +//int (ODE_API *dSpaceGetSublevel)(dSpaceID space); +//void (ODE_API *dSpaceSetManualCleanup)(dSpaceID space, int mode); +//int (ODE_API *dSpaceGetManualCleanup)(dSpaceID space); +//void (ODE_API *dSpaceAdd)(dSpaceID, dGeomID); +//void (ODE_API *dSpaceRemove)(dSpaceID, dGeomID); +//int (ODE_API *dSpaceQuery)(dSpaceID, dGeomID); +//void (ODE_API *dSpaceClean)(dSpaceID); +//int (ODE_API *dSpaceGetNumGeoms)(dSpaceID); +//dGeomID (ODE_API *dSpaceGetGeom)(dSpaceID, int i); +//int (ODE_API *dSpaceGetClass)(dSpaceID space); +// +void (ODE_API *dGeomDestroy)(dGeomID geom); +void (ODE_API *dGeomSetData)(dGeomID geom, void* data); +void * (ODE_API *dGeomGetData)(dGeomID geom); +void (ODE_API *dGeomSetBody)(dGeomID geom, dBodyID body); +dBodyID (ODE_API *dGeomGetBody)(dGeomID geom); +void (ODE_API *dGeomSetPosition)(dGeomID geom, dReal x, dReal y, dReal z); +void (ODE_API *dGeomSetRotation)(dGeomID geom, const dMatrix3 R); +//void (ODE_API *dGeomSetQuaternion)(dGeomID geom, const dQuaternion Q); +//const dReal * (ODE_API *dGeomGetPosition)(dGeomID geom); +//void (ODE_API *dGeomCopyPosition)(dGeomID geom, dVector3 pos); +//const dReal * (ODE_API *dGeomGetRotation)(dGeomID geom); +//void (ODE_API *dGeomCopyRotation)(dGeomID geom, dMatrix3 R); +//void (ODE_API *dGeomGetQuaternion)(dGeomID geom, dQuaternion result); +//void (ODE_API *dGeomGetAABB)(dGeomID geom, dReal aabb[6]); +int (ODE_API *dGeomIsSpace)(dGeomID geom); +//dSpaceID (ODE_API *dGeomGetSpace)(dGeomID); +//int (ODE_API *dGeomGetClass)(dGeomID geom); +//void (ODE_API *dGeomSetCategoryBits)(dGeomID geom, unsigned long bits); +//void (ODE_API *dGeomSetCollideBits)(dGeomID geom, unsigned long bits); +//unsigned long (ODE_API *dGeomGetCategoryBits)(dGeomID); +//unsigned long (ODE_API *dGeomGetCollideBits)(dGeomID); +//void (ODE_API *dGeomEnable)(dGeomID geom); +//void (ODE_API *dGeomDisable)(dGeomID geom); +//int (ODE_API *dGeomIsEnabled)(dGeomID geom); +//void (ODE_API *dGeomSetOffsetPosition)(dGeomID geom, dReal x, dReal y, dReal z); +//void (ODE_API *dGeomSetOffsetRotation)(dGeomID geom, const dMatrix3 R); +//void (ODE_API *dGeomSetOffsetQuaternion)(dGeomID geom, const dQuaternion Q); +//void (ODE_API *dGeomSetOffsetWorldPosition)(dGeomID geom, dReal x, dReal y, dReal z); +//void (ODE_API *dGeomSetOffsetWorldRotation)(dGeomID geom, const dMatrix3 R); +//void (ODE_API *dGeomSetOffsetWorldQuaternion)(dGeomID geom, const dQuaternion); +//void (ODE_API *dGeomClearOffset)(dGeomID geom); +//int (ODE_API *dGeomIsOffset)(dGeomID geom); +//const dReal * (ODE_API *dGeomGetOffsetPosition)(dGeomID geom); +//void (ODE_API *dGeomCopyOffsetPosition)(dGeomID geom, dVector3 pos); +//const dReal * (ODE_API *dGeomGetOffsetRotation)(dGeomID geom); +//void (ODE_API *dGeomCopyOffsetRotation)(dGeomID geom, dMatrix3 R); +//void (ODE_API *dGeomGetOffsetQuaternion)(dGeomID geom, dQuaternion result); +int (ODE_API *dCollide)(dGeomID o1, dGeomID o2, int flags, dContactGeom *contact, int skip); +// +void (ODE_API *dSpaceCollide)(dSpaceID space, void *data, dNearCallback *callback); +void (ODE_API *dSpaceCollide2)(dGeomID space1, dGeomID space2, void *data, dNearCallback *callback); +// +dGeomID (ODE_API *dCreateSphere)(dSpaceID space, dReal radius); +//void (ODE_API *dGeomSphereSetRadius)(dGeomID sphere, dReal radius); +//dReal (ODE_API *dGeomSphereGetRadius)(dGeomID sphere); +//dReal (ODE_API *dGeomSpherePointDepth)(dGeomID sphere, dReal x, dReal y, dReal z); +// +dGeomID (ODE_API *dCreateConvex)(dSpaceID space, dReal *_planes, unsigned int _planecount, dReal *_points, unsigned int _pointcount,unsigned int *_polygons); +//void (ODE_API *dGeomSetConvex)(dGeomID g, dReal *_planes, unsigned int _count, dReal *_points, unsigned int _pointcount,unsigned int *_polygons); +// +dGeomID (ODE_API *dCreateBox)(dSpaceID space, dReal lx, dReal ly, dReal lz); +//void (ODE_API *dGeomBoxSetLengths)(dGeomID box, dReal lx, dReal ly, dReal lz); +//void (ODE_API *dGeomBoxGetLengths)(dGeomID box, dVector3 result); +//dReal (ODE_API *dGeomBoxPointDepth)(dGeomID box, dReal x, dReal y, dReal z); +//dReal (ODE_API *dGeomBoxPointDepth)(dGeomID box, dReal x, dReal y, dReal z); +// +//dGeomID (ODE_API *dCreatePlane)(dSpaceID space, dReal a, dReal b, dReal c, dReal d); +//void (ODE_API *dGeomPlaneSetParams)(dGeomID plane, dReal a, dReal b, dReal c, dReal d); +//void (ODE_API *dGeomPlaneGetParams)(dGeomID plane, dVector4 result); +//dReal (ODE_API *dGeomPlanePointDepth)(dGeomID plane, dReal x, dReal y, dReal z); +// +dGeomID (ODE_API *dCreateCapsule)(dSpaceID space, dReal radius, dReal length); +//void (ODE_API *dGeomCapsuleSetParams)(dGeomID ccylinder, dReal radius, dReal length); +//void (ODE_API *dGeomCapsuleGetParams)(dGeomID ccylinder, dReal *radius, dReal *length); +//dReal (ODE_API *dGeomCapsulePointDepth)(dGeomID ccylinder, dReal x, dReal y, dReal z); +// +dGeomID (ODE_API *dCreateCylinder)(dSpaceID space, dReal radius, dReal length); +//void (ODE_API *dGeomCylinderSetParams)(dGeomID cylinder, dReal radius, dReal length); +//void (ODE_API *dGeomCylinderGetParams)(dGeomID cylinder, dReal *radius, dReal *length); +// +//dGeomID (ODE_API *dCreateRay)(dSpaceID space, dReal length); +//void (ODE_API *dGeomRaySetLength)(dGeomID ray, dReal length); +//dReal (ODE_API *dGeomRayGetLength)(dGeomID ray); +//void (ODE_API *dGeomRaySet)(dGeomID ray, dReal px, dReal py, dReal pz, dReal dx, dReal dy, dReal dz); +//void (ODE_API *dGeomRayGet)(dGeomID ray, dVector3 start, dVector3 dir); +// +dGeomID (ODE_API *dCreateGeomTransform)(dSpaceID space); +void (ODE_API *dGeomTransformSetGeom)(dGeomID g, dGeomID obj); +//dGeomID (ODE_API *dGeomTransformGetGeom)(dGeomID g); +void (ODE_API *dGeomTransformSetCleanup)(dGeomID g, int mode); +//int (ODE_API *dGeomTransformGetCleanup)(dGeomID g); +//void (ODE_API *dGeomTransformSetInfo)(dGeomID g, int mode); +//int (ODE_API *dGeomTransformGetInfo)(dGeomID g); + +enum { TRIMESH_FACE_NORMALS }; +typedef int dTriCallback(dGeomID TriMesh, dGeomID RefObject, int TriangleIndex); +typedef void dTriArrayCallback(dGeomID TriMesh, dGeomID RefObject, const int* TriIndices, int TriCount); +typedef int dTriRayCallback(dGeomID TriMesh, dGeomID Ray, int TriangleIndex, dReal u, dReal v); +typedef int dTriTriMergeCallback(dGeomID TriMesh, int FirstTriangleIndex, int SecondTriangleIndex); + +dTriMeshDataID (ODE_API *dGeomTriMeshDataCreate)(void); +void (ODE_API *dGeomTriMeshDataDestroy)(dTriMeshDataID g); +//void (ODE_API *dGeomTriMeshDataSet)(dTriMeshDataID g, int data_id, void* in_data); +//void* (ODE_API *dGeomTriMeshDataGet)(dTriMeshDataID g, int data_id); +//void (*dGeomTriMeshSetLastTransform)( (ODE_API *dGeomID g, dMatrix4 last_trans ); +//dReal* (*dGeomTriMeshGetLastTransform)( (ODE_API *dGeomID g ); +void (ODE_API *dGeomTriMeshDataBuildSingle)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride); +//void (ODE_API *dGeomTriMeshDataBuildSingle1)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride, const void* Normals); +//void (ODE_API *dGeomTriMeshDataBuildDouble)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride); +//void (ODE_API *dGeomTriMeshDataBuildDouble1)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride, const void* Normals); +//void (ODE_API *dGeomTriMeshDataBuildSimple)(dTriMeshDataID g, const dReal* Vertices, int VertexCount, const dTriIndex* Indices, int IndexCount); +//void (ODE_API *dGeomTriMeshDataBuildSimple1)(dTriMeshDataID g, const dReal* Vertices, int VertexCount, const dTriIndex* Indices, int IndexCount, const int* Normals); +//void (ODE_API *dGeomTriMeshDataPreprocess)(dTriMeshDataID g); +//void (ODE_API *dGeomTriMeshDataGetBuffer)(dTriMeshDataID g, unsigned char** buf, int* bufLen); +//void (ODE_API *dGeomTriMeshDataSetBuffer)(dTriMeshDataID g, unsigned char* buf); +//void (ODE_API *dGeomTriMeshSetCallback)(dGeomID g, dTriCallback* Callback); +//dTriCallback* (ODE_API *dGeomTriMeshGetCallback)(dGeomID g); +//void (ODE_API *dGeomTriMeshSetArrayCallback)(dGeomID g, dTriArrayCallback* ArrayCallback); +//dTriArrayCallback* (ODE_API *dGeomTriMeshGetArrayCallback)(dGeomID g); +//void (ODE_API *dGeomTriMeshSetRayCallback)(dGeomID g, dTriRayCallback* Callback); +//dTriRayCallback* (ODE_API *dGeomTriMeshGetRayCallback)(dGeomID g); +//void (ODE_API *dGeomTriMeshSetTriMergeCallback)(dGeomID g, dTriTriMergeCallback* Callback); +//dTriTriMergeCallback* (ODE_API *dGeomTriMeshGetTriMergeCallback)(dGeomID g); +dGeomID (ODE_API *dCreateTriMesh)(dSpaceID space, dTriMeshDataID Data, dTriCallback* Callback, dTriArrayCallback* ArrayCallback, dTriRayCallback* RayCallback); +//void (ODE_API *dGeomTriMeshSetData)(dGeomID g, dTriMeshDataID Data); +//dTriMeshDataID (ODE_API *dGeomTriMeshGetData)(dGeomID g); +//void (ODE_API *dGeomTriMeshEnableTC)(dGeomID g, int geomClass, int enable); +//int (ODE_API *dGeomTriMeshIsTCEnabled)(dGeomID g, int geomClass); +//void (ODE_API *dGeomTriMeshClearTCCache)(dGeomID g); +//dTriMeshDataID (ODE_API *dGeomTriMeshGetTriMeshDataID)(dGeomID g); +//void (ODE_API *dGeomTriMeshGetTriangle)(dGeomID g, int Index, dVector3* v0, dVector3* v1, dVector3* v2); +//void (ODE_API *dGeomTriMeshGetPoint)(dGeomID g, int Index, dReal u, dReal v, dVector3 Out); +//int (ODE_API *dGeomTriMeshGetTriangleCount )(dGeomID g); +//void (ODE_API *dGeomTriMeshDataUpdate)(dTriMeshDataID g); + +static dllfunction_t odefuncs[] = +{ + {"dGetConfiguration", (void **) &dGetConfiguration}, + {"dCheckConfiguration", (void **) &dCheckConfiguration}, + {"dInitODE", (void **) &dInitODE}, +// {"dInitODE2", (void **) &dInitODE2}, +// {"dAllocateODEDataForThread", (void **) &dAllocateODEDataForThread}, +// {"dCleanupODEAllDataForThread", (void **) &dCleanupODEAllDataForThread}, + {"dCloseODE", (void **) &dCloseODE}, +// {"dMassCheck", (void **) &dMassCheck}, +// {"dMassSetZero", (void **) &dMassSetZero}, +// {"dMassSetParameters", (void **) &dMassSetParameters}, +// {"dMassSetSphere", (void **) &dMassSetSphere}, + {"dMassSetSphereTotal", (void **) &dMassSetSphereTotal}, +// {"dMassSetCapsule", (void **) &dMassSetCapsule}, + {"dMassSetCapsuleTotal", (void **) &dMassSetCapsuleTotal}, +// {"dMassSetCylinder", (void **) &dMassSetCylinder}, + {"dMassSetCylinderTotal", (void **) &dMassSetCylinderTotal}, +// {"dMassSetBox", (void **) &dMassSetBox}, + {"dMassSetBoxTotal", (void **) &dMassSetBoxTotal}, +// {"dMassSetTrimesh", (void **) &dMassSetTrimesh}, +// {"dMassSetTrimeshTotal", (void **) &dMassSetTrimeshTotal}, +// {"dMassAdjust", (void **) &dMassAdjust}, +// {"dMassTranslate", (void **) &dMassTranslate}, +// {"dMassRotate", (void **) &dMassRotate}, +// {"dMassAdd", (void **) &dMassAdd}, + + {"dWorldCreate", (void **) &dWorldCreate}, + {"dWorldDestroy", (void **) &dWorldDestroy}, + {"dWorldSetGravity", (void **) &dWorldSetGravity}, + {"dWorldGetGravity", (void **) &dWorldGetGravity}, + {"dWorldSetERP", (void **) &dWorldSetERP}, +// {"dWorldGetERP", (void **) &dWorldGetERP}, + {"dWorldSetCFM", (void **) &dWorldSetCFM}, +// {"dWorldGetCFM", (void **) &dWorldGetCFM}, +// {"dWorldStep", (void **) &dWorldStep}, +// {"dWorldImpulseToForce", (void **) &dWorldImpulseToForce}, + {"dWorldQuickStep", (void **) &dWorldQuickStep}, + {"dWorldSetQuickStepNumIterations", (void **) &dWorldSetQuickStepNumIterations}, +// {"dWorldGetQuickStepNumIterations", (void **) &dWorldGetQuickStepNumIterations}, +// {"dWorldSetQuickStepW", (void **) &dWorldSetQuickStepW}, +// {"dWorldGetQuickStepW", (void **) &dWorldGetQuickStepW}, +// {"dWorldSetContactMaxCorrectingVel", (void **) &dWorldSetContactMaxCorrectingVel}, +// {"dWorldGetContactMaxCorrectingVel", (void **) &dWorldGetContactMaxCorrectingVel}, + {"dWorldSetContactSurfaceLayer", (void **) &dWorldSetContactSurfaceLayer}, +// {"dWorldGetContactSurfaceLayer", (void **) &dWorldGetContactSurfaceLayer}, +// {"dWorldStepFast1", (void **) &dWorldStepFast1}, +// {"dWorldSetAutoEnableDepthSF1", (void **) &dWorldSetAutoEnableDepthSF1}, +// {"dWorldGetAutoEnableDepthSF1", (void **) &dWorldGetAutoEnableDepthSF1}, +// {"dWorldGetAutoDisableLinearThreshold", (void **) &dWorldGetAutoDisableLinearThreshold}, + {"dWorldSetAutoDisableLinearThreshold", (void **) &dWorldSetAutoDisableLinearThreshold}, +// {"dWorldGetAutoDisableAngularThreshold", (void **) &dWorldGetAutoDisableAngularThreshold}, + {"dWorldSetAutoDisableAngularThreshold", (void **) &dWorldSetAutoDisableAngularThreshold}, +// {"dWorldGetAutoDisableLinearAverageThreshold", (void **) &dWorldGetAutoDisableLinearAverageThreshold}, +// {"dWorldSetAutoDisableLinearAverageThreshold", (void **) &dWorldSetAutoDisableLinearAverageThreshold}, +// {"dWorldGetAutoDisableAngularAverageThreshold", (void **) &dWorldGetAutoDisableAngularAverageThreshold}, +// {"dWorldSetAutoDisableAngularAverageThreshold", (void **) &dWorldSetAutoDisableAngularAverageThreshold}, +// {"dWorldGetAutoDisableAverageSamplesCount", (void **) &dWorldGetAutoDisableAverageSamplesCount}, + {"dWorldSetAutoDisableAverageSamplesCount", (void **) &dWorldSetAutoDisableAverageSamplesCount}, +// {"dWorldGetAutoDisableSteps", (void **) &dWorldGetAutoDisableSteps}, + {"dWorldSetAutoDisableSteps", (void **) &dWorldSetAutoDisableSteps}, +// {"dWorldGetAutoDisableTime", (void **) &dWorldGetAutoDisableTime}, + {"dWorldSetAutoDisableTime", (void **) &dWorldSetAutoDisableTime}, +// {"dWorldGetAutoDisableFlag", (void **) &dWorldGetAutoDisableFlag}, + {"dWorldSetAutoDisableFlag", (void **) &dWorldSetAutoDisableFlag}, +// {"dWorldGetLinearDampingThreshold", (void **) &dWorldGetLinearDampingThreshold}, + {"dWorldSetLinearDampingThreshold", (void **) &dWorldSetLinearDampingThreshold}, +// {"dWorldGetAngularDampingThreshold", (void **) &dWorldGetAngularDampingThreshold}, + {"dWorldSetAngularDampingThreshold", (void **) &dWorldSetAngularDampingThreshold}, +// {"dWorldGetLinearDamping", (void **) &dWorldGetLinearDamping}, + {"dWorldSetLinearDamping", (void **) &dWorldSetLinearDamping}, +// {"dWorldGetAngularDamping", (void **) &dWorldGetAngularDamping}, + {"dWorldSetAngularDamping", (void **) &dWorldSetAngularDamping}, +// {"dWorldSetDamping", (void **) &dWorldSetDamping}, +// {"dWorldGetMaxAngularSpeed", (void **) &dWorldGetMaxAngularSpeed}, +// {"dWorldSetMaxAngularSpeed", (void **) &dWorldSetMaxAngularSpeed}, +// {"dBodyGetAutoDisableLinearThreshold", (void **) &dBodyGetAutoDisableLinearThreshold}, +// {"dBodySetAutoDisableLinearThreshold", (void **) &dBodySetAutoDisableLinearThreshold}, +// {"dBodyGetAutoDisableAngularThreshold", (void **) &dBodyGetAutoDisableAngularThreshold}, +// {"dBodySetAutoDisableAngularThreshold", (void **) &dBodySetAutoDisableAngularThreshold}, +// {"dBodyGetAutoDisableAverageSamplesCount", (void **) &dBodyGetAutoDisableAverageSamplesCount}, +// {"dBodySetAutoDisableAverageSamplesCount", (void **) &dBodySetAutoDisableAverageSamplesCount}, +// {"dBodyGetAutoDisableSteps", (void **) &dBodyGetAutoDisableSteps}, +// {"dBodySetAutoDisableSteps", (void **) &dBodySetAutoDisableSteps}, +// {"dBodyGetAutoDisableTime", (void **) &dBodyGetAutoDisableTime}, +// {"dBodySetAutoDisableTime", (void **) &dBodySetAutoDisableTime}, +// {"dBodyGetAutoDisableFlag", (void **) &dBodyGetAutoDisableFlag}, +// {"dBodySetAutoDisableFlag", (void **) &dBodySetAutoDisableFlag}, +// {"dBodySetAutoDisableDefaults", (void **) &dBodySetAutoDisableDefaults}, +// {"dBodyGetWorld", (void **) &dBodyGetWorld}, + {"dBodyCreate", (void **) &dBodyCreate}, + {"dBodyDestroy", (void **) &dBodyDestroy}, + {"dBodySetData", (void **) &dBodySetData}, + {"dBodyGetData", (void **) &dBodyGetData}, + {"dBodySetPosition", (void **) &dBodySetPosition}, + {"dBodySetRotation", (void **) &dBodySetRotation}, +// {"dBodySetQuaternion", (void **) &dBodySetQuaternion}, + {"dBodySetLinearVel", (void **) &dBodySetLinearVel}, + {"dBodySetAngularVel", (void **) &dBodySetAngularVel}, + {"dBodyGetPosition", (void **) &dBodyGetPosition}, +// {"dBodyCopyPosition", (void **) &dBodyCopyPosition}, + {"dBodyGetRotation", (void **) &dBodyGetRotation}, +// {"dBodyCopyRotation", (void **) &dBodyCopyRotation}, +// {"dBodyGetQuaternion", (void **) &dBodyGetQuaternion}, +// {"dBodyCopyQuaternion", (void **) &dBodyCopyQuaternion}, + {"dBodyGetLinearVel", (void **) &dBodyGetLinearVel}, + {"dBodyGetAngularVel", (void **) &dBodyGetAngularVel}, + {"dBodySetMass", (void **) &dBodySetMass}, +// {"dBodyGetMass", (void **) &dBodyGetMass}, + {"dBodyAddForce", (void **) &dBodyAddForce}, + {"dBodyAddTorque", (void **) &dBodyAddTorque}, +// {"dBodyAddRelForce", (void **) &dBodyAddRelForce}, +// {"dBodyAddRelTorque", (void **) &dBodyAddRelTorque}, + {"dBodyAddForceAtPos", (void **) &dBodyAddForceAtPos}, +// {"dBodyAddForceAtRelPos", (void **) &dBodyAddForceAtRelPos}, +// {"dBodyAddRelForceAtPos", (void **) &dBodyAddRelForceAtPos}, +// {"dBodyAddRelForceAtRelPos", (void **) &dBodyAddRelForceAtRelPos}, +// {"dBodyGetForce", (void **) &dBodyGetForce}, +// {"dBodyGetTorque", (void **) &dBodyGetTorque}, +// {"dBodySetForce", (void **) &dBodySetForce}, +// {"dBodySetTorque", (void **) &dBodySetTorque}, +// {"dBodyGetRelPointPos", (void **) &dBodyGetRelPointPos}, +// {"dBodyGetRelPointVel", (void **) &dBodyGetRelPointVel}, +// {"dBodyGetPointVel", (void **) &dBodyGetPointVel}, +// {"dBodyGetPosRelPoint", (void **) &dBodyGetPosRelPoint}, +// {"dBodyVectorToWorld", (void **) &dBodyVectorToWorld}, +// {"dBodyVectorFromWorld", (void **) &dBodyVectorFromWorld}, +// {"dBodySetFiniteRotationMode", (void **) &dBodySetFiniteRotationMode}, +// {"dBodySetFiniteRotationAxis", (void **) &dBodySetFiniteRotationAxis}, +// {"dBodyGetFiniteRotationMode", (void **) &dBodyGetFiniteRotationMode}, +// {"dBodyGetFiniteRotationAxis", (void **) &dBodyGetFiniteRotationAxis}, + {"dBodyGetNumJoints", (void **) &dBodyGetNumJoints}, + {"dBodyGetJoint", (void **) &dBodyGetJoint}, +// {"dBodySetDynamic", (void **) &dBodySetDynamic}, +// {"dBodySetKinematic", (void **) &dBodySetKinematic}, +// {"dBodyIsKinematic", (void **) &dBodyIsKinematic}, + {"dBodyEnable", (void **) &dBodyEnable}, + {"dBodyDisable", (void **) &dBodyDisable}, + {"dBodyIsEnabled", (void **) &dBodyIsEnabled}, + {"dBodySetGravityMode", (void **) &dBodySetGravityMode}, + {"dBodyGetGravityMode", (void **) &dBodyGetGravityMode}, +// {"dBodySetMovedCallback", (void **) &dBodySetMovedCallback}, +// {"dBodyGetFirstGeom", (void **) &dBodyGetFirstGeom}, +// {"dBodyGetNextGeom", (void **) &dBodyGetNextGeom}, +// {"dBodySetDampingDefaults", (void **) &dBodySetDampingDefaults}, +// {"dBodyGetLinearDamping", (void **) &dBodyGetLinearDamping}, +// {"dBodySetLinearDamping", (void **) &dBodySetLinearDamping}, +// {"dBodyGetAngularDamping", (void **) &dBodyGetAngularDamping}, +// {"dBodySetAngularDamping", (void **) &dBodySetAngularDamping}, +// {"dBodySetDamping", (void **) &dBodySetDamping}, +// {"dBodyGetLinearDampingThreshold", (void **) &dBodyGetLinearDampingThreshold}, +// {"dBodySetLinearDampingThreshold", (void **) &dBodySetLinearDampingThreshold}, +// {"dBodyGetAngularDampingThreshold", (void **) &dBodyGetAngularDampingThreshold}, +// {"dBodySetAngularDampingThreshold", (void **) &dBodySetAngularDampingThreshold}, +// {"dBodyGetMaxAngularSpeed", (void **) &dBodyGetMaxAngularSpeed}, +// {"dBodySetMaxAngularSpeed", (void **) &dBodySetMaxAngularSpeed}, +// {"dBodyGetGyroscopicMode", (void **) &dBodyGetGyroscopicMode}, +// {"dBodySetGyroscopicMode", (void **) &dBodySetGyroscopicMode}, + {"dJointCreateBall", (void **) &dJointCreateBall}, + {"dJointCreateHinge", (void **) &dJointCreateHinge}, + {"dJointCreateSlider", (void **) &dJointCreateSlider}, + {"dJointCreateContact", (void **) &dJointCreateContact}, + {"dJointCreateHinge2", (void **) &dJointCreateHinge2}, + {"dJointCreateUniversal", (void **) &dJointCreateUniversal}, +// {"dJointCreatePR", (void **) &dJointCreatePR}, +// {"dJointCreatePU", (void **) &dJointCreatePU}, +// {"dJointCreatePiston", (void **) &dJointCreatePiston}, + {"dJointCreateFixed", (void **) &dJointCreateFixed}, +// {"dJointCreateNull", (void **) &dJointCreateNull}, +// {"dJointCreateAMotor", (void **) &dJointCreateAMotor}, +// {"dJointCreateLMotor", (void **) &dJointCreateLMotor}, +// {"dJointCreatePlane2D", (void **) &dJointCreatePlane2D}, + {"dJointDestroy", (void **) &dJointDestroy}, + {"dJointGroupCreate", (void **) &dJointGroupCreate}, + {"dJointGroupDestroy", (void **) &dJointGroupDestroy}, + {"dJointGroupEmpty", (void **) &dJointGroupEmpty}, +// {"dJointGetNumBodies", (void **) &dJointGetNumBodies}, + {"dJointAttach", (void **) &dJointAttach}, +// {"dJointEnable", (void **) &dJointEnable}, +// {"dJointDisable", (void **) &dJointDisable}, +// {"dJointIsEnabled", (void **) &dJointIsEnabled}, + {"dJointSetData", (void **) &dJointSetData}, + {"dJointGetData", (void **) &dJointGetData}, +// {"dJointGetType", (void **) &dJointGetType}, + {"dJointGetBody", (void **) &dJointGetBody}, +// {"dJointSetFeedback", (void **) &dJointSetFeedback}, +// {"dJointGetFeedback", (void **) &dJointGetFeedback}, + {"dJointSetBallAnchor", (void **) &dJointSetBallAnchor}, +// {"dJointSetBallAnchor2", (void **) &dJointSetBallAnchor2}, + {"dJointSetBallParam", (void **) &dJointSetBallParam}, + {"dJointSetHingeAnchor", (void **) &dJointSetHingeAnchor}, +// {"dJointSetHingeAnchorDelta", (void **) &dJointSetHingeAnchorDelta}, + {"dJointSetHingeAxis", (void **) &dJointSetHingeAxis}, +// {"dJointSetHingeAxisOffset", (void **) &dJointSetHingeAxisOffset}, + {"dJointSetHingeParam", (void **) &dJointSetHingeParam}, +// {"dJointAddHingeTorque", (void **) &dJointAddHingeTorque}, + {"dJointSetSliderAxis", (void **) &dJointSetSliderAxis}, +// {"dJointSetSliderAxisDelta", (void **) &dJointSetSliderAxisDelta}, + {"dJointSetSliderParam", (void **) &dJointSetSliderParam}, +// {"dJointAddSliderForce", (void **) &dJointAddSliderForce}, + {"dJointSetHinge2Anchor", (void **) &dJointSetHinge2Anchor}, + {"dJointSetHinge2Axis1", (void **) &dJointSetHinge2Axis1}, + {"dJointSetHinge2Axis2", (void **) &dJointSetHinge2Axis2}, + {"dJointSetHinge2Param", (void **) &dJointSetHinge2Param}, +// {"dJointAddHinge2Torques", (void **) &dJointAddHinge2Torques}, + {"dJointSetUniversalAnchor", (void **) &dJointSetUniversalAnchor}, + {"dJointSetUniversalAxis1", (void **) &dJointSetUniversalAxis1}, +// {"dJointSetUniversalAxis1Offset", (void **) &dJointSetUniversalAxis1Offset}, + {"dJointSetUniversalAxis2", (void **) &dJointSetUniversalAxis2}, +// {"dJointSetUniversalAxis2Offset", (void **) &dJointSetUniversalAxis2Offset}, + {"dJointSetUniversalParam", (void **) &dJointSetUniversalParam}, +// {"dJointAddUniversalTorques", (void **) &dJointAddUniversalTorques}, +// {"dJointSetPRAnchor", (void **) &dJointSetPRAnchor}, +// {"dJointSetPRAxis1", (void **) &dJointSetPRAxis1}, +// {"dJointSetPRAxis2", (void **) &dJointSetPRAxis2}, +// {"dJointSetPRParam", (void **) &dJointSetPRParam}, +// {"dJointAddPRTorque", (void **) &dJointAddPRTorque}, +// {"dJointSetPUAnchor", (void **) &dJointSetPUAnchor}, +// {"dJointSetPUAnchorOffset", (void **) &dJointSetPUAnchorOffset}, +// {"dJointSetPUAxis1", (void **) &dJointSetPUAxis1}, +// {"dJointSetPUAxis2", (void **) &dJointSetPUAxis2}, +// {"dJointSetPUAxis3", (void **) &dJointSetPUAxis3}, +// {"dJointSetPUAxisP", (void **) &dJointSetPUAxisP}, +// {"dJointSetPUParam", (void **) &dJointSetPUParam}, +// {"dJointAddPUTorque", (void **) &dJointAddPUTorque}, +// {"dJointSetPistonAnchor", (void **) &dJointSetPistonAnchor}, +// {"dJointSetPistonAnchorOffset", (void **) &dJointSetPistonAnchorOffset}, +// {"dJointSetPistonParam", (void **) &dJointSetPistonParam}, +// {"dJointAddPistonForce", (void **) &dJointAddPistonForce}, +// {"dJointSetFixed", (void **) &dJointSetFixed}, +// {"dJointSetFixedParam", (void **) &dJointSetFixedParam}, +// {"dJointSetAMotorNumAxes", (void **) &dJointSetAMotorNumAxes}, +// {"dJointSetAMotorAxis", (void **) &dJointSetAMotorAxis}, +// {"dJointSetAMotorAngle", (void **) &dJointSetAMotorAngle}, +// {"dJointSetAMotorParam", (void **) &dJointSetAMotorParam}, +// {"dJointSetAMotorMode", (void **) &dJointSetAMotorMode}, +// {"dJointAddAMotorTorques", (void **) &dJointAddAMotorTorques}, +// {"dJointSetLMotorNumAxes", (void **) &dJointSetLMotorNumAxes}, +// {"dJointSetLMotorAxis", (void **) &dJointSetLMotorAxis}, +// {"dJointSetLMotorParam", (void **) &dJointSetLMotorParam}, +// {"dJointSetPlane2DXParam", (void **) &dJointSetPlane2DXParam}, +// {"dJointSetPlane2DYParam", (void **) &dJointSetPlane2DYParam}, +// {"dJointSetPlane2DAngleParam", (void **) &dJointSetPlane2DAngleParam}, +// {"dJointGetBallAnchor", (void **) &dJointGetBallAnchor}, +// {"dJointGetBallAnchor2", (void **) &dJointGetBallAnchor2}, +// {"dJointGetBallParam", (void **) &dJointGetBallParam}, +// {"dJointGetHingeAnchor", (void **) &dJointGetHingeAnchor}, +// {"dJointGetHingeAnchor2", (void **) &dJointGetHingeAnchor2}, +// {"dJointGetHingeAxis", (void **) &dJointGetHingeAxis}, +// {"dJointGetHingeParam", (void **) &dJointGetHingeParam}, +// {"dJointGetHingeAngle", (void **) &dJointGetHingeAngle}, +// {"dJointGetHingeAngleRate", (void **) &dJointGetHingeAngleRate}, +// {"dJointGetSliderPosition", (void **) &dJointGetSliderPosition}, +// {"dJointGetSliderPositionRate", (void **) &dJointGetSliderPositionRate}, +// {"dJointGetSliderAxis", (void **) &dJointGetSliderAxis}, +// {"dJointGetSliderParam", (void **) &dJointGetSliderParam}, +// {"dJointGetHinge2Anchor", (void **) &dJointGetHinge2Anchor}, +// {"dJointGetHinge2Anchor2", (void **) &dJointGetHinge2Anchor2}, +// {"dJointGetHinge2Axis1", (void **) &dJointGetHinge2Axis1}, +// {"dJointGetHinge2Axis2", (void **) &dJointGetHinge2Axis2}, +// {"dJointGetHinge2Param", (void **) &dJointGetHinge2Param}, +// {"dJointGetHinge2Angle1", (void **) &dJointGetHinge2Angle1}, +// {"dJointGetHinge2Angle1Rate", (void **) &dJointGetHinge2Angle1Rate}, +// {"dJointGetHinge2Angle2Rate", (void **) &dJointGetHinge2Angle2Rate}, +// {"dJointGetUniversalAnchor", (void **) &dJointGetUniversalAnchor}, +// {"dJointGetUniversalAnchor2", (void **) &dJointGetUniversalAnchor2}, +// {"dJointGetUniversalAxis1", (void **) &dJointGetUniversalAxis1}, +// {"dJointGetUniversalAxis2", (void **) &dJointGetUniversalAxis2}, +// {"dJointGetUniversalParam", (void **) &dJointGetUniversalParam}, +// {"dJointGetUniversalAngles", (void **) &dJointGetUniversalAngles}, +// {"dJointGetUniversalAngle1", (void **) &dJointGetUniversalAngle1}, +// {"dJointGetUniversalAngle2", (void **) &dJointGetUniversalAngle2}, +// {"dJointGetUniversalAngle1Rate", (void **) &dJointGetUniversalAngle1Rate}, +// {"dJointGetUniversalAngle2Rate", (void **) &dJointGetUniversalAngle2Rate}, +// {"dJointGetPRAnchor", (void **) &dJointGetPRAnchor}, +// {"dJointGetPRPosition", (void **) &dJointGetPRPosition}, +// {"dJointGetPRPositionRate", (void **) &dJointGetPRPositionRate}, +// {"dJointGetPRAngle", (void **) &dJointGetPRAngle}, +// {"dJointGetPRAngleRate", (void **) &dJointGetPRAngleRate}, +// {"dJointGetPRAxis1", (void **) &dJointGetPRAxis1}, +// {"dJointGetPRAxis2", (void **) &dJointGetPRAxis2}, +// {"dJointGetPRParam", (void **) &dJointGetPRParam}, +// {"dJointGetPUAnchor", (void **) &dJointGetPUAnchor}, +// {"dJointGetPUPosition", (void **) &dJointGetPUPosition}, +// {"dJointGetPUPositionRate", (void **) &dJointGetPUPositionRate}, +// {"dJointGetPUAxis1", (void **) &dJointGetPUAxis1}, +// {"dJointGetPUAxis2", (void **) &dJointGetPUAxis2}, +// {"dJointGetPUAxis3", (void **) &dJointGetPUAxis3}, +// {"dJointGetPUAxisP", (void **) &dJointGetPUAxisP}, +// {"dJointGetPUAngles", (void **) &dJointGetPUAngles}, +// {"dJointGetPUAngle1", (void **) &dJointGetPUAngle1}, +// {"dJointGetPUAngle1Rate", (void **) &dJointGetPUAngle1Rate}, +// {"dJointGetPUAngle2", (void **) &dJointGetPUAngle2}, +// {"dJointGetPUAngle2Rate", (void **) &dJointGetPUAngle2Rate}, +// {"dJointGetPUParam", (void **) &dJointGetPUParam}, +// {"dJointGetPistonPosition", (void **) &dJointGetPistonPosition}, +// {"dJointGetPistonPositionRate", (void **) &dJointGetPistonPositionRate}, +// {"dJointGetPistonAngle", (void **) &dJointGetPistonAngle}, +// {"dJointGetPistonAngleRate", (void **) &dJointGetPistonAngleRate}, +// {"dJointGetPistonAnchor", (void **) &dJointGetPistonAnchor}, +// {"dJointGetPistonAnchor2", (void **) &dJointGetPistonAnchor2}, +// {"dJointGetPistonAxis", (void **) &dJointGetPistonAxis}, +// {"dJointGetPistonParam", (void **) &dJointGetPistonParam}, +// {"dJointGetAMotorNumAxes", (void **) &dJointGetAMotorNumAxes}, +// {"dJointGetAMotorAxis", (void **) &dJointGetAMotorAxis}, +// {"dJointGetAMotorAxisRel", (void **) &dJointGetAMotorAxisRel}, +// {"dJointGetAMotorAngle", (void **) &dJointGetAMotorAngle}, +// {"dJointGetAMotorAngleRate", (void **) &dJointGetAMotorAngleRate}, +// {"dJointGetAMotorParam", (void **) &dJointGetAMotorParam}, +// {"dJointGetAMotorMode", (void **) &dJointGetAMotorMode}, +// {"dJointGetLMotorNumAxes", (void **) &dJointGetLMotorNumAxes}, +// {"dJointGetLMotorAxis", (void **) &dJointGetLMotorAxis}, +// {"dJointGetLMotorParam", (void **) &dJointGetLMotorParam}, +// {"dJointGetFixedParam", (void **) &dJointGetFixedParam}, +// {"dConnectingJoint", (void **) &dConnectingJoint}, +// {"dConnectingJointList", (void **) &dConnectingJointList}, + {"dAreConnected", (void **) &dAreConnected}, + {"dAreConnectedExcluding", (void **) &dAreConnectedExcluding}, + {"dSimpleSpaceCreate", (void **) &dSimpleSpaceCreate}, + {"dHashSpaceCreate", (void **) &dHashSpaceCreate}, + {"dQuadTreeSpaceCreate", (void **) &dQuadTreeSpaceCreate}, +// {"dSweepAndPruneSpaceCreate", (void **) &dSweepAndPruneSpaceCreate}, + {"dSpaceDestroy", (void **) &dSpaceDestroy}, +// {"dHashSpaceSetLevels", (void **) &dHashSpaceSetLevels}, +// {"dHashSpaceGetLevels", (void **) &dHashSpaceGetLevels}, +// {"dSpaceSetCleanup", (void **) &dSpaceSetCleanup}, +// {"dSpaceGetCleanup", (void **) &dSpaceGetCleanup}, +// {"dSpaceSetSublevel", (void **) &dSpaceSetSublevel}, +// {"dSpaceGetSublevel", (void **) &dSpaceGetSublevel}, +// {"dSpaceSetManualCleanup", (void **) &dSpaceSetManualCleanup}, +// {"dSpaceGetManualCleanup", (void **) &dSpaceGetManualCleanup}, +// {"dSpaceAdd", (void **) &dSpaceAdd}, +// {"dSpaceRemove", (void **) &dSpaceRemove}, +// {"dSpaceQuery", (void **) &dSpaceQuery}, +// {"dSpaceClean", (void **) &dSpaceClean}, +// {"dSpaceGetNumGeoms", (void **) &dSpaceGetNumGeoms}, +// {"dSpaceGetGeom", (void **) &dSpaceGetGeom}, +// {"dSpaceGetClass", (void **) &dSpaceGetClass}, + {"dGeomDestroy", (void **) &dGeomDestroy}, + {"dGeomSetData", (void **) &dGeomSetData}, + {"dGeomGetData", (void **) &dGeomGetData}, + {"dGeomSetBody", (void **) &dGeomSetBody}, + {"dGeomGetBody", (void **) &dGeomGetBody}, + {"dGeomSetPosition", (void **) &dGeomSetPosition}, + {"dGeomSetRotation", (void **) &dGeomSetRotation}, +// {"dGeomSetQuaternion", (void **) &dGeomSetQuaternion}, +// {"dGeomGetPosition", (void **) &dGeomGetPosition}, +// {"dGeomCopyPosition", (void **) &dGeomCopyPosition}, +// {"dGeomGetRotation", (void **) &dGeomGetRotation}, +// {"dGeomCopyRotation", (void **) &dGeomCopyRotation}, +// {"dGeomGetQuaternion", (void **) &dGeomGetQuaternion}, +// {"dGeomGetAABB", (void **) &dGeomGetAABB}, + {"dGeomIsSpace", (void **) &dGeomIsSpace}, +// {"dGeomGetSpace", (void **) &dGeomGetSpace}, +// {"dGeomGetClass", (void **) &dGeomGetClass}, +// {"dGeomSetCategoryBits", (void **) &dGeomSetCategoryBits}, +// {"dGeomSetCollideBits", (void **) &dGeomSetCollideBits}, +// {"dGeomGetCategoryBits", (void **) &dGeomGetCategoryBits}, +// {"dGeomGetCollideBits", (void **) &dGeomGetCollideBits}, +// {"dGeomEnable", (void **) &dGeomEnable}, +// {"dGeomDisable", (void **) &dGeomDisable}, +// {"dGeomIsEnabled", (void **) &dGeomIsEnabled}, +// {"dGeomSetOffsetPosition", (void **) &dGeomSetOffsetPosition}, +// {"dGeomSetOffsetRotation", (void **) &dGeomSetOffsetRotation}, +// {"dGeomSetOffsetQuaternion", (void **) &dGeomSetOffsetQuaternion}, +// {"dGeomSetOffsetWorldPosition", (void **) &dGeomSetOffsetWorldPosition}, +// {"dGeomSetOffsetWorldRotation", (void **) &dGeomSetOffsetWorldRotation}, +// {"dGeomSetOffsetWorldQuaternion", (void **) &dGeomSetOffsetWorldQuaternion}, +// {"dGeomClearOffset", (void **) &dGeomClearOffset}, +// {"dGeomIsOffset", (void **) &dGeomIsOffset}, +// {"dGeomGetOffsetPosition", (void **) &dGeomGetOffsetPosition}, +// {"dGeomCopyOffsetPosition", (void **) &dGeomCopyOffsetPosition}, +// {"dGeomGetOffsetRotation", (void **) &dGeomGetOffsetRotation}, +// {"dGeomCopyOffsetRotation", (void **) &dGeomCopyOffsetRotation}, +// {"dGeomGetOffsetQuaternion", (void **) &dGeomGetOffsetQuaternion}, + {"dCollide", (void **) &dCollide}, + {"dSpaceCollide", (void **) &dSpaceCollide}, + {"dSpaceCollide2", (void **) &dSpaceCollide2}, + {"dCreateSphere", (void **) &dCreateSphere}, +// {"dGeomSphereSetRadius", (void **) &dGeomSphereSetRadius}, +// {"dGeomSphereGetRadius", (void **) &dGeomSphereGetRadius}, +// {"dGeomSpherePointDepth", (void **) &dGeomSpherePointDepth}, + {"dCreateConvex", (void **) &dCreateConvex}, +// {"dGeomSetConvex", (void **) &dGeomSetConvex}, + {"dCreateBox", (void **) &dCreateBox}, +// {"dGeomBoxSetLengths", (void **) &dGeomBoxSetLengths}, +// {"dGeomBoxGetLengths", (void **) &dGeomBoxGetLengths}, +// {"dGeomBoxPointDepth", (void **) &dGeomBoxPointDepth}, +// {"dGeomBoxPointDepth", (void **) &dGeomBoxPointDepth}, +// {"dCreatePlane", (void **) &dCreatePlane}, +// {"dGeomPlaneSetParams", (void **) &dGeomPlaneSetParams}, +// {"dGeomPlaneGetParams", (void **) &dGeomPlaneGetParams}, +// {"dGeomPlanePointDepth", (void **) &dGeomPlanePointDepth}, + {"dCreateCapsule", (void **) &dCreateCapsule}, +// {"dGeomCapsuleSetParams", (void **) &dGeomCapsuleSetParams}, +// {"dGeomCapsuleGetParams", (void **) &dGeomCapsuleGetParams}, +// {"dGeomCapsulePointDepth", (void **) &dGeomCapsulePointDepth}, + {"dCreateCylinder", (void **) &dCreateCylinder}, +// {"dGeomCylinderSetParams", (void **) &dGeomCylinderSetParams}, +// {"dGeomCylinderGetParams", (void **) &dGeomCylinderGetParams}, +// {"dCreateRay", (void **) &dCreateRay}, +// {"dGeomRaySetLength", (void **) &dGeomRaySetLength}, +// {"dGeomRayGetLength", (void **) &dGeomRayGetLength}, +// {"dGeomRaySet", (void **) &dGeomRaySet}, +// {"dGeomRayGet", (void **) &dGeomRayGet}, + {"dCreateGeomTransform", (void **) &dCreateGeomTransform}, + {"dGeomTransformSetGeom", (void **) &dGeomTransformSetGeom}, +// {"dGeomTransformGetGeom", (void **) &dGeomTransformGetGeom}, + {"dGeomTransformSetCleanup", (void **) &dGeomTransformSetCleanup}, +// {"dGeomTransformGetCleanup", (void **) &dGeomTransformGetCleanup}, +// {"dGeomTransformSetInfo", (void **) &dGeomTransformSetInfo}, +// {"dGeomTransformGetInfo", (void **) &dGeomTransformGetInfo}, + {"dGeomTriMeshDataCreate", (void **) &dGeomTriMeshDataCreate}, + {"dGeomTriMeshDataDestroy", (void **) &dGeomTriMeshDataDestroy}, +// {"dGeomTriMeshDataSet", (void **) &dGeomTriMeshDataSet}, +// {"dGeomTriMeshDataGet", (void **) &dGeomTriMeshDataGet}, +// {"dGeomTriMeshSetLastTransform", (void **) &dGeomTriMeshSetLastTransform}, +// {"dGeomTriMeshGetLastTransform", (void **) &dGeomTriMeshGetLastTransform}, + {"dGeomTriMeshDataBuildSingle", (void **) &dGeomTriMeshDataBuildSingle}, +// {"dGeomTriMeshDataBuildSingle1", (void **) &dGeomTriMeshDataBuildSingle1}, +// {"dGeomTriMeshDataBuildDouble", (void **) &dGeomTriMeshDataBuildDouble}, +// {"dGeomTriMeshDataBuildDouble1", (void **) &dGeomTriMeshDataBuildDouble1}, +// {"dGeomTriMeshDataBuildSimple", (void **) &dGeomTriMeshDataBuildSimple}, +// {"dGeomTriMeshDataBuildSimple1", (void **) &dGeomTriMeshDataBuildSimple1}, +// {"dGeomTriMeshDataPreprocess", (void **) &dGeomTriMeshDataPreprocess}, +// {"dGeomTriMeshDataGetBuffer", (void **) &dGeomTriMeshDataGetBuffer}, +// {"dGeomTriMeshDataSetBuffer", (void **) &dGeomTriMeshDataSetBuffer}, +// {"dGeomTriMeshSetCallback", (void **) &dGeomTriMeshSetCallback}, +// {"dGeomTriMeshGetCallback", (void **) &dGeomTriMeshGetCallback}, +// {"dGeomTriMeshSetArrayCallback", (void **) &dGeomTriMeshSetArrayCallback}, +// {"dGeomTriMeshGetArrayCallback", (void **) &dGeomTriMeshGetArrayCallback}, +// {"dGeomTriMeshSetRayCallback", (void **) &dGeomTriMeshSetRayCallback}, +// {"dGeomTriMeshGetRayCallback", (void **) &dGeomTriMeshGetRayCallback}, +// {"dGeomTriMeshSetTriMergeCallback", (void **) &dGeomTriMeshSetTriMergeCallback}, +// {"dGeomTriMeshGetTriMergeCallback", (void **) &dGeomTriMeshGetTriMergeCallback}, + {"dCreateTriMesh", (void **) &dCreateTriMesh}, +// {"dGeomTriMeshSetData", (void **) &dGeomTriMeshSetData}, +// {"dGeomTriMeshGetData", (void **) &dGeomTriMeshGetData}, +// {"dGeomTriMeshEnableTC", (void **) &dGeomTriMeshEnableTC}, +// {"dGeomTriMeshIsTCEnabled", (void **) &dGeomTriMeshIsTCEnabled}, +// {"dGeomTriMeshClearTCCache", (void **) &dGeomTriMeshClearTCCache}, +// {"dGeomTriMeshGetTriMeshDataID", (void **) &dGeomTriMeshGetTriMeshDataID}, +// {"dGeomTriMeshGetTriangle", (void **) &dGeomTriMeshGetTriangle}, +// {"dGeomTriMeshGetPoint", (void **) &dGeomTriMeshGetPoint}, +// {"dGeomTriMeshGetTriangleCount", (void **) &dGeomTriMeshGetTriangleCount}, +// {"dGeomTriMeshDataUpdate", (void **) &dGeomTriMeshDataUpdate}, + {NULL, NULL} +}; + +// Handle for ODE DLL +dllhandle_t ode_dll = NULL; +#endif +#endif + +static void World_Physics_Init(void) +{ +#ifdef USEODE +#ifdef ODE_DYNAMIC + const char* dllnames [] = + { +# if defined(WIN32) + "libode3.dll", + "libode2.dll", + "libode1.dll", +# elif defined(MACOSX) + "libode.3.dylib", + "libode.2.dylib", + "libode.1.dylib", +# else + "libode.so.3", + "libode.so.2", + "libode.so.1", +# endif + NULL + }; +#endif + + Cvar_RegisterVariable(&physics_ode_quadtree_depth); + Cvar_RegisterVariable(&physics_ode_contactsurfacelayer); + Cvar_RegisterVariable(&physics_ode_worldstep_iterations); + Cvar_RegisterVariable(&physics_ode_contact_mu); + Cvar_RegisterVariable(&physics_ode_contact_erp); + Cvar_RegisterVariable(&physics_ode_contact_cfm); + Cvar_RegisterVariable(&physics_ode_contact_maxpoints); + Cvar_RegisterVariable(&physics_ode_world_erp); + Cvar_RegisterVariable(&physics_ode_world_cfm); + Cvar_RegisterVariable(&physics_ode_world_damping); + Cvar_RegisterVariable(&physics_ode_world_damping_linear); + Cvar_RegisterVariable(&physics_ode_world_damping_linear_threshold); + Cvar_RegisterVariable(&physics_ode_world_damping_angular); + Cvar_RegisterVariable(&physics_ode_world_damping_angular_threshold); + Cvar_RegisterVariable(&physics_ode_world_gravitymod); + Cvar_RegisterVariable(&physics_ode_iterationsperframe); + Cvar_RegisterVariable(&physics_ode_constantstep); + Cvar_RegisterVariable(&physics_ode_movelimit); + Cvar_RegisterVariable(&physics_ode_spinlimit); + Cvar_RegisterVariable(&physics_ode_trick_fixnan); + Cvar_RegisterVariable(&physics_ode_autodisable); + Cvar_RegisterVariable(&physics_ode_autodisable_steps); + Cvar_RegisterVariable(&physics_ode_autodisable_time); + Cvar_RegisterVariable(&physics_ode_autodisable_threshold_linear); + Cvar_RegisterVariable(&physics_ode_autodisable_threshold_angular); + Cvar_RegisterVariable(&physics_ode_autodisable_threshold_samples); + Cvar_RegisterVariable(&physics_ode_printstats); + Cvar_RegisterVariable(&physics_ode_allowconvex); + Cvar_RegisterVariable(&physics_ode); + +#ifdef ODE_DYNAMIC + // Load the DLL + if (Sys_LoadLibrary (dllnames, &ode_dll, odefuncs)) +#endif + { + dInitODE(); +// dInitODE2(0); +#ifdef ODE_DYNAMIC +# ifdef dSINGLE + if (!dCheckConfiguration("ODE_single_precision")) +# else + if (!dCheckConfiguration("ODE_double_precision")) +# endif + { +# ifdef dSINGLE + Con_Printf("ODE library not compiled for single precision - incompatible! Not using ODE physics.\n"); +# else + Con_Printf("ODE library not compiled for double precision - incompatible! Not using ODE physics.\n"); +# endif + Sys_UnloadLibrary(&ode_dll); + ode_dll = NULL; + } + else + { +# ifdef dSINGLE + Con_Printf("ODE library loaded with single precision.\n"); +# else + Con_Printf("ODE library loaded with double precision.\n"); +# endif + Con_Printf("ODE configuration list: %s\n", dGetConfiguration()); + } +#endif + } +#endif +} + +static void World_Physics_Shutdown(void) +{ +#ifdef USEODE +#ifdef ODE_DYNAMIC + if (ode_dll) +#endif + { + dCloseODE(); +#ifdef ODE_DYNAMIC + Sys_UnloadLibrary(&ode_dll); + ode_dll = NULL; +#endif + } +#endif +} + +#ifdef USEODE +static void World_Physics_UpdateODE(world_t *world) +{ + dWorldID odeworld; + + odeworld = (dWorldID)world->physics.ode_world; + + // ERP and CFM + if (physics_ode_world_erp.value >= 0) + dWorldSetERP(odeworld, physics_ode_world_erp.value); + if (physics_ode_world_cfm.value >= 0) + dWorldSetCFM(odeworld, physics_ode_world_cfm.value); + // Damping + if (physics_ode_world_damping.integer) + { + dWorldSetLinearDamping(odeworld, (physics_ode_world_damping_linear.value >= 0) ? (physics_ode_world_damping_linear.value * physics_ode_world_damping.value) : 0); + dWorldSetLinearDampingThreshold(odeworld, (physics_ode_world_damping_linear_threshold.value >= 0) ? (physics_ode_world_damping_linear_threshold.value * physics_ode_world_damping.value) : 0); + dWorldSetAngularDamping(odeworld, (physics_ode_world_damping_angular.value >= 0) ? (physics_ode_world_damping_angular.value * physics_ode_world_damping.value) : 0); + dWorldSetAngularDampingThreshold(odeworld, (physics_ode_world_damping_angular_threshold.value >= 0) ? (physics_ode_world_damping_angular_threshold.value * physics_ode_world_damping.value) : 0); + } + else + { + dWorldSetLinearDamping(odeworld, 0); + dWorldSetLinearDampingThreshold(odeworld, 0); + dWorldSetAngularDamping(odeworld, 0); + dWorldSetAngularDampingThreshold(odeworld, 0); + } + // Autodisable + dWorldSetAutoDisableFlag(odeworld, (physics_ode_autodisable.integer) ? 1 : 0); + if (physics_ode_autodisable.integer) + { + dWorldSetAutoDisableSteps(odeworld, bound(1, physics_ode_autodisable_steps.integer, 100)); + dWorldSetAutoDisableTime(odeworld, physics_ode_autodisable_time.value); + dWorldSetAutoDisableAverageSamplesCount(odeworld, bound(1, physics_ode_autodisable_threshold_samples.integer, 100)); + dWorldSetAutoDisableLinearThreshold(odeworld, physics_ode_autodisable_threshold_linear.value); + dWorldSetAutoDisableAngularThreshold(odeworld, physics_ode_autodisable_threshold_angular.value); + } +} + +static void World_Physics_EnableODE(world_t *world) +{ + dVector3 center, extents; + if (world->physics.ode) + return; +#ifdef ODE_DYNAMIC + if (!ode_dll) + return; +#endif + world->physics.ode = true; + VectorMAM(0.5f, world->mins, 0.5f, world->maxs, center); + VectorSubtract(world->maxs, center, extents); + world->physics.ode_world = dWorldCreate(); + world->physics.ode_space = dQuadTreeSpaceCreate(NULL, center, extents, bound(1, physics_ode_quadtree_depth.integer, 10)); + world->physics.ode_contactgroup = dJointGroupCreate(0); + + World_Physics_UpdateODE(world); +} +#endif + +static void World_Physics_Start(world_t *world) +{ +#ifdef USEODE + if (world->physics.ode) + return; + World_Physics_EnableODE(world); +#endif +} + +static void World_Physics_End(world_t *world) +{ +#ifdef USEODE + if (world->physics.ode) + { + dWorldDestroy((dWorldID)world->physics.ode_world); + dSpaceDestroy((dSpaceID)world->physics.ode_space); + dJointGroupDestroy((dJointGroupID)world->physics.ode_contactgroup); + world->physics.ode = false; + } +#endif +} + +void World_Physics_RemoveJointFromEntity(world_t *world, prvm_edict_t *ed) +{ + ed->priv.server->ode_joint_type = 0; +#ifdef USEODE + if(ed->priv.server->ode_joint) + dJointDestroy((dJointID)ed->priv.server->ode_joint); + ed->priv.server->ode_joint = NULL; +#endif +} + +void World_Physics_RemoveFromEntity(world_t *world, prvm_edict_t *ed) +{ + edict_odefunc_t *f, *nf; + + // entity is not physics controlled, free any physics data + ed->priv.server->ode_physics = false; +#ifdef USEODE + if (ed->priv.server->ode_geom) + dGeomDestroy((dGeomID)ed->priv.server->ode_geom); + ed->priv.server->ode_geom = NULL; + if (ed->priv.server->ode_body) + { + dJointID j; + dBodyID b1, b2; + prvm_edict_t *ed2; + while(dBodyGetNumJoints((dBodyID)ed->priv.server->ode_body)) + { + j = dBodyGetJoint((dBodyID)ed->priv.server->ode_body, 0); + ed2 = (prvm_edict_t *) dJointGetData(j); + b1 = dJointGetBody(j, 0); + b2 = dJointGetBody(j, 1); + if(b1 == (dBodyID)ed->priv.server->ode_body) + { + b1 = 0; + ed2->priv.server->ode_joint_enemy = 0; + } + if(b2 == (dBodyID)ed->priv.server->ode_body) + { + b2 = 0; + ed2->priv.server->ode_joint_aiment = 0; + } + dJointAttach(j, b1, b2); + } + dBodyDestroy((dBodyID)ed->priv.server->ode_body); + } + ed->priv.server->ode_body = NULL; +#endif + if (ed->priv.server->ode_vertex3f) + Mem_Free(ed->priv.server->ode_vertex3f); + ed->priv.server->ode_vertex3f = NULL; + ed->priv.server->ode_numvertices = 0; + if (ed->priv.server->ode_element3i) + Mem_Free(ed->priv.server->ode_element3i); + ed->priv.server->ode_element3i = NULL; + ed->priv.server->ode_numtriangles = 0; + if(ed->priv.server->ode_massbuf) + Mem_Free(ed->priv.server->ode_massbuf); + ed->priv.server->ode_massbuf = NULL; + // clear functions stack + for(f = ed->priv.server->ode_func; f; f = nf) + { + nf = f->next; + Mem_Free(f); + } + ed->priv.server->ode_func = NULL; +} + +void World_Physics_ApplyCmd(prvm_edict_t *ed, edict_odefunc_t *f) +{ + dBodyID body = (dBodyID)ed->priv.server->ode_body; + +#ifdef USEODE + switch(f->type) + { + case ODEFUNC_ENABLE: + dBodyEnable(body); + break; + case ODEFUNC_DISABLE: + dBodyDisable(body); + break; + case ODEFUNC_FORCE: + dBodyEnable(body); + dBodyAddForceAtPos(body, f->v1[0], f->v1[1], f->v1[2], f->v2[0], f->v2[1], f->v2[2]); + break; + case ODEFUNC_TORQUE: + dBodyEnable(body); + dBodyAddTorque(body, f->v1[0], f->v1[1], f->v1[2]); + break; + default: + break; + } +#endif +} + +#ifdef USEODE +static void World_Physics_Frame_BodyToEntity(world_t *world, prvm_edict_t *ed) +{ + prvm_prog_t *prog = world->prog; + const dReal *avel; + const dReal *o; + const dReal *r; // for some reason dBodyGetRotation returns a [3][4] matrix + const dReal *vel; + dBodyID body = (dBodyID)ed->priv.server->ode_body; + int movetype; + matrix4x4_t bodymatrix; + matrix4x4_t entitymatrix; + vec3_t angles; + vec3_t avelocity; + vec3_t forward, left, up; + vec3_t origin; + vec3_t spinvelocity; + vec3_t velocity; + int jointtype; + if (!body) + return; + movetype = (int)PRVM_gameedictfloat(ed, movetype); + if (movetype != MOVETYPE_PHYSICS) + { + jointtype = (int)PRVM_gameedictfloat(ed, jointtype); + switch(jointtype) + { + // TODO feed back data from physics + case JOINTTYPE_POINT: + break; + case JOINTTYPE_HINGE: + break; + case JOINTTYPE_SLIDER: + break; + case JOINTTYPE_UNIVERSAL: + break; + case JOINTTYPE_HINGE2: + break; + case JOINTTYPE_FIXED: + break; + } + return; + } + // store the physics engine data into the entity + o = dBodyGetPosition(body); + r = dBodyGetRotation(body); + vel = dBodyGetLinearVel(body); + avel = dBodyGetAngularVel(body); + VectorCopy(o, origin); + forward[0] = r[0]; + forward[1] = r[4]; + forward[2] = r[8]; + left[0] = r[1]; + left[1] = r[5]; + left[2] = r[9]; + up[0] = r[2]; + up[1] = r[6]; + up[2] = r[10]; + VectorCopy(vel, velocity); + VectorCopy(avel, spinvelocity); + Matrix4x4_FromVectors(&bodymatrix, forward, left, up, origin); + Matrix4x4_Concat(&entitymatrix, &bodymatrix, &ed->priv.server->ode_offsetimatrix); + Matrix4x4_ToVectors(&entitymatrix, forward, left, up, origin); + + AnglesFromVectors(angles, forward, up, false); + VectorSet(avelocity, RAD2DEG(spinvelocity[PITCH]), RAD2DEG(spinvelocity[ROLL]), RAD2DEG(spinvelocity[YAW])); + + { + float pitchsign = 1; + if(prog == SVVM_prog) // FIXME some better way? + { + pitchsign = SV_GetPitchSign(prog, ed); + } + else if(prog == CLVM_prog) + { + pitchsign = CL_GetPitchSign(prog, ed); + } + angles[PITCH] *= pitchsign; + avelocity[PITCH] *= pitchsign; + } + + VectorCopy(origin, PRVM_gameedictvector(ed, origin)); + VectorCopy(velocity, PRVM_gameedictvector(ed, velocity)); + //VectorCopy(forward, PRVM_gameedictvector(ed, axis_forward)); + //VectorCopy(left, PRVM_gameedictvector(ed, axis_left)); + //VectorCopy(up, PRVM_gameedictvector(ed, axis_up)); + //VectorCopy(spinvelocity, PRVM_gameedictvector(ed, spinvelocity)); + VectorCopy(angles, PRVM_gameedictvector(ed, angles)); + VectorCopy(avelocity, PRVM_gameedictvector(ed, avelocity)); + + // values for BodyFromEntity to check if the qc modified anything later + VectorCopy(origin, ed->priv.server->ode_origin); + VectorCopy(velocity, ed->priv.server->ode_velocity); + VectorCopy(angles, ed->priv.server->ode_angles); + VectorCopy(avelocity, ed->priv.server->ode_avelocity); + ed->priv.server->ode_gravity = dBodyGetGravityMode(body) != 0; + + if(prog == SVVM_prog) // FIXME some better way? + { + SV_LinkEdict(ed); + SV_LinkEdict_TouchAreaGrid(ed); + } +} + +static void World_Physics_Frame_ForceFromEntity(world_t *world, prvm_edict_t *ed) +{ + prvm_prog_t *prog = world->prog; + int forcetype = 0, movetype = 0, enemy = 0; + vec3_t movedir, origin; + + movetype = (int)PRVM_gameedictfloat(ed, movetype); + forcetype = (int)PRVM_gameedictfloat(ed, forcetype); + if (movetype == MOVETYPE_PHYSICS) + forcetype = FORCETYPE_NONE; // can't have both + if (!forcetype) + return; + enemy = PRVM_gameedictedict(ed, enemy); + if (enemy <= 0 || enemy >= prog->num_edicts || prog->edicts[enemy].priv.required->free || prog->edicts[enemy].priv.server->ode_body == 0) + return; + VectorCopy(PRVM_gameedictvector(ed, movedir), movedir); + VectorCopy(PRVM_gameedictvector(ed, origin), origin); + dBodyEnable((dBodyID)prog->edicts[enemy].priv.server->ode_body); + switch(forcetype) + { + case FORCETYPE_FORCE: + if (movedir[0] || movedir[1] || movedir[2]) + dBodyAddForce((dBodyID)prog->edicts[enemy].priv.server->ode_body, movedir[0], movedir[1], movedir[2]); + break; + case FORCETYPE_FORCEATPOS: + if (movedir[0] || movedir[1] || movedir[2]) + dBodyAddForceAtPos((dBodyID)prog->edicts[enemy].priv.server->ode_body, movedir[0], movedir[1], movedir[2], origin[0], origin[1], origin[2]); + break; + case FORCETYPE_TORQUE: + if (movedir[0] || movedir[1] || movedir[2]) + dBodyAddTorque((dBodyID)prog->edicts[enemy].priv.server->ode_body, movedir[0], movedir[1], movedir[2]); + break; + case FORCETYPE_NONE: + default: + // bad force + break; + } +} + +static void World_Physics_Frame_JointFromEntity(world_t *world, prvm_edict_t *ed) +{ + prvm_prog_t *prog = world->prog; + dJointID j = 0; + dBodyID b1 = 0; + dBodyID b2 = 0; + int movetype = 0; + int jointtype = 0; + int enemy = 0, aiment = 0; + vec3_t origin, velocity, angles, forward, left, up, movedir; + vec_t CFM, ERP, FMax, Stop, Vel; + + movetype = (int)PRVM_gameedictfloat(ed, movetype); + jointtype = (int)PRVM_gameedictfloat(ed, jointtype); + VectorClear(origin); + VectorClear(velocity); + VectorClear(angles); + VectorClear(movedir); + enemy = PRVM_gameedictedict(ed, enemy); + aiment = PRVM_gameedictedict(ed, aiment); + VectorCopy(PRVM_gameedictvector(ed, origin), origin); + VectorCopy(PRVM_gameedictvector(ed, velocity), velocity); + VectorCopy(PRVM_gameedictvector(ed, angles), angles); + VectorCopy(PRVM_gameedictvector(ed, movedir), movedir); + if(movetype == MOVETYPE_PHYSICS) + jointtype = JOINTTYPE_NONE; // can't have both + if(enemy <= 0 || enemy >= prog->num_edicts || prog->edicts[enemy].priv.required->free || prog->edicts[enemy].priv.server->ode_body == 0) + enemy = 0; + if(aiment <= 0 || aiment >= prog->num_edicts || prog->edicts[aiment].priv.required->free || prog->edicts[aiment].priv.server->ode_body == 0) + aiment = 0; + // see http://www.ode.org/old_list_archives/2006-January/017614.html + // we want to set ERP? make it fps independent and work like a spring constant + // note: if movedir[2] is 0, it becomes ERP = 1, CFM = 1.0 / (H * K) + if(movedir[0] > 0 && movedir[1] > 0) + { + float K = movedir[0]; + float D = movedir[1]; + float R = 2.0 * D * sqrt(K); // we assume D is premultiplied by sqrt(sprungMass) + CFM = 1.0 / (world->physics.ode_step * K + R); // always > 0 + ERP = world->physics.ode_step * K * CFM; + Vel = 0; + FMax = 0; + Stop = movedir[2]; + } + else if(movedir[1] < 0) + { + CFM = 0; + ERP = 0; + Vel = movedir[0]; + FMax = -movedir[1]; // TODO do we need to multiply with world.physics.ode_step? + Stop = movedir[2] > 0 ? movedir[2] : dInfinity; + } + else // movedir[0] > 0, movedir[1] == 0 or movedir[0] < 0, movedir[1] >= 0 + { + CFM = 0; + ERP = 0; + Vel = 0; + FMax = 0; + Stop = dInfinity; + } + if(jointtype == ed->priv.server->ode_joint_type && VectorCompare(origin, ed->priv.server->ode_joint_origin) && VectorCompare(velocity, ed->priv.server->ode_joint_velocity) && VectorCompare(angles, ed->priv.server->ode_joint_angles) && enemy == ed->priv.server->ode_joint_enemy && aiment == ed->priv.server->ode_joint_aiment && VectorCompare(movedir, ed->priv.server->ode_joint_movedir)) + return; // nothing to do + AngleVectorsFLU(angles, forward, left, up); + switch(jointtype) + { + case JOINTTYPE_POINT: + j = dJointCreateBall((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_HINGE: + j = dJointCreateHinge((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_SLIDER: + j = dJointCreateSlider((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_UNIVERSAL: + j = dJointCreateUniversal((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_HINGE2: + j = dJointCreateHinge2((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_FIXED: + j = dJointCreateFixed((dWorldID)world->physics.ode_world, 0); + break; + case JOINTTYPE_NONE: + default: + // no joint + j = 0; + break; + } + if(ed->priv.server->ode_joint) + { + //Con_Printf("deleted old joint %i\n", (int) (ed - prog->edicts)); + dJointAttach((dJointID)ed->priv.server->ode_joint, 0, 0); + dJointDestroy((dJointID)ed->priv.server->ode_joint); + } + ed->priv.server->ode_joint = (void *) j; + ed->priv.server->ode_joint_type = jointtype; + ed->priv.server->ode_joint_enemy = enemy; + ed->priv.server->ode_joint_aiment = aiment; + VectorCopy(origin, ed->priv.server->ode_joint_origin); + VectorCopy(velocity, ed->priv.server->ode_joint_velocity); + VectorCopy(angles, ed->priv.server->ode_joint_angles); + VectorCopy(movedir, ed->priv.server->ode_joint_movedir); + if(j) + { + //Con_Printf("made new joint %i\n", (int) (ed - prog->edicts)); + dJointSetData(j, (void *) ed); + if(enemy) + b1 = (dBodyID)prog->edicts[enemy].priv.server->ode_body; + if(aiment) + b2 = (dBodyID)prog->edicts[aiment].priv.server->ode_body; + dJointAttach(j, b1, b2); + + switch(jointtype) + { + case JOINTTYPE_POINT: + dJointSetBallAnchor(j, origin[0], origin[1], origin[2]); + break; + case JOINTTYPE_HINGE: + dJointSetHingeAnchor(j, origin[0], origin[1], origin[2]); + dJointSetHingeAxis(j, forward[0], forward[1], forward[2]); + dJointSetHingeParam(j, dParamFMax, FMax); + dJointSetHingeParam(j, dParamHiStop, Stop); + dJointSetHingeParam(j, dParamLoStop, -Stop); + dJointSetHingeParam(j, dParamStopCFM, CFM); + dJointSetHingeParam(j, dParamStopERP, ERP); + dJointSetHingeParam(j, dParamVel, Vel); + break; + case JOINTTYPE_SLIDER: + dJointSetSliderAxis(j, forward[0], forward[1], forward[2]); + dJointSetSliderParam(j, dParamFMax, FMax); + dJointSetSliderParam(j, dParamHiStop, Stop); + dJointSetSliderParam(j, dParamLoStop, -Stop); + dJointSetSliderParam(j, dParamStopCFM, CFM); + dJointSetSliderParam(j, dParamStopERP, ERP); + dJointSetSliderParam(j, dParamVel, Vel); + break; + case JOINTTYPE_UNIVERSAL: + dJointSetUniversalAnchor(j, origin[0], origin[1], origin[2]); + dJointSetUniversalAxis1(j, forward[0], forward[1], forward[2]); + dJointSetUniversalAxis2(j, up[0], up[1], up[2]); + dJointSetUniversalParam(j, dParamFMax, FMax); + dJointSetUniversalParam(j, dParamHiStop, Stop); + dJointSetUniversalParam(j, dParamLoStop, -Stop); + dJointSetUniversalParam(j, dParamStopCFM, CFM); + dJointSetUniversalParam(j, dParamStopERP, ERP); + dJointSetUniversalParam(j, dParamVel, Vel); + dJointSetUniversalParam(j, dParamFMax2, FMax); + dJointSetUniversalParam(j, dParamHiStop2, Stop); + dJointSetUniversalParam(j, dParamLoStop2, -Stop); + dJointSetUniversalParam(j, dParamStopCFM2, CFM); + dJointSetUniversalParam(j, dParamStopERP2, ERP); + dJointSetUniversalParam(j, dParamVel2, Vel); + break; + case JOINTTYPE_HINGE2: + dJointSetHinge2Anchor(j, origin[0], origin[1], origin[2]); + dJointSetHinge2Axis1(j, forward[0], forward[1], forward[2]); + dJointSetHinge2Axis2(j, velocity[0], velocity[1], velocity[2]); + dJointSetHinge2Param(j, dParamFMax, FMax); + dJointSetHinge2Param(j, dParamHiStop, Stop); + dJointSetHinge2Param(j, dParamLoStop, -Stop); + dJointSetHinge2Param(j, dParamStopCFM, CFM); + dJointSetHinge2Param(j, dParamStopERP, ERP); + dJointSetHinge2Param(j, dParamVel, Vel); + dJointSetHinge2Param(j, dParamFMax2, FMax); + dJointSetHinge2Param(j, dParamHiStop2, Stop); + dJointSetHinge2Param(j, dParamLoStop2, -Stop); + dJointSetHinge2Param(j, dParamStopCFM2, CFM); + dJointSetHinge2Param(j, dParamStopERP2, ERP); + dJointSetHinge2Param(j, dParamVel2, Vel); + break; + case JOINTTYPE_FIXED: + break; + case 0: + default: + Sys_Error("what? but above the joint was valid...\n"); + break; + } +#undef SETPARAMS + + } +} + +// test convex geometry data +// planes for a cube, these should coincide with the +dReal test_convex_planes[] = +{ + 1.0f ,0.0f ,0.0f ,2.25f, + 0.0f ,1.0f ,0.0f ,2.25f, + 0.0f ,0.0f ,1.0f ,2.25f, + -1.0f,0.0f ,0.0f ,2.25f, + 0.0f ,-1.0f,0.0f ,2.25f, + 0.0f ,0.0f ,-1.0f,2.25f +}; +const unsigned int test_convex_planecount = 6; +// points for a cube +dReal test_convex_points[] = +{ + 2.25f,2.25f,2.25f, // point 0 + -2.25f,2.25f,2.25f, // point 1 + 2.25f,-2.25f,2.25f, // point 2 + -2.25f,-2.25f,2.25f, // point 3 + 2.25f,2.25f,-2.25f, // point 4 + -2.25f,2.25f,-2.25f, // point 5 + 2.25f,-2.25f,-2.25f, // point 6 + -2.25f,-2.25f,-2.25f, // point 7 +}; +const unsigned int test_convex_pointcount = 8; +// polygons for a cube (6 squares), index +unsigned int test_convex_polygons[] = +{ + 4,0,2,6,4, // positive X + 4,1,0,4,5, // positive Y + 4,0,1,3,2, // positive Z + 4,3,1,5,7, // negative X + 4,2,3,7,6, // negative Y + 4,5,4,6,7, // negative Z +}; + +static void World_Physics_Frame_BodyFromEntity(world_t *world, prvm_edict_t *ed) +{ + prvm_prog_t *prog = world->prog; + const float *iv; + const int *ie; + dBodyID body; + dMass mass; + const dReal *ovelocity, *ospinvelocity; + void *dataID; + dp_model_t *model; + float *ov; + int *oe; + int axisindex; + int modelindex = 0; + int movetype = MOVETYPE_NONE; + int numtriangles; + int numvertices; + int solid = SOLID_NOT, geomtype = 0; + int triangleindex; + int vertexindex; + mempool_t *mempool; + qboolean modified = false; + vec3_t angles; + vec3_t avelocity; + vec3_t entmaxs; + vec3_t entmins; + vec3_t forward; + vec3_t geomcenter; + vec3_t geomsize; + vec3_t left; + vec3_t origin; + vec3_t spinvelocity; + vec3_t up; + vec3_t velocity; + vec_t f; + vec_t length; + vec_t massval = 1.0f; + vec_t movelimit; + vec_t radius; + vec3_t scale; + vec_t spinlimit; + vec_t test; + qboolean gravity; + qboolean geom_modified = false; + edict_odefunc_t *func, *nextf; + + dReal *planes, *planesData, *pointsData; + unsigned int *polygons, *polygonsData, polyvert; + qboolean *mapped, *used, convex_compatible; + int numplanes = 0, numpoints = 0, i; + +#ifdef ODE_DYNAMIC + if (!ode_dll) + return; +#endif + VectorClear(entmins); + VectorClear(entmaxs); + + solid = (int)PRVM_gameedictfloat(ed, solid); + geomtype = (int)PRVM_gameedictfloat(ed, geomtype); + movetype = (int)PRVM_gameedictfloat(ed, movetype); + // support scale and q3map/radiant's modelscale_vec + if (PRVM_gameedictvector(ed, modelscale_vec)[0] != 0.0 || PRVM_gameedictvector(ed, modelscale_vec)[1] != 0.0 || PRVM_gameedictvector(ed, modelscale_vec)[2] != 0.0) + VectorCopy(PRVM_gameedictvector(ed, modelscale_vec), scale); + else if (PRVM_gameedictfloat(ed, scale)) + VectorSet(scale, PRVM_gameedictfloat(ed, scale), PRVM_gameedictfloat(ed, scale), PRVM_gameedictfloat(ed, scale)); + else + VectorSet(scale, 1.0f, 1.0f, 1.0f); + modelindex = 0; + if (PRVM_gameedictfloat(ed, mass)) + massval = PRVM_gameedictfloat(ed, mass); + if (movetype != MOVETYPE_PHYSICS) + massval = 1.0f; + mempool = prog->progs_mempool; + model = NULL; + if (!geomtype) + { + // VorteX: keep support for deprecated solid fields to not break mods + if (solid == SOLID_PHYSICS_TRIMESH || solid == SOLID_BSP) + geomtype = GEOMTYPE_TRIMESH; + else if (solid == SOLID_NOT || solid == SOLID_TRIGGER) + geomtype = GEOMTYPE_NONE; + else if (solid == SOLID_PHYSICS_SPHERE) + geomtype = GEOMTYPE_SPHERE; + else if (solid == SOLID_PHYSICS_CAPSULE) + geomtype = GEOMTYPE_CAPSULE; + else if (solid == SOLID_PHYSICS_CYLINDER) + geomtype = GEOMTYPE_CYLINDER; + else if (solid == SOLID_PHYSICS_BOX) + geomtype = GEOMTYPE_BOX; + else + geomtype = GEOMTYPE_BOX; + } + if (geomtype == GEOMTYPE_TRIMESH) + { + modelindex = (int)PRVM_gameedictfloat(ed, modelindex); + if (world == &sv.world) + model = SV_GetModelByIndex(modelindex); + else if (world == &cl.world) + model = CL_GetModelByIndex(modelindex); + else + model = NULL; + if (model) + { + entmins[0] = model->normalmins[0] * scale[0]; + entmins[1] = model->normalmins[1] * scale[1]; + entmins[2] = model->normalmins[2] * scale[2]; + entmaxs[0] = model->normalmaxs[0] * scale[0]; + entmaxs[1] = model->normalmaxs[1] * scale[1]; + entmaxs[2] = model->normalmaxs[2] * scale[2]; + geom_modified = !VectorCompare(ed->priv.server->ode_scale, scale) || ed->priv.server->ode_modelindex != modelindex; + } + else + { + Con_Printf("entity %i (classname %s) has no model\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(prog, PRVM_gameedictstring(ed, classname))); + geomtype = GEOMTYPE_BOX; + VectorCopy(PRVM_gameedictvector(ed, mins), entmins); + VectorCopy(PRVM_gameedictvector(ed, maxs), entmaxs); + modelindex = 0; + geom_modified = !VectorCompare(ed->priv.server->ode_mins, entmins) || !VectorCompare(ed->priv.server->ode_maxs, entmaxs); + } + } + else if (geomtype && geomtype != GEOMTYPE_NONE) + { + VectorCopy(PRVM_gameedictvector(ed, mins), entmins); + VectorCopy(PRVM_gameedictvector(ed, maxs), entmaxs); + geom_modified = !VectorCompare(ed->priv.server->ode_mins, entmins) || !VectorCompare(ed->priv.server->ode_maxs, entmaxs); + } + else + { + // geometry type not set, falling back + if (ed->priv.server->ode_physics) + World_Physics_RemoveFromEntity(world, ed); + return; + } + + VectorSubtract(entmaxs, entmins, geomsize); + if (VectorLength2(geomsize) == 0) + { + // we don't allow point-size physics objects... + if (ed->priv.server->ode_physics) + World_Physics_RemoveFromEntity(world, ed); + return; + } + + // get friction + ed->priv.server->ode_friction = PRVM_gameedictfloat(ed, friction) ? PRVM_gameedictfloat(ed, friction) : 1.0f; + + // check if we need to create or replace the geom + if (!ed->priv.server->ode_physics || ed->priv.server->ode_mass != massval || geom_modified) + { + modified = true; + World_Physics_RemoveFromEntity(world, ed); + ed->priv.server->ode_physics = true; + VectorMAM(0.5f, entmins, 0.5f, entmaxs, geomcenter); + if (PRVM_gameedictvector(ed, massofs)) + VectorCopy(geomcenter, PRVM_gameedictvector(ed, massofs)); + + // check geomsize + if (geomsize[0] * geomsize[1] * geomsize[2] == 0) + { + if (movetype == MOVETYPE_PHYSICS) + Con_Printf("entity %i (classname %s) .mass * .size_x * .size_y * .size_z == 0\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(prog, PRVM_gameedictstring(ed, classname))); + VectorSet(geomsize, 1.0f, 1.0f, 1.0f); + } + + // greate geom + switch(geomtype) + { + case GEOMTYPE_TRIMESH: + // add an optimized mesh to the model containing only the SUPERCONTENTS_SOLID surfaces + if (!model->brush.collisionmesh) + Mod_CreateCollisionMesh(model); + if (!model->brush.collisionmesh) + { + Con_Printf("entity %i (classname %s) has no geometry\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(prog, PRVM_gameedictstring(ed, classname))); + goto treatasbox; + } + + // check if trimesh can be defined with convex + convex_compatible = false; + for (i = 0;i < model->nummodelsurfaces;i++) + { + if (!strcmp(((msurface_t *)(model->data_surfaces + model->firstmodelsurface + i))->texture->name, "collisionconvex")) + { + convex_compatible = true; + break; + } + } + + // ODE requires persistent mesh storage, so we need to copy out + // the data from the model because renderer restarts could free it + // during the game, additionally we need to flip the triangles... + // note: ODE does preprocessing of the mesh for culling, removing + // concave edges, etc., so this is not a lightweight operation + ed->priv.server->ode_numvertices = numvertices = model->brush.collisionmesh->numverts; + ed->priv.server->ode_vertex3f = (float *)Mem_Alloc(mempool, numvertices * sizeof(float[3])); + + // VorteX: rebuild geomsize based on entity's collision mesh, honor scale + VectorSet(entmins, 0, 0, 0); + VectorSet(entmaxs, 0, 0, 0); + for (vertexindex = 0, ov = ed->priv.server->ode_vertex3f, iv = model->brush.collisionmesh->vertex3f;vertexindex < numvertices;vertexindex++, ov += 3, iv += 3) + { + ov[0] = iv[0] * scale[0]; + ov[1] = iv[1] * scale[1]; + ov[2] = iv[2] * scale[2]; + entmins[0] = min(entmins[0], ov[0]); + entmins[1] = min(entmins[1], ov[1]); + entmins[2] = min(entmins[2], ov[2]); + entmaxs[0] = max(entmaxs[0], ov[0]); + entmaxs[1] = max(entmaxs[1], ov[1]); + entmaxs[2] = max(entmaxs[2], ov[2]); + } + if (!PRVM_gameedictvector(ed, massofs)) + VectorMAM(0.5f, entmins, 0.5f, entmaxs, geomcenter); + for (vertexindex = 0, ov = ed->priv.server->ode_vertex3f, iv = model->brush.collisionmesh->vertex3f;vertexindex < numvertices;vertexindex++, ov += 3, iv += 3) + { + ov[0] = ov[0] - geomcenter[0]; + ov[1] = ov[1] - geomcenter[1]; + ov[2] = ov[2] - geomcenter[2]; + } + VectorSubtract(entmaxs, entmins, geomsize); + if (VectorLength2(geomsize) == 0) + { + if (movetype == MOVETYPE_PHYSICS) + Con_Printf("entity %i collision mesh has null geomsize\n", PRVM_NUM_FOR_EDICT(ed)); + VectorSet(geomsize, 1.0f, 1.0f, 1.0f); + } + ed->priv.server->ode_numtriangles = numtriangles = model->brush.collisionmesh->numtriangles; + ed->priv.server->ode_element3i = (int *)Mem_Alloc(mempool, numtriangles * sizeof(int[3])); + //memcpy(ed->priv.server->ode_element3i, model->brush.collisionmesh->element3i, ed->priv.server->ode_numtriangles * sizeof(int[3])); + for (triangleindex = 0, oe = ed->priv.server->ode_element3i, ie = model->brush.collisionmesh->element3i;triangleindex < numtriangles;triangleindex++, oe += 3, ie += 3) + { + oe[0] = ie[2]; + oe[1] = ie[1]; + oe[2] = ie[0]; + } + // create geom + Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); + if (!convex_compatible || !physics_ode_allowconvex.integer) + { + // trimesh + dataID = dGeomTriMeshDataCreate(); + dGeomTriMeshDataBuildSingle((dTriMeshDataID)dataID, (void*)ed->priv.server->ode_vertex3f, sizeof(float[3]), ed->priv.server->ode_numvertices, ed->priv.server->ode_element3i, ed->priv.server->ode_numtriangles*3, sizeof(int[3])); + ed->priv.server->ode_geom = (void *)dCreateTriMesh((dSpaceID)world->physics.ode_space, (dTriMeshDataID)dataID, NULL, NULL, NULL); + dMassSetBoxTotal(&mass, massval, geomsize[0], geomsize[1], geomsize[2]); + } + else + { + // VorteX: this code is unfinished in two ways + // - no duplicate vertex merging are done + // - triangles that shares same edge and havee sam plane are not merget into poly + // so, currently it only works for geosphere meshes with no UV + + Con_Printf("Build convex hull for model %s...\n", model->name); + // build convex geometry from trimesh data + // this ensures that trimesh's triangles can form correct convex geometry + // not many of error checking is performed + // ODE's conve hull data consist of: + // planes : an array of planes in the form: normal X, normal Y, normal Z, distance + // points : an array of points X,Y,Z + // polygons: an array of indices to the points of each polygon,it should be the number of vertices + // followed by that amount of indices to "points" in counter clockwise order + polygonsData = polygons = (unsigned int *)Mem_Alloc(mempool, numtriangles*sizeof(int)*4); + planesData = planes = (dReal *)Mem_Alloc(mempool, numtriangles*sizeof(dReal)*4); + mapped = (qboolean *)Mem_Alloc(mempool, numvertices*sizeof(qboolean)); + used = (qboolean *)Mem_Alloc(mempool, numtriangles*sizeof(qboolean)); + memset(mapped, 0, numvertices*sizeof(qboolean)); + memset(used, 0, numtriangles*sizeof(qboolean)); + numplanes = numpoints = polyvert = 0; + // build convex hull + // todo: merge duplicated verts here + Con_Printf("Building...\n"); + iv = ed->priv.server->ode_vertex3f; + for (triangleindex = 0; triangleindex < numtriangles; triangleindex++) + { + // already formed a polygon? + if (used[triangleindex]) + continue; + // init polygon + // switch clockwise->counterclockwise + ie = &model->brush.collisionmesh->element3i[triangleindex*3]; + used[triangleindex] = true; + TriangleNormal(&iv[ie[0]*3], &iv[ie[1]*3], &iv[ie[2]*3], planes); + VectorNormalize(planes); + polygons[0] = 3; + polygons[3] = (unsigned int)ie[0]; mapped[polygons[3]] = true; + polygons[2] = (unsigned int)ie[1]; mapped[polygons[2]] = true; + polygons[1] = (unsigned int)ie[2]; mapped[polygons[1]] = true; + + // now find and include concave triangles + for (i = triangleindex; i < numtriangles; i++) + { + if (used[i]) + continue; + // should share at least 2 vertexes + for (polyvert = 1; polyvert <= polygons[0]; polyvert++) + { + // todo: merge in triangles that shares an edge and have same plane here + } + } + + // add polygon to overall stats + planes[3] = DotProduct(&iv[polygons[1]*3], planes); + polygons += (polygons[0]+1); + planes += 4; + numplanes++; + } + Mem_Free(used); + // save points + for (vertexindex = 0, numpoints = 0; vertexindex < numvertices; vertexindex++) + if (mapped[vertexindex]) + numpoints++; + pointsData = (dReal *)Mem_Alloc(mempool, numpoints*sizeof(dReal)*3 + numplanes*sizeof(dReal)*4); // planes is appended + for (vertexindex = 0, numpoints = 0; vertexindex < numvertices; vertexindex++) + { + if (mapped[vertexindex]) + { + VectorCopy(&iv[vertexindex*3], &pointsData[numpoints*3]); + numpoints++; + } + } + Mem_Free(mapped); + Con_Printf("Points: \n"); + for (i = 0; i < (int)numpoints; i++) + Con_Printf("%3i: %3.1f %3.1f %3.1f\n", i, pointsData[i*3], pointsData[i*3+1], pointsData[i*3+2]); + // save planes + planes = planesData; + planesData = pointsData + numpoints*3; + memcpy(planesData, planes, numplanes*sizeof(dReal)*4); + Mem_Free(planes); + Con_Printf("planes...\n"); + for (i = 0; i < numplanes; i++) + Con_Printf("%3i: %1.1f %1.1f %1.1f %1.1f\n", i, planesData[i*4], planesData[i*4 + 1], planesData[i*4 + 2], planesData[i*4 + 3]); + // save polygons + polyvert = polygons - polygonsData; + polygons = polygonsData; + polygonsData = (unsigned int *)Mem_Alloc(mempool, polyvert*sizeof(int)); + memcpy(polygonsData, polygons, polyvert*sizeof(int)); + Mem_Free(polygons); + Con_Printf("Polygons: \n"); + polygons = polygonsData; + for (i = 0; i < numplanes; i++) + { + Con_Printf("%3i : %i ", i, polygons[0]); + for (triangleindex = 1; triangleindex <= (int)polygons[0]; triangleindex++) + Con_Printf("%3i ", polygons[triangleindex]); + polygons += (polygons[0]+1); + Con_Printf("\n"); + } + Mem_Free(ed->priv.server->ode_element3i); + ed->priv.server->ode_element3i = (int *)polygonsData; + Mem_Free(ed->priv.server->ode_vertex3f); + ed->priv.server->ode_vertex3f = (float *)pointsData; + // check for properly build polygons by calculating the determinant of the 3x3 matrix composed of the first 3 points in the polygon + // this code is picked from ODE Source + Con_Printf("Check...\n"); + polygons = polygonsData; + for (i = 0; i < numplanes; i++) + { + if((pointsData[(polygons[1]*3)+0]*pointsData[(polygons[2]*3)+1]*pointsData[(polygons[3]*3)+2] + + pointsData[(polygons[1]*3)+1]*pointsData[(polygons[2]*3)+2]*pointsData[(polygons[3]*3)+0] + + pointsData[(polygons[1]*3)+2]*pointsData[(polygons[2]*3)+0]*pointsData[(polygons[3]*3)+1] - + pointsData[(polygons[1]*3)+2]*pointsData[(polygons[2]*3)+1]*pointsData[(polygons[3]*3)+0] - + pointsData[(polygons[1]*3)+1]*pointsData[(polygons[2]*3)+0]*pointsData[(polygons[3]*3)+2] - + pointsData[(polygons[1]*3)+0]*pointsData[(polygons[2]*3)+2]*pointsData[(polygons[3]*3)+1]) < 0) + Con_Printf("WARNING: Polygon %d is not defined counterclockwise\n", i); + if (planesData[(i*4)+3] < 0) + Con_Printf("WARNING: Plane %d does not contain the origin\n", i); + polygons += (*polygons + 1); + } + // create geom + Con_Printf("Create geom...\n"); + ed->priv.server->ode_geom = (void *)dCreateConvex((dSpaceID)world->physics.ode_space, planesData, numplanes, pointsData, numpoints, polygonsData); + dMassSetBoxTotal(&mass, massval, geomsize[0], geomsize[1], geomsize[2]); + Con_Printf("Done!\n"); + } + break; + case GEOMTYPE_BOX: +treatasbox: + Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); + ed->priv.server->ode_geom = (void *)dCreateBox((dSpaceID)world->physics.ode_space, geomsize[0], geomsize[1], geomsize[2]); + dMassSetBoxTotal(&mass, massval, geomsize[0], geomsize[1], geomsize[2]); + break; + case GEOMTYPE_SPHERE: + Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); + ed->priv.server->ode_geom = (void *)dCreateSphere((dSpaceID)world->physics.ode_space, geomsize[0] * 0.5f); + dMassSetSphereTotal(&mass, massval, geomsize[0] * 0.5f); + break; + case GEOMTYPE_CAPSULE: + axisindex = 0; + if (geomsize[axisindex] < geomsize[1]) + axisindex = 1; + if (geomsize[axisindex] < geomsize[2]) + axisindex = 2; + // the qc gives us 3 axis radius, the longest axis is the capsule + // axis, since ODE doesn't like this idea we have to create a + // capsule which uses the standard orientation, and apply a + // transform to it + if (axisindex == 0) + { + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); + radius = min(geomsize[1], geomsize[2]) * 0.5f; + } + else if (axisindex == 1) + { + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); + radius = min(geomsize[0], geomsize[2]) * 0.5f; + } + else + { + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); + radius = min(geomsize[0], geomsize[1]) * 0.5f; + } + length = geomsize[axisindex] - radius*2; + // because we want to support more than one axisindex, we have to + // create a transform, and turn on its cleanup setting (which will + // cause the child to be destroyed when it is destroyed) + ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCapsuleTotal(&mass, massval, axisindex+1, radius, length); + break; + case GEOMTYPE_CAPSULE_X: + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); + radius = min(geomsize[1], geomsize[2]) * 0.5f; + length = geomsize[0] - radius*2; + // check if length is not enough, reduce radius then + if (length <= 0) + { + radius -= (1 - length)*0.5; + length = 1; + } + ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCapsuleTotal(&mass, massval, 1, radius, length); + break; + case GEOMTYPE_CAPSULE_Y: + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); + radius = min(geomsize[0], geomsize[2]) * 0.5f; + length = geomsize[1] - radius*2; + // check if length is not enough, reduce radius then + if (length <= 0) + { + radius -= (1 - length)*0.5; + length = 1; + } + ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCapsuleTotal(&mass, massval, 2, radius, length); + break; + case GEOMTYPE_CAPSULE_Z: + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); + radius = min(geomsize[1], geomsize[0]) * 0.5f; + length = geomsize[2] - radius*2; + // check if length is not enough, reduce radius then + if (length <= 0) + { + radius -= (1 - length)*0.5; + length = 1; + } + ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCapsuleTotal(&mass, massval, 3, radius, length); + break; + case GEOMTYPE_CYLINDER: + axisindex = 0; + if (geomsize[axisindex] < geomsize[1]) + axisindex = 1; + if (geomsize[axisindex] < geomsize[2]) + axisindex = 2; + // the qc gives us 3 axis radius, the longest axis is the capsule + // axis, since ODE doesn't like this idea we have to create a + // capsule which uses the standard orientation, and apply a + // transform to it + if (axisindex == 0) + { + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); + radius = min(geomsize[1], geomsize[2]) * 0.5f; + } + else if (axisindex == 1) + { + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); + radius = min(geomsize[0], geomsize[2]) * 0.5f; + } + else + { + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); + radius = min(geomsize[0], geomsize[1]) * 0.5f; + } + length = geomsize[axisindex]; + // check if length is not enough, reduce radius then + if (length <= 0) + { + radius -= (1 - length)*0.5; + length = 1; + } + ed->priv.server->ode_geom = (void *)dCreateCylinder((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCylinderTotal(&mass, massval, axisindex+1, radius, length); + break; + case GEOMTYPE_CYLINDER_X: + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); + radius = min(geomsize[1], geomsize[2]) * 0.5f; + length = geomsize[0]; + ed->priv.server->ode_geom = (void *)dCreateCylinder((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCylinderTotal(&mass, massval, 1, radius, length); + break; + case GEOMTYPE_CYLINDER_Y: + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); + radius = min(geomsize[0], geomsize[2]) * 0.5f; + length = geomsize[1]; + ed->priv.server->ode_geom = (void *)dCreateCylinder((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCylinderTotal(&mass, massval, 2, radius, length); + break; + case GEOMTYPE_CYLINDER_Z: + Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); + radius = min(geomsize[0], geomsize[1]) * 0.5f; + length = geomsize[2]; + ed->priv.server->ode_geom = (void *)dCreateCylinder((dSpaceID)world->physics.ode_space, radius, length); + dMassSetCylinderTotal(&mass, massval, 3, radius, length); + break; + default: + Sys_Error("World_Physics_BodyFromEntity: unrecognized geomtype value %i was accepted by filter\n", solid); + // this goto only exists to prevent warnings from the compiler + // about uninitialized variables (mass), while allowing it to + // catch legitimate uninitialized variable warnings + goto treatasbox; + } + ed->priv.server->ode_mass = massval; + ed->priv.server->ode_modelindex = modelindex; + VectorCopy(entmins, ed->priv.server->ode_mins); + VectorCopy(entmaxs, ed->priv.server->ode_maxs); + VectorCopy(scale, ed->priv.server->ode_scale); + ed->priv.server->ode_movelimit = min(geomsize[0], min(geomsize[1], geomsize[2])); + Matrix4x4_Invert_Simple(&ed->priv.server->ode_offsetimatrix, &ed->priv.server->ode_offsetmatrix); + ed->priv.server->ode_massbuf = Mem_Alloc(mempool, sizeof(mass)); + memcpy(ed->priv.server->ode_massbuf, &mass, sizeof(dMass)); + } + + if (ed->priv.server->ode_geom) + dGeomSetData((dGeomID)ed->priv.server->ode_geom, (void*)ed); + if (movetype == MOVETYPE_PHYSICS && ed->priv.server->ode_geom) + { + // entity is dynamic + if (ed->priv.server->ode_body == NULL) + { + ed->priv.server->ode_body = (void *)(body = dBodyCreate((dWorldID)world->physics.ode_world)); + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); + dBodySetData(body, (void*)ed); + dBodySetMass(body, (dMass *) ed->priv.server->ode_massbuf); + modified = true; + } + } + else + { + // entity is deactivated + if (ed->priv.server->ode_body != NULL) + { + if(ed->priv.server->ode_geom) + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); + dBodyDestroy((dBodyID) ed->priv.server->ode_body); + ed->priv.server->ode_body = NULL; + modified = true; + } + } + + // get current data from entity + VectorClear(origin); + VectorClear(velocity); + //VectorClear(forward); + //VectorClear(left); + //VectorClear(up); + //VectorClear(spinvelocity); + VectorClear(angles); + VectorClear(avelocity); + gravity = true; + VectorCopy(PRVM_gameedictvector(ed, origin), origin); + VectorCopy(PRVM_gameedictvector(ed, velocity), velocity); + //VectorCopy(PRVM_gameedictvector(ed, axis_forward), forward); + //VectorCopy(PRVM_gameedictvector(ed, axis_left), left); + //VectorCopy(PRVM_gameedictvector(ed, axis_up), up); + //VectorCopy(PRVM_gameedictvector(ed, spinvelocity), spinvelocity); + VectorCopy(PRVM_gameedictvector(ed, angles), angles); + VectorCopy(PRVM_gameedictvector(ed, avelocity), avelocity); + if (PRVM_gameedictfloat(ed, gravity) != 0.0f && PRVM_gameedictfloat(ed, gravity) < 0.5f) gravity = false; + if (ed == prog->edicts) + gravity = false; + + // compatibility for legacy entities + //if (!VectorLength2(forward) || solid == SOLID_BSP) + { + float pitchsign = 1; + vec3_t qangles, qavelocity; + VectorCopy(angles, qangles); + VectorCopy(avelocity, qavelocity); + + if(prog == SVVM_prog) // FIXME some better way? + { + pitchsign = SV_GetPitchSign(prog, ed); + } + else if(prog == CLVM_prog) + { + pitchsign = CL_GetPitchSign(prog, ed); + } + qangles[PITCH] *= pitchsign; + qavelocity[PITCH] *= pitchsign; + + AngleVectorsFLU(qangles, forward, left, up); + // convert single-axis rotations in avelocity to spinvelocity + // FIXME: untested math - check signs + VectorSet(spinvelocity, DEG2RAD(qavelocity[PITCH]), DEG2RAD(qavelocity[ROLL]), DEG2RAD(qavelocity[YAW])); + } + + // compatibility for legacy entities + switch (solid) + { + case SOLID_BBOX: + case SOLID_SLIDEBOX: + case SOLID_CORPSE: + VectorSet(forward, 1, 0, 0); + VectorSet(left, 0, 1, 0); + VectorSet(up, 0, 0, 1); + VectorSet(spinvelocity, 0, 0, 0); + break; + } + + + // we must prevent NANs... + if (physics_ode_trick_fixnan.integer) + { + test = VectorLength2(origin) + VectorLength2(forward) + VectorLength2(left) + VectorLength2(up) + VectorLength2(velocity) + VectorLength2(spinvelocity); + if (VEC_IS_NAN(test)) + { + modified = true; + //Con_Printf("Fixing NAN values on entity %i : .classname = \"%s\" .origin = '%f %f %f' .velocity = '%f %f %f' .axis_forward = '%f %f %f' .axis_left = '%f %f %f' .axis_up = %f %f %f' .spinvelocity = '%f %f %f'\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(PRVM_gameedictstring(ed, classname)), origin[0], origin[1], origin[2], velocity[0], velocity[1], velocity[2], forward[0], forward[1], forward[2], left[0], left[1], left[2], up[0], up[1], up[2], spinvelocity[0], spinvelocity[1], spinvelocity[2]); + if (physics_ode_trick_fixnan.integer >= 2) + Con_Printf("Fixing NAN values on entity %i : .classname = \"%s\" .origin = '%f %f %f' .velocity = '%f %f %f' .angles = '%f %f %f' .avelocity = '%f %f %f'\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(prog, PRVM_gameedictstring(ed, classname)), origin[0], origin[1], origin[2], velocity[0], velocity[1], velocity[2], angles[0], angles[1], angles[2], avelocity[0], avelocity[1], avelocity[2]); + test = VectorLength2(origin); + if (VEC_IS_NAN(test)) + VectorClear(origin); + test = VectorLength2(forward) * VectorLength2(left) * VectorLength2(up); + if (VEC_IS_NAN(test)) + { + VectorSet(angles, 0, 0, 0); + VectorSet(forward, 1, 0, 0); + VectorSet(left, 0, 1, 0); + VectorSet(up, 0, 0, 1); + } + test = VectorLength2(velocity); + if (VEC_IS_NAN(test)) + VectorClear(velocity); + test = VectorLength2(spinvelocity); + if (VEC_IS_NAN(test)) + { + VectorClear(avelocity); + VectorClear(spinvelocity); + } + } + } + + // check if the qc edited any position data + if (!VectorCompare(origin, ed->priv.server->ode_origin) + || !VectorCompare(velocity, ed->priv.server->ode_velocity) + || !VectorCompare(angles, ed->priv.server->ode_angles) + || !VectorCompare(avelocity, ed->priv.server->ode_avelocity) + || gravity != ed->priv.server->ode_gravity) + modified = true; + + // store the qc values into the physics engine + body = (dBodyID)ed->priv.server->ode_body; + if (modified && ed->priv.server->ode_geom) + { + dVector3 r[3]; + matrix4x4_t entitymatrix; + matrix4x4_t bodymatrix; + +#if 0 + Con_Printf("entity %i got changed by QC\n", (int) (ed - prog->edicts)); + if(!VectorCompare(origin, ed->priv.server->ode_origin)) + Con_Printf(" origin: %f %f %f -> %f %f %f\n", ed->priv.server->ode_origin[0], ed->priv.server->ode_origin[1], ed->priv.server->ode_origin[2], origin[0], origin[1], origin[2]); + if(!VectorCompare(velocity, ed->priv.server->ode_velocity)) + Con_Printf(" velocity: %f %f %f -> %f %f %f\n", ed->priv.server->ode_velocity[0], ed->priv.server->ode_velocity[1], ed->priv.server->ode_velocity[2], velocity[0], velocity[1], velocity[2]); + if(!VectorCompare(angles, ed->priv.server->ode_angles)) + Con_Printf(" angles: %f %f %f -> %f %f %f\n", ed->priv.server->ode_angles[0], ed->priv.server->ode_angles[1], ed->priv.server->ode_angles[2], angles[0], angles[1], angles[2]); + if(!VectorCompare(avelocity, ed->priv.server->ode_avelocity)) + Con_Printf(" avelocity: %f %f %f -> %f %f %f\n", ed->priv.server->ode_avelocity[0], ed->priv.server->ode_avelocity[1], ed->priv.server->ode_avelocity[2], avelocity[0], avelocity[1], avelocity[2]); + if(gravity != ed->priv.server->ode_gravity) + Con_Printf(" gravity: %i -> %i\n", ed->priv.server->ode_gravity, gravity); +#endif + // values for BodyFromEntity to check if the qc modified anything later + VectorCopy(origin, ed->priv.server->ode_origin); + VectorCopy(velocity, ed->priv.server->ode_velocity); + VectorCopy(angles, ed->priv.server->ode_angles); + VectorCopy(avelocity, ed->priv.server->ode_avelocity); + ed->priv.server->ode_gravity = gravity; + + Matrix4x4_FromVectors(&entitymatrix, forward, left, up, origin); + Matrix4x4_Concat(&bodymatrix, &entitymatrix, &ed->priv.server->ode_offsetmatrix); + Matrix4x4_ToVectors(&bodymatrix, forward, left, up, origin); + r[0][0] = forward[0]; + r[1][0] = forward[1]; + r[2][0] = forward[2]; + r[0][1] = left[0]; + r[1][1] = left[1]; + r[2][1] = left[2]; + r[0][2] = up[0]; + r[1][2] = up[1]; + r[2][2] = up[2]; + if (body) + { + if (movetype == MOVETYPE_PHYSICS) + { + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); + dBodySetPosition(body, origin[0], origin[1], origin[2]); + dBodySetRotation(body, r[0]); + dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); + dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); + dBodySetGravityMode(body, gravity); + } + else + { + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); + dBodySetPosition(body, origin[0], origin[1], origin[2]); + dBodySetRotation(body, r[0]); + dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); + dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); + dBodySetGravityMode(body, gravity); + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); + } + } + else + { + // no body... then let's adjust the parameters of the geom directly + dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); // just in case we previously HAD a body (which should never happen) + dGeomSetPosition((dGeomID)ed->priv.server->ode_geom, origin[0], origin[1], origin[2]); + dGeomSetRotation((dGeomID)ed->priv.server->ode_geom, r[0]); + } + } + + if (body) + { + + // limit movement speed to prevent missed collisions at high speed + ovelocity = dBodyGetLinearVel(body); + ospinvelocity = dBodyGetAngularVel(body); + movelimit = ed->priv.server->ode_movelimit * world->physics.ode_movelimit; + test = VectorLength2(ovelocity); + if (test > movelimit*movelimit) + { + // scale down linear velocity to the movelimit + // scale down angular velocity the same amount for consistency + f = movelimit / sqrt(test); + VectorScale(ovelocity, f, velocity); + VectorScale(ospinvelocity, f, spinvelocity); + dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); + dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); + } + + // make sure the angular velocity is not exploding + spinlimit = physics_ode_spinlimit.value; + test = VectorLength2(ospinvelocity); + if (test > spinlimit) + { + dBodySetAngularVel(body, 0, 0, 0); + } + + // apply functions and clear stack + for(func = ed->priv.server->ode_func; func; func = nextf) + { + nextf = func->next; + World_Physics_ApplyCmd(ed, func); + Mem_Free(func); + } + ed->priv.server->ode_func = NULL; + } +} + +#define MAX_CONTACTS 32 +static void nearCallback (void *data, dGeomID o1, dGeomID o2) +{ + world_t *world = (world_t *)data; + prvm_prog_t *prog = world->prog; + dContact contact[MAX_CONTACTS]; // max contacts per collision pair + int b1enabled = 0, b2enabled = 0; + dBodyID b1, b2; + dJointID c; + int i; + int numcontacts; + float bouncefactor1 = 0.0f; + float bouncestop1 = 60.0f / 800.0f; + float bouncefactor2 = 0.0f; + float bouncestop2 = 60.0f / 800.0f; + float erp; + dVector3 grav; + prvm_edict_t *ed1, *ed2; + + if (dGeomIsSpace(o1) || dGeomIsSpace(o2)) + { + // colliding a space with something + dSpaceCollide2(o1, o2, data, &nearCallback); + // Note we do not want to test intersections within a space, + // only between spaces. + //if (dGeomIsSpace(o1)) dSpaceCollide(o1, data, &nearCallback); + //if (dGeomIsSpace(o2)) dSpaceCollide(o2, data, &nearCallback); + return; + } + + b1 = dGeomGetBody(o1); + if (b1) + b1enabled = dBodyIsEnabled(b1); + b2 = dGeomGetBody(o2); + if (b2) + b2enabled = dBodyIsEnabled(b2); + + // at least one object has to be using MOVETYPE_PHYSICS and should be enabled or we just don't care + if (!b1enabled && !b2enabled) + return; + + // exit without doing anything if the two bodies are connected by a joint + if (b1 && b2 && dAreConnectedExcluding(b1, b2, dJointTypeContact)) + return; + + ed1 = (prvm_edict_t *) dGeomGetData(o1); + if(ed1 && ed1->priv.server->free) + ed1 = NULL; + if(ed1) + { + bouncefactor1 = PRVM_gameedictfloat(ed1, bouncefactor); + bouncestop1 = PRVM_gameedictfloat(ed1, bouncestop); + if (!bouncestop1) + bouncestop1 = 60.0f / 800.0f; + } + + ed2 = (prvm_edict_t *) dGeomGetData(o2); + if(ed2 && ed2->priv.server->free) + ed2 = NULL; + if(ed2) + { + bouncefactor2 = PRVM_gameedictfloat(ed2, bouncefactor); + bouncestop2 = PRVM_gameedictfloat(ed2, bouncestop); + if (!bouncestop2) + bouncestop2 = 60.0f / 800.0f; + } + + if(prog == SVVM_prog) + { + if(ed1 && PRVM_serveredictfunction(ed1, touch)) + { + SV_LinkEdict_TouchAreaGrid_Call(ed1, ed2 ? ed2 : prog->edicts); + } + if(ed2 && PRVM_serveredictfunction(ed2, touch)) + { + SV_LinkEdict_TouchAreaGrid_Call(ed2, ed1 ? ed1 : prog->edicts); + } + } + + // merge bounce factors and bounce stop + if(bouncefactor2 > 0) + { + if(bouncefactor1 > 0) + { + // TODO possibly better logic to merge bounce factor data? + if(bouncestop2 < bouncestop1) + bouncestop1 = bouncestop2; + if(bouncefactor2 > bouncefactor1) + bouncefactor1 = bouncefactor2; + } + else + { + bouncestop1 = bouncestop2; + bouncefactor1 = bouncefactor2; + } + } + dWorldGetGravity((dWorldID)world->physics.ode_world, grav); + bouncestop1 *= fabs(grav[2]); + + // get erp + // select object that moves faster ang get it's erp + erp = (VectorLength2(PRVM_gameedictvector(ed1, velocity)) > VectorLength2(PRVM_gameedictvector(ed2, velocity))) ? PRVM_gameedictfloat(ed1, erp) : PRVM_gameedictfloat(ed2, erp); + + // get max contact points for this collision + numcontacts = (int)PRVM_gameedictfloat(ed1, maxcontacts); + if (!numcontacts) + numcontacts = physics_ode_contact_maxpoints.integer; + if (PRVM_gameedictfloat(ed2, maxcontacts)) + numcontacts = max(numcontacts, (int)PRVM_gameedictfloat(ed2, maxcontacts)); + else + numcontacts = max(numcontacts, physics_ode_contact_maxpoints.integer); + + // generate contact points between the two non-space geoms + numcontacts = dCollide(o1, o2, min(MAX_CONTACTS, numcontacts), &(contact[0].geom), sizeof(contact[0])); + // add these contact points to the simulation + for (i = 0;i < numcontacts;i++) + { + contact[i].surface.mode = (physics_ode_contact_mu.value != -1 ? dContactApprox1 : 0) | (physics_ode_contact_erp.value != -1 ? dContactSoftERP : 0) | (physics_ode_contact_cfm.value != -1 ? dContactSoftCFM : 0) | (bouncefactor1 > 0 ? dContactBounce : 0); + contact[i].surface.mu = physics_ode_contact_mu.value * ed1->priv.server->ode_friction * ed2->priv.server->ode_friction; + contact[i].surface.soft_erp = physics_ode_contact_erp.value + erp; + contact[i].surface.soft_cfm = physics_ode_contact_cfm.value; + contact[i].surface.bounce = bouncefactor1; + contact[i].surface.bounce_vel = bouncestop1; + c = dJointCreateContact((dWorldID)world->physics.ode_world, (dJointGroupID)world->physics.ode_contactgroup, contact + i); + dJointAttach(c, b1, b2); + } +} +#endif + +void World_Physics_Frame(world_t *world, double frametime, double gravity) +{ + prvm_prog_t *prog = world->prog; + double tdelta, tdelta2, tdelta3, simulationtime, collisiontime; + + tdelta = Sys_DirtyTime(); +#ifdef USEODE + if (world->physics.ode && physics_ode.integer) + { + int i; + prvm_edict_t *ed; + + if (!physics_ode_constantstep.value) + { + world->physics.ode_iterations = bound(1, physics_ode_iterationsperframe.integer, 1000); + world->physics.ode_step = frametime / world->physics.ode_iterations; + } + else + { + world->physics.ode_time += frametime; + // step size + if (physics_ode_constantstep.value > 0 && physics_ode_constantstep.value < 1) + world->physics.ode_step = physics_ode_constantstep.value; + else + world->physics.ode_step = sys_ticrate.value; + if (world->physics.ode_time > 0.2f) + world->physics.ode_time = world->physics.ode_step; + // set number of iterations to process + world->physics.ode_iterations = 0; + while(world->physics.ode_time >= world->physics.ode_step) + { + world->physics.ode_iterations++; + world->physics.ode_time -= world->physics.ode_step; + } + } + world->physics.ode_movelimit = physics_ode_movelimit.value / world->physics.ode_step; + World_Physics_UpdateODE(world); + + // copy physics properties from entities to physics engine + if (prog) + { + for (i = 0, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) + if (!prog->edicts[i].priv.required->free) + World_Physics_Frame_BodyFromEntity(world, ed); + // oh, and it must be called after all bodies were created + for (i = 0, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) + if (!prog->edicts[i].priv.required->free) + World_Physics_Frame_JointFromEntity(world, ed); + } + + tdelta2 = Sys_DirtyTime(); + collisiontime = 0; + for (i = 0;i < world->physics.ode_iterations;i++) + { + // set the gravity + dWorldSetGravity((dWorldID)world->physics.ode_world, 0, 0, -gravity * physics_ode_world_gravitymod.value); + // set the tolerance for closeness of objects + dWorldSetContactSurfaceLayer((dWorldID)world->physics.ode_world, max(0, physics_ode_contactsurfacelayer.value)); + // run collisions for the current world state, creating JointGroup + tdelta3 = Sys_DirtyTime(); + dSpaceCollide((dSpaceID)world->physics.ode_space, (void *)world, nearCallback); + collisiontime += (Sys_DirtyTime() - tdelta3)*10000; + // apply forces + if (prog) + { + int j; + for (j = 0, ed = prog->edicts + j;j < prog->num_edicts;j++, ed++) + if (!prog->edicts[j].priv.required->free) + World_Physics_Frame_ForceFromEntity(world, ed); + } + // run physics (move objects, calculate new velocities) + // be sure not to pass 0 as step time because that causes an ODE error + dWorldSetQuickStepNumIterations((dWorldID)world->physics.ode_world, bound(1, physics_ode_worldstep_iterations.integer, 200)); + if (world->physics.ode_step > 0) + dWorldQuickStep((dWorldID)world->physics.ode_world, world->physics.ode_step); + // clear the JointGroup now that we're done with it + dJointGroupEmpty((dJointGroupID)world->physics.ode_contactgroup); + } + simulationtime = (Sys_DirtyTime() - tdelta2)*10000; + + // copy physics properties from physics engine to entities and do some stats + if (prog) + { + for (i = 1, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) + if (!prog->edicts[i].priv.required->free) + World_Physics_Frame_BodyToEntity(world, ed); + + // print stats + if (physics_ode_printstats.integer) + { + dBodyID body; + + world->physics.ode_numobjects = 0; + world->physics.ode_activeovjects = 0; + for (i = 1, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) + { + if (prog->edicts[i].priv.required->free) + continue; + body = (dBodyID)prog->edicts[i].priv.server->ode_body; + if (!body) + continue; + world->physics.ode_numobjects++; + if (dBodyIsEnabled(body)) + world->physics.ode_activeovjects++; + } + Con_Printf("ODE Stats(%s): %i iterations, %3.01f (%3.01f collision) %3.01f total : %i objects %i active %i disabled\n", prog->name, world->physics.ode_iterations, simulationtime, collisiontime, (Sys_DirtyTime() - tdelta)*10000, world->physics.ode_numobjects, world->physics.ode_activeovjects, (world->physics.ode_numobjects - world->physics.ode_activeovjects)); + } + } + } +#endif +} diff --git a/app/jni/world.h b/app/jni/world.h new file mode 100644 index 0000000..18e9b00 --- /dev/null +++ b/app/jni/world.h @@ -0,0 +1,146 @@ +/* +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. + +*/ +// world.h + +#ifndef WORLD_H +#define WORLD_H + +#include "collision.h" + +#define MOVE_NORMAL 0 +#define MOVE_NOMONSTERS 1 +#define MOVE_MISSILE 2 +#define MOVE_WORLDONLY 3 +#define MOVE_HITMODEL 4 + +#define AREA_GRID 128 +#define AREA_GRIDNODES (AREA_GRID * AREA_GRID) + +typedef struct link_s +{ + int entitynumber; + struct link_s *prev, *next; +} link_t; + +typedef struct world_physics_s +{ + // for ODE physics engine + qboolean ode; // if true then ode is activated + void *ode_world; + void *ode_space; + void *ode_contactgroup; + // number of constraint solver iterations to use (for dWorldQuickStep) + int ode_iterations; + // actual step (server frametime / ode_iterations) + vec_t ode_step; + // time we need to simulate, for constantstep + vec_t ode_time; + // stats + int ode_numobjects; // total objects cound + int ode_activeovjects; // active objects count + // max velocity for a 1-unit radius object at current step to prevent + // missed collisions + vec_t ode_movelimit; +} +world_physics_t; + +struct prvm_prog_s; + +typedef struct world_s +{ + // convenient fields + char filename[MAX_QPATH]; + vec3_t mins; + vec3_t maxs; + struct prvm_prog_s *prog; + + int areagrid_stats_calls; + int areagrid_stats_nodechecks; + int areagrid_stats_entitychecks; + + link_t areagrid[AREA_GRIDNODES]; + link_t areagrid_outside; + vec3_t areagrid_bias; + vec3_t areagrid_scale; + vec3_t areagrid_mins; + vec3_t areagrid_maxs; + vec3_t areagrid_size; + int areagrid_marknumber; + + // if the QC uses a physics engine, the data for it is here + world_physics_t physics; +} +world_t; + +struct prvm_edict_s; + +// cyclic doubly-linked list functions +void World_ClearLink(link_t *l); +void World_RemoveLink(link_t *l); +void World_InsertLinkBefore(link_t *l, link_t *before, int entitynumber); + +void World_Init(void); +void World_Shutdown(void); + +/// called after the world model has been loaded, before linking any entities +void World_SetSize(world_t *world, const char *filename, const vec3_t mins, const vec3_t maxs, struct prvm_prog_s *prog); +/// unlinks all entities (used before reallocation of edicts) +void World_UnlinkAll(world_t *world); + +void World_PrintAreaStats(world_t *world, const char *worldname); + +/// call before removing an entity, and before trying to move one, +/// so it doesn't clip against itself +void World_UnlinkEdict(struct prvm_edict_s *ent); + +/// Needs to be called any time an entity changes origin, mins, maxs +void World_LinkEdict(world_t *world, struct prvm_edict_s *ent, const vec3_t mins, const vec3_t maxs); + +/// \returns list of entities touching a box +int World_EntitiesInBox(world_t *world, const vec3_t mins, const vec3_t maxs, int maxlist, struct prvm_edict_s **list); + +void World_Start(world_t *world); +void World_End(world_t *world); + +// physics macros +#ifndef ODE_STATIC +# define ODE_DYNAMIC 1 +#endif + +#if defined(ODE_STATIC) || defined(ODE_DYNAMIC) +# define USEODE 1 +#endif + +// update physics +// this is called by SV_Physics +void World_Physics_Frame(world_t *world, double frametime, double gravity); + +// change physics properties of entity +struct prvm_edict_s; +struct edict_odefunc_s; +void World_Physics_ApplyCmd(struct prvm_edict_s *ed, struct edict_odefunc_s *f); + +// remove physics data from entity +// this is called by entity removal +void World_Physics_RemoveFromEntity(world_t *world, struct prvm_edict_s *ed); +void World_Physics_RemoveJointFromEntity(world_t *world, struct prvm_edict_s *ed); + +#endif + diff --git a/app/jni/zone.c b/app/jni/zone.c new file mode 100644 index 0000000..2c72c11 --- /dev/null +++ b/app/jni/zone.c @@ -0,0 +1,984 @@ +/* +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. + +*/ +// Z_zone.c + +#include "quakedef.h" +#include "thread.h" + +#ifdef WIN32 +#include +#include +#else +#include +#endif + +#ifdef _MSC_VER +#include +#else +#include +#endif +#define MEMHEADER_SENTINEL_FOR_ADDRESS(p) ((sentinel_seed ^ (unsigned int) (uintptr_t) (p)) + sentinel_seed) +unsigned int sentinel_seed; + +qboolean mem_bigendian = false; +void *mem_mutex = NULL; + +// divVerent: enables file backed malloc using mmap to conserve swap space (instead of malloc) +#ifndef FILE_BACKED_MALLOC +# define FILE_BACKED_MALLOC 0 +#endif + +// LordHavoc: enables our own low-level allocator (instead of malloc) +#ifndef MEMCLUMPING +# define MEMCLUMPING 0 +#endif +#ifndef MEMCLUMPING_FREECLUMPS +# define MEMCLUMPING_FREECLUMPS 0 +#endif + +#if MEMCLUMPING +// smallest unit we care about is this many bytes +#define MEMUNIT 128 +// try to do 32MB clumps, but overhead eats into this +#ifndef MEMWANTCLUMPSIZE +# define MEMWANTCLUMPSIZE (1<<27) +#endif +// give malloc padding so we can't waste most of a page at the end +#define MEMCLUMPSIZE (MEMWANTCLUMPSIZE - MEMWANTCLUMPSIZE/MEMUNIT/32 - 128) +#define MEMBITS (MEMCLUMPSIZE / MEMUNIT) +#define MEMBITINTS (MEMBITS / 32) + +typedef struct memclump_s +{ + // contents of the clump + unsigned char block[MEMCLUMPSIZE]; + // should always be MEMCLUMP_SENTINEL + unsigned int sentinel1; + // if a bit is on, it means that the MEMUNIT bytes it represents are + // allocated, otherwise free + unsigned int bits[MEMBITINTS]; + // should always be MEMCLUMP_SENTINEL + unsigned int sentinel2; + // if this drops to 0, the clump is freed + size_t blocksinuse; + // largest block of memory available (this is reset to an optimistic + // number when anything is freed, and updated when alloc fails the clump) + size_t largestavailable; + // next clump in the chain + struct memclump_s *chain; +} +memclump_t; + +#if MEMCLUMPING == 2 +static memclump_t masterclump; +#endif +static memclump_t *clumpchain = NULL; +#endif + + +cvar_t developer_memory = {0, "developer_memory", "0", "prints debugging information about memory allocations"}; +cvar_t developer_memorydebug = {0, "developer_memorydebug", "0", "enables memory corruption checks (very slow)"}; +cvar_t sys_memsize_physical = {CVAR_READONLY, "sys_memsize_physical", "", "physical memory size in MB (or empty if unknown)"}; +cvar_t sys_memsize_virtual = {CVAR_READONLY, "sys_memsize_virtual", "", "virtual memory size in MB (or empty if unknown)"}; + +static mempool_t *poolchain = NULL; + +void Mem_PrintStats(void); +void Mem_PrintList(size_t minallocationsize); + +#if FILE_BACKED_MALLOC +#include +#include +typedef struct mmap_data_s +{ + size_t len; +} +mmap_data_t; +static void *mmap_malloc(size_t size) +{ + char vabuf[MAX_OSPATH + 1]; + char *tmpdir = getenv("TEMP"); + mmap_data_t *data; + int fd; + size += sizeof(mmap_data_t); // waste block + dpsnprintf(vabuf, sizeof(vabuf), "%s/darkplaces.XXXXXX", tmpdir ? tmpdir : "/tmp"); + fd = mkstemp(vabuf); + if(fd < 0) + return NULL; + ftruncate(fd, size); + data = (unsigned char *) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0); + close(fd); + unlink(vabuf); + if(!data) + return NULL; + data->len = size; + return (void *) (data + 1); +} +static void mmap_free(void *mem) +{ + mmap_data_t *data; + if(!mem) + return; + data = ((mmap_data_t *) mem) - 1; + munmap(data, data->len); +} +#define malloc mmap_malloc +#define free mmap_free +#endif + +#if MEMCLUMPING != 2 +// some platforms have a malloc that returns NULL but succeeds later +// (Windows growing its swapfile for example) +static void *attempt_malloc(size_t size) +{ + void *base; + // try for half a second or so + unsigned int attempts = 500; + while (attempts--) + { + base = (void *)malloc(size); + if (base) + return base; + Sys_Sleep(1000); + } + return NULL; +} +#endif + +#if MEMCLUMPING +static memclump_t *Clump_NewClump(void) +{ + memclump_t **clumpchainpointer; + memclump_t *clump; +#if MEMCLUMPING == 2 + if (clumpchain) + return NULL; + clump = &masterclump; +#else + clump = (memclump_t*)attempt_malloc(sizeof(memclump_t)); + if (!clump) + return NULL; +#endif + + // initialize clump + if (developer_memorydebug.integer) + memset(clump, 0xEF, sizeof(*clump)); + clump->sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1); + memset(clump->bits, 0, sizeof(clump->bits)); + clump->sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2); + clump->blocksinuse = 0; + clump->largestavailable = 0; + clump->chain = NULL; + + // link clump into chain + for (clumpchainpointer = &clumpchain;*clumpchainpointer;clumpchainpointer = &(*clumpchainpointer)->chain) + ; + *clumpchainpointer = clump; + + return clump; +} +#endif + +// low level clumping functions, all other memory functions use these +static void *Clump_AllocBlock(size_t size) +{ + unsigned char *base; +#if MEMCLUMPING + if (size <= MEMCLUMPSIZE) + { + int index; + unsigned int bit; + unsigned int needbits; + unsigned int startbit; + unsigned int endbit; + unsigned int needints; + int startindex; + int endindex; + unsigned int value; + unsigned int mask; + unsigned int *array; + memclump_t **clumpchainpointer; + memclump_t *clump; + needbits = (size + MEMUNIT - 1) / MEMUNIT; + needints = (needbits+31)>>5; + for (clumpchainpointer = &clumpchain;;clumpchainpointer = &(*clumpchainpointer)->chain) + { + clump = *clumpchainpointer; + if (!clump) + { + clump = Clump_NewClump(); + if (!clump) + return NULL; + } + if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) + Sys_Error("Clump_AllocBlock: trashed sentinel1\n"); + if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) + Sys_Error("Clump_AllocBlock: trashed sentinel2\n"); + startbit = 0; + endbit = startbit + needbits; + array = clump->bits; + // do as fast a search as possible, even if it means crude alignment + if (needbits >= 32) + { + // large allocations are aligned to large boundaries + // furthermore, they are allocated downward from the top... + endindex = MEMBITINTS; + startindex = endindex - needints; + index = endindex; + while (--index >= startindex) + { + if (array[index]) + { + endindex = index; + startindex = endindex - needints; + if (startindex < 0) + goto nofreeblock; + } + } + startbit = startindex*32; + goto foundblock; + } + else + { + // search for a multi-bit gap in a single int + // (not dealing with the cases that cross two ints) + mask = (1<bits[bit>>5] & (1<<(bit & 31))) + Sys_Error("Clump_AllocBlock: internal error (%i needbits)\n", needbits); + for (bit = startbit;bit < endbit;bit++) + clump->bits[bit>>5] |= (1<<(bit & 31)); + clump->blocksinuse += needbits; + base = clump->block + startbit * MEMUNIT; + if (developer_memorydebug.integer) + memset(base, 0xBF, needbits * MEMUNIT); + return base; +nofreeblock: + ; + } + // never reached + return NULL; + } + // too big, allocate it directly +#endif +#if MEMCLUMPING == 2 + return NULL; +#else + base = (unsigned char *)attempt_malloc(size); + if (base && developer_memorydebug.integer) + memset(base, 0xAF, size); + return base; +#endif +} +static void Clump_FreeBlock(void *base, size_t size) +{ +#if MEMCLUMPING + unsigned int needbits; + unsigned int startbit; + unsigned int endbit; + unsigned int bit; + memclump_t **clumpchainpointer; + memclump_t *clump; + unsigned char *start = (unsigned char *)base; + for (clumpchainpointer = &clumpchain;(clump = *clumpchainpointer);clumpchainpointer = &(*clumpchainpointer)->chain) + { + if (start >= clump->block && start < clump->block + MEMCLUMPSIZE) + { + if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) + Sys_Error("Clump_FreeBlock: trashed sentinel1\n"); + if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) + Sys_Error("Clump_FreeBlock: trashed sentinel2\n"); + if (start + size > clump->block + MEMCLUMPSIZE) + Sys_Error("Clump_FreeBlock: block overrun\n"); + // the block belongs to this clump, clear the range + needbits = (size + MEMUNIT - 1) / MEMUNIT; + startbit = (start - clump->block) / MEMUNIT; + endbit = startbit + needbits; + // first verify all bits are set, otherwise this may be misaligned or a double free + for (bit = startbit;bit < endbit;bit++) + if ((clump->bits[bit>>5] & (1<<(bit & 31))) == 0) + Sys_Error("Clump_FreeBlock: double free\n"); + for (bit = startbit;bit < endbit;bit++) + clump->bits[bit>>5] &= ~(1<<(bit & 31)); + clump->blocksinuse -= needbits; + memset(base, 0xFF, needbits * MEMUNIT); + // if all has been freed, free the clump itself + if (clump->blocksinuse == 0) + { + *clumpchainpointer = clump->chain; + if (developer_memorydebug.integer) + memset(clump, 0xFF, sizeof(*clump)); +#if MEMCLUMPING != 2 + free(clump); +#endif + } + return; + } + } + // does not belong to any known chunk... assume it was a direct allocation +#endif +#if MEMCLUMPING != 2 + memset(base, 0xFF, size); + free(base); +#endif +} + +void *_Mem_Alloc(mempool_t *pool, void *olddata, size_t size, size_t alignment, const char *filename, int fileline) +{ + unsigned int sentinel1; + unsigned int sentinel2; + size_t realsize; + size_t sharedsize; + size_t remainsize; + memheader_t *mem; + memheader_t *oldmem; + unsigned char *base; + + if (size <= 0) + { + if (olddata) + _Mem_Free(olddata, filename, fileline); + return NULL; + } + if (pool == NULL) + { + if(olddata) + pool = ((memheader_t *)((unsigned char *) olddata - sizeof(memheader_t)))->pool; + else + Sys_Error("Mem_Alloc: pool == NULL (alloc at %s:%i)", filename, fileline); + } + if (mem_mutex) + Thread_LockMutex(mem_mutex); + if (developer_memory.integer) + Con_DPrintf("Mem_Alloc: pool %s, file %s:%i, size %i bytes\n", pool->name, filename, fileline, (int)size); + //if (developer.integer > 0 && developer_memorydebug.integer) + // _Mem_CheckSentinelsGlobal(filename, fileline); + pool->totalsize += size; + realsize = alignment + sizeof(memheader_t) + size + sizeof(sentinel2); + pool->realsize += realsize; + base = (unsigned char *)Clump_AllocBlock(realsize); + if (base== NULL) + { + Mem_PrintList(0); + Mem_PrintStats(); + Mem_PrintList(1<<30); + Mem_PrintStats(); + Sys_Error("Mem_Alloc: out of memory (alloc at %s:%i)", filename, fileline); + } + // calculate address that aligns the end of the memheader_t to the specified alignment + mem = (memheader_t*)((((size_t)base + sizeof(memheader_t) + (alignment-1)) & ~(alignment-1)) - sizeof(memheader_t)); + mem->baseaddress = (void*)base; + mem->filename = filename; + mem->fileline = fileline; + mem->size = size; + mem->pool = pool; + + // calculate sentinels (detects buffer overruns, in a way that is hard to exploit) + sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); + sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); + mem->sentinel = sentinel1; + memcpy((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2)); + + // append to head of list + mem->next = pool->chain; + mem->prev = NULL; + pool->chain = mem; + if (mem->next) + mem->next->prev = mem; + + if (mem_mutex) + Thread_UnlockMutex(mem_mutex); + + // copy the shared portion in the case of a realloc, then memset the rest + sharedsize = 0; + remainsize = size; + if (olddata) + { + oldmem = (memheader_t*)olddata - 1; + sharedsize = min(oldmem->size, size); + memcpy((void *)((unsigned char *) mem + sizeof(memheader_t)), olddata, sharedsize); + remainsize -= sharedsize; + _Mem_Free(olddata, filename, fileline); + } + memset((void *)((unsigned char *) mem + sizeof(memheader_t) + sharedsize), 0, remainsize); + return (void *)((unsigned char *) mem + sizeof(memheader_t)); +} + +// only used by _Mem_Free and _Mem_FreePool +static void _Mem_FreeBlock(memheader_t *mem, const char *filename, int fileline) +{ + mempool_t *pool; + size_t size; + size_t realsize; + unsigned int sentinel1; + unsigned int sentinel2; + + // check sentinels (detects buffer overruns, in a way that is hard to exploit) + sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); + sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); + if (mem->sentinel != sentinel1) + Sys_Error("Mem_Free: trashed head sentinel (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline); + if (memcmp((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2))) + Sys_Error("Mem_Free: trashed tail sentinel (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline); + + pool = mem->pool; + if (developer_memory.integer) + Con_DPrintf("Mem_Free: pool %s, alloc %s:%i, free %s:%i, size %i bytes\n", pool->name, mem->filename, mem->fileline, filename, fileline, (int)(mem->size)); + // unlink memheader from doubly linked list + if ((mem->prev ? mem->prev->next != mem : pool->chain != mem) || (mem->next && mem->next->prev != mem)) + Sys_Error("Mem_Free: not allocated or double freed (free at %s:%i)", filename, fileline); + if (mem_mutex) + Thread_LockMutex(mem_mutex); + if (mem->prev) + mem->prev->next = mem->next; + else + pool->chain = mem->next; + if (mem->next) + mem->next->prev = mem->prev; + // memheader has been unlinked, do the actual free now + size = mem->size; + realsize = sizeof(memheader_t) + size + sizeof(sentinel2); + pool->totalsize -= size; + pool->realsize -= realsize; + Clump_FreeBlock(mem->baseaddress, realsize); + if (mem_mutex) + Thread_UnlockMutex(mem_mutex); +} + +void _Mem_Free(void *data, const char *filename, int fileline) +{ + if (data == NULL) + { + Con_DPrintf("Mem_Free: data == NULL (called at %s:%i)\n", filename, fileline); + return; + } + + if (developer_memorydebug.integer) + { + //_Mem_CheckSentinelsGlobal(filename, fileline); + if (!Mem_IsAllocated(NULL, data)) + Sys_Error("Mem_Free: data is not allocated (called at %s:%i)", filename, fileline); + } + + _Mem_FreeBlock((memheader_t *)((unsigned char *) data - sizeof(memheader_t)), filename, fileline); +} + +mempool_t *_Mem_AllocPool(const char *name, int flags, mempool_t *parent, const char *filename, int fileline) +{ + mempool_t *pool; + if (developer_memorydebug.integer) + _Mem_CheckSentinelsGlobal(filename, fileline); + pool = (mempool_t *)Clump_AllocBlock(sizeof(mempool_t)); + if (pool == NULL) + { + Mem_PrintList(0); + Mem_PrintStats(); + Mem_PrintList(1<<30); + Mem_PrintStats(); + Sys_Error("Mem_AllocPool: out of memory (allocpool at %s:%i)", filename, fileline); + } + memset(pool, 0, sizeof(mempool_t)); + pool->sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1); + pool->sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2); + pool->filename = filename; + pool->fileline = fileline; + pool->flags = flags; + pool->chain = NULL; + pool->totalsize = 0; + pool->realsize = sizeof(mempool_t); + strlcpy (pool->name, name, sizeof (pool->name)); + pool->parent = parent; + pool->next = poolchain; + poolchain = pool; + return pool; +} + +void _Mem_FreePool(mempool_t **poolpointer, const char *filename, int fileline) +{ + mempool_t *pool = *poolpointer; + mempool_t **chainaddress, *iter, *temp; + + if (developer_memorydebug.integer) + _Mem_CheckSentinelsGlobal(filename, fileline); + if (pool) + { + // unlink pool from chain + for (chainaddress = &poolchain;*chainaddress && *chainaddress != pool;chainaddress = &((*chainaddress)->next)); + if (*chainaddress != pool) + Sys_Error("Mem_FreePool: pool already free (freepool at %s:%i)", filename, fileline); + if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) + Sys_Error("Mem_FreePool: trashed pool sentinel 1 (allocpool at %s:%i, freepool at %s:%i)", pool->filename, pool->fileline, filename, fileline); + if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) + Sys_Error("Mem_FreePool: trashed pool sentinel 2 (allocpool at %s:%i, freepool at %s:%i)", pool->filename, pool->fileline, filename, fileline); + *chainaddress = pool->next; + + // free memory owned by the pool + while (pool->chain) + _Mem_FreeBlock(pool->chain, filename, fileline); + + // free child pools, too + for(iter = poolchain; iter; temp = iter = iter->next) + if(iter->parent == pool) + _Mem_FreePool(&temp, filename, fileline); + + // free the pool itself + Clump_FreeBlock(pool, sizeof(*pool)); + + *poolpointer = NULL; + } +} + +void _Mem_EmptyPool(mempool_t *pool, const char *filename, int fileline) +{ + mempool_t *chainaddress; + + if (developer_memorydebug.integer) + { + //_Mem_CheckSentinelsGlobal(filename, fileline); + // check if this pool is in the poolchain + for (chainaddress = poolchain;chainaddress;chainaddress = chainaddress->next) + if (chainaddress == pool) + break; + if (!chainaddress) + Sys_Error("Mem_EmptyPool: pool is already free (emptypool at %s:%i)", filename, fileline); + } + if (pool == NULL) + Sys_Error("Mem_EmptyPool: pool == NULL (emptypool at %s:%i)", filename, fileline); + if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) + Sys_Error("Mem_EmptyPool: trashed pool sentinel 1 (allocpool at %s:%i, emptypool at %s:%i)", pool->filename, pool->fileline, filename, fileline); + if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) + Sys_Error("Mem_EmptyPool: trashed pool sentinel 2 (allocpool at %s:%i, emptypool at %s:%i)", pool->filename, pool->fileline, filename, fileline); + + // free memory owned by the pool + while (pool->chain) + _Mem_FreeBlock(pool->chain, filename, fileline); + + // empty child pools, too + for(chainaddress = poolchain; chainaddress; chainaddress = chainaddress->next) + if(chainaddress->parent == pool) + _Mem_EmptyPool(chainaddress, filename, fileline); + +} + +void _Mem_CheckSentinels(void *data, const char *filename, int fileline) +{ + memheader_t *mem; + unsigned int sentinel1; + unsigned int sentinel2; + + if (data == NULL) + Sys_Error("Mem_CheckSentinels: data == NULL (sentinel check at %s:%i)", filename, fileline); + + mem = (memheader_t *)((unsigned char *) data - sizeof(memheader_t)); + sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); + sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); + if (mem->sentinel != sentinel1) + Sys_Error("Mem_Free: trashed head sentinel (alloc at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline); + if (memcmp((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2))) + Sys_Error("Mem_Free: trashed tail sentinel (alloc at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline); +} + +#if MEMCLUMPING +static void _Mem_CheckClumpSentinels(memclump_t *clump, const char *filename, int fileline) +{ + // this isn't really very useful + if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) + Sys_Error("Mem_CheckClumpSentinels: trashed sentinel 1 (sentinel check at %s:%i)", filename, fileline); + if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) + Sys_Error("Mem_CheckClumpSentinels: trashed sentinel 2 (sentinel check at %s:%i)", filename, fileline); +} +#endif + +void _Mem_CheckSentinelsGlobal(const char *filename, int fileline) +{ + memheader_t *mem; +#if MEMCLUMPING + memclump_t *clump; +#endif + mempool_t *pool; + for (pool = poolchain;pool;pool = pool->next) + { + if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) + Sys_Error("Mem_CheckSentinelsGlobal: trashed pool sentinel 1 (allocpool at %s:%i, sentinel check at %s:%i)", pool->filename, pool->fileline, filename, fileline); + if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) + Sys_Error("Mem_CheckSentinelsGlobal: trashed pool sentinel 2 (allocpool at %s:%i, sentinel check at %s:%i)", pool->filename, pool->fileline, filename, fileline); + } + for (pool = poolchain;pool;pool = pool->next) + for (mem = pool->chain;mem;mem = mem->next) + _Mem_CheckSentinels((void *)((unsigned char *) mem + sizeof(memheader_t)), filename, fileline); +#if MEMCLUMPING + for (pool = poolchain;pool;pool = pool->next) + for (clump = clumpchain;clump;clump = clump->chain) + _Mem_CheckClumpSentinels(clump, filename, fileline); +#endif +} + +qboolean Mem_IsAllocated(mempool_t *pool, void *data) +{ + memheader_t *header; + memheader_t *target; + + if (pool) + { + // search only one pool + target = (memheader_t *)((unsigned char *) data - sizeof(memheader_t)); + for( header = pool->chain ; header ; header = header->next ) + if( header == target ) + return true; + } + else + { + // search all pools + for (pool = poolchain;pool;pool = pool->next) + if (Mem_IsAllocated(pool, data)) + return true; + } + return false; +} + +void Mem_ExpandableArray_NewArray(memexpandablearray_t *l, mempool_t *mempool, size_t recordsize, int numrecordsperarray) +{ + memset(l, 0, sizeof(*l)); + l->mempool = mempool; + l->recordsize = recordsize; + l->numrecordsperarray = numrecordsperarray; +} + +void Mem_ExpandableArray_FreeArray(memexpandablearray_t *l) +{ + size_t i; + if (l->maxarrays) + { + for (i = 0;i != l->numarrays;i++) + Mem_Free(l->arrays[i].data); + Mem_Free(l->arrays); + } + memset(l, 0, sizeof(*l)); +} + +void *Mem_ExpandableArray_AllocRecord(memexpandablearray_t *l) +{ + size_t i, j; + for (i = 0;;i++) + { + if (i == l->numarrays) + { + if (l->numarrays == l->maxarrays) + { + memexpandablearray_array_t *oldarrays = l->arrays; + l->maxarrays = max(l->maxarrays * 2, 128); + l->arrays = (memexpandablearray_array_t*) Mem_Alloc(l->mempool, l->maxarrays * sizeof(*l->arrays)); + if (oldarrays) + { + memcpy(l->arrays, oldarrays, l->numarrays * sizeof(*l->arrays)); + Mem_Free(oldarrays); + } + } + l->arrays[i].numflaggedrecords = 0; + l->arrays[i].data = (unsigned char *) Mem_Alloc(l->mempool, (l->recordsize + 1) * l->numrecordsperarray); + l->arrays[i].allocflags = l->arrays[i].data + l->recordsize * l->numrecordsperarray; + l->numarrays++; + } + if (l->arrays[i].numflaggedrecords < l->numrecordsperarray) + { + for (j = 0;j < l->numrecordsperarray;j++) + { + if (!l->arrays[i].allocflags[j]) + { + l->arrays[i].allocflags[j] = true; + l->arrays[i].numflaggedrecords++; + memset(l->arrays[i].data + l->recordsize * j, 0, l->recordsize); + return (void *)(l->arrays[i].data + l->recordsize * j); + } + } + } + } +} + +/***************************************************************************** + * IF YOU EDIT THIS: + * If this function was to change the size of the "expandable" array, you have + * to update r_shadow.c + * Just do a search for "range =", R_ShadowClearWorldLights would be the first + * function to look at. (And also seems like the only one?) You might have to + * move the call to Mem_ExpandableArray_IndexRange back into for(...) loop's + * condition + */ +void Mem_ExpandableArray_FreeRecord(memexpandablearray_t *l, void *record) // const! +{ + size_t i, j; + unsigned char *p = (unsigned char *)record; + for (i = 0;i != l->numarrays;i++) + { + if (p >= l->arrays[i].data && p < (l->arrays[i].data + l->recordsize * l->numrecordsperarray)) + { + j = (p - l->arrays[i].data) / l->recordsize; + if (p != l->arrays[i].data + j * l->recordsize) + Sys_Error("Mem_ExpandableArray_FreeRecord: no such record %p\n", p); + if (!l->arrays[i].allocflags[j]) + Sys_Error("Mem_ExpandableArray_FreeRecord: record %p is already free!\n", p); + l->arrays[i].allocflags[j] = false; + l->arrays[i].numflaggedrecords--; + return; + } + } +} + +size_t Mem_ExpandableArray_IndexRange(const memexpandablearray_t *l) +{ + size_t i, j, k, end = 0; + for (i = 0;i < l->numarrays;i++) + { + for (j = 0, k = 0;k < l->arrays[i].numflaggedrecords;j++) + { + if (l->arrays[i].allocflags[j]) + { + end = l->numrecordsperarray * i + j + 1; + k++; + } + } + } + return end; +} + +void *Mem_ExpandableArray_RecordAtIndex(const memexpandablearray_t *l, size_t index) +{ + size_t i, j; + i = index / l->numrecordsperarray; + j = index % l->numrecordsperarray; + if (i >= l->numarrays || !l->arrays[i].allocflags[j]) + return NULL; + return (void *)(l->arrays[i].data + j * l->recordsize); +} + + +// used for temporary memory allocations around the engine, not for longterm +// storage, if anything in this pool stays allocated during gameplay, it is +// considered a leak +mempool_t *tempmempool; +// only for zone +mempool_t *zonemempool; + +void Mem_PrintStats(void) +{ + size_t count = 0, size = 0, realsize = 0; + mempool_t *pool; + memheader_t *mem; + Mem_CheckSentinelsGlobal(); + for (pool = poolchain;pool;pool = pool->next) + { + count++; + size += pool->totalsize; + realsize += pool->realsize; + } + Con_Printf("%lu memory pools, totalling %lu bytes (%.3fMB)\n", (unsigned long)count, (unsigned long)size, size / 1048576.0); + Con_Printf("total allocated size: %lu bytes (%.3fMB)\n", (unsigned long)realsize, realsize / 1048576.0); + for (pool = poolchain;pool;pool = pool->next) + { + if ((pool->flags & POOLFLAG_TEMP) && pool->chain) + { + Con_Printf("Memory pool %p has sprung a leak totalling %lu bytes (%.3fMB)! Listing contents...\n", (void *)pool, (unsigned long)pool->totalsize, pool->totalsize / 1048576.0); + for (mem = pool->chain;mem;mem = mem->next) + Con_Printf("%10lu bytes allocated at %s:%i\n", (unsigned long)mem->size, mem->filename, mem->fileline); + } + } +} + +void Mem_PrintList(size_t minallocationsize) +{ + mempool_t *pool; + memheader_t *mem; + Mem_CheckSentinelsGlobal(); + Con_Print("memory pool list:\n" + "size name\n"); + for (pool = poolchain;pool;pool = pool->next) + { + Con_Printf("%10luk (%10luk actual) %s (%+li byte change) %s\n", (unsigned long) ((pool->totalsize + 1023) / 1024), (unsigned long)((pool->realsize + 1023) / 1024), pool->name, (long)(pool->totalsize - pool->lastchecksize), (pool->flags & POOLFLAG_TEMP) ? "TEMP" : ""); + pool->lastchecksize = pool->totalsize; + for (mem = pool->chain;mem;mem = mem->next) + if (mem->size >= minallocationsize) + Con_Printf("%10lu bytes allocated at %s:%i\n", (unsigned long)mem->size, mem->filename, mem->fileline); + } +} + +static void MemList_f(void) +{ + switch(Cmd_Argc()) + { + case 1: + Mem_PrintList(1<<30); + Mem_PrintStats(); + break; + case 2: + Mem_PrintList(atoi(Cmd_Argv(1)) * 1024); + Mem_PrintStats(); + break; + default: + Con_Print("MemList_f: unrecognized options\nusage: memlist [all]\n"); + break; + } +} + +static void MemStats_f(void) +{ + Mem_CheckSentinelsGlobal(); + R_TextureStats_Print(false, false, true); + GL_Mesh_ListVBOs(false); + Mem_PrintStats(); +} + + +char* Mem_strdup (mempool_t *pool, const char* s) +{ + char* p; + size_t sz; + if (s == NULL) + return NULL; + sz = strlen (s) + 1; + p = (char*)Mem_Alloc (pool, sz); + strlcpy (p, s, sz); + return p; +} + +/* +======================== +Memory_Init +======================== +*/ +void Memory_Init (void) +{ + static union {unsigned short s;unsigned char b[2];} u; + u.s = 0x100; + mem_bigendian = u.b[0] != 0; + + sentinel_seed = rand(); + poolchain = NULL; + tempmempool = Mem_AllocPool("Temporary Memory", POOLFLAG_TEMP, NULL); + zonemempool = Mem_AllocPool("Zone", 0, NULL); + + if (Thread_HasThreads()) + mem_mutex = Thread_CreateMutex(); +} + +void Memory_Shutdown (void) +{ +// Mem_FreePool (&zonemempool); +// Mem_FreePool (&tempmempool); + + if (mem_mutex) + Thread_DestroyMutex(mem_mutex); + mem_mutex = NULL; +} + +void Memory_Init_Commands (void) +{ + Cmd_AddCommand ("memstats", MemStats_f, "prints memory system statistics"); + Cmd_AddCommand ("memlist", MemList_f, "prints memory pool information (or if used as memlist 5 lists individual allocations of 5K or larger, 0 lists all allocations)"); + Cvar_RegisterVariable (&developer_memory); + Cvar_RegisterVariable (&developer_memorydebug); + Cvar_RegisterVariable (&sys_memsize_physical); + Cvar_RegisterVariable (&sys_memsize_virtual); + +#if defined(WIN32) +#ifdef _WIN64 + { + MEMORYSTATUSEX status; + // first guess + Cvar_SetValueQuick(&sys_memsize_virtual, 8388608); + // then improve + status.dwLength = sizeof(status); + if(GlobalMemoryStatusEx(&status)) + { + Cvar_SetValueQuick(&sys_memsize_physical, status.ullTotalPhys / 1048576.0); + Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value, status.ullTotalVirtual / 1048576.0)); + } + } +#else + { + MEMORYSTATUS status; + // first guess + Cvar_SetValueQuick(&sys_memsize_virtual, 2048); + // then improve + status.dwLength = sizeof(status); + GlobalMemoryStatus(&status); + Cvar_SetValueQuick(&sys_memsize_physical, status.dwTotalPhys / 1048576.0); + Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value, status.dwTotalVirtual / 1048576.0)); + } +#endif +#else + { + // first guess + Cvar_SetValueQuick(&sys_memsize_virtual, (sizeof(void*) == 4) ? 2048 : 268435456); + // then improve + { + // Linux, and BSD with linprocfs mounted + FILE *f = fopen("/proc/meminfo", "r"); + if(f) + { + static char buf[1024]; + while(fgets(buf, sizeof(buf), f)) + { + const char *p = buf; + if(!COM_ParseToken_Console(&p)) + continue; + if(!strcmp(com_token, "MemTotal:")) + { + if(!COM_ParseToken_Console(&p)) + continue; + Cvar_SetValueQuick(&sys_memsize_physical, atof(com_token) / 1024.0); + } + if(!strcmp(com_token, "SwapTotal:")) + { + if(!COM_ParseToken_Console(&p)) + continue; + Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value , atof(com_token) / 1024.0 + sys_memsize_physical.value)); + } + } + fclose(f); + } + } + } +#endif +} + diff --git a/app/jni/zone.h b/app/jni/zone.h new file mode 100644 index 0000000..6caa039 --- /dev/null +++ b/app/jni/zone.h @@ -0,0 +1,144 @@ +/* +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. + +*/ + +#ifndef ZONE_H +#define ZONE_H + +extern qboolean mem_bigendian; + +// div0: heap overflow detection paranoia +#define MEMPARANOIA 0 + +#define POOLNAMESIZE 128 +// if set this pool will be printed in memlist reports +#define POOLFLAG_TEMP 1 + +typedef struct memheader_s +{ + // address returned by Chunk_Alloc (may be significantly before this header to satisify alignment) + void *baseaddress; + // next and previous memheaders in chain belonging to pool + struct memheader_s *next; + struct memheader_s *prev; + // pool this memheader belongs to + struct mempool_s *pool; + // size of the memory after the header (excluding header and sentinel2) + size_t size; + // file name and line where Mem_Alloc was called + const char *filename; + int fileline; + // should always be equal to MEMHEADER_SENTINEL_FOR_ADDRESS() + unsigned int sentinel; + // immediately followed by data, which is followed by another copy of mem_sentinel[] +} +memheader_t; + +typedef struct mempool_s +{ + // should always be MEMPOOL_SENTINEL + unsigned int sentinel1; + // chain of individual memory allocations + struct memheader_s *chain; + // POOLFLAG_* + int flags; + // total memory allocated in this pool (inside memheaders) + size_t totalsize; + // total memory allocated in this pool (actual malloc total) + size_t realsize; + // updated each time the pool is displayed by memlist, shows change from previous time (unless pool was freed) + size_t lastchecksize; + // linked into global mempool list + struct mempool_s *next; + // parent object (used for nested memory pools) + struct mempool_s *parent; + // file name and line where Mem_AllocPool was called + const char *filename; + int fileline; + // name of the pool + char name[POOLNAMESIZE]; + // should always be MEMPOOL_SENTINEL + unsigned int sentinel2; +} +mempool_t; + +#define Mem_Alloc(pool,size) _Mem_Alloc(pool, NULL, size, 16, __FILE__, __LINE__) +#define Mem_Memalign(pool,alignment,size) _Mem_Alloc(pool, NULL, size, alignment, __FILE__, __LINE__) +#define Mem_Realloc(pool,data,size) _Mem_Alloc(pool, data, size, 16, __FILE__, __LINE__) +#define Mem_Free(mem) _Mem_Free(mem, __FILE__, __LINE__) +#define Mem_CheckSentinels(data) _Mem_CheckSentinels(data, __FILE__, __LINE__) +#define Mem_CheckSentinelsGlobal() _Mem_CheckSentinelsGlobal(__FILE__, __LINE__) +#define Mem_AllocPool(name, flags, parent) _Mem_AllocPool(name, flags, parent, __FILE__, __LINE__) +#define Mem_FreePool(pool) _Mem_FreePool(pool, __FILE__, __LINE__) +#define Mem_EmptyPool(pool) _Mem_EmptyPool(pool, __FILE__, __LINE__) + +void *_Mem_Alloc(mempool_t *pool, void *data, size_t size, size_t alignment, const char *filename, int fileline); +void _Mem_Free(void *data, const char *filename, int fileline); +mempool_t *_Mem_AllocPool(const char *name, int flags, mempool_t *parent, const char *filename, int fileline); +void _Mem_FreePool(mempool_t **pool, const char *filename, int fileline); +void _Mem_EmptyPool(mempool_t *pool, const char *filename, int fileline); +void _Mem_CheckSentinels(void *data, const char *filename, int fileline); +void _Mem_CheckSentinelsGlobal(const char *filename, int fileline); +// if pool is NULL this searches ALL pools for the allocation +qboolean Mem_IsAllocated(mempool_t *pool, void *data); + +char* Mem_strdup (mempool_t *pool, const char* s); + +typedef struct memexpandablearray_array_s +{ + unsigned char *data; + unsigned char *allocflags; + size_t numflaggedrecords; +} +memexpandablearray_array_t; + +typedef struct memexpandablearray_s +{ + mempool_t *mempool; + size_t recordsize; + size_t numrecordsperarray; + size_t numarrays; + size_t maxarrays; + memexpandablearray_array_t *arrays; +} +memexpandablearray_t; + +void Mem_ExpandableArray_NewArray(memexpandablearray_t *l, mempool_t *mempool, size_t recordsize, int numrecordsperarray); +void Mem_ExpandableArray_FreeArray(memexpandablearray_t *l); +void *Mem_ExpandableArray_AllocRecord(memexpandablearray_t *l); +void Mem_ExpandableArray_FreeRecord(memexpandablearray_t *l, void *record); +size_t Mem_ExpandableArray_IndexRange(const memexpandablearray_t *l) DP_FUNC_PURE; +void *Mem_ExpandableArray_RecordAtIndex(const memexpandablearray_t *l, size_t index) DP_FUNC_PURE; + +// used for temporary allocations +extern mempool_t *tempmempool; + +void Memory_Init (void); +void Memory_Shutdown (void); +void Memory_Init_Commands (void); + +extern mempool_t *zonemempool; +#define Z_Malloc(size) Mem_Alloc(zonemempool,size) +#define Z_Free(data) Mem_Free(data) + +extern struct cvar_s developer_memory; +extern struct cvar_s developer_memorydebug; + +#endif + diff --git a/app/libs/cardboard.jar b/app/libs/cardboard.jar new file mode 100644 index 0000000..06d5dcf Binary files /dev/null and b/app/libs/cardboard.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..93d38ee --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\Simon\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7b8e199 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/commandline.txt b/app/src/main/assets/commandline.txt new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/assets/config.cfg b/app/src/main/assets/config.cfg new file mode 100644 index 0000000..b72b6d5 --- /dev/null +++ b/app/src/main/assets/config.cfg @@ -0,0 +1,58 @@ +bind TAB "+showscores" +bind ENTER "+jump" +bind ESCAPE "togglemenu" +bind SPACE "+jump" +bind + "sizeup" +bind , "+moveleft" +bind - "sizedown" +bind . "+moveright" +bind # "impulse 12" +bind / "impulse 10" +bind 0 "impulse 0" +bind 1 "impulse 1" +bind 2 "impulse 2" +bind 3 "impulse 3" +bind 4 "impulse 4" +bind 5 "impulse 5" +bind 6 "impulse 6" +bind 7 "impulse 7" +bind 8 "impulse 8" +bind = "sizeup" +bind BACKSLASH "+mlook" +bind BACKQUOTE "toggleconsole" +bind a "+moveleft" +bind c "+movedown" +bind d "+moveright" +bind t "messagemode" +bind z "+lookdown" +bind TILDE "toggleconsole" +bind UPARROW "+forward" +bind DOWNARROW "+back" +bind LEFTARROW "+left" +bind RIGHTARROW "+right" +bind ALT "+strafe" +bind CTRL "+attack" +bind SHIFT "+speed" +bind F1 "help" +bind F2 "menu_save" +bind F3 "menu_load" +bind F4 "menu_options" +bind F5 "menu_multiplayer" +bind F6 "echo Quicksaving...; wait; save quick" +bind F9 "echo Quickloading...; wait; load quick" +bind F10 "quit" +bind F11 "zoom_in" +bind F12 "screenshot" +bind INS "+klook" +bind DEL "+lookdown" +bind PGDN "+lookup" +bind END "centerview" +bind PAUSE "pause" +bind MOUSE1 "+attack" +bind MOUSE2 "+forward" +bind MOUSE3 "+mlook" +"cl_particles_quality" "2" +"cl_stainmaps" "1" +"fov" "110" +"sensitivity" "4" +"snd_speed" "44100" diff --git a/app/src/main/assets/source/GPL.txt b/app/src/main/assets/source/GPL.txt new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/app/src/main/assets/source/GPL.txt @@ -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/src/main/assets/source/QVRSource.zip b/app/src/main/assets/source/QVRSource.zip new file mode 100644 index 0000000..517671e Binary files /dev/null and b/app/src/main/assets/source/QVRSource.zip differ diff --git a/app/src/main/assets/splash.jpg b/app/src/main/assets/splash.jpg new file mode 100644 index 0000000..205b6ae Binary files /dev/null and b/app/src/main/assets/splash.jpg differ diff --git a/app/src/main/java/com/drbeef/qvr/AudioCallback.java b/app/src/main/java/com/drbeef/qvr/AudioCallback.java new file mode 100644 index 0000000..9e9c5d8 --- /dev/null +++ b/app/src/main/java/com/drbeef/qvr/AudioCallback.java @@ -0,0 +1,108 @@ +package com.drbeef.qvr; + +import java.nio.ByteBuffer; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; + +public class AudioCallback { + + public QVRAudioTrack mAudioTrack; + public ScheduledThreadPoolExecutor stpe; + byte[] mAudioData; + public static boolean reqThreadrunning=true; + public void initAudio(int size) + { + if(mAudioTrack != null) return; + size/=8; + + mAudioData=new byte[size]; + int sampleFreq = 44100; + + int bufferSize = Math.max(size,AudioTrack.getMinBufferSize(sampleFreq, AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT)); + mAudioTrack = new QVRAudioTrack(AudioManager.STREAM_MUSIC, + sampleFreq, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT, + bufferSize, + AudioTrack.MODE_STREAM); + mAudioTrack.play(); + long sleeptime=(size*1000000000l)/(2*2*sampleFreq); + stpe=new ScheduledThreadPoolExecutor(5); + stpe.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + if (reqThreadrunning) + { + QVRJNILib.requestAudioData( ); + } + } + }, 0, sleeptime, TimeUnit.NANOSECONDS); + } + + int sync=0; + + public void writeAudio(ByteBuffer audioData, int offset, int len) + { + if(mAudioTrack == null) + return; + if (!reqThreadrunning) + return; + audioData.position(offset); + audioData.get(mAudioData, 0, len); + if (sync++%128==0) + mAudioTrack.flush(); + mAudioTrack.write(mAudioData, 0, len); + } + + public void pauseAudio() + { + if(mAudioTrack == null) + return; + + mAudioTrack.pause(); + mAudioTrack.flush(); + reqThreadrunning=false; + } + + public void resumeAudio() + { + if(mAudioTrack == null) + return; + + mAudioTrack.play(); + reqThreadrunning=true; + } + + public void terminateAudio() + { + mAudioTrack.flush(); + mAudioTrack.release(); + + mAudioTrack = null; + + reqThreadrunning=false; + + stpe.shutdown(); + stpe = null; + } +} + +class QVRAudioTrack extends AudioTrack +{ + public QVRAudioTrack(int streamType, int sampleRateInHz, + int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) + throws IllegalStateException { + super(streamType, sampleRateInHz, channelConfig, audioFormat, + bufferSizeInBytes, mode); + } + + @Override + public void play() throws IllegalStateException { + flush(); + super.play(); + } +} diff --git a/app/src/main/java/com/drbeef/qvr/DownloadTask.java b/app/src/main/java/com/drbeef/qvr/DownloadTask.java new file mode 100644 index 0000000..5a56bcc --- /dev/null +++ b/app/src/main/java/com/drbeef/qvr/DownloadTask.java @@ -0,0 +1,299 @@ +package com.drbeef.qvr; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.io.RandomAccessFile; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.AsyncTask; +import android.os.Environment; +import android.os.SystemClock; +import android.util.Log; + + +class DownloadTask extends AsyncTask { + + private Context context; + private ProgressDialog pd; + + public boolean please_abort = false; + + private String sdcard = Environment.getExternalStorageDirectory().getPath(); + + private String url = "https://www.dropbox.com/s/cgd8ynsvp8aen5v/quake106.zip?dl=1"; + private String demofile = sdcard + "/Q4C/quake106.zip"; + private String pakfile = sdcard + "/Q4C/id1/pak0.pak"; + + public DownloadTask set_context(Context context){ + this.context = context; + return this; + } + + @Override + protected void onPreExecute (){ + + pd = new ProgressDialog(context); + + pd.setTitle("Downloading data file ..."); + pd.setMessage("starting"); + pd.setIndeterminate(true); + pd.setCancelable(true); + + pd.setOnDismissListener( new DialogInterface.OnDismissListener(){ + public void onDismiss(DialogInterface dialog) { + Log.i( "DownloadTask.java", "onDismiss"); + please_abort = true; + } + }); + + pd.setOnCancelListener( new DialogInterface.OnCancelListener(){ + public void onCancel(DialogInterface dialog) { + Log.i( "DownloadTask.java", "onCancel"); + please_abort = true; + } + }); + + + pd.setButton(ProgressDialog.BUTTON_POSITIVE, "Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + pd.dismiss(); + please_abort = true; + } + }); + + pd.show(); + + } + + + public static String printSize( int size ){ + + if ( size >= (1<<20) ) + return String.format("%.1f MB", size * (1.0/(1<<20))); + + if ( size >= (1<<10) ) + return String.format("%.1f KB", size * (1.0/(1<<10))); + + return String.format("%d bytes", size); + + } + + + private void download_demo() throws Exception{ + + Log.i( "DownloadTask.java", "starting to download "+ url); + + if (new File(demofile).exists()){ + Log.i( "DownloadTask.java", demofile + " already there. skipping."); + return; + } + + /// setup output directory + new File(sdcard + "/Q4C/id1").mkdirs(); + + InputStream is = null; + FileOutputStream fos = null; + + is = new URL(url).openStream(); + fos = new FileOutputStream ( demofile+".part"); + + byte[] buffer = new byte [4096]; + + int totalcount =0; + + long tprint = SystemClock.uptimeMillis(); + int partialcount = 0; + + while(true){ + + if (please_abort) + throw new Exception("aborting") ; + + + int count = is.read (buffer); + //Log.i( "DownloadTask.java", "received " + count + " bytes"); + + if ( count<=0 ) break; + fos.write (buffer, 0, count); + + totalcount += count; + partialcount += count; + + long tnow = SystemClock.uptimeMillis(); + if ( (tnow-tprint)> 1000) { + + float size_MB = totalcount * (1.0f/(1<<20)); + float speed_KB = partialcount * (1.0f/(1<<10)) * ((tnow-tprint)/1000.0f); + + publishProgress( String.format("downloaded %.1f MB (%.1f KB/sec)", + size_MB, speed_KB)); + + tprint = tnow; + partialcount = 0; + + } + + + } + + is.close(); + fos.close(); + + + new File(demofile+".part" ) + .renameTo(new File(demofile)); + + // done + publishProgress("download done" ); + + SystemClock.sleep(2000); + + } + + private void extract_data() throws Exception{ + Log.i("DownloadTask.java", "extracting PAK data"); + + ZipFile file = new ZipFile (demofile); + extract_file( file, "ID1/PAK0.PAK", pakfile); + + file.close(); + + // done + publishProgress("extract done" ); + + pd.getButton(ProgressDialog.BUTTON_POSITIVE).setText("Done"); + + SystemClock.sleep(1000); + + } + + private void extract_file( ZipFile file, String entry_name, String output_name ) throws Exception{ + + Log.i( "DownloadTask.java", "extracting " + entry_name + " to " + output_name); + + String short_name = new File(output_name).getName(); + + // create output directory + new File(output_name).getParentFile().mkdirs(); + + ZipEntry entry = file.getEntry(entry_name); + + if ( entry.isDirectory() ){ + Log.i( "DownloadTask.java", entry_name + " is a directory"); + new File(output_name).mkdir(); + return; + } + + + InputStream is = null; + FileOutputStream fos = null; + + is = file.getInputStream(entry); + + fos = new FileOutputStream ( output_name+".part" ); + + byte[] buffer = new byte [4096]; + + int totalcount =0; + + long tprint = SystemClock.uptimeMillis(); + + while(true){ + + if (please_abort) + throw new Exception("aborting") ; + + + int count = is.read (buffer); + //Log.i( "DownloadTask.java", "extracted " + count + " bytes"); + + if ( count<=0 ) break; + fos.write (buffer, 0, count); + + totalcount += count; + + long tnow = SystemClock.uptimeMillis(); + if ( (tnow-tprint)> 1000) { + + float size_MB = totalcount * (1.0f/(1<<20)); + + publishProgress( String.format("%s : extracted %.1f MB", + short_name, size_MB)); + + tprint = tnow; + } + } + + is.close(); + fos.close(); + + /// rename part file + + new File(output_name+".part" ) + .renameTo(new File(output_name)); + + // done + publishProgress( String.format("%s : done.", + short_name)); + } + + @Override + protected Void doInBackground(Void... unused) { + + try { + + //Inform game we are now downloading + QVRJNILib.setDownloadStatus(2); + + long t = SystemClock.uptimeMillis(); + + download_demo(); + + extract_data(); + + t = SystemClock.uptimeMillis() - t; + + Log.i( "DownloadTask.java", "done in " + t + " ms"); + + } catch (Exception e) { + + e.printStackTrace(); + + QVRJNILib.setDownloadStatus(0); + publishProgress("Error: " + e ); + } + + return(null); + } + + @Override + protected void onProgressUpdate(String... progress) { + Log.i( "DownloadTask.java", progress[0]); + pd.setMessage( progress[0]); + } + + @Override + protected void onPostExecute(Void unused) { + File f = new File(sdcard + "/Q4C/id1/pak0.pak"); + if (f.exists()) { + QVRJNILib.setDownloadStatus(1); + } else + { + QVRJNILib.setDownloadStatus(0); + } + pd.hide(); + } +} diff --git a/app/src/main/java/com/drbeef/qvr/MainActivity.java b/app/src/main/java/com/drbeef/qvr/MainActivity.java new file mode 100644 index 0000000..438f6b9 --- /dev/null +++ b/app/src/main/java/com/drbeef/qvr/MainActivity.java @@ -0,0 +1,1195 @@ +package com.drbeef.qvr; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.ActivityInfo; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.MediaPlayer; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.os.Bundle; +import android.os.Environment; +import android.os.Vibrator; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.InputDevice; +import android.view.Window; +import android.view.WindowManager; + +import com.google.vrtoolkit.cardboard.CardboardActivity; +import com.google.vrtoolkit.cardboard.CardboardDeviceParams; +import com.google.vrtoolkit.cardboard.CardboardView; +import com.google.vrtoolkit.cardboard.Eye; +import com.google.vrtoolkit.cardboard.HeadTransform; +import com.google.vrtoolkit.cardboard.ScreenParams; +import com.google.vrtoolkit.cardboard.Viewport; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import javax.microedition.khronos.egl.EGLConfig; + + +public class MainActivity + extends CardboardActivity + implements CardboardView.StereoRenderer, QVRCallback +{ + private static final String TAG = "QVR"; + + private static final int GL_RGBA8 = 0x8058; + + private int[] currentFBO = new int[1]; + private int fboEyeResolution = 0; + //This is set when the user opts to use a different resolution to the one picked as the default + private int desiredEyeBufferResolution = -1; + + //Head orientation + private float[] eulerAngles = new float[3]; + + private int MONO = 0; + private int STEREO = 1; + + private int mStereoMode = STEREO; + private int eyeID = 0; + + /** + * 0 = no big screen (in game) + * 1 = big screen whilst menu or console active + * 2 = big screen all the time + */ + private int bigScreen = 1; + + //-1 means start button isn't pressed + private long startButtonDownCounter = -1; + //Don't allow the trigger to fire more than once per 200ms + private long triggerTimeout = 0; + + private Vibrator vibrator; + private float M_PI = 3.14159265358979323846f; + public static AudioCallback mAudio; + //Read these from a file and pass through + String commandLineParams = new String(""); + + private CardboardView cardboardView; + + private FloatBuffer screenVertices; + private FloatBuffer splashScreenVertices; + + private int positionParam; + private int texCoordParam; + private int samplerParam; + private int modelViewProjectionParam; + + private float[] modelScreen; + private float[] camera; + private float[] view; + private float[] modelViewProjection; + private float[] modelView; + + private float screenDistance = 8f; + private float screenScale = 4f; + + public static final String vs_Image = + "uniform mat4 u_MVPMatrix;" + + "attribute vec4 a_Position;" + + "attribute vec2 a_texCoord;" + + "varying vec2 v_texCoord;" + + "void main() {" + + " gl_Position = u_MVPMatrix * a_Position;" + + " v_texCoord = a_texCoord;" + + "}"; + + + public static final String fs_Image = + "precision mediump float;" + + "varying vec2 v_texCoord;" + + "uniform sampler2D s_texture;" + + "void main() {" + + " gl_FragColor = texture2D( s_texture, v_texCoord );" + + "}"; + + public static int loadShader(int type, String shaderCode){ + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, shaderCode); + GLES20.glCompileShader(shader); + return shader; + } + + //FBO render eye buffer + private QVRFBO fbo; + + // Geometric variables + public static float vertices[]; + public static final short[] indices = new short[] {0, 1, 2, 0, 2, 3}; + public static final float uvs[] = new float[] { + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f + }; + + public static final float[] SCREEN_COORDS = new float[] { + -1.3f, 1.0f, 1.0f, + -1.3f, -1.0f, 1.0f, + 1.3f, -1.0f, 1.0f, + 1.3f, 1.0f, 1.0f + }; + + public static final float[] SPLASH_SCREEN_COORDS = new float[] { + -1.3f, -1.0f, 0.0f, + -1.3f, 1.0f, 0.0f, + 1.3f, 1.0f, 0.0f, + 1.3f, -1.0f, 0.0f + }; + + public FloatBuffer vertexBuffer; + public ShortBuffer listBuffer; + public FloatBuffer uvBuffer; + + //Shader Program + public static int sp_Image; + + private String sdcard = Environment.getExternalStorageDirectory().getPath(); + + private DownloadTask mDownloadTask = null; + + public static boolean mQVRInitialised = false; + public static boolean mVRModeChanged = true; + public static int mVRMode = 2; + public static boolean sideBySide = false; + //Can't rebuild eye buffers until surface changed flag recorded + public static boolean mSurfaceChanged = false; + + private int VRMODE_OFF = 0; + private int VRMODE_SIDEBYSIDE = 1; + private int VRMODE_CARDBOARD = 2; + + private boolean mShowingSpashScreen = true; + private int[] splashTexture = new int[1]; + private MediaPlayer mPlayer; + + + static { + try { + Log.i("JNI", "Trying to load libqvr.so"); + System.loadLibrary("qvr"); + } catch (UnsatisfiedLinkError ule) { + Log.e("JNI", "WARNING: Could not load libqvr.so"); + } + } + + public void copy_asset(String name, String folder) { + File f = new File(sdcard + folder + name); + if (!f.exists() || + //If file was somehow corrupted, copy the back-up + f.length() < 500) { + + //Ensure we have an appropriate folder + new File(sdcard + folder).mkdirs(); + _copy_asset(name, sdcard + folder + name); + } + } + + public void _copy_asset(String name_in, String name_out) { + AssetManager assets = this.getAssets(); + + try { + InputStream in = assets.open(name_in); + OutputStream out = new FileOutputStream(name_out); + + copy_stream(in, out); + + out.close(); + in.close(); + + } catch (Exception e) { + + e.printStackTrace(); + } + + } + + public static void copy_stream(InputStream in, OutputStream out) + throws IOException { + byte[] buf = new byte[512]; + while (true) { + int count = in.read(buf); + if (count <= 0) + break; + out.write(buf, 0, count); + } + } + + static boolean CreateFBO( QVRFBO fbo, int offset, int width, int height) + { + Log.d(TAG, "CreateFBO"); + // Create the color buffer texture. + GLES20.glGenTextures(1, fbo.ColorTexture, 0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fbo.ColorTexture[0]); + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + + // Create depth buffer. + GLES20.glGenRenderbuffers(1, fbo.DepthBuffer, 0); + GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, fbo.DepthBuffer[0]); + GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES11Ext.GL_DEPTH_COMPONENT24_OES, width, height); + GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0); + + // Create the frame buffer. + GLES20.glGenFramebuffers(1, fbo.FrameBuffer, 0); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo.FrameBuffer[0]); + GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, fbo.DepthBuffer[0]); + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, fbo.ColorTexture[0], 0); + int renderFramebufferStatus = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + if ( renderFramebufferStatus != GLES20.GL_FRAMEBUFFER_COMPLETE ) + { + Log.d(TAG, "Incomplete frame buffer object!!"); + return false; + } + + fbo.width = width; + fbo.height = height; + + return true; + } + + static void DestroyFBO( QVRFBO fbo ) + { + GLES20.glDeleteFramebuffers( 1, fbo.FrameBuffer, 0 ); + fbo.FrameBuffer[0] = 0; + GLES20.glDeleteRenderbuffers( 1, fbo.DepthBuffer, 0 ); + fbo.DepthBuffer[0] = 0; + GLES20.glDeleteTextures( 1, fbo.ColorTexture, 0 ); + fbo.ColorTexture[0] = 0; + fbo.width = 0; + fbo.height = 0; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + cardboardView = (CardboardView) findViewById(R.id.cardboard_view); + cardboardView.setEGLConfigChooser(5, 6, 5, 0, 16, 0); + cardboardView.setLowLatencyModeEnabled(true); + cardboardView.setRenderer(this); + setCardboardView(cardboardView); + + modelScreen = new float[16]; + camera = new float[16]; + view = new float[16]; + modelViewProjection = new float[16]; + modelView = new float[16]; + + vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + + //At the very least ensure we have a directory containing a config file + copy_asset("config.cfg", "/QVR/id1/"); + copy_asset("commandline.txt", "/QVR/"); + + //See if user is trying to use command line params + BufferedReader br; + try { + br = new BufferedReader(new FileReader(sdcard + "/QVR/commandline.txt")); + String s; + StringBuilder sb=new StringBuilder(0); + while ((s=br.readLine())!=null) + sb.append(s + " "); + br.close(); + + commandLineParams = new String(sb.toString()); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if (commandLineParams.contains("-game")) + { + //No need to download, user is playing something else + } + else + { + File f = new File(sdcard + "/QVR/id1/pak0.pak"); + if (!f.exists()) { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder( + this); + + // set title + alertDialogBuilder.setTitle("No game assets found"); + + // set dialog message + alertDialogBuilder + .setMessage("Would you like to download the shareware version of Quake (8MB)?\n\nIf you own or purchase the full game (On Steam: http://store.steampowered.com/app/2310/) you can click \'Cancel\' and copy the pak files to the folder:\n\n{phonememory}/QVR/id1") + .setCancelable(false) + .setPositiveButton("Download", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + MainActivity.this.startDownload(); + } + }) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + // create alert dialog + AlertDialog alertDialog = alertDialogBuilder.create(); + + // show it + alertDialog.show(); + } + } + + //Create the FBOs + fbo = new QVRFBO(); + + if (mAudio==null) + { + mAudio = new AudioCallback(); + } + + QVRJNILib.setCallbackObjects(mAudio, this); + + } + + public void startDownload() + { + mDownloadTask = new DownloadTask(); + mDownloadTask.set_context(MainActivity.this); + mDownloadTask.execute(); + } + + @Override + public void onRendererShutdown() { + Log.i(TAG, "onRendererShutdown"); + } + + @Override + public void onSurfaceChanged(int width, int height) + { + Log.d(TAG, "onSurfaceChanged width = " + width + " height = " + height); + mSurfaceChanged = true; + } + + @Override + public void onSurfaceCreated(EGLConfig config) { + Log.i(TAG, "onSurfaceCreated"); + + ByteBuffer bbVertices = ByteBuffer.allocateDirect(SCREEN_COORDS.length * 4); + bbVertices.order(ByteOrder.nativeOrder()); + screenVertices = bbVertices.asFloatBuffer(); + screenVertices.put(SCREEN_COORDS); + screenVertices.position(0); + + bbVertices = ByteBuffer.allocateDirect(SPLASH_SCREEN_COORDS.length * 4); + bbVertices.order(ByteOrder.nativeOrder()); + splashScreenVertices = bbVertices.asFloatBuffer(); + splashScreenVertices.put(SPLASH_SCREEN_COORDS); + splashScreenVertices.position(0); + + // initialize byte buffer for the draw list + ByteBuffer dlb = ByteBuffer.allocateDirect(indices.length * 2); + dlb.order(ByteOrder.nativeOrder()); + listBuffer = dlb.asShortBuffer(); + listBuffer.put(indices); + listBuffer.position(0); + + // Create the shaders, images + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vs_Image); + int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fs_Image); + + sp_Image = GLES20.glCreateProgram(); // create empty OpenGL ES Program + GLES20.glAttachShader(sp_Image, vertexShader); // add the vertex shader to program + GLES20.glAttachShader(sp_Image, fragmentShader); // add the fragment shader to program + GLES20.glLinkProgram(sp_Image); // creates OpenGL ES program executable + + positionParam = GLES20.glGetAttribLocation(sp_Image, "a_Position"); + texCoordParam = GLES20.glGetAttribLocation(sp_Image, "a_texCoord"); + modelViewProjectionParam = GLES20.glGetUniformLocation(sp_Image, "u_MVPMatrix"); + samplerParam = GLES20.glGetUniformLocation(sp_Image, "s_texture"); + + + GLES20.glEnableVertexAttribArray(positionParam); + GLES20.glEnableVertexAttribArray(texCoordParam); + + // Build the camera matrix + Matrix.setLookAtM(camera, 0, 0.0f, 0.0f, 0.01f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); + + //Start intro music + mPlayer = MediaPlayer.create(this, R.raw.m010912339); + mPlayer.start(); + + //Load bitmap for splash screen + splashTexture[0] = 0; + GLES20.glGenTextures(1, splashTexture, 0); + + Bitmap bmp = null; + try { + AssetManager assets = this.getAssets(); + InputStream in = assets.open("splash.jpg"); + bmp = BitmapFactory.decodeStream(in); + in.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + // Bind texture to texturename + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, splashTexture[0]); + + // Set filtering + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + + // Set wrapping mode + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + + // Load the bitmap into the bound texture. + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0); + + //unbind + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + + // We are done using the bitmap so we should recycle it. + //bmp.recycle(); + + } + + public void BigScreenMode(int mode) + { + if (mode == -1) + bigScreen = 1; + else if (bigScreen != 2) + bigScreen = mode; + } + + public void SwitchStereoMode(int stereo_mode) + { + mStereoMode = stereo_mode; + } + + int getDesiredfboEyeResolution(int viewportWidth) { + + desiredEyeBufferResolution = QVRJNILib.getEyeBufferResolution(); + if (desiredEyeBufferResolution != 0) + return desiredEyeBufferResolution; + + //Select based on viewport width + if (viewportWidth > 1024) + desiredEyeBufferResolution = 1024; + else if (viewportWidth > 512) + desiredEyeBufferResolution = 512; + else + desiredEyeBufferResolution = 256; + + return desiredEyeBufferResolution; + } + + @Override + public void onNewFrame(HeadTransform headTransform) { + if (mQVRInitialised) { + headTransform.getEulerAngles(eulerAngles, 0); + QVRJNILib.onNewFrame(-eulerAngles[0] / (M_PI / 180.0f), eulerAngles[1] / (M_PI / 180.0f), -eulerAngles[2] / (M_PI / 180.0f)); + + //Check to see if we should update the eye buffer resolution + int checkRes = QVRJNILib.getEyeBufferResolution(); + if (checkRes != 0 && checkRes != desiredEyeBufferResolution) + mVRModeChanged = true; + } + } + + @Override + public void onDrawEye(Eye eye) { + if (mVRModeChanged) + { + if (!mSurfaceChanged) + return; + + Log.i(TAG, "mVRModeChanged"); + if (fbo.FrameBuffer[0] != 0) + DestroyFBO(fbo); + + if (mVRMode == VRMODE_SIDEBYSIDE) { + CreateFBO(fbo, 0, eye.getViewport().width / 2, eye.getViewport().height); + QVRJNILib.setResolution(eye.getViewport().width / 2, eye.getViewport().height); + } + else if (mVRMode == VRMODE_CARDBOARD) { + fboEyeResolution = getDesiredfboEyeResolution(eye.getViewport().width); + CreateFBO(fbo, 0, fboEyeResolution, fboEyeResolution); + QVRJNILib.setResolution(fboEyeResolution, fboEyeResolution); + } + else // VRMODE_OFF + { + CreateFBO(fbo, 0, eye.getViewport().width, eye.getViewport().height); + QVRJNILib.setResolution(eye.getViewport().width, eye.getViewport().height); + } + + SetupUVCoords(); + + //Reset our orientation + cardboardView.resetHeadTracker(); + + mVRModeChanged = false; + } + + if (!mQVRInitialised && !mShowingSpashScreen) + { + QVRJNILib.initialise(sdcard + "/QVR", commandLineParams); + + //Now calculate the auto lens centre correction + CardboardDeviceParams device = cardboardView.getHeadMountedDisplay().getCardboardDeviceParams(); + ScreenParams scr = cardboardView.getScreenParams(); + Display display = getWindowManager().getDefaultDisplay(); + DisplayMetrics met = new DisplayMetrics(); + display.getMetrics(met); + float dpmil = (met.xdpi / 25.4f); + float qscreen = (scr.getWidthMeters() * 1000.0f) / 4.0f; + float halflens = (device.getInterLensDistance() * 1000.0f) / 2.0f; + //Multiply by small fudge factor (20%) + float lensCentreOffset = ((halflens - qscreen) * dpmil) * 1.2f; + + if (mVRMode == VRMODE_CARDBOARD) { + //Viewport size is not the same as screen resolution, so convert + lensCentreOffset = (lensCentreOffset / (scr.getWidth() / 2.0f)) * eye.getViewport().width; + } + else if (mVRMode == VRMODE_SIDEBYSIDE) { + //do nothing, no correction needed + } + else + { + //No offset required + lensCentreOffset = 0; + } + + QVRJNILib.setCentreOffset((int)lensCentreOffset); + + mQVRInitialised = true; + } + + if (mQVRInitialised || mShowingSpashScreen) { + + //Record the curent fbo + GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, currentFBO, 0); + + for (int i = 0; i < 2; ++i) { + + if (mShowingSpashScreen) { + GLES20.glEnable(GLES20.GL_SCISSOR_TEST); + GLES20.glScissor(eye.getViewport().x, eye.getViewport().y, + eye.getViewport().width, eye.getViewport().height); + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + } + else if (mStereoMode == STEREO || + (mStereoMode == MONO && eye.getType() < 2)) { + //Bind our special fbo + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo.FrameBuffer[0]); + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + GLES20.glDepthFunc(GLES20.GL_LEQUAL); + GLES20.glEnable(GLES20.GL_SCISSOR_TEST); + + if (mVRMode == VRMODE_SIDEBYSIDE) { + GLES20.glScissor(0, 0, eye.getViewport().width / 2, eye.getViewport().height); + } else if (mVRMode == VRMODE_CARDBOARD) { + GLES20.glScissor(0, 0, fboEyeResolution, fboEyeResolution); + } else { + GLES20.glScissor(0, 0, eye.getViewport().width, eye.getViewport().height); + } + + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + + //Decide which eye we are drawing + if (mStereoMode == MONO) + eyeID = 0; + else if (mVRMode == 0) + eyeID = 0; + else if (mVRMode == VRMODE_SIDEBYSIDE) + eyeID = i; + else // mStereoMode == StereoMode.STEREO - Default behaviour for VR mode + eyeID = eye.getType() - 1; + + //We only draw from QVR if we arent showing the splash + if (!mShowingSpashScreen) + QVRJNILib.onDrawEye(eyeID, 0, 0); + + //Finished rendering to our frame buffer, now draw this to the target framebuffer + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, currentFBO[0]); + } + + GLES20.glDisable(GLES20.GL_SCISSOR_TEST); + + if (mVRMode == VRMODE_SIDEBYSIDE) { + GLES20.glViewport(eye.getViewport().x + ((eye.getViewport().width/2) * i), + eye.getViewport().y, + eye.getViewport().width/2, + eye.getViewport().height); + } + else + { + GLES20.glViewport(eye.getViewport().x, + eye.getViewport().y, + eye.getViewport().width, + eye.getViewport().height); + } + + GLES20.glUseProgram(sp_Image); + + if ((bigScreen != 0) && mVRMode > 0) { + // Apply the eye transformation to the camera. + Matrix.multiplyMM(view, 0, eye.getEyeView(), 0, camera, 0); + + // Build the ModelView and ModelViewProjection matrices + // for calculating screen position. + float[] perspective = eye.getPerspective(0.1f, 100.0f); + + float scale = screenScale; + if (mShowingSpashScreen) + scale /= 2; + + // Object first appears directly in front of user. + Matrix.setIdentityM(modelScreen, 0); + Matrix.translateM(modelScreen, 0, 0, 0, -screenDistance); + Matrix.scaleM(modelScreen, 0, scale, scale, 1.0f); + + // Set the position of the screen + if (mShowingSpashScreen) { + float mAngle = 360.0f * (float)((System.currentTimeMillis() % 3000) / 3000.0f); + Matrix.rotateM(modelScreen, 0, mAngle, 0.0f, 1.0f, 0.0f); + Matrix.multiplyMM(modelView, 0, view, 0, modelScreen, 0); + Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0); + GLES20.glVertexAttribPointer(positionParam, 3, GLES20.GL_FLOAT, false, 0, splashScreenVertices); + } + else { + Matrix.multiplyMM(modelView, 0, view, 0, modelScreen, 0); + Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0); + GLES20.glVertexAttribPointer(positionParam, 3, GLES20.GL_FLOAT, false, 0, screenVertices); + } + + } else { + + // Create the triangles for orthographic projection (if required) + int offset = 0; + + if (mVRMode == VRMODE_CARDBOARD) + offset = QVRJNILib.getCentreOffset(); + + if (eye.getType() == 1 || i == 1) + offset *= -1; + + int w = (int) eye.getViewport().width; + int h = (int) eye.getViewport().height; + int x = (int) 0; + int y = (int) 0; + if (mVRMode == VRMODE_CARDBOARD) + { + //This assumes that height > width for an eye + w = (int) eye.getViewport().width; + h = (int) eye.getViewport().width; + x = 0; + y = (eye.getViewport().height - eye.getViewport().width) / 2; + } + + SetupTriangle(offset + x, y, w, h); + + // Calculate the projection and view transformation + Matrix.orthoM(view, 0, 0, eye.getViewport().width, 0, eye.getViewport().height, 0, 50); + Matrix.multiplyMM(modelViewProjection, 0, view, 0, camera, 0); + + // Prepare the triangle coordinate data + GLES20.glVertexAttribPointer(positionParam, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer); + } + + // Prepare the texturecoordinates + GLES20.glVertexAttribPointer(texCoordParam, 2, GLES20.GL_FLOAT, false, 0, uvBuffer); + + // Apply the projection and view transformation + GLES20.glUniformMatrix4fv(modelViewProjectionParam, 1, false, modelViewProjection, 0); + + // Bind texture to fbo's color texture + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + IntBuffer activeTex0 = IntBuffer.allocate(2); + GLES20.glGetIntegerv(GLES20.GL_TEXTURE_BINDING_2D, activeTex0); + + if (mShowingSpashScreen) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, splashTexture[0]); + else + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fbo.ColorTexture[0]); + + // Set the sampler texture unit to our fbo's color texture + GLES20.glUniform1i(samplerParam, 0); + + // Draw the triangles + GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, listBuffer); + + int error = GLES20.glGetError(); + if (error != GLES20.GL_NO_ERROR) + Log.d(TAG, "GLES20 Error = " + error); + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, activeTex0.get(0)); + + //Only loop round again for side by side + if (mVRMode != VRMODE_SIDEBYSIDE && i == 0) + break; + } + } + } + + @Override + public void onFinishFrame(Viewport viewport) { + if (mQVRInitialised) { + QVRJNILib.onFinishFrame(); + } + } + + /** + * Called when the Cardboard trigger is pulled. + */ + //@Override + public void onCardboardTrigger() { + Log.i(TAG, "onCardboardTrigger"); + + if (System.currentTimeMillis() - triggerTimeout > 200) { + + QVRJNILib.onKeyEvent(K_ENTER, KeyEvent.ACTION_DOWN, 0); + + cardboardView.resetHeadTracker(); + + dismissSplashScreen(); + + triggerTimeout = System.currentTimeMillis(); + } + } + + + public int getCharacter(int keyCode, KeyEvent event) + { + if (keyCode==KeyEvent.KEYCODE_DEL) return '\b'; + return event.getUnicodeChar(); + } + + private void dismissSplashScreen() + { + if (mShowingSpashScreen) { + mPlayer.stop(); + mPlayer.release(); + mShowingSpashScreen = false; + } + } + + @Override public boolean dispatchKeyEvent( KeyEvent event ) + { + int keyCode = event.getKeyCode(); + int action = event.getAction(); + int character = 0; + + if ( action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_UP ) + { + return super.dispatchKeyEvent( event ); + } + if ( action == KeyEvent.ACTION_UP ) + { + dismissSplashScreen(); + Log.v( TAG, "GLES3JNIActivity::dispatchKeyEvent( " + keyCode + ", " + action + " )" ); + } + + //Allow user to switch vr mode by holding the start button down + if (keyCode == KeyEvent.KEYCODE_BUTTON_START) + { + if (action == KeyEvent.ACTION_DOWN && + startButtonDownCounter == -1) + { + startButtonDownCounter = System.currentTimeMillis(); + + } + else if (action == KeyEvent.ACTION_UP) + { + startButtonDownCounter = -1; + } + } + + if (startButtonDownCounter != -1) + { + if ((System.currentTimeMillis() - startButtonDownCounter) > 2000) + { + //Switch VR mode + startButtonDownCounter = -1; + SwitchVRMode(); + //Now make sure qvr is aware! + QVRJNILib.onSwitchVRMode(mVRMode); + } + } + + //Following buttons must not be handled here + if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || + keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || + keyCode == KeyEvent.KEYCODE_BUTTON_THUMBL + ) + return false; + + //Convert to QVR keys + character = getCharacter(keyCode, event); + int qKeyCode = convertKeyCode(keyCode, event); + + //Don't hijack all keys (volume etc) + if (qKeyCode != -1) + keyCode = qKeyCode; + + if (keyCode == K_ESCAPE) + cardboardView.resetHeadTracker(); + + QVRJNILib.onKeyEvent( keyCode, action, character ); + return true; + } + + private static float getCenteredAxis(MotionEvent event, + int axis) { + final InputDevice.MotionRange range = event.getDevice().getMotionRange(axis, event.getSource()); + if (range != null) { + final float flat = range.getFlat(); + final float value = event.getAxisValue(axis); + if (Math.abs(value) > flat) { + return value; + } + } + return 0; + } + + + //Save the game pad type once known: + // 1 - Generic BT gamepad + // 2 - Samsung gamepad that uses different axes for right stick + int gamepadType = 0; + + int lTrigAction = KeyEvent.ACTION_UP; + int rTrigAction = KeyEvent.ACTION_UP; + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + int source = event.getSource(); + int action = event.getAction(); + if ((source==InputDevice.SOURCE_JOYSTICK)||(event.getSource()==InputDevice.SOURCE_GAMEPAD)) + { + if (event.getAction() == MotionEvent.ACTION_MOVE) + { + float x = getCenteredAxis(event, MotionEvent.AXIS_X); + float y = -getCenteredAxis(event, MotionEvent.AXIS_Y); + QVRJNILib.onTouchEvent( source, action, x, y ); + + float z = getCenteredAxis(event, MotionEvent.AXIS_Z); + float rz = -getCenteredAxis(event, MotionEvent.AXIS_RZ); + //For the samsung game pad (uses different axes for the second stick) + float rx = getCenteredAxis(event, MotionEvent.AXIS_RX); + float ry = -getCenteredAxis(event, MotionEvent.AXIS_RY); + + //let's figure it out + if (gamepadType == 0) + { + if (z != 0.0f || rz != 0.0f) + gamepadType = 1; + else if (rx != 0.0f || ry != 0.0f) + gamepadType = 2; + } + + switch (gamepadType) + { + case 0: + break; + case 1: + QVRJNILib.onMotionEvent( source, action, z, rz ); + break; + case 2: + QVRJNILib.onMotionEvent( source, action, rx, ry ); + break; + } + + //Fire weapon using shoulder trigger + float axisRTrigger = max(event.getAxisValue(MotionEvent.AXIS_RTRIGGER), + event.getAxisValue(MotionEvent.AXIS_GAS)); + int newRTrig = axisRTrigger > 0.6 ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; + if (rTrigAction != newRTrig) + { + QVRJNILib.onKeyEvent( K_MOUSE1, newRTrig, 0); + rTrigAction = newRTrig; + } + + //Run using L shoulder + float axisLTrigger = max(event.getAxisValue(MotionEvent.AXIS_LTRIGGER), + event.getAxisValue(MotionEvent.AXIS_BRAKE)); + int newLTrig = axisLTrigger > 0.6 ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP; + if (lTrigAction != newLTrig) + { + QVRJNILib.onKeyEvent( K_SHIFT, newLTrig, 0); + lTrigAction = newLTrig; + } + } + } + return false; + } + + private float max(float axisValue, float axisValue2) { + return (axisValue > axisValue2) ? axisValue : axisValue2; + } + + public static final int K_TAB = 9; + public static final int K_ENTER = 13; + public static final int K_ESCAPE = 27; + public static final int K_SPACE = 32; + public static final int K_BACKSPACE = 127; + public static final int K_UPARROW = 128; + public static final int K_DOWNARROW = 129; + public static final int K_LEFTARROW = 130; + public static final int K_RIGHTARROW = 131; + public static final int K_ALT = 132; + public static final int K_CTRL = 133; + public static final int K_SHIFT = 134; + public static final int K_F1 = 135; + public static final int K_F2 = 136; + public static final int K_F3 = 137; + public static final int K_F4 = 138; + public static final int K_F5 = 139; + public static final int K_F6 = 140; + public static final int K_F7 = 141; + public static final int K_F8 = 142; + public static final int K_F9 = 143; + public static final int K_F10 = 144; + public static final int K_F11 = 145; + public static final int K_F12 = 146; + public static final int K_INS = 147; + public static final int K_DEL = 148; + public static final int K_PGDN = 149; + public static final int K_PGUP = 150; + public static final int K_HOME = 151; + public static final int K_END = 152; + public static final int K_PAUSE = 153; + public static final int K_NUMLOCK = 154; + public static final int K_CAPSLOCK = 155; + public static final int K_SCROLLOCK = 156; + public static final int K_MOUSE1 = 512; + public static final int K_MOUSE2 = 513; + public static final int K_MOUSE3 = 514; + public static final int K_MWHEELUP = 515; + public static final int K_MWHEELDOWN = 516; + public static final int K_MOUSE4 = 517; + public static final int K_MOUSE5 = 518; + + public static int convertKeyCode(int keyCode, KeyEvent event) + { + switch(keyCode) + { + case KeyEvent.KEYCODE_FOCUS: + return K_F1; + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_W: + return K_UPARROW; + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_S: + return K_DOWNARROW; + case KeyEvent.KEYCODE_DPAD_LEFT: + return 'a'; + case KeyEvent.KEYCODE_DPAD_RIGHT: + return 'd'; + case KeyEvent.KEYCODE_DPAD_CENTER: + return K_CTRL; + case KeyEvent.KEYCODE_ENTER: + return K_ENTER; + case KeyEvent.KEYCODE_BACK: + return K_ESCAPE; + case KeyEvent.KEYCODE_APOSTROPHE: + return K_ESCAPE; + case KeyEvent.KEYCODE_DEL: + return K_BACKSPACE; + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + return K_ALT; + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + return K_SHIFT; + case KeyEvent.KEYCODE_CTRL_LEFT: + case KeyEvent.KEYCODE_CTRL_RIGHT: + return K_CTRL; + case KeyEvent.KEYCODE_INSERT: + return K_INS; + case 122: + return K_HOME; + case KeyEvent.KEYCODE_FORWARD_DEL: + return K_DEL; + case 123: + return K_END; + case KeyEvent.KEYCODE_ESCAPE: + return K_ESCAPE; + case KeyEvent.KEYCODE_TAB: + return K_TAB; + case KeyEvent.KEYCODE_F1: + return K_F1; + case KeyEvent.KEYCODE_F2: + return K_F2; + case KeyEvent.KEYCODE_F3: + return K_F3; + case KeyEvent.KEYCODE_F4: + return K_F4; + case KeyEvent.KEYCODE_F5: + return K_F5; + case KeyEvent.KEYCODE_F6: + return K_F6; + case KeyEvent.KEYCODE_F7: + return K_F7; + case KeyEvent.KEYCODE_F8: + return K_F8; + case KeyEvent.KEYCODE_F9: + return K_F9; + case KeyEvent.KEYCODE_F10: + return K_F10; + case KeyEvent.KEYCODE_F11: + return K_F11; + case KeyEvent.KEYCODE_F12: + return K_F12; + case KeyEvent.KEYCODE_CAPS_LOCK: + return K_CAPSLOCK; + case KeyEvent.KEYCODE_PAGE_DOWN: + return K_PGDN; + case KeyEvent.KEYCODE_PAGE_UP: + return K_PGUP; + case KeyEvent.KEYCODE_BUTTON_A: + return K_ENTER; + case KeyEvent.KEYCODE_BUTTON_B: + return K_MOUSE1; + case KeyEvent.KEYCODE_BUTTON_X: + return '#'; //prev weapon, set in the config.txt as impulse 12 + case KeyEvent.KEYCODE_BUTTON_Y: + return '/';//Next weapon, set in the config.txt as impulse 10 + //These buttons are not so popular + case KeyEvent.KEYCODE_BUTTON_C: + return 'a';//That's why here is a, nobody cares. + case KeyEvent.KEYCODE_BUTTON_Z: + return 'z'; + //-------------------------------- + case KeyEvent.KEYCODE_BUTTON_START: + return K_ESCAPE; + case KeyEvent.KEYCODE_BUTTON_SELECT: + return K_ENTER; + case KeyEvent.KEYCODE_MENU: + return K_ESCAPE; + + //Both shoulder buttons will "fire" + case KeyEvent.KEYCODE_BUTTON_R1: + case KeyEvent.KEYCODE_BUTTON_R2: + return K_MOUSE1; + + //enables "run" + case KeyEvent.KEYCODE_BUTTON_L1: + case KeyEvent.KEYCODE_BUTTON_L2: + return K_SHIFT; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + return -1; + } + int uchar = event.getUnicodeChar(0); + if((uchar < 127)&&(uchar!=0)) + return uchar; + return keyCode%95+32;//Magic + } + + + public void SetupUVCoords() + { + // The texture buffer + ByteBuffer bb = ByteBuffer.allocateDirect(uvs.length * 4); + bb.order(ByteOrder.nativeOrder()); + uvBuffer = bb.asFloatBuffer(); + uvBuffer.put(uvs); + uvBuffer.position(0); + } + + public void SetupTriangle(int x, int y, int width, int height) + { + // We have to create the vertices of our triangle. + vertices = new float[] + { + x, y + height, 0.0f, + x, y, 0.0f, + x + width, y, 0.0f, + x + width, y + height, 0.0f, + }; + + // The vertex buffer. + ByteBuffer bb = ByteBuffer.allocateDirect(vertices.length * 4); + bb.order(ByteOrder.nativeOrder()); + vertexBuffer = bb.asFloatBuffer(); + vertexBuffer.put(vertices); + vertexBuffer.position(0); + } + + public void SwitchVRMode() { + mVRMode = (mVRMode + 1) % 3; + SwitchVRMode(mVRMode); + } + + @Override + public void SwitchVRMode(int vrMode) { + mVRMode = vrMode; + if (mVRMode == 0) { + cardboardView.setVRModeEnabled(false); + mSurfaceChanged = false; + sideBySide = false; + } + if (mVRMode == 1) { + cardboardView.setVRModeEnabled(false); + mSurfaceChanged = true; + sideBySide = true; + } + if (mVRMode == 2) { + cardboardView.setVRModeEnabled(true); + mSurfaceChanged = false; + sideBySide = false; + } + mVRModeChanged = true; + } + + @Override + public void Exit() { + mAudio.terminateAudio(); + try { + Thread.sleep(1000); + } + catch (InterruptedException ie){ + } + System.exit(0); + } + + +} diff --git a/app/src/main/java/com/drbeef/qvr/QVRCallback.java b/app/src/main/java/com/drbeef/qvr/QVRCallback.java new file mode 100644 index 0000000..24a0836 --- /dev/null +++ b/app/src/main/java/com/drbeef/qvr/QVRCallback.java @@ -0,0 +1,10 @@ +package com.drbeef.qvr; + + +public interface QVRCallback { + + void SwitchVRMode(int vrMode); + void BigScreenMode(int mode); + void SwitchStereoMode(int stereo_mode); + void Exit(); +} diff --git a/app/src/main/java/com/drbeef/qvr/QVRFBO.java b/app/src/main/java/com/drbeef/qvr/QVRFBO.java new file mode 100644 index 0000000..c19dccf --- /dev/null +++ b/app/src/main/java/com/drbeef/qvr/QVRFBO.java @@ -0,0 +1,20 @@ +package com.drbeef.qvr; + +public class QVRFBO { + + public int[] FrameBuffer; + public int[] DepthBuffer; + public int[] ColorTexture; + public int height; + public int width; + + public QVRFBO() + { + this.FrameBuffer = new int[1]; + this.FrameBuffer[0] = 0; + this.DepthBuffer = new int[1]; + this.DepthBuffer[0] = 0; + this.ColorTexture = new int[1]; + this.ColorTexture[0] = 0; + } +} diff --git a/app/src/main/java/com/drbeef/qvr/QVRJNILib.java b/app/src/main/java/com/drbeef/qvr/QVRJNILib.java new file mode 100644 index 0000000..86ae90a --- /dev/null +++ b/app/src/main/java/com/drbeef/qvr/QVRJNILib.java @@ -0,0 +1,26 @@ +package com.drbeef.qvr; + +public class QVRJNILib { + + // Input + public static native void onKeyEvent( int keyCode, int action, int character ); + public static native void onTouchEvent( int source, int action, float x, float y ); + public static native void onMotionEvent( int source, int action, float x, float y ); + + //Rendering and lifecycle + public static native void setResolution( int width, int height ); + public static native void initialise( String gameFolder, String commandLineParams ); + public static native void onNewFrame( float pitch, float yaw, float roll ); + public static native void onDrawEye( int eye, int x, int y ); + public static native void onFinishFrame( ); + public static native void onSwitchVRMode( int vrMode ); + public static native void onBigScreenMode( int mode ); + public static native int getCentreOffset( ); + public static native void setCentreOffset( int offset ); + public static native void setDownloadStatus( int status ); + public static native int getEyeBufferResolution( ); + + //Audio + public static native void requestAudioData(); + public static native void setCallbackObjects(Object obj1, Object obj2); +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..77369f8 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..3eec333 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..968be2e Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..c454a2b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..598686d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..59d4f7f Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/raw/m010912339 b/app/src/main/res/raw/m010912339 new file mode 100644 index 0000000..31b148a Binary files /dev/null and b/app/src/main/res/raw/m010912339 differ diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..7c485dd --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + QVR + MainActivity + Settings + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..1b7886d --- /dev/null +++ b/build.gradle @@ -0,0 +1,19 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.3.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1d3591c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8c0fb64 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6e250bc --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Sep 07 21:34:10 BST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..127c9a7 --- /dev/null +++ b/local.properties @@ -0,0 +1,11 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Fri Jan 29 22:26:37 GMT 2016 +sdk.dir=C\:\\Users\\Simon\\AppData\\Local\\Android\\sdk diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'